parent
2238ac5d17
commit
95ee7daf21
|
@ -57,6 +57,8 @@ namespace NzbDrone.Common.Instrumentation
|
||||||
RegisterAppFile(appFolderInfo);
|
RegisterAppFile(appFolderInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RegisterAuthLogger();
|
||||||
|
|
||||||
LogManager.ReconfigExistingLoggers();
|
LogManager.ReconfigExistingLoggers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,6 +197,23 @@ namespace NzbDrone.Common.Instrumentation
|
||||||
LogManager.Configuration.LoggingRules.Add(loggingRule);
|
LogManager.Configuration.LoggingRules.Add(loggingRule);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void RegisterAuthLogger()
|
||||||
|
{
|
||||||
|
var consoleTarget = LogManager.Configuration.FindTargetByName("console");
|
||||||
|
var fileTarget = LogManager.Configuration.FindTargetByName("appFileInfo");
|
||||||
|
|
||||||
|
var target = consoleTarget ?? fileTarget ?? new NullTarget();
|
||||||
|
|
||||||
|
// Send Auth to Console and info app file, but not the log database
|
||||||
|
var rule = new LoggingRule("Auth", LogLevel.Info, target) { Final = true };
|
||||||
|
if (consoleTarget != null && fileTarget != null)
|
||||||
|
{
|
||||||
|
rule.Targets.Add(fileTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
LogManager.Configuration.LoggingRules.Insert(0, rule);
|
||||||
|
}
|
||||||
|
|
||||||
public static Logger GetLogger(Type obj)
|
public static Logger GetLogger(Type obj)
|
||||||
{
|
{
|
||||||
return LogManager.GetLogger(obj.Name.Replace("NzbDrone.", ""));
|
return LogManager.GetLogger(obj.Name.Replace("NzbDrone.", ""));
|
||||||
|
|
|
@ -3,6 +3,8 @@ using Nancy;
|
||||||
using Nancy.Authentication.Forms;
|
using Nancy.Authentication.Forms;
|
||||||
using Nancy.Extensions;
|
using Nancy.Extensions;
|
||||||
using Nancy.ModelBinding;
|
using Nancy.ModelBinding;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Instrumentation;
|
||||||
using NzbDrone.Core.Authentication;
|
using NzbDrone.Core.Authentication;
|
||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Core.Configuration;
|
||||||
|
|
||||||
|
@ -10,12 +12,12 @@ namespace Sonarr.Http.Authentication
|
||||||
{
|
{
|
||||||
public class AuthenticationModule : NancyModule
|
public class AuthenticationModule : NancyModule
|
||||||
{
|
{
|
||||||
private readonly IUserService _userService;
|
private readonly IAuthenticationService _authService;
|
||||||
private readonly IConfigFileProvider _configFileProvider;
|
private readonly IConfigFileProvider _configFileProvider;
|
||||||
|
|
||||||
public AuthenticationModule(IUserService userService, IConfigFileProvider configFileProvider)
|
public AuthenticationModule(IAuthenticationService authService, IConfigFileProvider configFileProvider)
|
||||||
{
|
{
|
||||||
_userService = userService;
|
_authService = authService;
|
||||||
_configFileProvider = configFileProvider;
|
_configFileProvider = configFileProvider;
|
||||||
Post["/login"] = x => Login(this.Bind<LoginResource>());
|
Post["/login"] = x => Login(this.Bind<LoginResource>());
|
||||||
Get["/logout"] = x => Logout();
|
Get["/logout"] = x => Logout();
|
||||||
|
@ -23,7 +25,7 @@ namespace Sonarr.Http.Authentication
|
||||||
|
|
||||||
private Response Login(LoginResource resource)
|
private Response Login(LoginResource resource)
|
||||||
{
|
{
|
||||||
var user = _userService.FindUser(resource.Username, resource.Password);
|
var user = _authService.Login(Context, resource.Username, resource.Password);
|
||||||
|
|
||||||
if (user == null)
|
if (user == null)
|
||||||
{
|
{
|
||||||
|
@ -43,6 +45,8 @@ namespace Sonarr.Http.Authentication
|
||||||
|
|
||||||
private Response Logout()
|
private Response Logout()
|
||||||
{
|
{
|
||||||
|
_authService.Logout(Context);
|
||||||
|
|
||||||
return this.LogoutAndRedirect(_configFileProvider.UrlBase + "/");
|
return this.LogoutAndRedirect(_configFileProvider.UrlBase + "/");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,9 @@ using Nancy;
|
||||||
using Nancy.Authentication.Basic;
|
using Nancy.Authentication.Basic;
|
||||||
using Nancy.Authentication.Forms;
|
using Nancy.Authentication.Forms;
|
||||||
using Nancy.Security;
|
using Nancy.Security;
|
||||||
|
using NLog;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Common.Instrumentation;
|
||||||
using NzbDrone.Core.Authentication;
|
using NzbDrone.Core.Authentication;
|
||||||
using NzbDrone.Core.Configuration;
|
using NzbDrone.Core.Configuration;
|
||||||
using Sonarr.Http.Extensions;
|
using Sonarr.Http.Extensions;
|
||||||
|
@ -13,24 +15,75 @@ namespace Sonarr.Http.Authentication
|
||||||
{
|
{
|
||||||
public interface IAuthenticationService : IUserValidator, IUserMapper
|
public interface IAuthenticationService : IUserValidator, IUserMapper
|
||||||
{
|
{
|
||||||
|
void SetContext(NancyContext context);
|
||||||
|
|
||||||
|
void LogUnauthorized(NancyContext context);
|
||||||
|
User Login(NancyContext context, string username, string password);
|
||||||
|
void Logout(NancyContext context);
|
||||||
bool IsAuthenticated(NancyContext context);
|
bool IsAuthenticated(NancyContext context);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AuthenticationService : IAuthenticationService
|
public class AuthenticationService : IAuthenticationService
|
||||||
{
|
{
|
||||||
private readonly IUserService _userService;
|
private static readonly Logger _authLogger = LogManager.GetLogger("Auth");
|
||||||
private static readonly NzbDroneUser AnonymousUser = new NzbDroneUser { UserName = "Anonymous" };
|
private static readonly NzbDroneUser AnonymousUser = new NzbDroneUser { UserName = "Anonymous" };
|
||||||
|
private readonly IUserService _userService;
|
||||||
|
private readonly NancyContext _nancyContext;
|
||||||
|
|
||||||
private static string API_KEY;
|
private static string API_KEY;
|
||||||
private static AuthenticationType AUTH_METHOD;
|
private static AuthenticationType AUTH_METHOD;
|
||||||
|
|
||||||
public AuthenticationService(IConfigFileProvider configFileProvider, IUserService userService)
|
[ThreadStatic]
|
||||||
|
private static NancyContext _context;
|
||||||
|
|
||||||
|
public AuthenticationService(IConfigFileProvider configFileProvider, IUserService userService, NancyContext nancyContext)
|
||||||
{
|
{
|
||||||
_userService = userService;
|
_userService = userService;
|
||||||
|
_nancyContext = nancyContext;
|
||||||
API_KEY = configFileProvider.ApiKey;
|
API_KEY = configFileProvider.ApiKey;
|
||||||
AUTH_METHOD = configFileProvider.AuthenticationMethod;
|
AUTH_METHOD = configFileProvider.AuthenticationMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetContext(NancyContext context)
|
||||||
|
{
|
||||||
|
// Validate and GetUserIdentifier don't have access to the NancyContext so get it from the pipeline earlier
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public User Login(NancyContext context, string username, string password)
|
||||||
|
{
|
||||||
|
if (AUTH_METHOD == AuthenticationType.None)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var user = _userService.FindUser(username, password);
|
||||||
|
|
||||||
|
if (user != null)
|
||||||
|
{
|
||||||
|
LogSuccess(context, username);
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogFailure(context, username);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Logout(NancyContext context)
|
||||||
|
{
|
||||||
|
if (AUTH_METHOD == AuthenticationType.None)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.CurrentUser != null)
|
||||||
|
{
|
||||||
|
LogLogout(context, context.CurrentUser.UserName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public IUserIdentity Validate(string username, string password)
|
public IUserIdentity Validate(string username, string password)
|
||||||
{
|
{
|
||||||
if (AUTH_METHOD == AuthenticationType.None)
|
if (AUTH_METHOD == AuthenticationType.None)
|
||||||
|
@ -42,9 +95,17 @@ namespace Sonarr.Http.Authentication
|
||||||
|
|
||||||
if (user != null)
|
if (user != null)
|
||||||
{
|
{
|
||||||
|
if (AUTH_METHOD != AuthenticationType.Basic)
|
||||||
|
{
|
||||||
|
// Don't log success for basic auth
|
||||||
|
LogSuccess(_context, username);
|
||||||
|
}
|
||||||
|
|
||||||
return new NzbDroneUser { UserName = user.Username };
|
return new NzbDroneUser { UserName = user.Username };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LogFailure(_context, username);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,6 +123,8 @@ namespace Sonarr.Http.Authentication
|
||||||
return new NzbDroneUser { UserName = user.Username };
|
return new NzbDroneUser { UserName = user.Username };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LogInvalidated(_context);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,5 +201,30 @@ namespace Sonarr.Http.Authentication
|
||||||
|
|
||||||
return context.Request.Headers.Authorization;
|
return context.Request.Headers.Authorization;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void LogUnauthorized(NancyContext context)
|
||||||
|
{
|
||||||
|
_authLogger.Info("Auth-Unauthorized ip {0} url '{1}'", context.Request.UserHostAddress, context.Request.Url.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LogInvalidated(NancyContext context)
|
||||||
|
{
|
||||||
|
_authLogger.Info("Auth-Invalidated ip {0}", context.Request.UserHostAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LogFailure(NancyContext context, string username)
|
||||||
|
{
|
||||||
|
_authLogger.Warn("Auth-Failure ip {0} username '{1}'", context.Request.UserHostAddress, username);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LogSuccess(NancyContext context, string username)
|
||||||
|
{
|
||||||
|
_authLogger.Info("Auth-Success ip {0} username '{1}'", context.Request.UserHostAddress, username);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LogLogout(NancyContext context, string username)
|
||||||
|
{
|
||||||
|
_authLogger.Info("Auth-Logout ip {0} username '{1}'", context.Request.UserHostAddress, username);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,18 +43,28 @@ namespace Sonarr.Http.Authentication
|
||||||
else if (_configFileProvider.AuthenticationMethod == AuthenticationType.Basic)
|
else if (_configFileProvider.AuthenticationMethod == AuthenticationType.Basic)
|
||||||
{
|
{
|
||||||
pipelines.EnableBasicAuthentication(new BasicAuthenticationConfiguration(_authenticationService, "Sonarr"));
|
pipelines.EnableBasicAuthentication(new BasicAuthenticationConfiguration(_authenticationService, "Sonarr"));
|
||||||
|
pipelines.BeforeRequest.AddItemToStartOfPipeline(CaptureContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
pipelines.BeforeRequest.AddItemToEndOfPipeline((Func<NancyContext, Response>)RequiresAuthentication);
|
pipelines.BeforeRequest.AddItemToEndOfPipeline((Func<NancyContext, Response>)RequiresAuthentication);
|
||||||
pipelines.AfterRequest.AddItemToEndOfPipeline((Action<NancyContext>)RemoveLoginHooksForApiCalls);
|
pipelines.AfterRequest.AddItemToEndOfPipeline((Action<NancyContext>)RemoveLoginHooksForApiCalls);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Response CaptureContext(NancyContext context)
|
||||||
|
{
|
||||||
|
_authenticationService.SetContext(context);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private Response RequiresAuthentication(NancyContext context)
|
private Response RequiresAuthentication(NancyContext context)
|
||||||
{
|
{
|
||||||
Response response = null;
|
Response response = null;
|
||||||
|
|
||||||
if (!_authenticationService.IsAuthenticated(context))
|
if (!_authenticationService.IsAuthenticated(context))
|
||||||
{
|
{
|
||||||
|
_authenticationService.LogUnauthorized(context);
|
||||||
response = new Response { StatusCode = HttpStatusCode.Unauthorized };
|
response = new Response { StatusCode = HttpStatusCode.Unauthorized };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue