From ce842644904d5b7c5b2a009affad6d563e973a18 Mon Sep 17 00:00:00 2001 From: flightlevel Date: Sat, 12 May 2018 12:44:47 +1000 Subject: [PATCH] Add Authorization --- .../Controllers/BlackholeController.cs | 3 +- .../Controllers/DownloadController.cs | 3 +- .../Controllers/IndexerApiController.cs | 1 - .../Controllers/ResultsController.cs | 9 +- .../ServerConfigurationController.cs | 1 - .../Controllers/UIController.cs | 109 ++++++++++-------- .../Middleware/RedirectRules.cs | 6 +- src/Jackett.Server/Startup.cs | 30 ++++- 8 files changed, 100 insertions(+), 62 deletions(-) diff --git a/src/Jackett.Server/Controllers/BlackholeController.cs b/src/Jackett.Server/Controllers/BlackholeController.cs index 68fa777ea..e71b3b741 100644 --- a/src/Jackett.Server/Controllers/BlackholeController.cs +++ b/src/Jackett.Server/Controllers/BlackholeController.cs @@ -1,6 +1,7 @@ using Jackett.Common.Models.Config; using Jackett.Common.Services.Interfaces; using Jackett.Common.Utils; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json.Linq; using NLog; @@ -11,7 +12,7 @@ using System.Threading.Tasks; namespace Jackett.Server.Controllers { - //[AllowAnonymous] + [AllowAnonymous] [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] [Route("bh/{indexerID}")] public class BlackholeController : Controller diff --git a/src/Jackett.Server/Controllers/DownloadController.cs b/src/Jackett.Server/Controllers/DownloadController.cs index 16da73ce8..c7db678d2 100644 --- a/src/Jackett.Server/Controllers/DownloadController.cs +++ b/src/Jackett.Server/Controllers/DownloadController.cs @@ -2,6 +2,7 @@ using Jackett.Common.Models.Config; using Jackett.Common.Services.Interfaces; using Jackett.Common.Utils; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.WebUtilities; using NLog; @@ -11,7 +12,7 @@ using System.Threading.Tasks; namespace Jackett.Server.Controllers { - //[AllowAnonymous] + [AllowAnonymous] [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] [Route("dl/{indexerID}")] public class DownloadController : Controller diff --git a/src/Jackett.Server/Controllers/IndexerApiController.cs b/src/Jackett.Server/Controllers/IndexerApiController.cs index 093a190e8..8e740266a 100644 --- a/src/Jackett.Server/Controllers/IndexerApiController.cs +++ b/src/Jackett.Server/Controllers/IndexerApiController.cs @@ -54,7 +54,6 @@ namespace Jackett.Server.Controllers } [Route("api/v2.0/indexers")] - //[JackettAuthorized] [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] public class IndexerApiController : Controller, IIndexerController { diff --git a/src/Jackett.Server/Controllers/ResultsController.cs b/src/Jackett.Server/Controllers/ResultsController.cs index 4c27e24cc..d4493473f 100644 --- a/src/Jackett.Server/Controllers/ResultsController.cs +++ b/src/Jackett.Server/Controllers/ResultsController.cs @@ -5,6 +5,7 @@ using Jackett.Common.Models; using Jackett.Common.Models.DTO; using Jackett.Common.Services.Interfaces; using Jackett.Common.Utils; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using NLog; @@ -155,7 +156,7 @@ namespace Jackett.Server.Controllers TorznabQuery CurrentQuery { get; set; } } - //[AllowAnonymous] + [AllowAnonymous] [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] [Route("api/v2.0/indexers/{indexerId}/results")] [TypeFilter(typeof(RequiresApiKey))] @@ -166,6 +167,9 @@ namespace Jackett.Server.Controllers public IIndexerManagerService IndexerService { get; private set; } public IIndexer CurrentIndexer { get; set; } public TorznabQuery CurrentQuery { get; set; } + private Logger logger; + private IServerService serverService; + private ICacheService cacheService; public ResultsController(IIndexerManagerService indexerManagerService, IServerService ss, ICacheService c, Logger logger) { @@ -481,8 +485,5 @@ namespace Jackett.Server.Controllers } } - private Logger logger; - private IServerService serverService; - private ICacheService cacheService; } } diff --git a/src/Jackett.Server/Controllers/ServerConfigurationController.cs b/src/Jackett.Server/Controllers/ServerConfigurationController.cs index 422c2f912..c37c84f6b 100644 --- a/src/Jackett.Server/Controllers/ServerConfigurationController.cs +++ b/src/Jackett.Server/Controllers/ServerConfigurationController.cs @@ -12,7 +12,6 @@ using System.Linq; namespace Jackett.Server.Controllers { [Route("api/v2.0/server/[action]")] - //[JackettAuthorized] [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] public class ServerConfigurationController : Controller { diff --git a/src/Jackett.Server/Controllers/UIController.cs b/src/Jackett.Server/Controllers/UIController.cs index 8221325d5..43b49c215 100644 --- a/src/Jackett.Server/Controllers/UIController.cs +++ b/src/Jackett.Server/Controllers/UIController.cs @@ -1,18 +1,19 @@ using Jackett.Common.Models.Config; using Jackett.Common.Services.Interfaces; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using MimeMapping; using NLog; -using System.IO; -using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; using System.Threading.Tasks; namespace Jackett.Server.Controllers { [Route("UI/[action]")] - //[JackettAuthorized] [ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)] public class WebUIController : Controller { @@ -29,66 +30,76 @@ namespace Jackett.Server.Controllers logger = l; } - private HttpResponseMessage GetFile(string path) + [HttpGet] + [AllowAnonymous] + public async Task Login() { - var result = new HttpResponseMessage(HttpStatusCode.OK); - var mappedPath = Path.Combine(config.GetContentFolder(), path); - var stream = new FileStream(mappedPath, FileMode.Open, FileAccess.Read, FileShare.Read); - result.Content = new StreamContent(stream); - result.Content.Headers.ContentType = new MediaTypeHeaderValue(MimeUtility.GetMimeMapping(mappedPath)); + if (string.IsNullOrEmpty(serverConfig.AdminPassword)) + { + await MakeUserAuthenticated(); + } - return result; + if (User.Identity.IsAuthenticated) + { + return Redirect("Dashboard"); + } + + return new PhysicalFileResult(config.GetContentFolder() + "/login.html", "text/html"); ; } [HttpGet] - //[AllowAnonymous] - public IActionResult Logout() + [AllowAnonymous] + public async Task Logout() { - var ctx = Request.HttpContext; - //TODO - //var authManager = ctx.Authentication; - //authManager.SignOut("ApplicationCookie"); + await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); + return Redirect("Login"); + } + + [HttpPost] + [AllowAnonymous] + public async Task Dashboard([FromForm] string password) + { + if (password != null && securityService.HashPassword(password) == serverConfig.AdminPassword) + { + await MakeUserAuthenticated(); + } + return Redirect("Dashboard"); } [HttpGet] - [HttpPost] - //[AllowAnonymous] public IActionResult Dashboard() { - var result = new PhysicalFileResult(config.GetContentFolder() + "/index.html", "text/html"); - return result; + bool logout = HttpContext.Request.Query.Where(x => String.Equals(x.Key, "logout", StringComparison.OrdinalIgnoreCase) + && String.Equals(x.Value, "true", StringComparison.OrdinalIgnoreCase)).Any(); + if (logout) + { + return Redirect("Logout"); + } - //if (Request.Path != null && Request.Path.ToString().Contains("logout")) - //{ - // var file = GetFile("login.html"); - // securityService.Logout(file); - // return file; - //} + return new PhysicalFileResult(config.GetContentFolder() + "/index.html", "text/html"); + } - //TODO + //TODO: Move this to security service once off Mono + private async Task MakeUserAuthenticated() + { + var claims = new List + { + new Claim(ClaimTypes.Name, "Jackett", ClaimValueTypes.String) + }; - //if (securityService.CheckAuthorised(Request)) - //{ - //return GetFile("index.html"); + var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); - //} - //else - //{ - // var formData = await Request.ReadFormAsync(); - - // if (formData != null && securityService.HashPassword(formData["password"]) == serverConfig.AdminPassword) - // { - // var file = GetFile("index.html"); - // securityService.Login(file); - // return file; - // } - // else - // { - // return GetFile("login.html"); - // } - //} + await HttpContext.SignInAsync( + CookieAuthenticationDefaults.AuthenticationScheme, + new ClaimsPrincipal(claimsIdentity), + new AuthenticationProperties + { + ExpiresUtc = DateTime.UtcNow.AddMinutes(20), + IsPersistent = false, + AllowRefresh = true + }); } } } diff --git a/src/Jackett.Server/Middleware/RedirectRules.cs b/src/Jackett.Server/Middleware/RedirectRules.cs index dc90e0a77..547c944cc 100644 --- a/src/Jackett.Server/Middleware/RedirectRules.cs +++ b/src/Jackett.Server/Middleware/RedirectRules.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Rewrite; using Microsoft.Net.Http.Headers; +using System; namespace Jackett.Server.Middleware { @@ -8,9 +9,10 @@ namespace Jackett.Server.Middleware { public static void RedirectToDashboard(RewriteContext context) { - var request = context.HttpContext.Request; + HttpRequest request = context.HttpContext.Request; - if (request.Path == null || string.IsNullOrWhiteSpace(request.Path.ToString()) || request.Path.ToString() == "/") + if (request.Path == null || string.IsNullOrWhiteSpace(request.Path.ToString()) || request.Path.ToString() == "/" + || request.Path.ToString().Equals("/index.html", StringComparison.OrdinalIgnoreCase)) { // 301 is the status code of permanent redirect var redir = Initialisation.ServerService.BasePath() + "/UI/Dashboard"; diff --git a/src/Jackett.Server/Startup.cs b/src/Jackett.Server/Startup.cs index 0a82f8467..9c8e74424 100644 --- a/src/Jackett.Server/Startup.cs +++ b/src/Jackett.Server/Startup.cs @@ -5,8 +5,12 @@ using Jackett.Common.Plumbing; using Jackett.Common.Services.Interfaces; using Jackett.Server.Middleware; using Jackett.Server.Services; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Authorization; using Microsoft.AspNetCore.Rewrite; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -29,9 +33,27 @@ namespace Jackett.Server // This method gets called by the runtime. Use this method to add services to the container. public IServiceProvider ConfigureServices(IServiceCollection services) { - services - .AddMvc() - .AddJsonOptions(options => options.SerializerSettings.ContractResolver = new DefaultContractResolver()); //Web app uses Pascal Case JSON + services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) + .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, + options => + { + options.LoginPath = new PathString("/UI/Login"); + options.AccessDeniedPath = new PathString("/UI/Login"); + options.LogoutPath = new PathString("/UI/Logout"); + options.Cookie.Name = "Jackett"; + }); + + services.AddMvc(config => + { + var policy = new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .Build(); + config.Filters.Add(new AuthorizeFilter(policy)); + }) + .AddJsonOptions(options => + { + options.SerializerSettings.ContractResolver = new DefaultContractResolver(); //Web app uses Pascal Case JSON + }); RuntimeSettings runtimeSettings = new RuntimeSettings(); Configuration.GetSection("RuntimeSettings").Bind(runtimeSettings); @@ -77,6 +99,8 @@ namespace Jackett.Server EnableDirectoryBrowsing = false }); + app.UseAuthentication(); + app.UseMvc(); } }