aboutsummaryrefslogtreecommitdiff
path: root/Emby.Server.Implementations/HttpServer/Security
diff options
context:
space:
mode:
Diffstat (limited to 'Emby.Server.Implementations/HttpServer/Security')
-rw-r--r--Emby.Server.Implementations/HttpServer/Security/AuthService.cs218
-rw-r--r--Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs217
-rw-r--r--Emby.Server.Implementations/HttpServer/Security/SessionContext.cs22
3 files changed, 125 insertions, 332 deletions
diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs
index 76c1d9bac..9afabf527 100644
--- a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs
+++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs
@@ -1,17 +1,8 @@
#pragma warning disable CS1591
-using System;
-using System.Linq;
-using Emby.Server.Implementations.SocketSharp;
-using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Authentication;
-using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Net;
-using MediaBrowser.Controller.Security;
-using MediaBrowser.Controller.Session;
-using MediaBrowser.Model.Services;
using Microsoft.AspNetCore.Http;
namespace Emby.Server.Implementations.HttpServer.Security
@@ -19,228 +10,33 @@ namespace Emby.Server.Implementations.HttpServer.Security
public class AuthService : IAuthService
{
private readonly IAuthorizationContext _authorizationContext;
- private readonly ISessionManager _sessionManager;
- private readonly IServerConfigurationManager _config;
- private readonly INetworkManager _networkManager;
public AuthService(
- IAuthorizationContext authorizationContext,
- IServerConfigurationManager config,
- ISessionManager sessionManager,
- INetworkManager networkManager)
+ IAuthorizationContext authorizationContext)
{
_authorizationContext = authorizationContext;
- _config = config;
- _sessionManager = sessionManager;
- _networkManager = networkManager;
- }
-
- public void Authenticate(IRequest request, IAuthenticationAttributes authAttributes)
- {
- ValidateUser(request, authAttributes);
- }
-
- public User Authenticate(HttpRequest request, IAuthenticationAttributes authAttributes)
- {
- var req = new WebSocketSharpRequest(request, null, request.Path);
- var user = ValidateUser(req, authAttributes);
- return user;
}
public AuthorizationInfo Authenticate(HttpRequest request)
{
var auth = _authorizationContext.GetAuthorizationInfo(request);
- if (auth?.User == null)
- {
- return null;
- }
-
- if (auth.User.HasPermission(PermissionKind.IsDisabled))
- {
- throw new SecurityException("User account has been disabled.");
- }
-
- return auth;
- }
-
- private User ValidateUser(IRequest request, IAuthenticationAttributes authAttributes)
- {
- // This code is executed before the service
- var auth = _authorizationContext.GetAuthorizationInfo(request);
-
- if (!IsExemptFromAuthenticationToken(authAttributes, request))
- {
- ValidateSecurityToken(request, auth.Token);
- }
-
- if (authAttributes.AllowLocalOnly && !request.IsLocal)
- {
- throw new SecurityException("Operation not found.");
- }
-
- var user = auth.User;
- if (user == null && auth.UserId != Guid.Empty)
+ if (!auth.HasToken)
{
- throw new AuthenticationException("User with Id " + auth.UserId + " not found");
+ throw new AuthenticationException("Request does not contain a token.");
}
- if (user != null)
+ if (!auth.IsAuthenticated)
{
- ValidateUserAccess(user, request, authAttributes);
+ throw new SecurityException("Invalid token.");
}
- var info = GetTokenInfo(request);
-
- if (!IsExemptFromRoles(auth, authAttributes, request, info))
- {
- var roles = authAttributes.GetRoles();
-
- ValidateRoles(roles, user);
- }
-
- if (!string.IsNullOrEmpty(auth.DeviceId) &&
- !string.IsNullOrEmpty(auth.Client) &&
- !string.IsNullOrEmpty(auth.Device))
- {
- _sessionManager.LogSessionActivity(
- auth.Client,
- auth.Version,
- auth.DeviceId,
- auth.Device,
- request.RemoteIp,
- user);
- }
-
- return user;
- }
-
- private void ValidateUserAccess(
- User user,
- IRequest request,
- IAuthenticationAttributes authAttributes)
- {
- if (user.HasPermission(PermissionKind.IsDisabled))
+ if (auth.User?.HasPermission(PermissionKind.IsDisabled) ?? false)
{
throw new SecurityException("User account has been disabled.");
}
- if (!user.HasPermission(PermissionKind.EnableRemoteAccess) && !_networkManager.IsInLocalNetwork(request.RemoteIp))
- {
- throw new SecurityException("User account has been disabled.");
- }
-
- if (!user.HasPermission(PermissionKind.IsAdministrator)
- && !authAttributes.EscapeParentalControl
- && !user.IsParentalScheduleAllowed())
- {
- request.Response.Headers.Add("X-Application-Error-Code", "ParentalControl");
-
- throw new SecurityException("This user account is not allowed access at this time.");
- }
- }
-
- private bool IsExemptFromAuthenticationToken(IAuthenticationAttributes authAttribtues, IRequest request)
- {
- if (!_config.Configuration.IsStartupWizardCompleted && authAttribtues.AllowBeforeStartupWizard)
- {
- return true;
- }
-
- if (authAttribtues.AllowLocal && request.IsLocal)
- {
- return true;
- }
-
- if (authAttribtues.AllowLocalOnly && request.IsLocal)
- {
- return true;
- }
-
- if (authAttribtues.IgnoreLegacyAuth)
- {
- return true;
- }
-
- return false;
- }
-
- private bool IsExemptFromRoles(AuthorizationInfo auth, IAuthenticationAttributes authAttribtues, IRequest request, AuthenticationInfo tokenInfo)
- {
- if (!_config.Configuration.IsStartupWizardCompleted && authAttribtues.AllowBeforeStartupWizard)
- {
- return true;
- }
-
- if (authAttribtues.AllowLocal && request.IsLocal)
- {
- return true;
- }
-
- if (authAttribtues.AllowLocalOnly && request.IsLocal)
- {
- return true;
- }
-
- if (string.IsNullOrEmpty(auth.Token))
- {
- return true;
- }
-
- if (tokenInfo != null && tokenInfo.UserId.Equals(Guid.Empty))
- {
- return true;
- }
-
- return false;
- }
-
- private static void ValidateRoles(string[] roles, User user)
- {
- if (roles.Contains("admin", StringComparer.OrdinalIgnoreCase))
- {
- if (user == null || !user.HasPermission(PermissionKind.IsAdministrator))
- {
- throw new SecurityException("User does not have admin access.");
- }
- }
-
- if (roles.Contains("delete", StringComparer.OrdinalIgnoreCase))
- {
- if (user == null || !user.HasPermission(PermissionKind.EnableContentDeletion))
- {
- throw new SecurityException("User does not have delete access.");
- }
- }
-
- if (roles.Contains("download", StringComparer.OrdinalIgnoreCase))
- {
- if (user == null || !user.HasPermission(PermissionKind.EnableContentDownloading))
- {
- throw new SecurityException("User does not have download access.");
- }
- }
- }
-
- private static AuthenticationInfo GetTokenInfo(IRequest request)
- {
- request.Items.TryGetValue("OriginalAuthenticationInfo", out var info);
- return info as AuthenticationInfo;
- }
-
- private void ValidateSecurityToken(IRequest request, string token)
- {
- if (string.IsNullOrEmpty(token))
- {
- throw new AuthenticationException("Access token is required.");
- }
-
- var info = GetTokenInfo(request);
-
- if (info == null)
- {
- throw new AuthenticationException("Access token is invalid or expired.");
- }
+ return auth;
}
}
}
diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs
index fb93fae3e..b2625a68c 100644
--- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs
+++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs
@@ -2,12 +2,11 @@
using System;
using System.Collections.Generic;
-using System.Linq;
using System.Net;
+using Jellyfin.Extensions;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Security;
-using MediaBrowser.Model.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.Net.Http.Headers;
@@ -24,16 +23,11 @@ namespace Emby.Server.Implementations.HttpServer.Security
_userManager = userManager;
}
- public AuthorizationInfo GetAuthorizationInfo(object requestContext)
+ public AuthorizationInfo GetAuthorizationInfo(HttpContext requestContext)
{
- return GetAuthorizationInfo((IRequest)requestContext);
- }
-
- public AuthorizationInfo GetAuthorizationInfo(IRequest requestContext)
- {
- if (requestContext.Items.TryGetValue("AuthorizationInfo", out var cached))
+ if (requestContext.Request.HttpContext.Items.TryGetValue("AuthorizationInfo", out var cached))
{
- return (AuthorizationInfo)cached;
+ return (AuthorizationInfo)cached!; // Cache should never contain null
}
return GetAuthorization(requestContext);
@@ -42,8 +36,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
public AuthorizationInfo GetAuthorizationInfo(HttpRequest requestContext)
{
var auth = GetAuthorizationDictionary(requestContext);
- var (authInfo, _) =
- GetAuthorizationInfoFromDictionary(auth, requestContext.Headers, requestContext.Query);
+ var authInfo = GetAuthorizationInfoFromDictionary(auth, requestContext.Headers, requestContext.Query);
return authInfo;
}
@@ -52,31 +45,25 @@ namespace Emby.Server.Implementations.HttpServer.Security
/// </summary>
/// <param name="httpReq">The HTTP req.</param>
/// <returns>Dictionary{System.StringSystem.String}.</returns>
- private AuthorizationInfo GetAuthorization(IRequest httpReq)
+ private AuthorizationInfo GetAuthorization(HttpContext httpReq)
{
var auth = GetAuthorizationDictionary(httpReq);
- var (authInfo, originalAuthInfo) =
- GetAuthorizationInfoFromDictionary(auth, httpReq.Headers, httpReq.QueryString);
+ var authInfo = GetAuthorizationInfoFromDictionary(auth, httpReq.Request.Headers, httpReq.Request.Query);
- if (originalAuthInfo != null)
- {
- httpReq.Items["OriginalAuthenticationInfo"] = originalAuthInfo;
- }
-
- httpReq.Items["AuthorizationInfo"] = authInfo;
+ httpReq.Request.HttpContext.Items["AuthorizationInfo"] = authInfo;
return authInfo;
}
- private (AuthorizationInfo authInfo, AuthenticationInfo originalAuthenticationInfo) GetAuthorizationInfoFromDictionary(
- in Dictionary<string, string> auth,
+ private AuthorizationInfo GetAuthorizationInfoFromDictionary(
+ in Dictionary<string, string>? auth,
in IHeaderDictionary headers,
in IQueryCollection queryString)
{
- string deviceId = null;
- string device = null;
- string client = null;
- string version = null;
- string token = null;
+ string? deviceId = null;
+ string? device = null;
+ string? client = null;
+ string? version = null;
+ string? token = null;
if (auth != null)
{
@@ -114,88 +101,104 @@ namespace Emby.Server.Implementations.HttpServer.Security
Device = device,
DeviceId = deviceId,
Version = version,
- Token = token
+ Token = token,
+ IsAuthenticated = false,
+ HasToken = false
};
- AuthenticationInfo originalAuthenticationInfo = null;
- if (!string.IsNullOrWhiteSpace(token))
+ if (string.IsNullOrWhiteSpace(token))
{
- var result = _authRepo.Get(new AuthenticationInfoQuery
- {
- AccessToken = token
- });
+ // Request doesn't contain a token.
+ return authInfo;
+ }
- originalAuthenticationInfo = result.Items.Count > 0 ? result.Items[0] : null;
+ authInfo.HasToken = true;
+ var result = _authRepo.Get(new AuthenticationInfoQuery
+ {
+ AccessToken = token
+ });
- if (originalAuthenticationInfo != null)
- {
- var updateToken = false;
+ if (result.Items.Count > 0)
+ {
+ authInfo.IsAuthenticated = true;
+ }
- // TODO: Remove these checks for IsNullOrWhiteSpace
- if (string.IsNullOrWhiteSpace(authInfo.Client))
- {
- authInfo.Client = originalAuthenticationInfo.AppName;
- }
+ var originalAuthenticationInfo = result.Items.Count > 0 ? result.Items[0] : null;
- if (string.IsNullOrWhiteSpace(authInfo.DeviceId))
- {
- authInfo.DeviceId = originalAuthenticationInfo.DeviceId;
- }
+ if (originalAuthenticationInfo != null)
+ {
+ var updateToken = false;
- // Temporary. TODO - allow clients to specify that the token has been shared with a casting device
- var allowTokenInfoUpdate = authInfo.Client == null || authInfo.Client.IndexOf("chromecast", StringComparison.OrdinalIgnoreCase) == -1;
+ // TODO: Remove these checks for IsNullOrWhiteSpace
+ if (string.IsNullOrWhiteSpace(authInfo.Client))
+ {
+ authInfo.Client = originalAuthenticationInfo.AppName;
+ }
- if (string.IsNullOrWhiteSpace(authInfo.Device))
- {
- authInfo.Device = originalAuthenticationInfo.DeviceName;
- }
- else if (!string.Equals(authInfo.Device, originalAuthenticationInfo.DeviceName, StringComparison.OrdinalIgnoreCase))
- {
- if (allowTokenInfoUpdate)
- {
- updateToken = true;
- originalAuthenticationInfo.DeviceName = authInfo.Device;
- }
- }
+ if (string.IsNullOrWhiteSpace(authInfo.DeviceId))
+ {
+ authInfo.DeviceId = originalAuthenticationInfo.DeviceId;
+ }
- if (string.IsNullOrWhiteSpace(authInfo.Version))
- {
- authInfo.Version = originalAuthenticationInfo.AppVersion;
- }
- else if (!string.Equals(authInfo.Version, originalAuthenticationInfo.AppVersion, StringComparison.OrdinalIgnoreCase))
+ // Temporary. TODO - allow clients to specify that the token has been shared with a casting device
+ var allowTokenInfoUpdate = authInfo.Client == null || !authInfo.Client.Contains("chromecast", StringComparison.OrdinalIgnoreCase);
+
+ if (string.IsNullOrWhiteSpace(authInfo.Device))
+ {
+ authInfo.Device = originalAuthenticationInfo.DeviceName;
+ }
+ else if (!string.Equals(authInfo.Device, originalAuthenticationInfo.DeviceName, StringComparison.OrdinalIgnoreCase))
+ {
+ if (allowTokenInfoUpdate)
{
- if (allowTokenInfoUpdate)
- {
- updateToken = true;
- originalAuthenticationInfo.AppVersion = authInfo.Version;
- }
+ updateToken = true;
+ originalAuthenticationInfo.DeviceName = authInfo.Device;
}
+ }
- if ((DateTime.UtcNow - originalAuthenticationInfo.DateLastActivity).TotalMinutes > 3)
+ if (string.IsNullOrWhiteSpace(authInfo.Version))
+ {
+ authInfo.Version = originalAuthenticationInfo.AppVersion;
+ }
+ else if (!string.Equals(authInfo.Version, originalAuthenticationInfo.AppVersion, StringComparison.OrdinalIgnoreCase))
+ {
+ if (allowTokenInfoUpdate)
{
- originalAuthenticationInfo.DateLastActivity = DateTime.UtcNow;
updateToken = true;
+ originalAuthenticationInfo.AppVersion = authInfo.Version;
}
+ }
- if (!originalAuthenticationInfo.UserId.Equals(Guid.Empty))
- {
- authInfo.User = _userManager.GetUserById(originalAuthenticationInfo.UserId);
+ if ((DateTime.UtcNow - originalAuthenticationInfo.DateLastActivity).TotalMinutes > 3)
+ {
+ originalAuthenticationInfo.DateLastActivity = DateTime.UtcNow;
+ updateToken = true;
+ }
- if (authInfo.User != null && !string.Equals(authInfo.User.Username, originalAuthenticationInfo.UserName, StringComparison.OrdinalIgnoreCase))
- {
- originalAuthenticationInfo.UserName = authInfo.User.Username;
- updateToken = true;
- }
- }
+ if (!originalAuthenticationInfo.UserId.Equals(Guid.Empty))
+ {
+ authInfo.User = _userManager.GetUserById(originalAuthenticationInfo.UserId);
- if (updateToken)
+ if (authInfo.User != null && !string.Equals(authInfo.User.Username, originalAuthenticationInfo.UserName, StringComparison.OrdinalIgnoreCase))
{
- _authRepo.Update(originalAuthenticationInfo);
+ originalAuthenticationInfo.UserName = authInfo.User.Username;
+ updateToken = true;
}
+
+ authInfo.IsApiKey = false;
+ }
+ else
+ {
+ authInfo.IsApiKey = true;
+ }
+
+ if (updateToken)
+ {
+ _authRepo.Update(originalAuthenticationInfo);
}
}
- return (authInfo, originalAuthenticationInfo);
+ return authInfo;
}
/// <summary>
@@ -203,16 +206,16 @@ namespace Emby.Server.Implementations.HttpServer.Security
/// </summary>
/// <param name="httpReq">The HTTP req.</param>
/// <returns>Dictionary{System.StringSystem.String}.</returns>
- private Dictionary<string, string> GetAuthorizationDictionary(IRequest httpReq)
+ private Dictionary<string, string>? GetAuthorizationDictionary(HttpContext httpReq)
{
- var auth = httpReq.Headers["X-Emby-Authorization"];
+ var auth = httpReq.Request.Headers["X-Emby-Authorization"];
if (string.IsNullOrEmpty(auth))
{
- auth = httpReq.Headers[HeaderNames.Authorization];
+ auth = httpReq.Request.Headers[HeaderNames.Authorization];
}
- return GetAuthorization(auth);
+ return GetAuthorization(auth.Count > 0 ? auth[0] : null);
}
/// <summary>
@@ -220,7 +223,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
/// </summary>
/// <param name="httpReq">The HTTP req.</param>
/// <returns>Dictionary{System.StringSystem.String}.</returns>
- private Dictionary<string, string> GetAuthorizationDictionary(HttpRequest httpReq)
+ private Dictionary<string, string>? GetAuthorizationDictionary(HttpRequest httpReq)
{
var auth = httpReq.Headers["X-Emby-Authorization"];
@@ -229,7 +232,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
auth = httpReq.Headers[HeaderNames.Authorization];
}
- return GetAuthorization(auth);
+ return GetAuthorization(auth.Count > 0 ? auth[0] : null);
}
/// <summary>
@@ -237,43 +240,43 @@ namespace Emby.Server.Implementations.HttpServer.Security
/// </summary>
/// <param name="authorizationHeader">The authorization header.</param>
/// <returns>Dictionary{System.StringSystem.String}.</returns>
- private Dictionary<string, string> GetAuthorization(string authorizationHeader)
+ private Dictionary<string, string>? GetAuthorization(ReadOnlySpan<char> authorizationHeader)
{
if (authorizationHeader == null)
{
return null;
}
- var parts = authorizationHeader.Split(new[] { ' ' }, 2);
+ var firstSpace = authorizationHeader.IndexOf(' ');
- // There should be at least to parts
- if (parts.Length != 2)
+ // There should be at least two parts
+ if (firstSpace == -1)
{
return null;
}
- var acceptedNames = new[] { "MediaBrowser", "Emby" };
+ var name = authorizationHeader[..firstSpace];
- // It has to be a digest request
- if (!acceptedNames.Contains(parts[0], StringComparer.OrdinalIgnoreCase))
+ if (!name.Equals("MediaBrowser", StringComparison.OrdinalIgnoreCase)
+ && !name.Equals("Emby", StringComparison.OrdinalIgnoreCase))
{
return null;
}
- // Remove uptil the first space
- authorizationHeader = parts[1];
- parts = authorizationHeader.Split(',');
+ authorizationHeader = authorizationHeader[(firstSpace + 1)..];
var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
- foreach (var item in parts)
+ foreach (var item in authorizationHeader.Split(','))
{
- var param = item.Trim().Split(new[] { '=' }, 2);
+ var trimmedItem = item.Trim();
+ var firstEqualsSign = trimmedItem.IndexOf('=');
- if (param.Length == 2)
+ if (firstEqualsSign > 0)
{
- var value = NormalizeValue(param[1].Trim(new[] { '"' }));
- result.Add(param[0], value);
+ var key = trimmedItem[..firstEqualsSign].ToString();
+ var value = NormalizeValue(trimmedItem[(firstEqualsSign + 1)..].Trim('"').ToString());
+ result[key] = value;
}
}
diff --git a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs
index 03fcfa53d..c375f36ce 100644
--- a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs
+++ b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs
@@ -2,11 +2,11 @@
using System;
using Jellyfin.Data.Entities;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
-using MediaBrowser.Controller.Security;
using MediaBrowser.Controller.Session;
-using MediaBrowser.Model.Services;
+using Microsoft.AspNetCore.Http;
namespace Emby.Server.Implementations.HttpServer.Security
{
@@ -23,35 +23,29 @@ namespace Emby.Server.Implementations.HttpServer.Security
_sessionManager = sessionManager;
}
- public SessionInfo GetSession(IRequest requestContext)
+ public SessionInfo GetSession(HttpContext requestContext)
{
var authorization = _authContext.GetAuthorizationInfo(requestContext);
var user = authorization.User;
- return _sessionManager.LogSessionActivity(authorization.Client, authorization.Version, authorization.DeviceId, authorization.Device, requestContext.RemoteIp, user);
- }
-
- private AuthenticationInfo GetTokenInfo(IRequest request)
- {
- request.Items.TryGetValue("OriginalAuthenticationInfo", out var info);
- return info as AuthenticationInfo;
+ return _sessionManager.LogSessionActivity(authorization.Client, authorization.Version, authorization.DeviceId, authorization.Device, requestContext.GetNormalizedRemoteIp().ToString(), user);
}
public SessionInfo GetSession(object requestContext)
{
- return GetSession((IRequest)requestContext);
+ return GetSession((HttpContext)requestContext);
}
- public User GetUser(IRequest requestContext)
+ public User? GetUser(HttpContext requestContext)
{
var session = GetSession(requestContext);
return session == null || session.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(session.UserId);
}
- public User GetUser(object requestContext)
+ public User? GetUser(object requestContext)
{
- return GetUser((IRequest)requestContext);
+ return GetUser(((HttpRequest)requestContext).HttpContext);
}
}
}