Added API key authentication

pull/30/head
Mark McDowall 11 years ago
parent 689f27bee6
commit 57fdbe6e08

@ -27,7 +27,10 @@ namespace NzbDrone.Api.Authentication
private Response RequiresAuthentication(NancyContext context)
{
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 };
}

@ -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 Nancy;
using Nancy.Responses;
using NLog;
using NzbDrone.Common;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Api.Frontend.Mappers
{
public class IndexHtmlMapper : StaticResourceMapperBase
{
private readonly IDiskProvider _diskProvider;
private readonly IConfigFileProvider _configFileProvider;
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)
{
_diskProvider = diskProvider;
_configFileProvider = configFileProvider;
_indexPath = Path.Combine(appFolderInfo.StartUpFolder, "UI", "index.html");
}
@ -30,7 +38,21 @@ namespace NzbDrone.Api.Frontend.Mappers
public override Response GetResponse(string resourceUrl)
{
string content;
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";
return response;
@ -51,6 +73,5 @@ namespace NzbDrone.Api.Frontend.Mappers
return text;
}
}
}

@ -24,13 +24,10 @@ namespace NzbDrone.Api.Frontend.Mappers
{
_caseSensitive = true;
}
}
protected abstract string Map(string resourceUrl);
public abstract bool CanHandle(string resourceUrl);
public virtual Response GetResponse(string resourceUrl)

@ -31,6 +31,7 @@ namespace NzbDrone.Api
container.Resolve<DatabaseTarget>().Register();
container.Resolve<IEnableBasicAuthInNancy>().Register(pipelines);
container.Resolve<IEnableStatelessAuthInNancy>().Register(pipelines);
container.Resolve<IEventAggregator>().PublishEvent(new ApplicationStartedEvent());
ApplicationPipelines.OnError.AddItemToEndOfPipeline(container.Resolve<NzbDroneErrorPipeline>().HandleException);

@ -74,6 +74,7 @@
<Link>Properties\SharedAssemblyInfo.cs</Link>
</Compile>
<Compile Include="Authentication\AuthenticationService.cs" />
<Compile Include="Authentication\EnableStatelessAuthInNancy.cs" />
<Compile Include="Authentication\EnableBasicAuthInNancy.cs" />
<Compile Include="Authentication\NzbDroneUser.cs" />
<Compile Include="Calendar\CalendarModule.cs" />

@ -26,6 +26,7 @@ namespace NzbDrone.Core.Configuration
string Password { get; }
string LogLevel { get; }
string Branch { get; }
string ApiKey { get; }
bool Torrent { get; }
}
@ -95,6 +96,14 @@ namespace NzbDrone.Core.Configuration
get { return GetValueBoolean("LaunchBrowser", true); }
}
public string ApiKey
{
get
{
return GetValue("ApiKey", Guid.NewGuid().ToString().Replace("-", ""));
}
}
public bool Torrent
{
get { return GetValueBoolean("Torrent", false, persist: false); }

@ -20,9 +20,15 @@ define(function () {
delete xhr.data;
}
if (xhr) {
if (!xhr.headers) {
xhr.headers = {};
}
xhr.headers["ApiKey"] = window.NzbDrone.ApiKey;
}
return original.apply(this, arguments);
};
};
});

@ -1,10 +1,12 @@
window.NzbDrone = {};
window.NzbDrone.ApiRoot = '/api';
var statusText = $.ajax({
type : 'GET',
url : window.NzbDrone.ApiRoot + '/system/status',
async: false
async: false,
headers: {
ApiKey: window.NzbDrone.ApiKey
}
}).responseText;
window.NzbDrone.ServerStatus = JSON.parse(statusText);

@ -33,12 +33,19 @@ require.config({
$: {
exports: '$',
init: function () {
deps :
[
'Mixins/jquery.ajax'
],
init: function (AjaxMixin) {
require(
[
'jQuery/ToTheTop',
'Instrumentation/ErrorHandler'
]);
AjaxMixin.apply($);
}
},
@ -75,14 +82,10 @@ require.config({
backbone: {
deps :
[
'Mixins/backbone.ajax',
'underscore',
'$'
],
exports: 'Backbone',
init : function (AjaxMixin) {
AjaxMixin.apply(Backbone);
}
exports: 'Backbone'
},

@ -60,6 +60,12 @@
</div>
</footer>
</body>
<script type="text/javascript">
window.NzbDrone = {};
window.NzbDrone.ApiKey = 'API_KEY';
</script>
<script src="/polyfills.js"></script>
<script src="/JsLibraries/jquery.js"></script>
<script src="/JsLibraries/messenger.js"></script>

Loading…
Cancel
Save