Added API key authentication

pull/3113/head
Mark McDowall 11 years ago
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);

@ -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…
Cancel
Save