diff options
Diffstat (limited to 'Emby.Server.Implementations/HttpServer/Security')
3 files changed, 40 insertions, 470 deletions
diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs index 499a334fc..e2ad07177 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs @@ -1,239 +1,43 @@ -using System; -using System.Linq; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; +#pragma warning disable CS1591 + +using System.Threading.Tasks; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Authentication; 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 { public class AuthService : IAuthService { - private readonly IServerConfigurationManager _config; - - public AuthService(IUserManager userManager, IAuthorizationContext authorizationContext, IServerConfigurationManager config, ISessionManager sessionManager, INetworkManager networkManager) - { - AuthorizationContext = authorizationContext; - _config = config; - SessionManager = sessionManager; - UserManager = userManager; - NetworkManager = networkManager; - } - - public IUserManager UserManager { get; private set; } - public IAuthorizationContext AuthorizationContext { get; private set; } - public ISessionManager SessionManager { get; private set; } - public INetworkManager NetworkManager { get; private set; } - - /// <summary> - /// Redirect the client to a specific URL if authentication failed. - /// If this property is null, simply `401 Unauthorized` is returned. - /// </summary> - public string HtmlRedirect { get; set; } + private readonly IAuthorizationContext _authorizationContext; - public void Authenticate(IRequest request, IAuthenticationAttributes authAttribtues) + public AuthService( + IAuthorizationContext authorizationContext) { - ValidateUser(request, authAttribtues); + _authorizationContext = authorizationContext; } - private void ValidateUser(IRequest request, IAuthenticationAttributes authAttribtues) + public async Task<AuthorizationInfo> Authenticate(HttpRequest request) { - // This code is executed before the service - var auth = AuthorizationContext.GetAuthorizationInfo(request); + var auth = await _authorizationContext.GetAuthorizationInfo(request).ConfigureAwait(false); - if (!IsExemptFromAuthenticationToken(auth, authAttribtues, request)) + if (!auth.HasToken) { - ValidateSecurityToken(request, auth.Token); + throw new AuthenticationException("Request does not contain a token."); } - if (authAttribtues.AllowLocalOnly && !request.IsLocal) + if (!auth.IsAuthenticated) { - throw new SecurityException("Operation not found."); + throw new SecurityException("Invalid token."); } - var user = auth.User; - - if (user == null & !auth.UserId.Equals(Guid.Empty)) + if (auth.User?.HasPermission(PermissionKind.IsDisabled) ?? false) { - throw new SecurityException("User with Id " + auth.UserId + " not found"); + throw new SecurityException("User account has been disabled."); } - 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); - } - } - - private void ValidateUserAccess(User user, IRequest request, - IAuthenticationAttributes authAttribtues, - AuthorizationInfo auth) - { - if (user.Policy.IsDisabled) - { - throw new SecurityException("User account has been disabled.") - { - SecurityExceptionType = SecurityExceptionType.Unauthenticated - }; - } - - if (!user.Policy.EnableRemoteAccess && !NetworkManager.IsInLocalNetwork(request.RemoteIp)) - { - throw new SecurityException("User account has been disabled.") - { - SecurityExceptionType = SecurityExceptionType.Unauthenticated - }; - } - - if (!user.Policy.IsAdministrator && - !authAttribtues.EscapeParentalControl && - !user.IsParentalScheduleAllowed()) - { - request.Response.AddHeader("X-Application-Error-Code", "ParentalControl"); - - throw new SecurityException("This user account is not allowed access at this time.") - { - SecurityExceptionType = SecurityExceptionType.ParentalControl - }; - } - } - - private bool IsExemptFromAuthenticationToken(AuthorizationInfo auth, 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.") - { - SecurityExceptionType = SecurityExceptionType.Unauthenticated - }; - } - } - if (roles.Contains("delete", StringComparer.OrdinalIgnoreCase)) - { - if (user == null || !user.Policy.EnableContentDeletion) - { - throw new SecurityException("User does not have delete access.") - { - SecurityExceptionType = SecurityExceptionType.Unauthenticated - }; - } - } - if (roles.Contains("download", StringComparer.OrdinalIgnoreCase)) - { - if (user == null || !user.Policy.EnableContentDownloading) - { - throw new SecurityException("User does not have download access.") - { - SecurityExceptionType = SecurityExceptionType.Unauthenticated - }; - } - } - } - - 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 SecurityException("Access token is required."); - } - - var info = GetTokenInfo(request); - - if (info == null) - { - throw new SecurityException("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 deleted file mode 100644 index cab41e65b..000000000 --- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs +++ /dev/null @@ -1,237 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Security; -using MediaBrowser.Model.Services; - -namespace Emby.Server.Implementations.HttpServer.Security -{ - public class AuthorizationContext : IAuthorizationContext - { - private readonly IAuthenticationRepository _authRepo; - private readonly IUserManager _userManager; - - public AuthorizationContext(IAuthenticationRepository authRepo, IUserManager userManager) - { - _authRepo = authRepo; - _userManager = userManager; - } - - public AuthorizationInfo GetAuthorizationInfo(object requestContext) - { - return GetAuthorizationInfo((IRequest)requestContext); - } - - public AuthorizationInfo GetAuthorizationInfo(IRequest requestContext) - { - if (requestContext.Items.TryGetValue("AuthorizationInfo", out var cached)) - { - return (AuthorizationInfo)cached; - } - - return GetAuthorization(requestContext); - } - - /// <summary> - /// Gets the authorization. - /// </summary> - /// <param name="httpReq">The HTTP req.</param> - /// <returns>Dictionary{System.StringSystem.String}.</returns> - private AuthorizationInfo GetAuthorization(IRequest httpReq) - { - var auth = GetAuthorizationDictionary(httpReq); - - string deviceId = null; - string device = null; - string client = null; - string version = null; - string token = null; - - if (auth != null) - { - auth.TryGetValue("DeviceId", out deviceId); - auth.TryGetValue("Device", out device); - auth.TryGetValue("Client", out client); - auth.TryGetValue("Version", out version); - auth.TryGetValue("Token", out token); - } - - if (string.IsNullOrEmpty(token)) - { - token = httpReq.Headers["X-Emby-Token"]; - } - - if (string.IsNullOrEmpty(token)) - { - token = httpReq.Headers["X-MediaBrowser-Token"]; - } - if (string.IsNullOrEmpty(token)) - { - token = httpReq.QueryString["api_key"]; - } - - var info = new AuthorizationInfo - { - Client = client, - Device = device, - DeviceId = deviceId, - Version = version, - Token = token - }; - - if (!string.IsNullOrWhiteSpace(token)) - { - var result = _authRepo.Get(new AuthenticationInfoQuery - { - AccessToken = token - }); - - var tokenInfo = result.Items.Length > 0 ? result.Items[0] : null; - - if (tokenInfo != null) - { - var updateToken = false; - - // TODO: Remove these checks for IsNullOrWhiteSpace - if (string.IsNullOrWhiteSpace(info.Client)) - { - info.Client = tokenInfo.AppName; - } - - if (string.IsNullOrWhiteSpace(info.DeviceId)) - { - info.DeviceId = tokenInfo.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; - - if (string.IsNullOrWhiteSpace(info.Device)) - { - info.Device = tokenInfo.DeviceName; - } - - else if (!string.Equals(info.Device, tokenInfo.DeviceName, StringComparison.OrdinalIgnoreCase)) - { - if (allowTokenInfoUpdate) - { - updateToken = true; - tokenInfo.DeviceName = info.Device; - } - } - - if (string.IsNullOrWhiteSpace(info.Version)) - { - info.Version = tokenInfo.AppVersion; - } - else if (!string.Equals(info.Version, tokenInfo.AppVersion, StringComparison.OrdinalIgnoreCase)) - { - if (allowTokenInfoUpdate) - { - updateToken = true; - tokenInfo.AppVersion = info.Version; - } - } - - if ((DateTime.UtcNow - tokenInfo.DateLastActivity).TotalMinutes > 3) - { - tokenInfo.DateLastActivity = DateTime.UtcNow; - updateToken = true; - } - - if (!tokenInfo.UserId.Equals(Guid.Empty)) - { - info.User = _userManager.GetUserById(tokenInfo.UserId); - - if (info.User != null && !string.Equals(info.User.Name, tokenInfo.UserName, StringComparison.OrdinalIgnoreCase)) - { - tokenInfo.UserName = info.User.Name; - updateToken = true; - } - } - - if (updateToken) - { - _authRepo.Update(tokenInfo); - } - } - httpReq.Items["OriginalAuthenticationInfo"] = tokenInfo; - } - - httpReq.Items["AuthorizationInfo"] = info; - - return info; - } - - /// <summary> - /// Gets the auth. - /// </summary> - /// <param name="httpReq">The HTTP req.</param> - /// <returns>Dictionary{System.StringSystem.String}.</returns> - private Dictionary<string, string> GetAuthorizationDictionary(IRequest httpReq) - { - var auth = httpReq.Headers["X-Emby-Authorization"]; - - if (string.IsNullOrEmpty(auth)) - { - auth = httpReq.Headers["Authorization"]; - } - - return GetAuthorization(auth); - } - - /// <summary> - /// Gets the authorization. - /// </summary> - /// <param name="authorizationHeader">The authorization header.</param> - /// <returns>Dictionary{System.StringSystem.String}.</returns> - private Dictionary<string, string> GetAuthorization(string authorizationHeader) - { - if (authorizationHeader == null) return null; - - var parts = authorizationHeader.Split(new[] { ' ' }, 2); - - // There should be at least to parts - if (parts.Length != 2) return null; - - var acceptedNames = new[] { "MediaBrowser", "Emby" }; - - // It has to be a digest request - if (!acceptedNames.Contains(parts[0] ?? string.Empty, StringComparer.OrdinalIgnoreCase)) - { - return null; - } - - // Remove uptil the first space - authorizationHeader = parts[1]; - parts = authorizationHeader.Split(','); - - var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); - - foreach (var item in parts) - { - var param = item.Trim().Split(new[] { '=' }, 2); - - if (param.Length == 2) - { - var value = NormalizeValue(param[1].Trim(new[] { '"' })); - result.Add(param[0], value); - } - } - - return result; - } - - private static string NormalizeValue(string value) - { - if (string.IsNullOrEmpty(value)) - { - return value; - } - - return System.Net.WebUtility.HtmlEncode(value); - } - } -} diff --git a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs index 81e11d312..a7647caf9 100644 --- a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs +++ b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs @@ -1,10 +1,13 @@ +#pragma warning disable CS1591 + using System; -using MediaBrowser.Controller.Entities; +using System.Threading.Tasks; +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 { @@ -21,35 +24,35 @@ namespace Emby.Server.Implementations.HttpServer.Security _sessionManager = sessionManager; } - public SessionInfo GetSession(IRequest requestContext) + public async Task<SessionInfo> GetSession(HttpContext requestContext) { - var authorization = _authContext.GetAuthorizationInfo(requestContext); + var authorization = await _authContext.GetAuthorizationInfo(requestContext).ConfigureAwait(false); 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 await _sessionManager.LogSessionActivity( + authorization.Client, + authorization.Version, + authorization.DeviceId, + authorization.Device, + requestContext.GetNormalizedRemoteIp().ToString(), + user).ConfigureAwait(false); } - public SessionInfo GetSession(object requestContext) + public Task<SessionInfo> GetSession(object requestContext) { - return GetSession((IRequest)requestContext); + return GetSession((HttpContext)requestContext); } - public User GetUser(IRequest requestContext) + public async Task<User?> GetUser(HttpContext requestContext) { - var session = GetSession(requestContext); + var session = await GetSession(requestContext).ConfigureAwait(false); return session == null || session.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(session.UserId); } - public User GetUser(object requestContext) + public Task<User?> GetUser(object requestContext) { - return GetUser((IRequest)requestContext); + return GetUser(((HttpRequest)requestContext).HttpContext); } } } |
