diff options
Diffstat (limited to 'Emby.Server.Implementations/HttpServer/Security')
3 files changed, 96 insertions, 269 deletions
diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs index 256b24924..68d981ad1 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs @@ -1,237 +1,35 @@ #pragma warning disable CS1591 -using System; -using System.Linq; -using System.Security.Authentication; -using Emby.Server.Implementations.SocketSharp; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Security; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.HttpServer.Security { public class AuthService : IAuthService { - private readonly ILogger<AuthService> _logger; private readonly IAuthorizationContext _authorizationContext; - private readonly ISessionManager _sessionManager; - private readonly IServerConfigurationManager _config; - private readonly INetworkManager _networkManager; public AuthService( - ILogger<AuthService> logger, - IAuthorizationContext authorizationContext, - IServerConfigurationManager config, - ISessionManager sessionManager, - INetworkManager networkManager) + IAuthorizationContext authorizationContext) { - _logger = logger; _authorizationContext = authorizationContext; - _config = config; - _sessionManager = sessionManager; - _networkManager = networkManager; } - public void Authenticate(IRequest request, IAuthenticationAttributes authAttribtues) + public AuthorizationInfo Authenticate(HttpRequest request) { - ValidateUser(request, authAttribtues); - } - - public User Authenticate(HttpRequest request, IAuthenticationAttributes authAttributes) - { - var req = new WebSocketSharpRequest(request, null, request.Path, _logger); - var user = ValidateUser(req, authAttributes); - return user; - } - - private User ValidateUser(IRequest request, IAuthenticationAttributes authAttribtues) - { - // This code is executed before the service var auth = _authorizationContext.GetAuthorizationInfo(request); - - if (!IsExemptFromAuthenticationToken(authAttribtues, request)) - { - ValidateSecurityToken(request, auth.Token); - } - - if (authAttribtues.AllowLocalOnly && !request.IsLocal) + if (auth?.User == null) { - throw new SecurityException("Operation not found."); + return null; } - var user = auth.User; - - if (user == null && auth.UserId != Guid.Empty) - { - throw new AuthenticationException("User with Id " + auth.UserId + " not found"); - } - - if (user != null) - { - ValidateUserAccess(user, request, authAttribtues, auth); - } - - var info = GetTokenInfo(request); - - if (!IsExemptFromRoles(auth, authAttribtues, request, info)) - { - var roles = authAttribtues.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 authAttribtues, - AuthorizationInfo auth) - { - if (user.Policy.IsDisabled) + if (auth.User.HasPermission(PermissionKind.IsDisabled)) { throw new SecurityException("User account has been disabled."); } - if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(request.RemoteIp)) - { - throw new SecurityException("User account has been disabled."); - } - - if (!user.Policy.IsAdministrator - && !authAttribtues.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; - } - - 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.Policy.IsAdministrator) - { - throw new SecurityException("User does not have admin access."); - } - } - - if (roles.Contains("delete", StringComparer.OrdinalIgnoreCase)) - { - if (user == null || !user.Policy.EnableContentDeletion) - { - throw new SecurityException("User does not have delete access."); - } - } - - if (roles.Contains("download", StringComparer.OrdinalIgnoreCase)) - { - if (user == null || !user.Policy.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."); - } - - //if (!string.IsNullOrEmpty(info.UserId)) - //{ - // var user = _userManager.GetUserById(info.UserId); - - // if (user == null || user.Configuration.IsDisabled) - // { - // throw new SecurityException("User account has been disabled."); - // } - //} + return auth; } } } diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs index 129faeaab..4b407dd9d 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs @@ -7,7 +7,7 @@ using System.Net; 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; namespace Emby.Server.Implementations.HttpServer.Security @@ -23,14 +23,9 @@ 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; } @@ -38,15 +33,39 @@ namespace Emby.Server.Implementations.HttpServer.Security return GetAuthorization(requestContext); } + public AuthorizationInfo GetAuthorizationInfo(HttpRequest requestContext) + { + var auth = GetAuthorizationDictionary(requestContext); + var (authInfo, _) = + GetAuthorizationInfoFromDictionary(auth, requestContext.Headers, requestContext.Query); + return authInfo; + } + /// <summary> /// Gets the authorization. /// </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.Request.Headers, httpReq.Request.Query); + + if (originalAuthInfo != null) + { + httpReq.Request.HttpContext.Items["OriginalAuthenticationInfo"] = originalAuthInfo; + } + httpReq.Request.HttpContext.Items["AuthorizationInfo"] = authInfo; + return authInfo; + } + + private (AuthorizationInfo authInfo, AuthenticationInfo originalAuthenticationInfo) GetAuthorizationInfoFromDictionary( + in Dictionary<string, string> auth, + in IHeaderDictionary headers, + in IQueryCollection queryString) + { string deviceId = null; string device = null; string client = null; @@ -64,19 +83,26 @@ namespace Emby.Server.Implementations.HttpServer.Security if (string.IsNullOrEmpty(token)) { - token = httpReq.Headers["X-Emby-Token"]; + token = headers["X-Emby-Token"]; + } + + if (string.IsNullOrEmpty(token)) + { + token = headers["X-MediaBrowser-Token"]; } if (string.IsNullOrEmpty(token)) { - token = httpReq.Headers["X-MediaBrowser-Token"]; + token = queryString["ApiKey"]; } + + // TODO deprecate this query parameter. if (string.IsNullOrEmpty(token)) { - token = httpReq.QueryString["api_key"]; + token = queryString["api_key"]; } - var info = new AuthorizationInfo + var authInfo = new AuthorizationInfo { Client = client, Device = device, @@ -85,6 +111,7 @@ namespace Emby.Server.Implementations.HttpServer.Security Token = token }; + AuthenticationInfo originalAuthenticationInfo = null; if (!string.IsNullOrWhiteSpace(token)) { var result = _authRepo.Get(new AuthenticationInfoQuery @@ -92,81 +119,94 @@ namespace Emby.Server.Implementations.HttpServer.Security AccessToken = token }); - var tokenInfo = result.Items.Count > 0 ? result.Items[0] : null; + originalAuthenticationInfo = result.Items.Count > 0 ? result.Items[0] : null; - if (tokenInfo != null) + if (originalAuthenticationInfo != null) { var updateToken = false; // TODO: Remove these checks for IsNullOrWhiteSpace - if (string.IsNullOrWhiteSpace(info.Client)) + if (string.IsNullOrWhiteSpace(authInfo.Client)) { - info.Client = tokenInfo.AppName; + authInfo.Client = originalAuthenticationInfo.AppName; } - if (string.IsNullOrWhiteSpace(info.DeviceId)) + if (string.IsNullOrWhiteSpace(authInfo.DeviceId)) { - info.DeviceId = tokenInfo.DeviceId; + authInfo.DeviceId = originalAuthenticationInfo.DeviceId; } // Temporary. TODO - allow clients to specify that the token has been shared with a casting device - var allowTokenInfoUpdate = info.Client == null || info.Client.IndexOf("chromecast", StringComparison.OrdinalIgnoreCase) == -1; + var allowTokenInfoUpdate = authInfo.Client == null || authInfo.Client.IndexOf("chromecast", StringComparison.OrdinalIgnoreCase) == -1; - if (string.IsNullOrWhiteSpace(info.Device)) + if (string.IsNullOrWhiteSpace(authInfo.Device)) { - info.Device = tokenInfo.DeviceName; + authInfo.Device = originalAuthenticationInfo.DeviceName; } - - else if (!string.Equals(info.Device, tokenInfo.DeviceName, StringComparison.OrdinalIgnoreCase)) + else if (!string.Equals(authInfo.Device, originalAuthenticationInfo.DeviceName, StringComparison.OrdinalIgnoreCase)) { if (allowTokenInfoUpdate) { updateToken = true; - tokenInfo.DeviceName = info.Device; + originalAuthenticationInfo.DeviceName = authInfo.Device; } } - if (string.IsNullOrWhiteSpace(info.Version)) + if (string.IsNullOrWhiteSpace(authInfo.Version)) { - info.Version = tokenInfo.AppVersion; + authInfo.Version = originalAuthenticationInfo.AppVersion; } - else if (!string.Equals(info.Version, tokenInfo.AppVersion, StringComparison.OrdinalIgnoreCase)) + else if (!string.Equals(authInfo.Version, originalAuthenticationInfo.AppVersion, StringComparison.OrdinalIgnoreCase)) { if (allowTokenInfoUpdate) { updateToken = true; - tokenInfo.AppVersion = info.Version; + originalAuthenticationInfo.AppVersion = authInfo.Version; } } - if ((DateTime.UtcNow - tokenInfo.DateLastActivity).TotalMinutes > 3) + if ((DateTime.UtcNow - originalAuthenticationInfo.DateLastActivity).TotalMinutes > 3) { - tokenInfo.DateLastActivity = DateTime.UtcNow; + originalAuthenticationInfo.DateLastActivity = DateTime.UtcNow; updateToken = true; } - if (!tokenInfo.UserId.Equals(Guid.Empty)) + if (!originalAuthenticationInfo.UserId.Equals(Guid.Empty)) { - info.User = _userManager.GetUserById(tokenInfo.UserId); + authInfo.User = _userManager.GetUserById(originalAuthenticationInfo.UserId); - if (info.User != null && !string.Equals(info.User.Name, tokenInfo.UserName, StringComparison.OrdinalIgnoreCase)) + if (authInfo.User != null && !string.Equals(authInfo.User.Username, originalAuthenticationInfo.UserName, StringComparison.OrdinalIgnoreCase)) { - tokenInfo.UserName = info.User.Name; + originalAuthenticationInfo.UserName = authInfo.User.Username; updateToken = true; } } if (updateToken) { - _authRepo.Update(tokenInfo); + _authRepo.Update(originalAuthenticationInfo); } } - httpReq.Items["OriginalAuthenticationInfo"] = tokenInfo; } - httpReq.Items["AuthorizationInfo"] = info; + return (authInfo, originalAuthenticationInfo); + } + + /// <summary> + /// Gets the auth. + /// </summary> + /// <param name="httpReq">The HTTP req.</param> + /// <returns>Dictionary{System.StringSystem.String}.</returns> + private Dictionary<string, string> GetAuthorizationDictionary(HttpContext httpReq) + { + var auth = httpReq.Request.Headers["X-Emby-Authorization"]; - return info; + if (string.IsNullOrEmpty(auth)) + { + auth = httpReq.Request.Headers[HeaderNames.Authorization]; + } + + return GetAuthorization(auth); } /// <summary> @@ -174,7 +214,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(IRequest httpReq) + private Dictionary<string, string> GetAuthorizationDictionary(HttpRequest httpReq) { var auth = httpReq.Headers["X-Emby-Authorization"]; @@ -236,12 +276,7 @@ namespace Emby.Server.Implementations.HttpServer.Security private static string NormalizeValue(string value) { - if (string.IsNullOrEmpty(value)) - { - return value; - } - - return WebUtility.HtmlEncode(value); + return string.IsNullOrEmpty(value) ? value : WebUtility.HtmlEncode(value); } } } diff --git a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs index 166952c64..86914dea2 100644 --- a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs @@ -1,12 +1,12 @@ #pragma warning disable CS1591 using System; -using MediaBrowser.Controller.Entities; +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,26 +23,20 @@ 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(), 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); @@ -51,7 +45,7 @@ namespace Emby.Server.Implementations.HttpServer.Security public User GetUser(object requestContext) { - return GetUser((IRequest)requestContext); + return GetUser((HttpContext)requestContext); } } } |
