Added API key authentication
This commit is contained in:
parent
689f27bee6
commit
57fdbe6e08
|
@ -27,7 +27,10 @@ namespace NzbDrone.Api.Authentication
|
||||||
private Response RequiresAuthentication(NancyContext context)
|
private Response RequiresAuthentication(NancyContext context)
|
||||||
{
|
{
|
||||||
Response response = null;
|
Response response = null;
|
||||||
if (context.CurrentUser == null && _authenticationService.Enabled)
|
|
||||||
|
if (!context.Request.Path.StartsWith("/api/") &&
|
||||||
|
context.CurrentUser == null &&
|
||||||
|
_authenticationService.Enabled)
|
||||||
{
|
{
|
||||||
response = new Response { StatusCode = HttpStatusCode.Unauthorized };
|
response = new Response { StatusCode = HttpStatusCode.Unauthorized };
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
using System.Linq;
|
||||||
|
using Nancy;
|
||||||
|
using Nancy.Bootstrapper;
|
||||||
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
|
|
||||||
|
namespace NzbDrone.Api.Authentication
|
||||||
|
{
|
||||||
|
public interface IEnableStatelessAuthInNancy
|
||||||
|
{
|
||||||
|
void Register(IPipelines pipelines);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class EnableStatelessAuthInNancy : IEnableStatelessAuthInNancy
|
||||||
|
{
|
||||||
|
private readonly IConfigFileProvider _configFileProvider;
|
||||||
|
|
||||||
|
public EnableStatelessAuthInNancy(IConfigFileProvider configFileProvider)
|
||||||
|
{
|
||||||
|
_configFileProvider = configFileProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Register(IPipelines pipelines)
|
||||||
|
{
|
||||||
|
pipelines.BeforeRequest.AddItemToEndOfPipeline(ValidateApiKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Response ValidateApiKey(NancyContext context)
|
||||||
|
{
|
||||||
|
Response response = null;
|
||||||
|
var apiKey = context.Request.Headers["ApiKey"].FirstOrDefault();
|
||||||
|
|
||||||
|
if (!RuntimeInfo.IsProduction &&
|
||||||
|
(context.Request.UserHostAddress.Equals("localhost") ||
|
||||||
|
context.Request.UserHostAddress.Equals("127.0.0.1") ||
|
||||||
|
context.Request.UserHostAddress.Equals("::1")))
|
||||||
|
{
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.Request.Path.StartsWith("/api/") &&
|
||||||
|
(apiKey == null || !apiKey.Equals(_configFileProvider.ApiKey)))
|
||||||
|
{
|
||||||
|
response = new Response { StatusCode = HttpStatusCode.Unauthorized };
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,20 +1,28 @@
|
||||||
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Nancy;
|
using Nancy;
|
||||||
|
using Nancy.Responses;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common;
|
using NzbDrone.Common;
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
|
|
||||||
namespace NzbDrone.Api.Frontend.Mappers
|
namespace NzbDrone.Api.Frontend.Mappers
|
||||||
{
|
{
|
||||||
public class IndexHtmlMapper : StaticResourceMapperBase
|
public class IndexHtmlMapper : StaticResourceMapperBase
|
||||||
{
|
{
|
||||||
private readonly IDiskProvider _diskProvider;
|
private readonly IDiskProvider _diskProvider;
|
||||||
|
private readonly IConfigFileProvider _configFileProvider;
|
||||||
private readonly string _indexPath;
|
private readonly string _indexPath;
|
||||||
|
|
||||||
public IndexHtmlMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, Logger logger)
|
public IndexHtmlMapper(IAppFolderInfo appFolderInfo,
|
||||||
|
IDiskProvider diskProvider,
|
||||||
|
IConfigFileProvider configFileProvider,
|
||||||
|
Logger logger)
|
||||||
: base(diskProvider, logger)
|
: base(diskProvider, logger)
|
||||||
{
|
{
|
||||||
_diskProvider = diskProvider;
|
_diskProvider = diskProvider;
|
||||||
|
_configFileProvider = configFileProvider;
|
||||||
_indexPath = Path.Combine(appFolderInfo.StartUpFolder, "UI", "index.html");
|
_indexPath = Path.Combine(appFolderInfo.StartUpFolder, "UI", "index.html");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +38,21 @@ namespace NzbDrone.Api.Frontend.Mappers
|
||||||
|
|
||||||
public override Response GetResponse(string resourceUrl)
|
public override Response GetResponse(string resourceUrl)
|
||||||
{
|
{
|
||||||
|
string content;
|
||||||
var response = base.GetResponse(resourceUrl);
|
var response = base.GetResponse(resourceUrl);
|
||||||
|
var stream = new MemoryStream();
|
||||||
|
|
||||||
|
response.Contents.Invoke(stream);
|
||||||
|
stream.Position = 0;
|
||||||
|
|
||||||
|
using (var reader = new StreamReader(stream))
|
||||||
|
{
|
||||||
|
content = reader.ReadToEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
content = content.Replace("API_KEY", _configFileProvider.ApiKey);
|
||||||
|
|
||||||
|
response = new StreamResponse(() => StringToStream(content), response.ContentType);
|
||||||
response.Headers["X-UA-Compatible"] = "IE=edge";
|
response.Headers["X-UA-Compatible"] = "IE=edge";
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
|
@ -51,6 +73,5 @@ namespace NzbDrone.Api.Frontend.Mappers
|
||||||
|
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -24,13 +24,10 @@ namespace NzbDrone.Api.Frontend.Mappers
|
||||||
{
|
{
|
||||||
_caseSensitive = true;
|
_caseSensitive = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract string Map(string resourceUrl);
|
protected abstract string Map(string resourceUrl);
|
||||||
|
|
||||||
|
|
||||||
public abstract bool CanHandle(string resourceUrl);
|
public abstract bool CanHandle(string resourceUrl);
|
||||||
|
|
||||||
public virtual Response GetResponse(string resourceUrl)
|
public virtual Response GetResponse(string resourceUrl)
|
||||||
|
|
|
@ -31,6 +31,7 @@ namespace NzbDrone.Api
|
||||||
|
|
||||||
container.Resolve<DatabaseTarget>().Register();
|
container.Resolve<DatabaseTarget>().Register();
|
||||||
container.Resolve<IEnableBasicAuthInNancy>().Register(pipelines);
|
container.Resolve<IEnableBasicAuthInNancy>().Register(pipelines);
|
||||||
|
container.Resolve<IEnableStatelessAuthInNancy>().Register(pipelines);
|
||||||
container.Resolve<IEventAggregator>().PublishEvent(new ApplicationStartedEvent());
|
container.Resolve<IEventAggregator>().PublishEvent(new ApplicationStartedEvent());
|
||||||
|
|
||||||
ApplicationPipelines.OnError.AddItemToEndOfPipeline(container.Resolve<NzbDroneErrorPipeline>().HandleException);
|
ApplicationPipelines.OnError.AddItemToEndOfPipeline(container.Resolve<NzbDroneErrorPipeline>().HandleException);
|
||||||
|
|
|
@ -74,6 +74,7 @@
|
||||||
<Link>Properties\SharedAssemblyInfo.cs</Link>
|
<Link>Properties\SharedAssemblyInfo.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="Authentication\AuthenticationService.cs" />
|
<Compile Include="Authentication\AuthenticationService.cs" />
|
||||||
|
<Compile Include="Authentication\EnableStatelessAuthInNancy.cs" />
|
||||||
<Compile Include="Authentication\EnableBasicAuthInNancy.cs" />
|
<Compile Include="Authentication\EnableBasicAuthInNancy.cs" />
|
||||||
<Compile Include="Authentication\NzbDroneUser.cs" />
|
<Compile Include="Authentication\NzbDroneUser.cs" />
|
||||||
<Compile Include="Calendar\CalendarModule.cs" />
|
<Compile Include="Calendar\CalendarModule.cs" />
|
||||||
|
|
|
@ -26,6 +26,7 @@ namespace NzbDrone.Core.Configuration
|
||||||
string Password { get; }
|
string Password { get; }
|
||||||
string LogLevel { get; }
|
string LogLevel { get; }
|
||||||
string Branch { get; }
|
string Branch { get; }
|
||||||
|
string ApiKey { get; }
|
||||||
bool Torrent { get; }
|
bool Torrent { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,6 +96,14 @@ namespace NzbDrone.Core.Configuration
|
||||||
get { return GetValueBoolean("LaunchBrowser", true); }
|
get { return GetValueBoolean("LaunchBrowser", true); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string ApiKey
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return GetValue("ApiKey", Guid.NewGuid().ToString().Replace("-", ""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool Torrent
|
public bool Torrent
|
||||||
{
|
{
|
||||||
get { return GetValueBoolean("Torrent", false, persist: false); }
|
get { return GetValueBoolean("Torrent", false, persist: false); }
|
||||||
|
|
|
@ -20,9 +20,15 @@ define(function () {
|
||||||
|
|
||||||
delete xhr.data;
|
delete xhr.data;
|
||||||
}
|
}
|
||||||
|
if (xhr) {
|
||||||
|
if (!xhr.headers) {
|
||||||
|
xhr.headers = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
xhr.headers["ApiKey"] = window.NzbDrone.ApiKey;
|
||||||
|
}
|
||||||
|
|
||||||
return original.apply(this, arguments);
|
return original.apply(this, arguments);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
});
|
});
|
|
@ -1,10 +1,12 @@
|
||||||
window.NzbDrone = {};
|
|
||||||
window.NzbDrone.ApiRoot = '/api';
|
window.NzbDrone.ApiRoot = '/api';
|
||||||
|
|
||||||
var statusText = $.ajax({
|
var statusText = $.ajax({
|
||||||
type : 'GET',
|
type : 'GET',
|
||||||
url : window.NzbDrone.ApiRoot + '/system/status',
|
url : window.NzbDrone.ApiRoot + '/system/status',
|
||||||
async: false
|
async: false,
|
||||||
|
headers: {
|
||||||
|
ApiKey: window.NzbDrone.ApiKey
|
||||||
|
}
|
||||||
}).responseText;
|
}).responseText;
|
||||||
|
|
||||||
window.NzbDrone.ServerStatus = JSON.parse(statusText);
|
window.NzbDrone.ServerStatus = JSON.parse(statusText);
|
||||||
|
|
15
UI/app.js
15
UI/app.js
|
@ -33,12 +33,19 @@ require.config({
|
||||||
$: {
|
$: {
|
||||||
exports: '$',
|
exports: '$',
|
||||||
|
|
||||||
init: function () {
|
deps :
|
||||||
|
[
|
||||||
|
'Mixins/jquery.ajax'
|
||||||
|
],
|
||||||
|
|
||||||
|
init: function (AjaxMixin) {
|
||||||
require(
|
require(
|
||||||
[
|
[
|
||||||
'jQuery/ToTheTop',
|
'jQuery/ToTheTop',
|
||||||
'Instrumentation/ErrorHandler'
|
'Instrumentation/ErrorHandler'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
AjaxMixin.apply($);
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
@ -75,14 +82,10 @@ require.config({
|
||||||
backbone: {
|
backbone: {
|
||||||
deps :
|
deps :
|
||||||
[
|
[
|
||||||
'Mixins/backbone.ajax',
|
|
||||||
'underscore',
|
'underscore',
|
||||||
'$'
|
'$'
|
||||||
],
|
],
|
||||||
exports: 'Backbone',
|
exports: 'Backbone'
|
||||||
init : function (AjaxMixin) {
|
|
||||||
AjaxMixin.apply(Backbone);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -60,6 +60,12 @@
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
window.NzbDrone = {};
|
||||||
|
window.NzbDrone.ApiKey = 'API_KEY';
|
||||||
|
</script>
|
||||||
|
|
||||||
<script src="/polyfills.js"></script>
|
<script src="/polyfills.js"></script>
|
||||||
<script src="/JsLibraries/jquery.js"></script>
|
<script src="/JsLibraries/jquery.js"></script>
|
||||||
<script src="/JsLibraries/messenger.js"></script>
|
<script src="/JsLibraries/messenger.js"></script>
|
||||||
|
|
Loading…
Reference in New Issue