Merge pull request #2861 from mark-monteiro/fix-auth-response-codes
Fix Auth Response Codes
This commit is contained in:
commit
97d7ffc458
|
@ -14,6 +14,7 @@ using Emby.Server.Implementations.Services;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
|
using MediaBrowser.Controller.Authentication;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Net;
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Model.Events;
|
using MediaBrowser.Model.Events;
|
||||||
|
@ -230,7 +231,8 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
switch (ex)
|
switch (ex)
|
||||||
{
|
{
|
||||||
case ArgumentException _: return 400;
|
case ArgumentException _: return 400;
|
||||||
case SecurityException _: return 401;
|
case AuthenticationException _: return 401;
|
||||||
|
case SecurityException _: return 403;
|
||||||
case DirectoryNotFoundException _:
|
case DirectoryNotFoundException _:
|
||||||
case FileNotFoundException _:
|
case FileNotFoundException _:
|
||||||
case ResourceNotFoundException _: return 404;
|
case ResourceNotFoundException _: return 404;
|
||||||
|
@ -239,55 +241,52 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ErrorHandler(Exception ex, IRequest httpReq, bool logExceptionStackTrace, string urlToLog)
|
private async Task ErrorHandler(Exception ex, IRequest httpReq, int statusCode, string urlToLog)
|
||||||
{
|
{
|
||||||
try
|
bool ignoreStackTrace =
|
||||||
|
ex is SocketException
|
||||||
|
|| ex is IOException
|
||||||
|
|| ex is OperationCanceledException
|
||||||
|
|| ex is SecurityException
|
||||||
|
|| ex is AuthenticationException
|
||||||
|
|| ex is FileNotFoundException;
|
||||||
|
|
||||||
|
if (ignoreStackTrace)
|
||||||
{
|
{
|
||||||
ex = GetActualException(ex);
|
_logger.LogError("Error processing request: {Message}. URL: {Url}", ex.Message.TrimEnd('.'), urlToLog);
|
||||||
|
|
||||||
if (logExceptionStackTrace)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Error processing request. URL: {Url}", urlToLog);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_logger.LogError("Error processing request: {Message}. URL: {Url}", ex.Message.TrimEnd('.'), urlToLog);
|
|
||||||
}
|
|
||||||
|
|
||||||
var httpRes = httpReq.Response;
|
|
||||||
|
|
||||||
if (httpRes.HasStarted)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var statusCode = GetStatusCode(ex);
|
|
||||||
httpRes.StatusCode = statusCode;
|
|
||||||
|
|
||||||
var errContent = NormalizeExceptionMessage(ex.Message);
|
|
||||||
httpRes.ContentType = "text/plain";
|
|
||||||
httpRes.ContentLength = errContent.Length;
|
|
||||||
await httpRes.WriteAsync(errContent).ConfigureAwait(false);
|
|
||||||
}
|
}
|
||||||
catch (Exception errorEx)
|
else
|
||||||
{
|
{
|
||||||
_logger.LogError(errorEx, "Error this.ProcessRequest(context)(Exception while writing error to the response). URL: {Url}", urlToLog);
|
_logger.LogError(ex, "Error processing request. URL: {Url}", urlToLog);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var httpRes = httpReq.Response;
|
||||||
|
|
||||||
|
if (httpRes.HasStarted)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
httpRes.StatusCode = statusCode;
|
||||||
|
|
||||||
|
var errContent = NormalizeExceptionMessage(ex) ?? string.Empty;
|
||||||
|
httpRes.ContentType = "text/plain";
|
||||||
|
httpRes.ContentLength = errContent.Length;
|
||||||
|
await httpRes.WriteAsync(errContent).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string NormalizeExceptionMessage(string msg)
|
private string NormalizeExceptionMessage(Exception ex)
|
||||||
{
|
{
|
||||||
if (msg == null)
|
// Do not expose the exception message for AuthenticationException
|
||||||
|
if (ex is AuthenticationException)
|
||||||
{
|
{
|
||||||
return string.Empty;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strip any information we don't want to reveal
|
// Strip any information we don't want to reveal
|
||||||
|
return ex.Message
|
||||||
msg = msg.Replace(_config.ApplicationPaths.ProgramSystemPath, string.Empty, StringComparison.OrdinalIgnoreCase);
|
?.Replace(_config.ApplicationPaths.ProgramSystemPath, string.Empty, StringComparison.OrdinalIgnoreCase)
|
||||||
msg = msg.Replace(_config.ApplicationPaths.ProgramDataPath, string.Empty, StringComparison.OrdinalIgnoreCase);
|
.Replace(_config.ApplicationPaths.ProgramDataPath, string.Empty, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
return msg;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -536,22 +535,32 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
throw new FileNotFoundException();
|
throw new FileNotFoundException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception requestEx)
|
||||||
{
|
{
|
||||||
// Do not handle exceptions manually when in development mode
|
try
|
||||||
// The framework-defined development exception page will be returned instead
|
|
||||||
if (_hostEnvironment.IsDevelopment())
|
|
||||||
{
|
{
|
||||||
throw;
|
var requestInnerEx = GetActualException(requestEx);
|
||||||
}
|
var statusCode = GetStatusCode(requestInnerEx);
|
||||||
|
|
||||||
bool ignoreStackTrace =
|
// Do not handle 500 server exceptions manually when in development mode
|
||||||
ex is SocketException
|
// The framework-defined development exception page will be returned instead
|
||||||
|| ex is IOException
|
if (statusCode == 500 && _hostEnvironment.IsDevelopment())
|
||||||
|| ex is OperationCanceledException
|
{
|
||||||
|| ex is SecurityException
|
throw;
|
||||||
|| ex is FileNotFoundException;
|
}
|
||||||
await ErrorHandler(ex, httpReq, !ignoreStackTrace, urlToLog).ConfigureAwait(false);
|
|
||||||
|
await ErrorHandler(requestInnerEx, httpReq, statusCode, urlToLog).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (Exception handlerException)
|
||||||
|
{
|
||||||
|
var aggregateEx = new AggregateException("Error while handling request exception", requestEx, handlerException);
|
||||||
|
_logger.LogError(aggregateEx, "Error while handling exception in response to {Url}", urlToLog);
|
||||||
|
|
||||||
|
if (_hostEnvironment.IsDevelopment())
|
||||||
|
{
|
||||||
|
throw aggregateEx;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Security.Authentication;
|
||||||
using Emby.Server.Implementations.SocketSharp;
|
using Emby.Server.Implementations.SocketSharp;
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
|
@ -68,7 +69,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||||
|
|
||||||
if (user == null && auth.UserId != Guid.Empty)
|
if (user == null && auth.UserId != Guid.Empty)
|
||||||
{
|
{
|
||||||
throw new SecurityException("User with Id " + auth.UserId + " not found");
|
throw new AuthenticationException("User with Id " + auth.UserId + " not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user != null)
|
if (user != null)
|
||||||
|
@ -108,18 +109,12 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||||
{
|
{
|
||||||
if (user.Policy.IsDisabled)
|
if (user.Policy.IsDisabled)
|
||||||
{
|
{
|
||||||
throw new SecurityException("User account has been disabled.")
|
throw new SecurityException("User account has been disabled.");
|
||||||
{
|
|
||||||
SecurityExceptionType = SecurityExceptionType.Unauthenticated
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(request.RemoteIp))
|
if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(request.RemoteIp))
|
||||||
{
|
{
|
||||||
throw new SecurityException("User account has been disabled.")
|
throw new SecurityException("User account has been disabled.");
|
||||||
{
|
|
||||||
SecurityExceptionType = SecurityExceptionType.Unauthenticated
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user.Policy.IsAdministrator
|
if (!user.Policy.IsAdministrator
|
||||||
|
@ -128,10 +123,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||||
{
|
{
|
||||||
request.Response.Headers.Add("X-Application-Error-Code", "ParentalControl");
|
request.Response.Headers.Add("X-Application-Error-Code", "ParentalControl");
|
||||||
|
|
||||||
throw new SecurityException("This user account is not allowed access at this time.")
|
throw new SecurityException("This user account is not allowed access at this time.");
|
||||||
{
|
|
||||||
SecurityExceptionType = SecurityExceptionType.ParentalControl
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,10 +182,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||||
{
|
{
|
||||||
if (user == null || !user.Policy.IsAdministrator)
|
if (user == null || !user.Policy.IsAdministrator)
|
||||||
{
|
{
|
||||||
throw new SecurityException("User does not have admin access.")
|
throw new SecurityException("User does not have admin access.");
|
||||||
{
|
|
||||||
SecurityExceptionType = SecurityExceptionType.Unauthenticated
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,10 +190,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||||
{
|
{
|
||||||
if (user == null || !user.Policy.EnableContentDeletion)
|
if (user == null || !user.Policy.EnableContentDeletion)
|
||||||
{
|
{
|
||||||
throw new SecurityException("User does not have delete access.")
|
throw new SecurityException("User does not have delete access.");
|
||||||
{
|
|
||||||
SecurityExceptionType = SecurityExceptionType.Unauthenticated
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,10 +198,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||||
{
|
{
|
||||||
if (user == null || !user.Policy.EnableContentDownloading)
|
if (user == null || !user.Policy.EnableContentDownloading)
|
||||||
{
|
{
|
||||||
throw new SecurityException("User does not have download access.")
|
throw new SecurityException("User does not have download access.");
|
||||||
{
|
|
||||||
SecurityExceptionType = SecurityExceptionType.Unauthenticated
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -230,14 +213,14 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(token))
|
if (string.IsNullOrEmpty(token))
|
||||||
{
|
{
|
||||||
throw new SecurityException("Access token is required.");
|
throw new AuthenticationException("Access token is required.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var info = GetTokenInfo(request);
|
var info = GetTokenInfo(request);
|
||||||
|
|
||||||
if (info == null)
|
if (info == null)
|
||||||
{
|
{
|
||||||
throw new SecurityException("Access token is invalid or expired.");
|
throw new AuthenticationException("Access token is invalid or expired.");
|
||||||
}
|
}
|
||||||
|
|
||||||
//if (!string.IsNullOrEmpty(info.UserId))
|
//if (!string.IsNullOrEmpty(info.UserId))
|
||||||
|
|
|
@ -47,7 +47,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
{
|
{
|
||||||
if (resolvedUser == null)
|
if (resolvedUser == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(resolvedUser));
|
throw new AuthenticationException($"Specified user does not exist.");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool success = false;
|
bool success = false;
|
||||||
|
|
|
@ -20,6 +20,7 @@ using MediaBrowser.Controller.Drawing;
|
||||||
using MediaBrowser.Controller.Dto;
|
using MediaBrowser.Controller.Dto;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Controller.Persistence;
|
using MediaBrowser.Controller.Persistence;
|
||||||
using MediaBrowser.Controller.Plugins;
|
using MediaBrowser.Controller.Plugins;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
|
@ -321,23 +322,19 @@ namespace Emby.Server.Implementations.Library
|
||||||
if (user.Policy.IsDisabled)
|
if (user.Policy.IsDisabled)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Authentication request for {UserName} has been denied because this account is currently disabled (IP: {IP}).", username, remoteEndPoint);
|
_logger.LogInformation("Authentication request for {UserName} has been denied because this account is currently disabled (IP: {IP}).", username, remoteEndPoint);
|
||||||
throw new AuthenticationException(
|
throw new SecurityException($"The {user.Name} account is currently disabled. Please consult with your administrator.");
|
||||||
string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
"The {0} account is currently disabled. Please consult with your administrator.",
|
|
||||||
user.Name));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint))
|
if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint))
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Authentication request for {UserName} forbidden: remote access disabled and user not in local network (IP: {IP}).", username, remoteEndPoint);
|
_logger.LogInformation("Authentication request for {UserName} forbidden: remote access disabled and user not in local network (IP: {IP}).", username, remoteEndPoint);
|
||||||
throw new AuthenticationException("Forbidden.");
|
throw new SecurityException("Forbidden.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user.IsParentalScheduleAllowed())
|
if (!user.IsParentalScheduleAllowed())
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Authentication request for {UserName} is not allowed at this time due parental restrictions (IP: {IP}).", username, remoteEndPoint);
|
_logger.LogInformation("Authentication request for {UserName} is not allowed at this time due parental restrictions (IP: {IP}).", username, remoteEndPoint);
|
||||||
throw new AuthenticationException("User is not allowed access at this time.");
|
throw new SecurityException("User is not allowed access at this time.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update LastActivityDate and LastLoginDate, then save
|
// Update LastActivityDate and LastLoginDate, then save
|
||||||
|
|
|
@ -1414,7 +1414,7 @@ namespace Emby.Server.Implementations.Session
|
||||||
if (user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
AuthenticationFailed?.Invoke(this, new GenericEventArgs<AuthenticationRequest>(request));
|
AuthenticationFailed?.Invoke(this, new GenericEventArgs<AuthenticationRequest>(request));
|
||||||
throw new SecurityException("Invalid username or password entered.");
|
throw new AuthenticationException("Invalid username or password entered.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(request.DeviceId)
|
if (!string.IsNullOrEmpty(request.DeviceId)
|
||||||
|
|
|
@ -426,7 +426,7 @@ namespace MediaBrowser.Api
|
||||||
catch (SecurityException e)
|
catch (SecurityException e)
|
||||||
{
|
{
|
||||||
// rethrow adding IP address to message
|
// rethrow adding IP address to message
|
||||||
throw new SecurityException($"[{Request.RemoteIp}] {e.Message}");
|
throw new SecurityException($"[{Request.RemoteIp}] {e.Message}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,23 +7,29 @@ namespace MediaBrowser.Controller.Authentication
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AuthenticationException : Exception
|
public class AuthenticationException : Exception
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AuthenticationException"/> class.
|
||||||
|
/// </summary>
|
||||||
public AuthenticationException() : base()
|
public AuthenticationException() : base()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AuthenticationException"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The message that describes the error.</param>
|
||||||
public AuthenticationException(string message) : base(message)
|
public AuthenticationException(string message) : base(message)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AuthenticationException"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The message that describes the error.</param>
|
||||||
|
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
|
||||||
public AuthenticationException(string message, Exception innerException)
|
public AuthenticationException(string message, Exception innerException)
|
||||||
: base(message, innerException)
|
: base(message, innerException)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,20 +2,36 @@ using System;
|
||||||
|
|
||||||
namespace MediaBrowser.Controller.Net
|
namespace MediaBrowser.Controller.Net
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The exception that is thrown when a user is authenticated, but not authorized to access a requested resource.
|
||||||
|
/// </summary>
|
||||||
public class SecurityException : Exception
|
public class SecurityException : Exception
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="SecurityException"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public SecurityException()
|
||||||
|
: base()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="SecurityException"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The message that describes the error.</param>
|
||||||
public SecurityException(string message)
|
public SecurityException(string message)
|
||||||
: base(message)
|
: base(message)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public SecurityExceptionType SecurityExceptionType { get; set; }
|
/// <summary>
|
||||||
}
|
/// Initializes a new instance of the <see cref="SecurityException"/> class.
|
||||||
|
/// </summary>
|
||||||
public enum SecurityExceptionType
|
/// <param name="message">The message that describes the error</param>
|
||||||
{
|
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
|
||||||
Unauthenticated = 0,
|
public SecurityException(string message, Exception innerException)
|
||||||
ParentalControl = 1
|
: base(message, innerException)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user