diff --git a/Ombi.Core/IStatusChecker.cs b/Ombi.Core/IStatusChecker.cs index f6c1069b8..9a5471aaa 100644 --- a/Ombi.Core/IStatusChecker.cs +++ b/Ombi.Core/IStatusChecker.cs @@ -1,4 +1,6 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; +using Nancy.Session; using Octokit; using Ombi.Core.Models; @@ -7,6 +9,8 @@ namespace Ombi.Core public interface IStatusChecker { Task GetStatus(); - Task ReportBug(string title, string body); + Task ReportBug(string title, string body, string oauthToken); + Task OAuth(string url, ISession session); + Task OAuthAccessToken(string code); } } \ No newline at end of file diff --git a/Ombi.Core/StatusChecker/StatusChecker.cs b/Ombi.Core/StatusChecker/StatusChecker.cs index 9670fed56..d83e7189c 100644 --- a/Ombi.Core/StatusChecker/StatusChecker.cs +++ b/Ombi.Core/StatusChecker/StatusChecker.cs @@ -28,8 +28,11 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Security.Policy; using System.Threading.Tasks; +using Nancy.Session; using Octokit; +using Octokit.Internal; using Ombi.Api; using Ombi.Core.Models; using Ombi.Core.SettingModels; @@ -43,7 +46,7 @@ namespace Ombi.Core.StatusChecker public StatusChecker(ISettingsService ss) { SystemSettings = ss; - Git = new GitHubClient(new ProductHeaderValue("Ombi-StatusChecker")); + Git = new GitHubClient(new ProductHeaderValue("Ombi")); } private ISettingsService SystemSettings { get; } @@ -180,15 +183,48 @@ namespace Ombi.Core.StatusChecker return model; } - public async Task ReportBug(string title, string body) + public async Task ReportBug(string title, string body, string oauthToken) { + Git.Connection.Credentials = new Credentials(oauthToken); var issue = new NewIssue(title) { Body = body }; + var result = await Git.Issue.Create(Owner, RepoName, issue); return result; } + + + string clientId = "f407108cdb1e660f68c5"; + string clientSecret = "84b56e22002da2929c34fc773d89f3402a19098a"; + + public async Task OAuth(string url, ISession session) + { + + var csrf = StringCipher.Encrypt(Guid.NewGuid().ToString("N"), "CSRF"); + session[SessionKeys.CSRF] = csrf; + + var request = new OauthLoginRequest(clientId) + { + Scopes = { "public_repo", "user" }, + State = csrf, + RedirectUri = new Uri(url) + }; + + // NOTE: user must be navigated to this URL + var oauthLoginUrl = Git.Oauth.GetGitHubLoginUrl(request); + + return oauthLoginUrl; + } + + public async Task OAuthAccessToken(string code) + { + var request = new OauthTokenRequest(clientId, clientSecret, code); + var token = await Git.Oauth.CreateAccessToken(request); + + return token; + } } } \ No newline at end of file diff --git a/Ombi.Helpers/SessionKeys.cs b/Ombi.Helpers/SessionKeys.cs index 265c6a349..32c1a7a19 100644 --- a/Ombi.Helpers/SessionKeys.cs +++ b/Ombi.Helpers/SessionKeys.cs @@ -33,5 +33,7 @@ namespace Ombi.Helpers public const string UserWizardPlexAuth = nameof(UserWizardPlexAuth); public const string UserWizardMachineId = nameof(UserWizardMachineId); public const string UserLoginName = nameof(UserLoginName); + public const string OAuthToken = nameof(OAuthToken); + public const string CSRF = "CSRF:State"; } } diff --git a/Ombi.UI/Models/AboutAdminViewModel.cs b/Ombi.UI/Models/AboutAdminViewModel.cs index dbe93dc4a..b173cea2a 100644 --- a/Ombi.UI/Models/AboutAdminViewModel.cs +++ b/Ombi.UI/Models/AboutAdminViewModel.cs @@ -33,5 +33,6 @@ namespace Ombi.UI.Models public string ApplicationVersion { get; set; } // File Version public string Branch { get; set; } public string LogLevel { get; set; } + public bool OAuthEnabled { get; set; } } } \ No newline at end of file diff --git a/Ombi.UI/Modules/Admin/AboutModule.cs b/Ombi.UI/Modules/Admin/AboutModule.cs index ecdfea7ea..714390365 100644 --- a/Ombi.UI/Modules/Admin/AboutModule.cs +++ b/Ombi.UI/Modules/Admin/AboutModule.cs @@ -28,10 +28,14 @@ using System; using System.Linq; using System.Reflection; +using System.Text; using System.Threading.Tasks; using Nancy; +using Nancy.Extensions; +using Nancy.Linker; using Nancy.Responses.Negotiation; using NLog; +using Octokit; using Ombi.Core; using Ombi.Core.SettingModels; using Ombi.Helpers; @@ -45,24 +49,41 @@ namespace Ombi.UI.Modules.Admin { public AboutModule(ISettingsService settingsService, ISettingsService systemService, ISecurityExtensions security, - IStatusChecker statusChecker) : base("admin", settingsService, security) + IStatusChecker statusChecker, IResourceLinker linker) : base("admin", settingsService, security) { Before += (ctx) => Security.AdminLoginRedirect(Permissions.Administrator, ctx); SettingsService = systemService; StatusChecker = statusChecker; + Linker = linker; - Get["/about", true] = async (x,ct) => await Index(); + Get["AboutPage","/about", true] = async (x,ct) => await Index(); Post["/about", true] = async (x,ct) => await ReportIssue(); + + Get["/OAuth", true] = async (x, ct) => await OAuth(); + Get["/authorize", true] = async (x, ct) => await Authorize(); } private ISettingsService SettingsService { get; } private IStatusChecker StatusChecker { get; } + private IResourceLinker Linker { get; } private async Task Index() + { + var vm = await GetModel(); + return View["About", vm]; + } + + private async Task GetModel() { var vm = new AboutAdminViewModel(); + var oAuth = Session[SessionKeys.OAuthToken]?.ToString() ?? string.Empty; + + if (!string.IsNullOrEmpty(oAuth)) + { + vm.OAuthEnabled = true; + } var systemSettings = await SettingsService.GetSettingsAsync(); @@ -88,7 +109,7 @@ namespace Ombi.UI.Modules.Admin vm.Branch = EnumHelper.GetDisplayValue(systemSettings.Branch); vm.LogLevel = LogManager.Configuration.LoggingRules.FirstOrDefault(x => x.NameMatches("database"))?.Levels?.FirstOrDefault()?.Name ?? "Unknown"; - return View["About", vm]; + return vm; } private async Task ReportIssue() @@ -107,8 +128,90 @@ namespace Ombi.UI.Modules.Admin }); } - var result = await StatusChecker.ReportBug(title,body); + var model = await GetModel(); + body = CreateReportBody(model, body); + var token = Session[SessionKeys.OAuthToken].ToString(); + var result = await StatusChecker.ReportBug(title, body, token); return Response.AsJson(new {result = true, url = result.HtmlUrl.ToString()}); } + + private async Task OAuth() + { + var path = Request.Url.Path; + + Request.Url.Path = path.Replace("oauth", "authorize"); + var uri = await StatusChecker.OAuth(Request.Url.ToString(), Session); + + return Response.AsJson(new { uri = uri.ToString()}); + } + + public async Task Authorize() + { + var code = Request.Query["code"].ToString(); + var state = Request.Query["state"].ToString(); + + var expectedState = Session[SessionKeys.CSRF] as string; + if (state != expectedState) + { + throw new InvalidOperationException("SECURITY FAIL!"); + } + Session[SessionKeys.CSRF] = null; + + var token = await StatusChecker.OAuthAccessToken(code); + Session[SessionKeys.OAuthToken] = token.AccessToken; + + return Context.GetRedirect(Linker.BuildRelativeUri(Context, "AboutPage").ToString()); + } + + private string CreateReportBody(AboutAdminViewModel model, string body) + { + var sb = new StringBuilder(); + + sb.AppendLine("#### Ombi Version"); + sb.AppendLine($"V {model.ApplicationVersion}"); + sb.AppendLine("#### Update Branch:"); + sb.AppendLine(model.Branch); + sb.AppendLine("#### Operating System:"); + sb.AppendLine(model.Os); + sb.AppendLine(body); + + return sb.ToString(); + + // + + //#### Ombi Version: + + //V 1.XX.XX + + //#### Update Branch: + + //Stable/Early Access Preview/development + + //#### Operating System: + + //(Place text here) + + //#### Mono Version (only if your not on windows) + + //(Place text here) + + //#### Applicable Logs (from `/logs/` directory or the Admin page): + + //``` + + //(Logs go here. Don't remove the ``` tags for showing your logs correctly. Please make sure you remove any personal information from the logs) + + //``` + + //#### Problem Description: + + //(Place text here) + + //#### Reproduction Steps: + + //Please include any steps to reproduce the issue, this the request that is causing the problem etc. + + } } } \ No newline at end of file diff --git a/Ombi.UI/Modules/RequestsModule.cs b/Ombi.UI/Modules/RequestsModule.cs index 2e30fbc1f..d707244c0 100644 --- a/Ombi.UI/Modules/RequestsModule.cs +++ b/Ombi.UI/Modules/RequestsModule.cs @@ -116,8 +116,6 @@ namespace Ombi.UI.Modules private async Task GetMovies() { - var settings = PrSettings.GetSettings(); - var allRequests = await Service.GetAllAsync(); allRequests = allRequests.Where(x => x.Type == RequestType.Movie); diff --git a/Ombi.UI/Views/About/About.cshtml b/Ombi.UI/Views/About/About.cshtml index c2dbf79f3..6256c6331 100644 --- a/Ombi.UI/Views/About/About.cshtml +++ b/Ombi.UI/Views/About/About.cshtml @@ -32,12 +32,23 @@ -
-
- + @if (Model.OAuthEnabled) + { +
+
+ +
-
+ } + else + { +
+
+ +
+
+ }
@@ -52,16 +63,32 @@ startBug(); }); + $('#oAuth').click(function () { + var url = "/admin/oauth"; + url = createBaseUrl(baseUrl, url); + $.ajax({ + type: "get", + url: url, + dataType: "json", + success: function (response) { + window.location.href = response.uri; + }, + error: function (e) { + console.log(e); + generateNotify("Something went wrong!", "danger"); + } + }); + }); function startBug() { - bootbox.prompt({ - size: "small", - title: "What is the title of the issue?", - inputType: 'textarea', - callback: mainContent - }); + bootbox.prompt({ + size: "small", + title: "What is the title of the issue?", + inputType: 'textarea', + callback: mainContent + }); } - + function mainContent(userTitle) { if (!userTitle) { @@ -91,7 +118,7 @@ $.ajax({ type: "post", url: url, - data: {title : issueTitle, body : additionalInfo}, + data: { title: issueTitle, body: additionalInfo }, dataType: "json", success: function (response) { if (response && response.result) { diff --git a/Ombi.UI/Views/Shared/Partial/_Navbar.cshtml b/Ombi.UI/Views/Shared/Partial/_Navbar.cshtml index 3107c5866..305936a39 100644 --- a/Ombi.UI/Views/Shared/Partial/_Navbar.cshtml +++ b/Ombi.UI/Views/Shared/Partial/_Navbar.cshtml @@ -58,7 +58,7 @@