The move!

This commit is contained in:
Jamie.Rees 2017-05-16 08:31:44 +01:00
commit 25526cc4d9
1147 changed files with 85 additions and 8524 deletions

View file

@ -0,0 +1,225 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: AboutModule.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
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;
using Ombi.Helpers.Permissions;
using Ombi.Store;
using Ombi.UI.Models;
using ISecurityExtensions = Ombi.Core.ISecurityExtensions;
namespace Ombi.UI.Modules.Admin
{
public class AboutModule : BaseModule
{
public AboutModule(ISettingsService<PlexRequestSettings> settingsService,
ISettingsService<SystemSettings> systemService, ISecurityExtensions security,
IStatusChecker statusChecker, IResourceLinker linker, ISqliteConfiguration config) : base("admin", settingsService, security)
{
Before += (ctx) => Security.AdminLoginRedirect(Permissions.Administrator, ctx);
SettingsService = systemService;
StatusChecker = statusChecker;
Linker = linker;
SqlConfig = config;
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<SystemSettings> SettingsService { get; }
private IStatusChecker StatusChecker { get; }
private IResourceLinker Linker { get; }
private ISqliteConfiguration SqlConfig { get; }
private async Task<Negotiator> Index()
{
var vm = await GetModel();
return View["About", vm];
}
private async Task<AboutAdminViewModel> 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();
var type = Type.GetType("Mono.Runtime");
if (type != null) // mono
{
vm.Os = "Mono";
var displayName = type.GetMethod("GetDisplayName", BindingFlags.NonPublic | BindingFlags.Static);
if (displayName != null)
{
vm.SystemVersion = displayName.Invoke(null, null).ToString();
}
}
else
{
// Windows
vm.Os = OperatingSystemHelper.GetOs();
vm.SystemVersion = Environment.Version.ToString();
}
vm.RunningDir = Environment.CurrentDirectory;
vm.DbLocation = SqlConfig.CurrentPath;
vm.ApplicationVersion = AssemblyHelper.GetFileVersion();
vm.Branch = EnumHelper<Branches>.GetBranchValue<BranchAttribute>(systemSettings.Branch).DisplayName;
vm.LogLevel = LogManager.Configuration.LoggingRules.FirstOrDefault(x => x.NameMatches("database"))?.Levels?.FirstOrDefault()?.Name ?? "Unknown";
return vm;
}
private async Task<Response> ReportIssue()
{
var title = Request.Form["title"];
var body = Request.Form["body"];
if (string.IsNullOrEmpty(title) || string.IsNullOrEmpty(body))
{
return
Response.AsJson(
new
{
result = false,
message = "The title or issue body is empty! Please give me a bit more detail :)"
});
}
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<Response> 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<Response> 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("#### Log Level:");
sb.AppendLine(model.LogLevel);
sb.AppendLine(body);
return sb.ToString();
// <!--- //!! Please use the Support / bug report template, otherwise we will close the Github issue !!
//(Pleas submit a feature request over here: http://feathub.com/tidusjar/Ombi) //--->
//#### 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.
}
}
}

View file

@ -0,0 +1,129 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: CustomizationModule.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Nancy;
using Nancy.ModelBinding;
using Nancy.Responses.Negotiation;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Helpers;
using Ombi.Helpers.Permissions;
using Ombi.UI.Models;
using Ombi.UI.Models.Admin;
using Ombi.UI.Models.UI;
using TMDbLib.Utilities;
using ISecurityExtensions = Ombi.Core.ISecurityExtensions;
namespace Ombi.UI.Modules.Admin
{
public class CustomizationModule : BaseModule
{
public CustomizationModule(ISettingsService<PlexRequestSettings> settingsService,
ISettingsService<CustomizationSettings> cust, ISecurityExtensions security)
: base("admin", settingsService, security)
{
Before += (ctx) => Security.AdminLoginRedirect(Permissions.Administrator, ctx);
Settings = cust;
Get["/customization", true] = async (x, ct) => await Index();
Post["/customization", true] = async (x, ct) => await Save();
}
private ISettingsService<CustomizationSettings> Settings { get; }
private async Task<Negotiator> Index()
{
var model = await Settings.GetSettingsAsync();
var viewModel = new CustomizationViewModel
{
Settings = model,
SortOptions = new List<Dropdown<SortOptions>>()
};
foreach (var value in EnumHelper<SortOptions>.GetValues(SortOptions.LatestRelease))
{
viewModel.SortOptions.Add(new Dropdown<SortOptions>
{
Value = value,
Name = EnumHelper<SortOptions>.GetDisplayDescription(value),
Selected = model.DefaultSort == (int) value
});
}
foreach (var value in EnumHelper<FilterOptions>.GetValues(FilterOptions.Available))
{
viewModel.FilterOptions.Add(new Dropdown<FilterOptions>
{
Value = value,
Name = EnumHelper<FilterOptions>.GetDisplayDescription(value),
Selected = model.DefaultFilter == (int) value
});
}
foreach (var value in EnumHelper<Languages>.GetValues(Languages.en))
{
viewModel.LanguageDropdown.Add(new Dropdown<Languages>
{
Value = value,
Name = EnumHelper<Languages>.GetDisplayValue(value),
Selected = model.DefaultLang == (int) value
});
}
return View["customization", viewModel];
}
private async Task<Response> Save()
{
try
{
var model = this.Bind<CustomizationSettings>();
var result = await Settings.SaveSettingsAsync(model);
return Response.AsJson(result
? new JsonResponseModel { Result = true }
: new JsonResponseModel { Result = false, Message = "We could not save to the database, please try again" });
}
catch (Exception e)
{
throw e;
}
}
}
}

View file

@ -0,0 +1,107 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: SystemStatusModule.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Linq;
using System.Threading.Tasks;
using Nancy;
using Nancy.Responses.Negotiation;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Helpers;
using Ombi.Helpers.Permissions;
using Ombi.Store;
using Ombi.Store.Models;
using Ombi.Store.Repository;
using Ombi.UI.Models;
using ISecurityExtensions = Ombi.Core.ISecurityExtensions;
namespace Ombi.UI.Modules.Admin
{
public class FaultQueueModule : BaseModule
{
public FaultQueueModule(ISettingsService<PlexRequestSettings> settingsService, IRepository<RequestQueue> requestQueue, ISecurityExtensions security) : base("admin", settingsService, security)
{
RequestQueue = requestQueue;
Before += (ctx) => Security.AdminLoginRedirect(Permissions.Administrator, ctx);
Get["Index", "/faultqueue"] = x => Index();
Get["DeleteFault", "/deleteFault", true] = async (x,ct) => await DeleteFault(Convert.ToInt32(Request.Form.id));
}
private IRepository<RequestQueue> RequestQueue { get; }
private Negotiator Index()
{
var requests = RequestQueue.GetAll();
var model = requests.Select(r => new FaultedRequestsViewModel
{
FaultType = (FaultTypeViewModel)(int)r.FaultType,
Type = (RequestTypeViewModel)(int)r.Type,
Title = ByteConverterHelper.ReturnObject<RequestedModel>(r.Content).Title,
Id = r.Id,
PrimaryIdentifier = r.PrimaryIdentifier,
LastRetry = r.LastRetry,
Message = r.Description
}).ToList();
return View["RequestFaultQueue", model];
}
public async Task<Response> DeleteFault(int faultId)
{
if (faultId == 0)
{
return Response.AsJson(new JsonResponseModel
{
Result = true,
Message = "Fault does not exist"
});
}
var fault = await RequestQueue.GetAsync(faultId);
if (fault == null)
{
return Response.AsJson(new JsonResponseModel
{
Result = true,
Message = "Fault does not exist"
});
}
await RequestQueue.DeleteAsync(fault);
return Response.AsJson(new JsonResponseModel
{
Result = true
});
}
}
}

View file

@ -0,0 +1,222 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: SystemStatusModule.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using MarkdownSharp;
using Nancy;
using Nancy.ModelBinding;
using Nancy.Responses.Negotiation;
using Nancy.Validation;
using Ombi.Api.Interfaces;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Core.StatusChecker;
using Ombi.Helpers;
using Ombi.Helpers.Analytics;
using Ombi.Helpers.Permissions;
using Ombi.UI.Helpers;
using Ombi.UI.Models;
using Action = Ombi.Helpers.Analytics.Action;
using ISecurityExtensions = Ombi.Core.ISecurityExtensions;
namespace Ombi.UI.Modules.Admin
{
public class IntegrationModule : BaseModule
{
public IntegrationModule(ISettingsService<PlexRequestSettings> settingsService, ISettingsService<WatcherSettings> watcher,
ISettingsService<CouchPotatoSettings> cp,ISecurityExtensions security, IAnalytics a, ISettingsService<RadarrSettings> radarrSettings,
ICacheProvider cache, IRadarrApi radarrApi, ISonarrApi sonarrApi) : base("admin", settingsService, security)
{
WatcherSettings = watcher;
Analytics = a;
CpSettings = cp;
Cache = cache;
RadarrApi = radarrApi;
RadarrSettings = radarrSettings;
SonarrApi = sonarrApi;
Before += (ctx) => Security.AdminLoginRedirect(Permissions.Administrator, ctx);
Post["/sonarrrootfolders"] = _ => GetSonarrRootFolders();
Post["/radarrrootfolders"] = _ => GetSonarrRootFolders();
Get["/watcher", true] = async (x, ct) => await Watcher();
Post["/watcher", true] = async (x, ct) => await SaveWatcher();
Get["/radarr", true] = async (x, ct) => await Radarr();
Post["/radarr", true] = async (x, ct) => await SaveRadarr();
Post["/radarrprofiles"] = _ => GetRadarrQualityProfiles();
}
private ISettingsService<WatcherSettings> WatcherSettings { get; }
private ISettingsService<CouchPotatoSettings> CpSettings { get; }
private ISettingsService<RadarrSettings> RadarrSettings { get; }
private IRadarrApi RadarrApi { get; }
private ICacheProvider Cache { get; }
private IAnalytics Analytics { get; }
private ISonarrApi SonarrApi { get; }
private async Task<Negotiator> Watcher()
{
var settings = await WatcherSettings.GetSettingsAsync();
return View["Watcher", settings];
}
private async Task<Response> SaveWatcher()
{
var settings = this.Bind<WatcherSettings>();
var valid = this.Validate(settings);
if (!valid.IsValid)
{
return Response.AsJson(valid.SendJsonError());
}
var cpSettings = await CpSettings.GetSettingsAsync().ConfigureAwait(false);
if (cpSettings.Enabled)
{
return
Response.AsJson(new JsonResponseModel
{
Result = false,
Message = "Cannot have Watcher and CouchPotato both enabled."
});
}
var radarrSettings = await RadarrSettings.GetSettingsAsync();
if (radarrSettings.Enabled)
{
return
Response.AsJson(new JsonResponseModel
{
Result = false,
Message = "Cannot have Radarr and CouchPotato both enabled."
});
}
settings.ApiKey = settings.ApiKey.Trim();
var result = await WatcherSettings.SaveSettingsAsync(settings);
return Response.AsJson(result
? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Watcher!" }
: new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." });
}
private async Task<Negotiator> Radarr()
{
var settings = await RadarrSettings.GetSettingsAsync();
return View["Radarr", settings];
}
private async Task<Response> SaveRadarr()
{
var radarrSettings = this.Bind<RadarrSettings>();
//Check Watcher and CP make sure they are not enabled
var watcher = await WatcherSettings.GetSettingsAsync();
if (watcher.Enabled)
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Watcher is enabled, we cannot enable Watcher and Radarr" });
}
var cp = await CpSettings.GetSettingsAsync();
if (cp.Enabled)
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "CouchPotato is enabled, we cannot enable Radarr and CouchPotato" });
}
var valid = this.Validate(radarrSettings);
if (!valid.IsValid)
{
return Response.AsJson(valid.SendJsonError());
}
radarrSettings.ApiKey = radarrSettings.ApiKey.Trim();
var result = await RadarrSettings.SaveSettingsAsync(radarrSettings);
return Response.AsJson(result
? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Radarr!" }
: new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." });
}
private Response GetRadarrQualityProfiles()
{
var settings = this.Bind<RadarrSettings>();
var profiles = RadarrApi.GetProfiles(settings.ApiKey, settings.FullUri);
// set the cache
if (profiles != null)
{
Cache.Set(CacheKeys.RadarrQualityProfiles, profiles);
}
return Response.AsJson(profiles);
}
private Response GetSonarrRootFolders()
{
var settings = this.Bind<SonarrSettings>();
var rootFolders = SonarrApi.GetRootFolders(settings.ApiKey, settings.FullUri);
// set the cache
if (rootFolders != null)
{
Cache.Set(CacheKeys.SonarrRootFolders, rootFolders);
}
return Response.AsJson(rootFolders);
}
private Response GetRadarrRootFolders()
{
var settings = this.Bind<RadarrSettings>();
var rootFolders = RadarrApi.GetRootFolders(settings.ApiKey, settings.FullUri);
// set the cache
if (rootFolders != null)
{
Cache.Set(CacheKeys.SonarrRootFolders, rootFolders);
}
return Response.AsJson(rootFolders);
}
}
}

View file

@ -0,0 +1,178 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: AboutModule.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Threading.Tasks;
using Nancy;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Helpers.Permissions;
using Ombi.Services.Interfaces;
using Ombi.Services.Jobs;
using Ombi.Services.Jobs.Interfaces;
using Ombi.UI.Models;
using ISecurityExtensions = Ombi.Core.ISecurityExtensions;
namespace Ombi.UI.Modules.Admin
{
public class ScheduledJobsRunnerModule : BaseModule
{
public ScheduledJobsRunnerModule(ISettingsService<PlexRequestSettings> settingsService,
ISecurityExtensions security, IPlexContentCacher contentCacher, ISonarrCacher sonarrCacher, IWatcherCacher watcherCacher,
IRadarrCacher radarrCacher, ICouchPotatoCacher cpCacher, IStoreBackup store, ISickRageCacher srCacher, IAvailabilityChecker plexChceker,
IStoreCleanup cleanup, IUserRequestLimitResetter requestLimit, IPlexEpisodeCacher episodeCacher, IRecentlyAdded recentlyAdded,
IFaultQueueHandler faultQueueHandler, IPlexUserChecker plexUserChecker, IEmbyAvailabilityChecker embyAvailabilityChecker, IEmbyEpisodeCacher embyEpisode,
IEmbyContentCacher embyContentCacher, IEmbyUserChecker embyUser) : base("admin", settingsService, security)
{
Before += (ctx) => Security.AdminLoginRedirect(Permissions.Administrator, ctx);
PlexContentCacher = contentCacher;
SonarrCacher = sonarrCacher;
RadarrCacher = radarrCacher;
WatcherCacher = watcherCacher;
CpCacher = cpCacher;
StoreBackup = store;
SrCacher = srCacher;
AvailabilityChecker = plexChceker;
StoreCleanup = cleanup;
RequestLimit = requestLimit;
EpisodeCacher = episodeCacher;
RecentlyAdded = recentlyAdded;
FaultQueueHandler = faultQueueHandler;
PlexUserChecker = plexUserChecker;
EmbyAvailabilityChecker = embyAvailabilityChecker;
EmbyContentCacher = embyContentCacher;
EmbyEpisodeCacher = embyEpisode;
EmbyUserChecker = embyUser;
Post["/schedulerun", true] = async (x, ct) => await ScheduleRun((string)Request.Form.key);
}
private IPlexContentCacher PlexContentCacher { get; }
private IRadarrCacher RadarrCacher { get; }
private ISonarrCacher SonarrCacher { get; }
private IWatcherCacher WatcherCacher { get; }
private ICouchPotatoCacher CpCacher { get; }
private IStoreBackup StoreBackup { get; }
private ISickRageCacher SrCacher { get; }
private IAvailabilityChecker AvailabilityChecker { get; }
private IStoreCleanup StoreCleanup { get; }
private IUserRequestLimitResetter RequestLimit { get; }
private IPlexEpisodeCacher EpisodeCacher { get; }
private IRecentlyAdded RecentlyAdded { get; }
private IFaultQueueHandler FaultQueueHandler { get; }
private IPlexUserChecker PlexUserChecker { get; }
private IEmbyAvailabilityChecker EmbyAvailabilityChecker { get; }
private IEmbyContentCacher EmbyContentCacher { get; }
private IEmbyEpisodeCacher EmbyEpisodeCacher { get; }
private IEmbyUserChecker EmbyUserChecker { get; }
private async Task<Response> ScheduleRun(string key)
{
await Task.Yield();
if (key.Equals(JobNames.PlexCacher, StringComparison.CurrentCultureIgnoreCase))
{
PlexContentCacher.CacheContent();
}
if (key.Equals(JobNames.WatcherCacher, StringComparison.CurrentCultureIgnoreCase))
{
WatcherCacher.Queued();
}
if (key.Equals(JobNames.SonarrCacher, StringComparison.CurrentCultureIgnoreCase))
{
SonarrCacher.Queued();
}
if (key.Equals(JobNames.RadarrCacher, StringComparison.CurrentCultureIgnoreCase))
{
RadarrCacher.Queued();
}
if (key.Equals(JobNames.CpCacher, StringComparison.CurrentCultureIgnoreCase))
{
CpCacher.Queued();
}
if (key.Equals(JobNames.StoreBackup, StringComparison.CurrentCultureIgnoreCase))
{
StoreBackup.Start();
}
if (key.Equals(JobNames.SrCacher, StringComparison.CurrentCultureIgnoreCase))
{
SrCacher.Queued();
}
if (key.Equals(JobNames.PlexChecker, StringComparison.CurrentCultureIgnoreCase))
{
AvailabilityChecker.Start();
}
if (key.Equals(JobNames.StoreCleanup, StringComparison.CurrentCultureIgnoreCase))
{
StoreCleanup.Start();
}
if (key.Equals(JobNames.RequestLimitReset, StringComparison.CurrentCultureIgnoreCase))
{
RequestLimit.Start();
}
if (key.Equals(JobNames.EpisodeCacher, StringComparison.CurrentCultureIgnoreCase))
{
EpisodeCacher.Start();
}
if (key.Equals(JobNames.RecentlyAddedEmail, StringComparison.CurrentCultureIgnoreCase))
{
RecentlyAdded.StartNewsLetter();
}
if (key.Equals(JobNames.FaultQueueHandler, StringComparison.CurrentCultureIgnoreCase))
{
FaultQueueHandler.Start();
}
if (key.Equals(JobNames.PlexUserChecker, StringComparison.CurrentCultureIgnoreCase))
{
RequestLimit.Start();
}
if (key.Equals(JobNames.EmbyEpisodeCacher, StringComparison.CurrentCultureIgnoreCase))
{
EmbyEpisodeCacher.Start();
}
if (key.Equals(JobNames.EmbyCacher, StringComparison.CurrentCultureIgnoreCase))
{
EmbyContentCacher.CacheContent();
}
if (key.Equals(JobNames.EmbyChecker, StringComparison.CurrentCultureIgnoreCase))
{
EmbyAvailabilityChecker.CheckAndUpdateAll();
}
if (key.Equals(JobNames.EmbyUserChecker, StringComparison.CurrentCultureIgnoreCase))
{
EmbyUserChecker.Start();
}
return Response.AsJson(new JsonResponseModel { Result = true });
}
}
}

View file

@ -0,0 +1,184 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: SystemStatusModule.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using MarkdownSharp;
using Nancy;
using Nancy.ModelBinding;
using Nancy.Responses.Negotiation;
using Ombi.Api.Interfaces;
using Ombi.Common.Processes;
using Ombi.Core;
using Ombi.Core.Models;
using Ombi.Core.SettingModels;
using Ombi.Core.StatusChecker;
using Ombi.Helpers;
using Ombi.Helpers.Analytics;
using Ombi.Helpers.Permissions;
using Ombi.UI.Models;
using Action = Ombi.Helpers.Analytics.Action;
using ISecurityExtensions = Ombi.Core.ISecurityExtensions;
namespace Ombi.UI.Modules.Admin
{
public class SystemStatusModule : BaseModule
{
public SystemStatusModule(ISettingsService<PlexRequestSettings> settingsService, ICacheProvider cache, ISettingsService<SystemSettings> ss,
ISecurityExtensions security, IAnalytics a, IAppveyorApi appveyor) : base("admin", settingsService, security)
{
Cache = cache;
SystemSettings = ss;
Analytics = a;
AppveyorApi = appveyor;
Before += (ctx) => Security.AdminLoginRedirect(Permissions.Administrator, ctx);
Get["/status", true] = async (x, ct) => await Status();
Post["/save", true] = async (x, ct) => await Save();
Post["/autoupdate"] = x => AutoUpdate();
Get["/changes", true] = async (x, ct) => await GetLatestChanges();
}
private ICacheProvider Cache { get; }
private ISettingsService<SystemSettings> SystemSettings { get; }
private IAnalytics Analytics { get; }
private IAppveyorApi AppveyorApi { get; }
private async Task<Negotiator> Status()
{
var settings = await SystemSettings.GetSettingsAsync();
var checker = new StatusChecker(SystemSettings);
var status = await Cache.GetOrSetAsync(CacheKeys.LastestProductVersion, async () => await checker.GetStatus(), 30);
var md = new Markdown(new MarkdownOptions { AutoNewLines = true, AutoHyperlink = true });
status.ReleaseNotes = md.Transform(status.ReleaseNotes);
settings.Status = status;
settings.BranchDropdown = new List<BranchDropdown>
{
new BranchDropdown
{
Name =EnumHelper<Branches>.GetBranchValue<BranchAttribute>(Branches.Stable).DisplayName,
Value = Branches.Stable,
Selected = settings.Branch == Branches.Stable
},
new BranchDropdown
{
Name = EnumHelper<Branches>.GetBranchValue<BranchAttribute>(Branches.EarlyAccessPreview).DisplayName,
Value = Branches.EarlyAccessPreview,
Selected = settings.Branch == Branches.EarlyAccessPreview
},
new BranchDropdown
{
Name = EnumHelper<Branches>.GetBranchValue<BranchAttribute>(Branches.Dev).DisplayName,
Value = Branches.Dev,
Selected = settings.Branch == Branches.Dev
},
};
return View["Status", settings];
}
public async Task<Response> GetLatestChanges()
{
var settings = await SystemSettings.GetSettingsAsync();
var branchName = EnumHelper<Branches>.GetBranchValue<BranchAttribute>(settings.Branch).BranchName;
var changes = AppveyorApi.GetProjectHistory(branchName);
var currentVersion = AssemblyHelper.GetProductVersion();
var model = new List<RecentUpdatesModel>();
foreach (var build in changes.builds)
{
model.Add(new RecentUpdatesModel
{
Date = build.finished,
Message = BuildAppveyorMessage(build.message, build.messageExtended),
Version = build.version,
Installed = currentVersion.Equals(build.version, StringComparison.CurrentCultureIgnoreCase) ,
Branch = branchName
});
}
return Response.AsJson(model);
}
private string BuildAppveyorMessage(string message, string extended)
{
return extended == null ? message : $"{message} {extended}";
}
private async Task<Response> Save()
{
var settings = this.Bind<SystemSettings>();
Analytics.TrackEventAsync(Category.Admin, Action.Update, $"Updated Branch {EnumHelper<Branches>.GetBranchValue<BranchAttribute>(settings.Branch).DisplayName}", Username, CookieHelper.GetAnalyticClientId(Cookies));
await SystemSettings.SaveSettingsAsync(settings);
// Clear the cache
Cache.Remove(CacheKeys.LastestProductVersion);
return Response.AsJson(new JsonResponseModel { Result = true, Message = "Successfully Saved your settings"});
}
private Response AutoUpdate()
{
Analytics.TrackEventAsync(Category.Admin, Action.Update, "AutoUpdate", Username, CookieHelper.GetAnalyticClientId(Cookies));
var url = Request.Form["url"];
var args = (string)Request.Form["args"].ToString();
var lowered = args.ToLower();
var appPath = Path.Combine(Path.GetDirectoryName(Assembly.GetAssembly(typeof(SystemStatusModule)).Location ?? string.Empty) ?? string.Empty, Path.Combine("UpdateService", "Ombi.Updater.exe"));
if (!string.IsNullOrEmpty(lowered))
{
if (lowered.Contains("ombi.exe"))
{
lowered = lowered.Replace("ombi.exe", "");
}
}
var startArgs = string.IsNullOrEmpty(lowered) || lowered == "Nancy.DynamicDictionaryValue".ToLower() ? appPath : $"{lowered} Ombi.Updater.exe";
var startInfo = Type.GetType("Mono.Runtime") != null
? new ProcessStartInfo(startArgs) { Arguments = $"{url} {lowered}", }
: new ProcessStartInfo(startArgs) { Arguments = $"{url} {lowered}" };
Process.Start(startInfo);
//Environment.Exit(0);
return Nancy.Response.NoBody;
}
}
}

View file

@ -0,0 +1,83 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: SystemStatusModule.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System.Threading.Tasks;
using Nancy;
using Nancy.ModelBinding;
using Nancy.Responses.Negotiation;
using Nancy.Validation;
using NLog;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Helpers.Permissions;
using Ombi.UI.Helpers;
using Ombi.UI.Models;
using ISecurityExtensions = Ombi.Core.ISecurityExtensions;
namespace Ombi.UI.Modules.Admin
{
public class UserManagementSettingsModule : BaseModule
{
public UserManagementSettingsModule(ISettingsService<PlexRequestSettings> settingsService, ISettingsService<UserManagementSettings> umSettings, ISecurityExtensions security) : base("admin", settingsService, security)
{
UserManagementSettings = umSettings;
Before += (ctx) => Security.AdminLoginRedirect(Permissions.Administrator, ctx);
Get["UserManagementSettings","/usermanagementsettings", true] = async(x,ct) => await Index();
Post["/usermanagementsettings", true] = async(x,ct) => await Update();
}
private ISettingsService<UserManagementSettings> UserManagementSettings { get; }
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
private async Task<Negotiator> Index()
{
var model = await UserManagementSettings.GetSettingsAsync();
return View["UserManagementSettings", model];
}
private async Task<Response> Update()
{
var settings = this.Bind<UserManagementSettings>();
var valid = this.Validate(settings);
if (!valid.IsValid)
{
var error = valid.SendJsonError();
Log.Info("Error validating User Management settings, message: {0}", error.Message);
return Response.AsJson(error);
}
var result = await UserManagementSettings.SaveSettingsAsync(settings);
return Response.AsJson(result
? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for User Management!" }
: new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." });
}
}
}

View file

@ -0,0 +1,46 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: ApiDocsModule.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using Nancy.Responses.Negotiation;
using Ombi.Core;
using Ombi.Core.SettingModels;
using ISecurityExtensions = Ombi.Core.ISecurityExtensions;
namespace Ombi.UI.Modules
{
public class ApiDocsModule : BaseModule
{
public ApiDocsModule(ISettingsService<PlexRequestSettings> pr, ISecurityExtensions security) : base("apidocs", pr, security)
{
Get["/"] = x => Documentation();
}
public Negotiator Documentation()
{
return View["Index"];
}
}
}

View file

@ -0,0 +1,115 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: ApiRequestMetadataModule.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System.Collections.Generic;
using Nancy.Metadata.Modules;
using Nancy.Swagger;
using Ombi.Store;
using Ombi.UI.Models;
using Ombi.UI.Models.UserManagement;
namespace Ombi.UI.Modules
{
public class ApiRequestMetadataModule: MetadataModule<SwaggerRouteData>
{
public ApiRequestMetadataModule()
{
Describe["GetRequests"] = description => description.AsSwagger(with =>
{
with.ResourcePath("/requests");
with.Summary("The list of requests");
with.Notes("This returns a list of requests");
with.QueryParam<string>("apikey", "The Api Key found in the settings", true);
with.Model<ApiModel<List<RequestedModel>>>();
});
Describe["GetRequest"] = description => description.AsSwagger(with =>
{
with.ResourcePath("/requests/{id}");
with.Summary("Get's a single request");
with.Notes("This returns a single request");
with.QueryParam<string>("apikey", "The Api Key found in the settings", true);
with.PathParam<int>("id");
with.Model<ApiModel<List<RequestedModel>>>();
});
Describe["PostRequests"] = description => description.AsSwagger(with =>
{
with.ResourcePath("/requests");
with.Summary("Create a new request");
with.Model<ApiModel<bool>>();
with.BodyParam<RequestedModel>("The request", true);
with.QueryParam<string>("apikey", "The Api Key found in the settings", true);
with.Notes("Creates a new request");
});
Describe["PutRequests"] = description => description.AsSwagger(with =>
{
with.ResourcePath("/requests");
with.Summary("Updates an existing request");
with.Model<ApiModel<bool>>();
with.BodyParam<RequestedModel>("The request", true);
with.QueryParam<string>("apikey", "The Api Key found in the settings", true);
with.Notes("Updates an existing request e.g. Add a issue to the request");
});
Describe["DeleteRequests"] = description => description.AsSwagger(with =>
{
with.ResourcePath("/requests/{id}");
with.Summary("Deletes an existing request");
with.Model<ApiModel<bool>>();
with.PathParam<int>("id", required:true);
with.QueryParam<string>("apikey", "The Api Key found in the settings", true);
with.Notes("Deletes an existing request. If the request doesn't exist we will return an error.");
});
Describe["GetApiKey"] = description => description.AsSwagger(with =>
{
with.ResourcePath("/apikey");
with.Summary("Gets the Api Key for Ombi");
with.Model<ApiModel<string>>();
with.QueryParam<string>("username", required:true );
with.QueryParam<string>("password", required: true );
with.Notes("Get's the current api key for the application");
});
Describe["PutCredentials"] = description => description.AsSwagger(with =>
{
with.ResourcePath("/credentials/{username}");
with.Summary("Sets a new password for the user");
with.Model<ApiModel<string>>();
with.PathParam<int>("username", required:true);
with.QueryParam<string>("apikey", "The Api Key found in the settings", true);
with.BodyParam<UserUpdateViewModel>("User update view model", true);
with.Notes("Sets a new password for the user");
});
}
}
}

View file

@ -0,0 +1,165 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: ApiRequestModule.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using Nancy;
using Nancy.Extensions;
using Nancy.Validation;
using Newtonsoft.Json;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Store;
using Ombi.UI.Models;
using ISecurityExtensions = Ombi.Core.ISecurityExtensions;
namespace Ombi.UI.Modules
{
public class ApiRequestModule : BaseApiModule
{
public ApiRequestModule(IRequestService service, ISettingsService<PlexRequestSettings> pr, ISecurityExtensions security) : base("api", pr, security)
{
Get["GetRequests","/requests"] = x => GetRequests();
Get["GetRequest","/requests/{id}"] = x => GetSingleRequests(x);
Post["PostRequests", "/requests"] = x => CreateRequest();
Put["PutRequests", "/requests"] = x => UpdateRequest();
Delete["DeleteRequests", "/requests/{id}"] = x => DeleteRequest(x);
RequestService = service;
SettingsService = pr;
}
private IRequestService RequestService { get; }
private ISettingsService<PlexRequestSettings> SettingsService { get; }
public Response GetRequests()
{
var apiModel = new ApiModel<List<RequestedModel>> { Data = new List<RequestedModel>() };
var requests = RequestService.GetAll();
apiModel.Data.AddRange(requests);
return ReturnReponse(apiModel);
}
public Response GetSingleRequests(dynamic x)
{
var id = (int)x.id;
var apiModel = new ApiModel<List<RequestedModel>> { Data = new List<RequestedModel>() };
var requests = RequestService.Get(id);
if (string.IsNullOrEmpty(requests.Title))
{
apiModel.Error = true;
apiModel.ErrorMessage = "Request does not exist";
return ReturnReponse(apiModel);
}
apiModel.Data.Add(requests);
return ReturnReponse(apiModel);
}
public Response CreateRequest()
{
var request = JsonConvert.DeserializeObject<RequestedModel>(Request.Body.AsString());
var a = this.Validate(request);
if (!a.IsValid)
{
return ReturnValidationReponse(a);
}
var apiModel = new ApiModel<bool>();
var result = RequestService.AddRequest(request);
if (result == -1)
{
apiModel.Error = true;
apiModel.ErrorMessage = "Could not insert the new request into the database. Internal error.";
return ReturnReponse(apiModel);
}
apiModel.Data = true;
return ReturnReponse(apiModel);
}
public Response UpdateRequest()
{
var request = JsonConvert.DeserializeObject<RequestedModel>(Request.Body.AsString());
var a = this.Validate(request);
if (!a.IsValid)
{
return ReturnValidationReponse(a);
}
var apiModel = new ApiModel<bool>();
var result = RequestService.UpdateRequest(request);
if (!result)
{
apiModel.Error = true;
apiModel.ErrorMessage = "Could not update the request into the database. Internal error.";
return ReturnReponse(apiModel);
}
apiModel.Data = true;
return ReturnReponse(apiModel);
}
public Response DeleteRequest(dynamic x)
{
var id = (int)x.id;
var apiModel = new ApiModel<bool>();
try
{
var exisitingRequest = RequestService.Get(id);
if (string.IsNullOrEmpty(exisitingRequest.Title))
{
apiModel.Error = true;
apiModel.ErrorMessage = $"The request id {id} does not exist";
return ReturnReponse(apiModel);
}
RequestService.DeleteRequest(exisitingRequest);
apiModel.Data = true;
return ReturnReponse(apiModel);
}
catch (Exception)
{
apiModel.Error = true;
apiModel.ErrorMessage = "Could not delete the request from the database. Internal error.";
return ReturnReponse(apiModel);
}
}
}
}

View file

@ -0,0 +1,175 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: ApiSettingsMetadataModule.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using Nancy.Metadata.Modules;
using Nancy.Swagger;
using Ombi.Core.SettingModels;
using Ombi.UI.Models;
namespace Ombi.UI.Modules
{
public class ApiSettingsMetadataModule: MetadataModule<SwaggerRouteData>
{
public ApiSettingsMetadataModule()
{
Describe["GetAuthSettings"] = description => description.AsSwagger(with =>
{
with.ResourcePath("/settings/authentication");
with.Summary("Gets the authentication settings saved in the application");
with.Model<ApiModel<AuthenticationSettings>>();
with.Notes("Gets the authentication settings saved in the application");
with.QueryParam<string>("apikey", "The Api Key found in the settings", true);
});
Describe["PostAuthSettings"] = description => description.AsSwagger(with =>
{
with.ResourcePath("/settings/authentication");
with.Summary("Saves the authentication settings saved in the application");
with.Model<ApiModel<bool>>();
with.QueryParam<string>("apikey", "The Api Key found in the settings", true);
with.BodyParam<AuthenticationSettings>("Authentication settings", true);
with.Notes("Saves the authentication settings saved in the application");
});
Describe["GetPlexSettings"] = description => description.AsSwagger(with =>
{
with.ResourcePath("/settings/plex");
with.Summary("Gets the Plex settings saved in the application");
with.Model<ApiModel<PlexSettings>>();
with.Notes("Gets the Plex settings saved in the application");
with.QueryParam<string>("apikey", "The Api Key found in the settings", true);
});
Describe["PostPlexSettings"] = description => description.AsSwagger(with =>
{
with.ResourcePath("/settings/plex");
with.Summary("Saves the Plex settings saved in the application");
with.Model<ApiModel<bool>>();
with.QueryParam<string>("apikey", "The Api Key found in the settings", true);
with.BodyParam<PlexSettings>("Plex settings", true);
with.Notes("Saves the Plex settings saved in the application");
});
Describe["GetCouchPotatoSettings"] = description => description.AsSwagger(with =>
{
with.ResourcePath("/settings/couchpotato");
with.Summary("Gets the CouchPotato settings saved in the application");
with.Model<ApiModel<CouchPotatoSettings>>();
with.Notes("Gets the CouchPotato settings saved in the application");
with.QueryParam<string>("apikey", "The Api Key found in the settings", true);
});
Describe["PostCouchPotatoSettings"] = description => description.AsSwagger(with =>
{
with.ResourcePath("/settings/couchpotato");
with.Summary("Saves the CouchPotato settings saved in the application");
with.Model<ApiModel<bool>>();
with.QueryParam<string>("apikey", "The Api Key found in the settings", true);
with.BodyParam<CouchPotatoSettings>("CouchPotato settings", true);
with.Notes("Saves the CouchPotato settings saved in the application");
});
Describe["GetSonarrSettings"] = description => description.AsSwagger(with =>
{
with.ResourcePath("/settings/sonarr");
with.Summary("Gets the sonarr settings saved in the application");
with.Model<ApiModel<SonarrSettings>>();
with.Notes("Gets the sonarr settings saved in the application");
with.QueryParam<string>("apikey", "The Api Key found in the settings", true);
});
Describe["PostSonarrSettings"] = description => description.AsSwagger(with =>
{
with.ResourcePath("/settings/sonarr");
with.Summary("Saves the sonarr settings saved in the application");
with.Model<ApiModel<bool>>();
with.QueryParam<string>("apikey", "The Api Key found in the settings", true);
with.BodyParam<SonarrSettings>("sonarr settings", true);
with.Notes("Saves the sonarr settings saved in the application");
});
Describe["GetSickRageSettings"] = description => description.AsSwagger(with =>
{
with.ResourcePath("/settings/sickrage");
with.Summary("Gets the SickRage settings saved in the application");
with.Model<ApiModel<SickRageSettings>>();
with.Notes("Gets the SickRage settings saved in the application");
with.QueryParam<string>("apikey", "The Api Key found in the settings", true);
});
Describe["PostSickRageSettings"] = description => description.AsSwagger(with =>
{
with.ResourcePath("/settings/sickrage");
with.Summary("Saves the SickRage settings saved in the application");
with.Model<ApiModel<bool>>();
with.QueryParam<string>("apikey", "The Api Key found in the settings", true);
with.BodyParam<SickRageSettings>("SickRage settings", true);
with.Notes("Saves the sickrage settings saved in the application");
});
Describe["GetHeadphonesSettings"] = description => description.AsSwagger(with =>
{
with.ResourcePath("/settings/headphones");
with.Summary("Gets the headphones settings saved in the application");
with.Model<ApiModel<HeadphonesSettings>>();
with.Notes("Gets the headphones settings saved in the application");
with.QueryParam<string>("apikey", "The Api Key found in the settings", true);
});
Describe["PostHeadphonesSettings"] = description => description.AsSwagger(with =>
{
with.ResourcePath("/settings/sickrage");
with.Summary("Saves the headphones settings saved in the application");
with.Model<ApiModel<bool>>();
with.QueryParam<string>("apikey", "The Api Key found in the settings", true);
with.BodyParam<HeadphonesSettings>("headphones settings", true);
with.Notes("Saves the headphones settings saved in the application");
});
Describe["GetPlexRequestSettings"] = description => description.AsSwagger(with =>
{
with.ResourcePath("/settings/plexrequest");
with.Summary("Gets the plexrequest settings saved in the application");
with.Model<ApiModel<PlexRequestSettings>>();
with.Notes("Gets the plexrequest settings saved in the application");
with.QueryParam<string>("apikey", "The Api Key found in the settings", true);
});
Describe["PostPlexRequestSettings"] = description => description.AsSwagger(with =>
{
with.ResourcePath("/settings/plexrequest");
with.Summary("Saves the plexrequest settings saved in the application");
with.Model<ApiModel<bool>>();
with.QueryParam<string>("apikey", "The Api Key found in the settings", true);
with.BodyParam<PlexRequestSettings>("plexrequest settings", true);
with.Notes("Saves the plexrequest settings saved in the application");
});
}
}
}

View file

@ -0,0 +1,366 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: ApiModule.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using Nancy;
using Nancy.Extensions;
using Nancy.Validation;
using Newtonsoft.Json;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Helpers;
using Ombi.UI.Models;
using ISecurityExtensions = Ombi.Core.ISecurityExtensions;
namespace Ombi.UI.Modules
{
public class ApiSettingsModule : BaseApiModule
{
public ApiSettingsModule(ISettingsService<PlexRequestSettings> pr, ISettingsService<AuthenticationSettings> auth,
ISettingsService<PlexSettings> plexSettings, ISettingsService<CouchPotatoSettings> cp,
ISettingsService<SonarrSettings> sonarr, ISettingsService<SickRageSettings> sr, ISettingsService<HeadphonesSettings> hp, ISecurityExtensions security) : base("api", pr, security)
{
Get["GetVersion", "/version"] = x => GetVersion();
Get["GetAuthSettings", "/settings/authentication"] = x => GetAuthSettings();
Post["PostAuthSettings", "/settings/authentication"] = x => PostAuthSettings();
Get["GetPlexRequestSettings", "/settings/plexrequest"] = x => GetPrSettings();
Post["PostPlexRequestSettings", "/settings/plexrequest"] = x => PostPrSettings();
Get["GetPlexSettings", "/settings/plex"] = x => GetPlexSettings();
Post["PostPlexSettings", "/settings/plex"] = x => PostPlexSettings();
Get["GetCouchPotatoSettings", "/settings/couchpotato"] = x => GetCpSettings();
Post["PostCouchPotatoSettings", "/settings/couchpotato"] = x => PostCpSettings();
Get["GetSonarrSettings", "/settings/sonarr"] = x => GetSonarrSettings();
Post["PostSonarrSettings", "/settings/sonarr"] = x => PostSonarrSettings();
Get["GetSickRageSettings", "/settings/sickrage"] = x => GetSickRageSettings();
Post["PostSickRageSettings", "/settings/sickrage"] = x => PostSickRageSettings();
Get["GetHeadphonesSettings", "/settings/headphones"] = x => GetHeadphonesSettings();
Post["PostHeadphonesSettings", "/settings/headphones"] = x => PostHeadphonesSettings();
SettingsService = pr;
AuthSettings = auth;
PlexSettings = plexSettings;
CpSettings = cp;
SonarrSettings = sonarr;
SickRageSettings = sr;
HeadphonesSettings = hp;
}
private ISettingsService<PlexRequestSettings> SettingsService { get; }
private ISettingsService<AuthenticationSettings> AuthSettings { get; }
private ISettingsService<PlexSettings> PlexSettings { get; }
private ISettingsService<CouchPotatoSettings> CpSettings { get; }
private ISettingsService<SonarrSettings> SonarrSettings { get; }
private ISettingsService<SickRageSettings> SickRageSettings { get; }
private ISettingsService<HeadphonesSettings> HeadphonesSettings { get; }
private Response GetVersion()
{
return ReturnReponse(AssemblyHelper.GetProductVersion());
}
private Response GetPrSettings()
{
var model = new ApiModel<PlexRequestSettings>();
try
{
var settings = SettingsService.GetSettings();
model.Data = settings;
return ReturnReponse(model);
}
catch (Exception e)
{
model.ErrorMessage = e.Message;
model.Error = true;
return ReturnReponse(model);
}
}
private Response PostPrSettings()
{
var newSettings = JsonConvert.DeserializeObject<PlexRequestSettings>(Request.Body.AsString());
var result = this.Validate(newSettings);
if (!result.IsValid)
{
return ReturnValidationReponse(result);
}
var model = new ApiModel<bool>();
var settings = SettingsService.SaveSettings(newSettings);
if (settings)
{
model.Data = true;
return ReturnReponse(model);
}
model.Error = true;
model.ErrorMessage = "Could not update the settings";
return ReturnReponse(model);
}
private Response GetAuthSettings()
{
var model = new ApiModel<AuthenticationSettings>();
try
{
var settings = AuthSettings.GetSettings();
model.Data = settings;
return ReturnReponse(model);
}
catch (Exception e)
{
model.ErrorMessage = e.Message;
model.Error = true;
return ReturnReponse(model);
}
}
private Response PostAuthSettings()
{
var newSettings = JsonConvert.DeserializeObject<AuthenticationSettings>(Request.Body.AsString());
var result = this.Validate(newSettings);
if (!result.IsValid)
{
return ReturnValidationReponse(result);
}
var model = new ApiModel<bool>();
var settings = AuthSettings.SaveSettings(newSettings);
if (settings)
{
model.Data = true;
return ReturnReponse(model);
}
model.Error = true;
model.ErrorMessage = "Could not update the settings";
return ReturnReponse(model);
}
private Response GetPlexSettings()
{
var model = new ApiModel<PlexSettings>();
try
{
var settings = PlexSettings.GetSettings();
model.Data = settings;
return ReturnReponse(model);
}
catch (Exception e)
{
model.ErrorMessage = e.Message;
model.Error = true;
return ReturnReponse(model);
}
}
private Response PostPlexSettings()
{
var newSettings = JsonConvert.DeserializeObject<PlexSettings>(Request.Body.AsString());
var result = this.Validate(newSettings);
if (!result.IsValid)
{
return ReturnValidationReponse(result);
}
var model = new ApiModel<bool>();
var settings = PlexSettings.SaveSettings(newSettings);
if (settings)
{
model.Data = true;
return ReturnReponse(model);
}
model.Error = true;
model.ErrorMessage = "Could not update the settings";
return ReturnReponse(model);
}
private Response GetCpSettings()
{
var model = new ApiModel<CouchPotatoSettings>();
try
{
var settings = CpSettings.GetSettings();
model.Data = settings;
return ReturnReponse(model);
}
catch (Exception e)
{
model.ErrorMessage = e.Message;
model.Error = true;
return ReturnReponse(model);
}
}
private Response PostCpSettings()
{
var newSettings = JsonConvert.DeserializeObject<CouchPotatoSettings>(Request.Body.AsString());
var result = this.Validate(newSettings);
if (!result.IsValid)
{
return ReturnValidationReponse(result);
}
var model = new ApiModel<bool>();
var settings = CpSettings.SaveSettings(newSettings);
if (settings)
{
model.Data = true;
return ReturnReponse(model);
}
model.Error = true;
model.ErrorMessage = "Could not update the settings";
return ReturnReponse(model);
}
private Response GetSonarrSettings()
{
var model = new ApiModel<SonarrSettings>();
try
{
var settings = SonarrSettings.GetSettings();
model.Data = settings;
return ReturnReponse(model);
}
catch (Exception e)
{
model.ErrorMessage = e.Message;
model.Error = true;
return ReturnReponse(model);
}
}
private Response PostSonarrSettings()
{
var newSettings = JsonConvert.DeserializeObject<SonarrSettings>(Request.Body.AsString());
var result = this.Validate(newSettings);
if (!result.IsValid)
{
return ReturnValidationReponse(result);
}
var model = new ApiModel<bool>();
var settings = SonarrSettings.SaveSettings(newSettings);
if (settings)
{
model.Data = true;
return ReturnReponse(model);
}
model.Error = true;
model.ErrorMessage = "Could not update the settings";
return ReturnReponse(model);
}
private Response GetSickRageSettings()
{
var model = new ApiModel<SickRageSettings>();
try
{
var settings = SickRageSettings.GetSettings();
model.Data = settings;
return ReturnReponse(model);
}
catch (Exception e)
{
model.ErrorMessage = e.Message;
model.Error = true;
return ReturnReponse(model);
}
}
private Response PostSickRageSettings()
{
var newSettings = JsonConvert.DeserializeObject<SickRageSettings>(Request.Body.AsString());
var result = this.Validate(newSettings);
if (!result.IsValid)
{
return ReturnValidationReponse(result);
}
var model = new ApiModel<bool>();
var settings = SickRageSettings.SaveSettings(newSettings);
if (settings)
{
model.Data = true;
return ReturnReponse(model);
}
model.Error = true;
model.ErrorMessage = "Could not update the settings";
return ReturnReponse(model);
}
private Response GetHeadphonesSettings()
{
var model = new ApiModel<HeadphonesSettings>();
try
{
var settings = HeadphonesSettings.GetSettings();
model.Data = settings;
return ReturnReponse(model);
}
catch (Exception e)
{
model.ErrorMessage = e.Message;
model.Error = true;
return ReturnReponse(model);
}
}
private Response PostHeadphonesSettings()
{
var newSettings = JsonConvert.DeserializeObject<HeadphonesSettings>(Request.Body.AsString());
var result = this.Validate(newSettings);
if (!result.IsValid)
{
return ReturnValidationReponse(result);
}
var model = new ApiModel<bool>();
var settings = HeadphonesSettings.SaveSettings(newSettings);
if (settings)
{
model.Data = true;
return ReturnReponse(model);
}
model.Error = true;
model.ErrorMessage = "Could not update the settings";
return ReturnReponse(model);
}
}
}

View file

@ -0,0 +1,62 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: ApiUserMetadataModule.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using Nancy.Metadata.Modules;
using Nancy.Swagger;
using Ombi.UI.Models;
using Ombi.UI.Models.UserManagement;
namespace Ombi.UI.Modules
{
public class ApiUserMetadataModule: MetadataModule<SwaggerRouteData>
{
public ApiUserMetadataModule()
{
Describe["GetApiKey"] = description => description.AsSwagger(with =>
{
with.ResourcePath("/apikey");
with.Summary("Gets the Api Key for Ombi");
with.Model<ApiModel<string>>();
with.QueryParam<string>("username", required:true );
with.QueryParam<string>("password", required: true );
with.Notes("Get's the current api key for the application");
});
Describe["PutCredentials"] = description => description.AsSwagger(with =>
{
with.ResourcePath("/credentials/{username}");
with.Summary("Sets a new password for the user");
with.Model<ApiModel<string>>();
with.PathParam<int>("username", required:true);
with.QueryParam<string>("apikey", "The Api Key found in the settings", true);
with.BodyParam<UserUpdateViewModel>("User update view model", true);
with.Notes("Sets a new password for the user");
});
}
}
}

View file

@ -0,0 +1,102 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: ApiModule.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using Nancy;
using Nancy.ModelBinding;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.UI.Models;
using Ombi.UI.Models.UserManagement;
using ISecurityExtensions = Ombi.Core.ISecurityExtensions;
namespace Ombi.UI.Modules
{
public class ApiUserModule : BaseApiModule
{
public ApiUserModule(ISettingsService<PlexRequestSettings> pr, ICustomUserMapper m, ISecurityExtensions security) : base("api", pr, security)
{
Put["PutCredentials", "/credentials/{username}"] = x => ChangePassword(x);
Get["GetApiKey", "/apikey"] = x => GetApiKey();
SettingsService = pr;
UserMapper = m;
}
private ISettingsService<PlexRequestSettings> SettingsService { get; }
private ICustomUserMapper UserMapper { get; }
public Response ChangePassword(dynamic x)
{
var username = (string)x.username;
var userModel = this.BindAndValidate<UserUpdateViewModel>();
if (!ModelValidationResult.IsValid)
{
return ReturnValidationReponse(ModelValidationResult);
}
var valid = UserMapper.ValidateUser(username, userModel.CurrentPassword);
if (valid == null)
{
var errorModel = new ApiModel<string> { Error = true, ErrorMessage = "Incorrect username or password" };
return ReturnReponse(errorModel);
}
var result = UserMapper.UpdatePassword(username, userModel.CurrentPassword, userModel.NewPassword);
if (!result)
{
var errorModel = new ApiModel<string> { Error = true, ErrorMessage = "Could not update the password. " };
return ReturnReponse(errorModel);
}
var model = new ApiModel<string> { Data = "Successfully updated the password"};
return ReturnReponse(model);
}
public Response GetApiKey()
{
var user = Request.Query["username"];
var password = Request.Query["password"];
var result = UserMapper.ValidateUser(user, password);
var model = new ApiModel<string>();
if (result == null)
{
model.Error = true;
model.ErrorMessage = "Incorrect username or password";
return ReturnReponse(model);
}
var settings = SettingsService.GetSettings();
model.Data = settings.ApiKey;
return ReturnReponse(model);
}
}
}

View file

@ -0,0 +1,122 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: BaseApiModule.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System.Collections.Generic;
using System.Linq;
using Nancy;
using Nancy.Validation;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Store;
using Ombi.UI.Models;
using ISecurityExtensions = Ombi.Core.ISecurityExtensions;
namespace Ombi.UI.Modules
{
public abstract class BaseApiModule : BaseModule
{
protected BaseApiModule(ISettingsService<PlexRequestSettings> s, ISecurityExtensions security) : base(s,security)
{
Settings = s;
Before += (ctx) => CheckAuth();
}
protected BaseApiModule(string modulePath, ISettingsService<PlexRequestSettings> s, ISecurityExtensions security) : base(modulePath, s, security)
{
Settings = s;
Before += (ctx) => CheckAuth();
}
private ISettingsService<PlexRequestSettings> Settings { get; }
protected Response ReturnReponse(object result)
{
var queryString = (DynamicDictionary)Context.Request.Query;
dynamic value;
if (queryString.TryGetValue("xml", out value))
{
if ((bool)value)
{
return Response.AsXml(result);
}
}
return Response.AsJson(result);
}
protected Response ReturnValidationReponse(ModelValidationResult result)
{
var errors = result.Errors;
var model = new ApiModel<List<string>>
{
Error = true,
ErrorMessage = "Please view the error messages inside the data node",
Data = new List<string>()
};
foreach (var error in errors)
{
model.Data.AddRange(error.Value.Select(x => x.ErrorMessage));
}
return ReturnReponse(model);
}
private Response CheckAuth()
{
if (Request.Path.Contains("api/apikey")) // We do not need the apikey for this call
{
return null;
}
var settings = Settings.GetSettings();
var apiModel = new ApiModel<List<RequestedModel>> { Data = new List<RequestedModel>() };
if (!Authenticated(settings))
{
apiModel.Error = true;
apiModel.ErrorMessage = "ApiKey is invalid or not present, Please use 'apikey' in the querystring.";
return ReturnReponse(apiModel);
}
return null;
}
private bool Authenticated(PlexRequestSettings settings)
{
var query = (DynamicDictionary)Context.Request.Query;
dynamic key;
if (!query.TryGetValue("apikey", out key))
{
return false;
}
if ((string)key == settings.ApiKey)
{
return true;
}
return false;
}
}
}

View file

@ -0,0 +1,385 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: ApplicationTesterModule.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.IO;
using Nancy;
using Nancy.ModelBinding;
using Nancy.Security;
using Nancy.Validation;
using NLog;
using Ombi.Api.Interfaces;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.UI.Helpers;
using Ombi.UI.Models;
using ISecurityExtensions = Ombi.Core.ISecurityExtensions;
namespace Ombi.UI.Modules
{
public class ApplicationTesterModule : BaseAuthModule
{
public ApplicationTesterModule(ICouchPotatoApi cpApi, ISonarrApi sonarrApi, IPlexApi plexApi,
ISickRageApi srApi, IHeadphonesApi hpApi, ISettingsService<PlexRequestSettings> pr, ISecurityExtensions security,
IWatcherApi watcherApi, IRadarrApi radarrApi, IEmbyApi emby) : base("test", pr, security)
{
this.RequiresAuthentication();
CpApi = cpApi;
SonarrApi = sonarrApi;
PlexApi = plexApi;
SickRageApi = srApi;
HeadphonesApi = hpApi;
WatcherApi = watcherApi;
RadarrApi = radarrApi;
Emby = emby;
Post["/cp"] = _ => CouchPotatoTest();
Post["/sonarr"] = _ => SonarrTest();
Post["/radarr"] = _ => RadarrTest();
Post["/plex"] = _ => PlexTest();
Post["/sickrage"] = _ => SickRageTest();
Post["/headphones"] = _ => HeadphonesTest();
Post["/plexdb"] = _ => TestPlexDb();
Post["/watcher"] = _ => WatcherTest();
Post["/emby"] = _ => EmbyTest();
}
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
private ISonarrApi SonarrApi { get; }
private ICouchPotatoApi CpApi { get; }
private IPlexApi PlexApi { get; }
private ISickRageApi SickRageApi { get; }
private IHeadphonesApi HeadphonesApi { get; }
private IWatcherApi WatcherApi { get; }
private IRadarrApi RadarrApi { get; }
private IEmbyApi Emby { get; set; }
private Response CouchPotatoTest()
{
var couchPotatoSettings = this.Bind<CouchPotatoSettings>();
var valid = this.Validate(couchPotatoSettings);
if (!valid.IsValid)
{
return Response.AsJson(valid.SendJsonError());
}
try
{
var status = CpApi.GetStatus(couchPotatoSettings.FullUri, couchPotatoSettings.ApiKey);
return status.success
? Response.AsJson(new JsonResponseModel { Result = true, Message = "Connected to CouchPotato successfully!" })
: Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not connect to CouchPotato, please check your settings." });
}
catch (Exception e) // Exceptions are expected if we cannot connect so we will just log and swallow them.
{
Log.Warn("Exception thrown when attempting to get CP's status: ");
Log.Warn(e);
var message = $"Could not connect to CouchPotato, please check your settings. <strong>Exception Message:</strong> {e.Message}";
if (e.InnerException != null)
{
message = $"Could not connect to CouchPotato, please check your settings. <strong>Exception Message:</strong> {e.InnerException.Message}";
}
return Response.AsJson(new JsonResponseModel { Result = false, Message = message });
}
}
private Response WatcherTest()
{
var settings = this.Bind<WatcherSettings>();
var valid = this.Validate(settings);
if (!valid.IsValid)
{
return Response.AsJson(valid.SendJsonError());
}
try
{
var status = WatcherApi.Version(settings.ApiKey, settings.FullUri);
return status.response
? Response.AsJson(new JsonResponseModel { Result = true, Message = "Connected to Watcher successfully!" })
: Response.AsJson(new JsonResponseModel { Result = false, Message = $"Could not connect to Watcher, Error: {status.ErrorMessage}" });
}
catch (Exception e) // Exceptions are expected if we cannot connect so we will just log and swallow them.
{
Log.Warn("Exception thrown when attempting to test Watcher ");
Log.Warn(e);
var message = $"Could not connect to Watcher, please check your settings. <strong>Exception Message:</strong> {e.Message}";
if (e.InnerException != null)
{
message = $"Could not connect to Watcher, please check your settings. <strong>Exception Message:</strong> {e.InnerException.Message}";
}
return Response.AsJson(new JsonResponseModel { Result = false, Message = message });
}
}
private Response SonarrTest()
{
var sonarrSettings = this.Bind<SonarrSettings>();
var valid = this.Validate(sonarrSettings);
if (!valid.IsValid)
{
return Response.AsJson(valid.SendJsonError());
}
try
{
var status = SonarrApi.SystemStatus(sonarrSettings.ApiKey, sonarrSettings.FullUri);
return status?.version != null
? Response.AsJson(new JsonResponseModel { Result = true, Message = "Connected to Sonarr successfully!" })
: Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not connect to Sonarr, please check your settings." });
}
catch (Exception e) // Exceptions are expected, if we cannot connect so we will just log and swallow them.
{
Log.Warn("Exception thrown when attempting to get Sonarr's status: ");
Log.Warn(e);
var message = $"Could not connect to Sonarr, please check your settings. <strong>Exception Message:</strong> {e.Message}";
if (e.InnerException != null)
{
message = $"Could not connect to Sonarr, please check your settings. <strong>Exception Message:</strong> {e.InnerException.Message}";
}
return Response.AsJson(new JsonResponseModel { Result = false, Message = message });
}
}
private Response RadarrTest()
{
var radarrSettings = this.Bind<RadarrSettings>();
var valid = this.Validate(radarrSettings);
if (!valid.IsValid)
{
return Response.AsJson(valid.SendJsonError());
}
try
{
var status = RadarrApi.SystemStatus(radarrSettings.ApiKey, radarrSettings.FullUri);
return status?.version != null
? Response.AsJson(new JsonResponseModel { Result = true, Message = "Connected to Radarr successfully!" })
: Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not connect to Radarr, please check your settings." });
}
catch (Exception e) // Exceptions are expected, if we cannot connect so we will just log and swallow them.
{
Log.Warn("Exception thrown when attempting to get Radarr's status: ");
Log.Warn(e);
var message = $"Could not connect to Radarr, please check your settings. <strong>Exception Message:</strong> {e.Message}";
if (e.InnerException != null)
{
message = $"Could not connect to Radarr, please check your settings. <strong>Exception Message:</strong> {e.InnerException.Message}";
}
return Response.AsJson(new JsonResponseModel { Result = false, Message = message });
}
}
private Response PlexTest()
{
var plexSettings = this.Bind<PlexSettings>();
var valid = this.Validate(plexSettings);
if (!valid.IsValid)
{
return Response.AsJson(valid.SendJsonError());
}
if (plexSettings?.PlexAuthToken == null)
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Plex is not setup yet, you need to update your Authentication settings" });
}
try
{
var status = PlexApi.GetStatus(plexSettings.PlexAuthToken, plexSettings.FullUri);
return status != null
? Response.AsJson(new JsonResponseModel { Result = true, Message = "Connected to Plex successfully!" })
: Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not connect to Plex, please check your settings." });
}
catch (Exception e) // Exceptions are expected, if we cannot connect so we will just log and swallow them.
{
Log.Warn("Exception thrown when attempting to get Plex's status: ");
Log.Warn(e);
var message = $"Could not connect to Plex, please check your settings. <strong>Exception Message:</strong> {e.Message}";
if (e.InnerException != null)
{
message = $"Could not connect to Plex, please check your settings. <strong>Exception Message:</strong> {e.InnerException.Message}";
}
return Response.AsJson(new JsonResponseModel { Result = false, Message = message });
}
}
private Response EmbyTest()
{
var emby = this.Bind<EmbySettings>();
var valid = this.Validate(emby);
if (!valid.IsValid)
{
return Response.AsJson(valid.SendJsonError());
}
try
{
var status = Emby.GetUsers(emby?.FullUri, emby?.ApiKey);
return status != null
? Response.AsJson(new JsonResponseModel { Result = true, Message = "Connected to Emby successfully!" })
: Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not connect to Emby, please check your settings." });
}
catch (Exception e) // Exceptions are expected, if we cannot connect so we will just log and swallow them.
{
Log.Warn("Exception thrown when attempting to get Emby's users: ");
Log.Warn(e);
var message = $"Could not connect to Emby, please check your settings. <strong>Exception Message:</strong> {e.Message}";
if (e.InnerException != null)
{
message = $"Could not connect to Emby, please check your settings. <strong>Exception Message:</strong> {e.InnerException.Message}";
}
return Response.AsJson(new JsonResponseModel { Result = false, Message = message });
}
}
private Response SickRageTest()
{
var sickRageSettings = this.Bind<SickRageSettings>();
var valid = this.Validate(sickRageSettings);
if (!valid.IsValid)
{
return Response.AsJson(valid.SendJsonError());
}
try
{
var status = SickRageApi.Ping(sickRageSettings.ApiKey, sickRageSettings.FullUri);
return status?.result == "success"
? Response.AsJson(new JsonResponseModel { Result = true, Message = "Connected to SickRage successfully!" })
: Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not connect to SickRage, please check your settings." });
}
catch (Exception e) // Exceptions are expected, if we cannot connect so we will just log and swallow them.
{
Log.Warn("Exception thrown when attempting to get SickRage's status: ");
Log.Warn(e);
var message = $"Could not connect to SickRage, please check your settings. <strong>Exception Message:</strong> {e.Message}";
if (e.InnerException != null)
{
message = $"Could not connect to SickRage, please check your settings. <strong>Exception Message:</strong> {e.InnerException.Message}";
}
return Response.AsJson(new JsonResponseModel { Result = false, Message = message });
}
}
private Response HeadphonesTest()
{
var settings = this.Bind<HeadphonesSettings>();
var valid = this.Validate(settings);
if (!valid.IsValid)
{
return Response.AsJson(valid.SendJsonError());
}
try
{
var result = HeadphonesApi.GetVersion(settings.ApiKey, settings.FullUri);
if (!string.IsNullOrEmpty(result.latest_version))
{
return
Response.AsJson(new JsonResponseModel
{
Result = true,
Message = "Connected to Headphones successfully!"
});
}
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not connect to Headphones, please check your settings." });
}
catch (Exception e)
{
Log.Warn("Exception thrown when attempting to get Headphones's status: ");
Log.Warn(e);
var message = $"Could not connect to Headphones, please check your settings. <strong>Exception Message:</strong> {e.Message}";
if (e.InnerException != null)
{
message = $"Could not connect to Headphones, please check your settings. <strong>Exception Message:</strong> {e.InnerException.Message}";
}
return Response.AsJson(new JsonResponseModel { Result = false, Message = message }); ;
}
}
private Response TestPlexDb()
{
var settings = this.Bind<PlexSettings>();
var valid = this.Validate(settings);
if (!valid.IsValid)
{
return Response.AsJson(valid.SendJsonError());
}
try
{
var location = string.Empty;
if (string.IsNullOrEmpty(settings.PlexDatabaseLocationOverride))
{
if (Type.GetType("Mono.Runtime") != null)
{
// Mono
location = Path.Combine("/var/lib/plexmediaserver/Library/Application Support/",
"Plex Media Server", "Plug-in Support", "Databases", "com.plexapp.plugins.library.db");
}
else
{
// Default Windows
location = Path.Combine(Environment.ExpandEnvironmentVariables("%LOCALAPPDATA%"),
"Plex Media Server", "Plug-in Support", "Databases", "com.plexapp.plugins.library.db");
}
}
else
{
location = Path.Combine(settings.PlexDatabaseLocationOverride, "Plug-in Support", "Databases", "com.plexapp.plugins.library.db");
}
if (File.Exists(location))
{
return Response.AsJson(new JsonResponseModel
{
Result = true,
Message = "Found the database!"
});
}
return Response.AsJson(new JsonResponseModel
{
Result = false,
Message = $"Could not find the database at the following full location : {location}"
});
}
catch (Exception e)
{
Log.Warn("Exception thrown when attempting to find the plex database: ");
Log.Warn(e);
var message = $"Could not find Plex's DB, please check your settings. <strong>Exception Message:</strong> {e.Message}";
if (e.InnerException != null)
{
message = $"Could not find Plex's DB, please check your settings. <strong>Exception Message:</strong> {e.InnerException.Message}";
}
return Response.AsJson(new JsonResponseModel { Result = false, Message = message }); ;
}
}
}
}

View file

@ -0,0 +1,511 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: ApprovalModule.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Nancy;
using NLog;
using Ombi.Api.Interfaces;
using Ombi.Core;
using Ombi.Core.Queue;
using Ombi.Core.SettingModels;
using Ombi.Helpers;
using Ombi.Helpers.Permissions;
using Ombi.Store;
using Ombi.UI.Helpers;
using Ombi.UI.Models;
using ISecurityExtensions = Ombi.Core.ISecurityExtensions;
namespace Ombi.UI.Modules
{
public class ApprovalModule : BaseAuthModule
{
public ApprovalModule(IRequestService service, ISonarrApi sonarrApi,
ISettingsService<SonarrSettings> sonarrSettings, ISickRageApi srApi, ISettingsService<SickRageSettings> srSettings,
ISettingsService<HeadphonesSettings> hpSettings, IHeadphonesApi hpApi, ISettingsService<PlexRequestSettings> pr, ITransientFaultQueue faultQueue
, ISecurityExtensions security, IMovieSender movieSender, ICacheProvider cache) : base("approval", pr, security)
{
Before += (ctx) => Security.AdminLoginRedirect(ctx, Permissions.Administrator,Permissions.ManageRequests);
Service = service;
SonarrApi = sonarrApi;
SonarrSettings = sonarrSettings;
SickRageApi = srApi;
SickRageSettings = srSettings;
HeadphonesSettings = hpSettings;
HeadphoneApi = hpApi;
FaultQueue = faultQueue;
MovieSender = movieSender;
Cache = cache;
Post["/approve", true] = async (x, ct) => await Approve((int)Request.Form.requestid, (string)Request.Form.qualityId);
Post["/deny", true] = async (x, ct) => await DenyRequest((int)Request.Form.requestid, (string)Request.Form.reason);
Post["/approveall", true] = async (x, ct) => await ApproveAll();
Post["/approveallmovies", true] = async (x, ct) => await ApproveAllMovies();
Post["/approvealltvshows", true] = async (x, ct) => await ApproveAllTVShows();
Post["/deleteallmovies", true] = async (x, ct) => await DeleteAllMovies();
Post["/deletealltvshows", true] = async (x, ct) => await DeleteAllTVShows();
Post["/deleteallalbums", true] = async (x, ct) => await DeleteAllAlbums();
}
private IRequestService Service { get; }
private IMovieSender MovieSender { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
private ISettingsService<SonarrSettings> SonarrSettings { get; }
private ISettingsService<SickRageSettings> SickRageSettings { get; }
private ISettingsService<HeadphonesSettings> HeadphonesSettings { get; }
private ISonarrApi SonarrApi { get; }
private ISickRageApi SickRageApi { get; }
private IHeadphonesApi HeadphoneApi { get; }
private ITransientFaultQueue FaultQueue { get; }
private ICacheProvider Cache { get; }
/// <summary>
/// Approves the specified request identifier.
/// </summary>
/// <param name="requestId">The request identifier.</param>
/// <returns></returns>
private async Task<Response> Approve(int requestId, string qualityId)
{
Log.Info("approving request {0}", requestId);
// Get the request from the DB
var request = await Service.GetAsync(requestId);
if (request == null)
{
Log.Warn("Tried approving a request, but the request did not exist in the database, requestId = {0}", requestId);
return Response.AsJson(new JsonResponseModel { Result = false, Message = "There are no requests to approve. Please refresh." });
}
switch (request.Type)
{
case RequestType.Movie:
return await RequestMovieAndUpdateStatus(request, qualityId);
case RequestType.TvShow:
return await RequestTvAndUpdateStatus(request, qualityId);
case RequestType.Album:
return await RequestAlbumAndUpdateStatus(request);
default:
throw new ArgumentOutOfRangeException(nameof(request));
}
}
private async Task<Response> RequestTvAndUpdateStatus(RequestedModel request, string qualityId)
{
var sender = new TvSenderOld(SonarrApi, SickRageApi, Cache); // TODO put back
var sonarrSettings = await SonarrSettings.GetSettingsAsync();
if (sonarrSettings.Enabled)
{
Log.Trace("Sending to Sonarr");
var result = await sender.SendToSonarr(sonarrSettings, request, qualityId);
Log.Trace("Sonarr Result: ");
Log.Trace(result.DumpJson());
if (!string.IsNullOrEmpty(result.title))
{
Log.Info("Sent successfully, Approving request now.");
request.Approved = true;
var requestResult = await Service.UpdateRequestAsync(request);
Log.Trace("Approval result: {0}", requestResult);
if (requestResult)
{
return Response.AsJson(new JsonResponseModel { Result = true });
}
return
Response.AsJson(new JsonResponseModel
{
Result = false,
Message = "Updated Sonarr but could not approve it in PlexRequests :("
});
}
return Response.AsJson(ValidationHelper.SendSonarrError(result.ErrorMessages));
}
var srSettings = await SickRageSettings.GetSettingsAsync();
if (srSettings.Enabled)
{
Log.Trace("Sending to SickRage");
var result = sender.SendToSickRage(srSettings, request, qualityId);
Log.Trace("SickRage Result: ");
Log.Trace(result.DumpJson());
if (result?.result == "success")
{
Log.Info("Sent successfully, Approving request now.");
request.Approved = true;
var requestResult = await Service.UpdateRequestAsync(request);
Log.Trace("Approval result: {0}", requestResult);
return Response.AsJson(requestResult
? new JsonResponseModel { Result = true }
: new JsonResponseModel { Result = false, Message = "Updated SickRage but could not approve it in PlexRequests :(" });
}
return Response.AsJson(new JsonResponseModel
{
Result = false,
Message = result?.message != null ? "<b>Message From SickRage: </b>" + result.message : "Could not add the series to SickRage"
});
}
request.Approved = true;
var res = await Service.UpdateRequestAsync(request);
return Response.AsJson(res
? new JsonResponseModel { Result = true, Message = "This has been approved, but It has not been sent to Sonarr/SickRage because it has not been configured" }
: new JsonResponseModel { Result = false, Message = "Updated SickRage but could not approve it in PlexRequests :(" });
}
private async Task<Response> RequestMovieAndUpdateStatus(RequestedModel request, string qualityId)
{
var result = await MovieSender.Send(request, qualityId);
if (!result.MovieSendingEnabled)
{
// Approve it
request.Approved = true;
Log.Warn("We approved movie: {0} but could not add it to CouchPotato/Watcher/Radarr because it has not been setup", request.Title);
// Update the record
var inserted = await Service.UpdateRequestAsync(request);
return Response.AsJson(inserted
? new JsonResponseModel { Result = true, Message = "This has been approved, but It has not been sent to CouchPotato/Watcher because it has not been configured." }
: new JsonResponseModel
{
Result = false,
Message = "We could not approve this request. Please try again or check the logs."
});
}
if (result.Result)
{
// Approve it
request.Approved = true;
// Update the record
var inserted = await Service.UpdateRequestAsync(request);
return Response.AsJson(inserted
? new JsonResponseModel { Result = true }
: new JsonResponseModel
{
Result = false,
Message = "We could not approve this request. Please try again or check the logs."
});
}
return
Response.AsJson(
new
{
Result = false,
Message =
"Something went wrong adding the movie! Please check your settings."
});
}
private async Task<Response> RequestAlbumAndUpdateStatus(RequestedModel request)
{
var hpSettings = await HeadphonesSettings.GetSettingsAsync();
Log.Info("Adding album to Headphones : {0}", request.Title);
if (!hpSettings.Enabled)
{
// Approve it
request.Approved = true;
Log.Warn("We approved Album: {0} but could not add it to Headphones because it has not been setup", request.Title);
// Update the record
var inserted = await Service.UpdateRequestAsync(request);
return Response.AsJson(inserted
? new JsonResponseModel { Result = true, Message = "This has been approved, but It has not been sent to Headphones because it has not been configured." }
: new JsonResponseModel
{
Result = false,
Message = "We could not approve this request. Please try again or check the logs."
});
}
var sender = new HeadphonesSender(HeadphoneApi, hpSettings, Service);
var result = sender.AddAlbum(request);
return Response.AsJson(new JsonResponseModel { Result = true, Message = "We have sent the approval to Headphones for processing, This can take a few minutes." });
}
private async Task<Response> ApproveAllMovies()
{
var requests = await Service.GetAllAsync();
requests = requests.Where(x => x.CanApprove && x.Type == RequestType.Movie);
var requestedModels = requests as RequestedModel[] ?? requests.ToArray();
if (!requestedModels.Any())
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "There are no movie requests to approve. Please refresh." });
}
try
{
return await UpdateRequestsAsync(requestedModels);
}
catch (Exception e)
{
Log.Fatal(e);
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Something bad happened, please check the logs!" });
}
}
private async Task<Response> DeleteAllMovies()
{
var requests = await Service.GetAllAsync();
requests = requests.Where(x => x.Type == RequestType.Movie);
var requestedModels = requests as RequestedModel[] ?? requests.ToArray();
if (!requestedModels.Any())
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "There are no movie requests to delete. Please refresh." });
}
try
{
return await DeleteRequestsAsync(requestedModels);
}
catch (Exception e)
{
Log.Fatal(e);
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Something bad happened, please check the logs!" });
}
}
private async Task<Response> DeleteAllAlbums()
{
var requests = await Service.GetAllAsync();
requests = requests.Where(x => x.Type == RequestType.Album);
var requestedModels = requests as RequestedModel[] ?? requests.ToArray();
if (!requestedModels.Any())
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "There are no album requests to delete. Please refresh." });
}
try
{
return await DeleteRequestsAsync(requestedModels);
}
catch (Exception e)
{
Log.Fatal(e);
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Something bad happened, please check the logs!" });
}
}
private async Task<Response> ApproveAllTVShows()
{
var requests = await Service.GetAllAsync();
requests = requests.Where(x => x.CanApprove && x.Type == RequestType.TvShow);
var requestedModels = requests as RequestedModel[] ?? requests.ToArray();
if (!requestedModels.Any())
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "There are no tv show requests to approve. Please refresh." });
}
try
{
return await UpdateRequestsAsync(requestedModels);
}
catch (Exception e)
{
Log.Fatal(e);
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Something bad happened, please check the logs!" });
}
}
private async Task<Response> DeleteAllTVShows()
{
var requests = await Service.GetAllAsync();
requests = requests.Where(x => x.Type == RequestType.TvShow);
var requestedModels = requests as RequestedModel[] ?? requests.ToArray();
if (!requestedModels.Any())
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "There are no tv show requests to delete. Please refresh." });
}
try
{
return await DeleteRequestsAsync(requestedModels);
}
catch (Exception e)
{
Log.Fatal(e);
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Something bad happened, please check the logs!" });
}
}
/// <summary>
/// Approves all.
/// </summary>
/// <returns></returns>
private async Task<Response> ApproveAll()
{
var requests = await Service.GetAllAsync();
requests = requests.Where(x => x.CanApprove);
var requestedModels = requests as RequestedModel[] ?? requests.ToArray();
if (!requestedModels.Any())
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "There are no requests to approve. Please refresh." });
}
try
{
return await UpdateRequestsAsync(requestedModels);
}
catch (Exception e)
{
Log.Fatal(e);
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Something bad happened, please check the logs!" });
}
}
private async Task<Response> DeleteRequestsAsync(IEnumerable<RequestedModel> requestedModels)
{
try
{
var result = await Service.BatchDeleteAsync(requestedModels);
return Response.AsJson(result
? new JsonResponseModel { Result = true }
: new JsonResponseModel { Result = false, Message = "We could not delete all of the requests. Please try again or check the logs." });
}
catch (Exception e)
{
Log.Fatal(e);
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Something bad happened, please check the logs!" });
}
}
private async Task<Response> UpdateRequestsAsync(RequestedModel[] requestedModels)
{
var updatedRequests = new List<RequestedModel>();
foreach (var r in requestedModels)
{
if (r.Type == RequestType.Movie)
{
var movieResult = await MovieSender.Send(r);
if (movieResult.Result)
{
r.Approved = true;
updatedRequests.Add(r);
}
else
{
Log.Error("Could not approve and send the movie {0} to couch potato!", r.Title);
}
if(!movieResult.MovieSendingEnabled)
{
r.Approved = true;
updatedRequests.Add(r);
}
}
if (r.Type == RequestType.TvShow)
{
var sender = new TvSenderOld(SonarrApi, SickRageApi, Cache); // TODO put back
var sr = await SickRageSettings.GetSettingsAsync();
var sonarr = await SonarrSettings.GetSettingsAsync();
if (sr.Enabled)
{
var res = sender.SendToSickRage(sr, r);
if (res?.result == "success")
{
r.Approved = true;
updatedRequests.Add(r);
}
else
{
Log.Error("Could not approve and send the TV {0} to SickRage!", r.Title);
Log.Error("SickRage Message: {0}", res?.message);
}
}
else if (sonarr.Enabled)
{
var res = await sender.SendToSonarr(sonarr, r);
if (!string.IsNullOrEmpty(res?.title))
{
r.Approved = true;
updatedRequests.Add(r);
}
else
{
Log.Error("Could not approve and send the TV {0} to Sonarr!", r.Title);
res?.ErrorMessages?.ForEach(x => Log.Error("Error messages: {0}", x));
}
}
else
{
r.Approved = true;
updatedRequests.Add(r);
}
}
}
try
{
var result = await Service.BatchUpdateAsync(updatedRequests);
return Response.AsJson(result
? new JsonResponseModel { Result = true }
: new JsonResponseModel { Result = false, Message = "We could not approve all of the requests. Please try again or check the logs." });
}
catch (Exception e)
{
Log.Fatal(e);
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Something bad happened, please check the logs!" });
}
}
private async Task<Response> DenyRequest(int requestId, string reason)
{
// Get the request from the DB
var request = await Service.GetAsync(requestId);
// Deny it
request.Denied = true;
request.DeniedReason = reason;
// Update the new value
var result = await Service.UpdateRequestAsync(request);
return result
? Response.AsJson(new JsonResponseModel { Result = true, Message = "Request has been denied" })
: Response.AsJson(new JsonResponseModel { Result = false, Message = "An error happened, could not update the DB" });
}
}
}

View file

@ -0,0 +1,78 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: BaseAuthModule.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using Nancy;
using Nancy.Extensions;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Helpers;
using ISecurityExtensions = Ombi.Core.ISecurityExtensions;
namespace Ombi.UI.Modules
{
public abstract class BaseAuthModule : BaseModule
{
protected BaseAuthModule(ISettingsService<PlexRequestSettings> pr, ISecurityExtensions security) : base(pr,security)
{
PlexRequestSettings = pr;
Before += (ctx) => CheckAuth();
}
protected BaseAuthModule(string modulePath, ISettingsService<PlexRequestSettings> pr, ISecurityExtensions security) : base(modulePath, pr, security)
{
PlexRequestSettings = pr;
Before += (ctx) => CheckAuth();
}
protected ISettingsService<PlexRequestSettings> PlexRequestSettings { get; }
private Response CheckAuth()
{
var settings = PlexRequestSettings.GetSettings();
var baseUrl = settings.BaseUrl;
// Have we been through the wizard?
if (!settings.Wizard)
{
return Context.GetRedirect(string.IsNullOrEmpty(baseUrl) ? "~/wizard" : $"~/{baseUrl}/wizard");
}
if (!Request.IsAjaxRequest())
{
var redirectPath = string.IsNullOrEmpty(baseUrl) ? "~/userlogin" : $"~/{baseUrl}/userlogin";
if (Session[SessionKeys.UsernameKey] == null && Context?.CurrentUser == null)
{
return Context.GetRedirect(redirectPath);
}
}
return null;
}
}
}

View file

@ -0,0 +1,193 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: BaseModule.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Nancy;
using Nancy.Security;
using NLog;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Helpers;
using Ombi.Helpers.Permissions;
using Ombi.UI.Helpers;
using ISecurityExtensions = Ombi.Core.ISecurityExtensions;
namespace Ombi.UI.Modules
{
public abstract class BaseModule : NancyModule
{
protected string BaseUrl { get; set; }
protected BaseModule(ISettingsService<PlexRequestSettings> settingsService, ISecurityExtensions security)
{
var settings = settingsService.GetSettings();
var baseUrl = settings.BaseUrl;
BaseUrl = baseUrl;
var modulePath = string.IsNullOrEmpty(baseUrl) ? string.Empty : baseUrl;
ModulePath = modulePath;
Security = security;
Before += (ctx) => SetCookie();
}
protected BaseModule(string modulePath, ISettingsService<PlexRequestSettings> settingsService, ISecurityExtensions security)
{
var settings = settingsService.GetSettings();
var baseUrl = settings.BaseUrl;
BaseUrl = baseUrl;
var settingModulePath = string.IsNullOrEmpty(baseUrl) ? modulePath : $"{baseUrl}/{modulePath}";
ModulePath = settingModulePath;
Security = security;
Before += (ctx) =>
{
SetCookie();
if (!string.IsNullOrEmpty(ctx.Request.Session["TempMessage"] as string))
{
ctx.ViewBag.TempMessage = ctx.Request.Session["TempMessage"];
ctx.ViewBag.TempType = ctx.Request.Session["TempType"];
ctx.Request.Session.DeleteAll();
}
return null;
};
}
private int _dateTimeOffset = -1;
protected int DateTimeOffset
{
get
{
if (_dateTimeOffset == -1)
{
_dateTimeOffset = (int?)Session[SessionKeys.ClientDateTimeOffsetKey] ?? new DateTimeOffset().Offset.Minutes;
}
return _dateTimeOffset;
}
}
private static Logger Log = LogManager.GetCurrentClassLogger();
private string _username;
/// <summary>
/// Returns the Username or UserAlias
/// </summary>
protected string Username
{
get
{
if (string.IsNullOrEmpty(_username))
{
try
{
var username = Security.GetUsername(User?.UserName, Session);
if (string.IsNullOrEmpty(username))
{
return string.Empty;
}
_username = username;
}
catch (Exception e)
{
Log.Info(e);
return string.Empty;
}
}
return _username;
}
}
protected IDictionary<string, string> Cookies => Request?.Cookies;
protected bool IsAdmin
{
get
{
if (!LoggedIn)
{
return false;
}
return Security.HasPermissions(Context?.CurrentUser, Permissions.Administrator);
}
}
protected IUserIdentity User => Context?.CurrentUser;
protected ISecurityExtensions Security { get; set; }
protected bool LoggedIn => Context?.CurrentUser != null;
private string Culture { get; set; }
protected const string CultureCookieName = "_culture";
private Response SetCookie()
{
try
{
string cultureName;
// Attempt to read the culture cookie from Request
var outCookie = string.Empty;
if (Cookies.TryGetValue(CultureCookieName, out outCookie))
{
cultureName = outCookie;
}
else
{
cultureName = Request.Headers?.AcceptLanguage?.FirstOrDefault()?.Item1;
}
// Validate culture name
cultureName = CultureHelper.GetImplementedCulture(cultureName); // This is safe
// Modify current thread's cultures
Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(cultureName);
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
Culture = Thread.CurrentThread.CurrentCulture.Name;
}
catch (Exception)
{
// Couldn't Set the culture
}
return null;
}
}
}

View file

@ -0,0 +1,39 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: BetaModule.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using Nancy;
namespace Ombi.UI.Modules
{
public class BetaModule : NancyModule
{
public BetaModule() : base("beta")
{
}
}
}

View file

@ -0,0 +1,79 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: CultureModule.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using Nancy;
using Nancy.Extensions;
using Nancy.Responses;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Helpers;
using Ombi.Helpers.Analytics;
using Ombi.UI.Helpers;
using Action = Ombi.Helpers.Analytics.Action;
using ISecurityExtensions = Ombi.Core.ISecurityExtensions;
namespace Ombi.UI.Modules
{
public class CultureModule : BaseModule
{
public CultureModule(ISettingsService<PlexRequestSettings> pr, IAnalytics a, ISecurityExtensions security) : base("culture",pr, security)
{
Analytics = a;
Get["/"] = x => SetCulture();
}
private IAnalytics Analytics { get; }
private RedirectResponse SetCulture()
{
var culture = (string)Request.Query["l"];
var returnUrl = (string)Request.Query["u"];
// Validate
culture = CultureHelper.GetImplementedCulture(culture);
var outCookie = string.Empty;
if (Cookies.TryGetValue(CultureCookieName, out outCookie))
{
Cookies[CultureCookieName] = culture;
}
else
{
Cookies.Add(CultureCookieName, culture);
}
var cookie = Cookies[CultureCookieName];
var response = Context.GetRedirect(returnUrl);
response.WithCookie(CultureCookieName, cookie ?? culture, DateTime.Now.AddYears(1));
Analytics.TrackEventAsync(Category.Navbar, Action.Language, culture, Username, CookieHelper.GetAnalyticClientId(Cookies));
return response;
}
}
}

View file

@ -0,0 +1,45 @@
using System;
using System.Threading.Tasks;
using Nancy;
using NLog;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Helpers;
using ISecurityExtensions = Ombi.Core.ISecurityExtensions;
namespace Ombi.UI.Modules
{
public class DonationLinkModule : BaseAuthModule
{
public DonationLinkModule(ICacheProvider provider, ISettingsService<PlexRequestSettings> pr, ISecurityExtensions security) : base("customDonation", pr, security)
{
Cache = provider;
Get["/", true] = async (x, ct) => await GetCustomDonationUrl(pr);
}
private ICacheProvider Cache { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
private async Task<Response> GetCustomDonationUrl(ISettingsService<PlexRequestSettings> pr)
{
PlexRequestSettings settings = await pr.GetSettingsAsync();
try
{
if (settings.EnableCustomDonationUrl && Security.IsLoggedIn(Context))
{
return Response.AsJson(new { url = settings.CustomDonationUrl, message = settings.CustomDonationMessage, enabled = true });
}
return Response.AsJson(new { enabled = false });
}
catch (Exception e)
{
Log.Warn("Exception Thrown when attempting to check the custom donation url");
Log.Warn(e);
return Response.AsJson(new { url = settings.CustomDonationUrl, message = settings.CustomDonationMessage });
}
}
}
}

View file

@ -0,0 +1,80 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: IndexModule.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System.Threading.Tasks;
using Nancy.Extensions;
using Nancy.Linker;
using Nancy.Responses;
using Ombi.Core;
using Ombi.Core.SettingModels;
using ISecurityExtensions = Ombi.Core.ISecurityExtensions;
namespace Ombi.UI.Modules
{
public class IndexModule : BaseAuthModule
{
public IndexModule(ISettingsService<PlexRequestSettings> pr, ISettingsService<LandingPageSettings> l, IResourceLinker rl, ISecurityExtensions security) : base(pr, security)
{
LandingPage = l;
Linker = rl;
Get["Index", "/", true] = async (x, ct) => await Index();
Get["/Index", true] = async (x, ct) => await Index();
}
private ISettingsService<LandingPageSettings> LandingPage { get; }
private IResourceLinker Linker { get; }
public async Task<RedirectResponse> Index()
{
var settings = await LandingPage.GetSettingsAsync();
if (settings.Enabled)
{
if (settings.BeforeLogin) // Before login
{
if (string.IsNullOrEmpty(Username))
{
// They are not logged in
return Context.GetRedirect(Linker.BuildRelativeUri(Context, "LandingPageIndex").ToString());
}
return Context.GetRedirect(Linker.BuildRelativeUri(Context, "SearchIndex").ToString());
}
// After login
if (string.IsNullOrEmpty(Username))
{
// Not logged in yet
return Context.GetRedirect(Linker.BuildRelativeUri(Context, "UserLoginIndex").ToString());
}
// Send them to landing
var landingUrl = Linker.BuildRelativeUri(Context, "LandingPageIndex").ToString();
return Context.GetRedirect(landingUrl);
}
return Context.GetRedirect(Linker.BuildRelativeUri(Context, "UserLoginIndex").ToString());
}
}
}

View file

@ -0,0 +1,478 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Nancy;
using Nancy.Responses.Negotiation;
using NLog;
using Ombi.Api;
using Ombi.Core;
using Ombi.Core.Models;
using Ombi.Core.SettingModels;
using Ombi.Helpers;
using Ombi.Helpers.Permissions;
using Ombi.Services.Interfaces;
using Ombi.Services.Notification;
using Ombi.Store;
using Ombi.UI.Models;
using ISecurityExtensions = Ombi.Core.ISecurityExtensions;
namespace Ombi.UI.Modules
{
public class IssuesModule : BaseAuthModule
{
public IssuesModule(ISettingsService<PlexRequestSettings> pr, IIssueService issueService, IRequestService request, INotificationService n, ISecurityExtensions security) : base("issues", pr, security)
{
IssuesService = issueService;
RequestService = request;
NotificationService = n;
Get["/"] = x => Index();
Get["/{id}", true] = async (x, ct) => await Details(x.id);
Post["/issue", true] = async (x, ct) => await ReportRequestIssue((int)Request.Form.requestId, (IssueState)(int)Request.Form.issue, null);
Get["/pending", true] = async (x, ct) => await GetIssues(IssueStatus.PendingIssue);
Get["/resolved", true] = async (x, ct) => await GetIssues(IssueStatus.ResolvedIssue);
Post["/remove", true] = async (x, ct) => await RemoveIssue((int)Request.Form.issueId);
Post["/resolvedUpdate", true] = async (x, ct) => await ChangeStatus((int)Request.Form.issueId, IssueStatus.ResolvedIssue);
Post["/clear", true] = async (x, ct) => await ClearIssue((int)Request.Form.issueId, (IssueState)(int)Request.Form.issue);
Get["/issuecount", true] = async (x, ct) => await IssueCount();
Get["/tabCount", true] = async (x, ct) => await TabCount();
Post["/issuecomment", true] = async (x, ct) => await ReportRequestIssue((int)Request.Form.providerId, IssueState.Other, (string)Request.Form.commentArea);
Post["/nonrequestissue", true] = async (x, ct) => await ReportNonRequestIssue((int)Request.Form.providerId, (string)Request.Form.type, (IssueState)(int)Request.Form.issue, null);
Post["/nonrequestissuecomment", true] = async (x, ct) => await ReportNonRequestIssue((int)Request.Form.providerId, (string)Request.Form.type, IssueState.Other, (string)Request.Form.commentArea);
Post["/addnote", true] = async (x, ct) => await AddNote((int)Request.Form.requestId, (string)Request.Form.noteArea, (IssueState)(int)Request.Form.issue);
}
private IIssueService IssuesService { get; }
private IRequestService RequestService { get; }
private INotificationService NotificationService { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
public Negotiator Index()
{
return View["Index"];
}
private async Task<Response> GetIssues(IssueStatus status)
{
var issues = await IssuesService.GetAllAsync();
issues = await FilterIssuesAsync(issues, status == IssueStatus.ResolvedIssue);
var issuesModels = issues as IssuesModel[] ?? issues.Where(x => x.IssueStatus == status).ToArray();
var viewModel = new List<IssuesViewModel>();
foreach (var i in issuesModels)
{
var model = new IssuesViewModel { Id = i.Id, RequestId = i.RequestId, Title = i.Title, Type = i.Type.ToString().ToCamelCaseWords(), Admin = Security.HasAnyPermissions(User, Permissions.Administrator, Permissions.ManageRequests)
};
// Create a string with all of the current issue states with a "," delimiter in e.g. Wrong Content, Playback Issues
var state = i.Issues.Select(x => x.Issue).ToArray();
var issueState = string.Empty;
for (var j = 0; j < state.Length; j++)
{
var word = state[j].ToString().ToCamelCaseWords();
if (j != state.Length - 1)
{
issueState += $"{word}, ";
}
else
{
issueState += word;
}
}
model.Issues = issueState;
viewModel.Add(model);
}
return Response.AsJson(viewModel);
}
public async Task<Response> IssueCount()
{
var issues = await IssuesService.GetAllAsync();
var myIssues = await FilterIssuesAsync(issues);
var count = myIssues.Count();
return Response.AsJson(count);
}
public async Task<Response> TabCount()
{
var issues = await IssuesService.GetAllAsync();
var myIssues = await FilterIssuesAsync(issues);
var count = new List<object>();
var issuesModels = myIssues as IssuesModel[] ?? myIssues.ToArray();
var pending = issuesModels.Where(x => x.IssueStatus == IssueStatus.PendingIssue);
var resolved = issuesModels.Where(x => x.IssueStatus == IssueStatus.ResolvedIssue);
count.Add(new { Name = IssueStatus.PendingIssue, Count = pending.Count() });
count.Add(new { Name = IssueStatus.ResolvedIssue, Count = resolved.Count() });
return Response.AsJson(count);
}
public async Task<Negotiator> Details(int id)
{
var issue = await IssuesService.GetAsync(id);
if (issue == null)
return Index();
issue = Order(issue);
var m = new IssuesDetailsViewModel
{
Issues = issue.Issues,
RequestId = issue.RequestId,
Title = issue.Title,
IssueStatus = issue.IssueStatus,
Deleted = issue.Deleted,
Type = issue.Type,
ProviderId = issue.ProviderId,
PosterUrl = issue.PosterUrl.Contains("https://image.tmdb.org/t/p/w150/") ? issue.PosterUrl : $"https://image.tmdb.org/t/p/w150/{issue.PosterUrl}",
Id = issue.Id
};
return View["Details", m];
}
private async Task<Response> ReportRequestIssue(int requestId, IssueState issue, string comment)
{
var model = new IssueModel
{
Issue = issue,
UserReported = Username,
UserNote = !string.IsNullOrEmpty(comment)
? $"{Username} - {comment}"
: string.Empty,
};
var request = await RequestService.GetAsync(requestId);
var issueEntity = await IssuesService.GetAllAsync();
var existingIssue = issueEntity.FirstOrDefault(x => x.RequestId == requestId);
var notifyModel = new NotificationModel
{
User = Username,
NotificationType = NotificationType.Issue,
Title = request.Title,
DateTime = DateTime.Now,
Body = issue == IssueState.Other ? comment : issue.ToString().ToCamelCaseWords()
};
// An issue already exists
if (existingIssue != null)
{
if (existingIssue.Issues.Any(x => x.Issue == issue))
{
return
Response.AsJson(new JsonResponseModel
{
Result = false,
Message = "This issue has already been reported!"
});
}
existingIssue.Issues.Add(model);
var result = await IssuesService.UpdateIssueAsync(existingIssue);
await NotificationService.Publish(notifyModel);
return Response.AsJson(result
? new JsonResponseModel { Result = true }
: new JsonResponseModel { Result = false });
}
// New issue
var issues = new IssuesModel
{
Title = request.Title,
PosterUrl = request.PosterPath,
RequestId = requestId,
Type = request.Type,
IssueStatus = IssueStatus.PendingIssue
};
issues.Issues.Add(model);
var issueId = await IssuesService.AddIssueAsync(issues);
request.IssueId = issueId;
await RequestService.UpdateRequestAsync(request);
await NotificationService.Publish(notifyModel);
return Response.AsJson(new JsonResponseModel { Result = true });
}
private async Task<Response> ReportNonRequestIssue(int providerId, string type, IssueState issue, string comment)
{
var currentIssues = await IssuesService.GetAllAsync();
var notifyModel = new NotificationModel
{
User = Username,
NotificationType = NotificationType.Issue,
DateTime = DateTime.Now,
Body = issue == IssueState.Other ? comment : issue.ToString().ToCamelCaseWords()
};
var model = new IssueModel
{
Issue = issue,
UserReported = Username,
UserNote = !string.IsNullOrEmpty(comment)
? $"{Username} - {comment}"
: string.Empty,
};
var existing = currentIssues.FirstOrDefault(x => x.ProviderId == providerId && !x.Deleted && x.IssueStatus == IssueStatus.PendingIssue);
if (existing != null)
{
existing.Issues.Add(model);
await IssuesService.UpdateIssueAsync(existing);
return Response.AsJson(new JsonResponseModel { Result = true });
}
if (type == "movie")
{
var movieApi = new TheMovieDbApi();
var result = await movieApi.GetMovieInformation(providerId);
if (result != null)
{
notifyModel.Title = result.Title;
// New issue
var issues = new IssuesModel
{
Title = result.Title,
PosterUrl = "https://image.tmdb.org/t/p/w150/" + result.PosterPath,
ProviderId = providerId,
Type = RequestType.Movie,
IssueStatus = IssueStatus.PendingIssue
};
issues.Issues.Add(model);
var issueId = await IssuesService.AddIssueAsync(issues);
await NotificationService.Publish(notifyModel);
return Response.AsJson(new JsonResponseModel { Result = true });
}
}
if (type == "tv")
{
var tv = new TvMazeApi();
var result = tv.ShowLookupByTheTvDbId(providerId);
if (result != null)
{
var banner = result.image?.medium;
if (!string.IsNullOrEmpty(banner))
{
banner = banner.Replace("http", "https");
}
notifyModel.Title = result.name;
// New issue
var issues = new IssuesModel
{
Title = result.name,
PosterUrl = banner,
ProviderId = providerId,
Type = RequestType.TvShow,
IssueStatus = IssueStatus.PendingIssue
};
issues.Issues.Add(model);
var issueId = await IssuesService.AddIssueAsync(issues);
await NotificationService.Publish(notifyModel);
return Response.AsJson(new JsonResponseModel { Result = true });
}
}
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Album Reports are not supported yet!"});
}
/// <summary>
/// Filters the issues. Checks to see if we have set <c>UsersCanViewOnlyOwnIssues</c> in the database and filters upon the user logged in and that setting.
/// </summary>
/// <param name="issues">The issues.</param>
private async Task<IEnumerable<IssuesModel>> FilterIssuesAsync(IEnumerable<IssuesModel> issues, bool showResolved = false)
{
var settings = await PlexRequestSettings.GetSettingsAsync();
IEnumerable<IssuesModel> myIssues;
// Is the user an Admin? If so show everything
if (IsAdmin)
{
var issuesModels = issues as IssuesModel[] ?? issues.ToArray();
myIssues = issuesModels.Where(x => x.Deleted == false);
if (!showResolved)
{
myIssues = issuesModels.Where(x => x.IssueStatus != IssueStatus.ResolvedIssue);
}
}
else if (Security.HasPermissions(User, Permissions.UsersCanViewOnlyOwnIssues)) // The user is not an Admin, do we have the settings to hide them?
{
if (!showResolved)
{
myIssues =
issues.Where(
x =>
x.Issues.Any(i => i.UserReported.Equals(Username, StringComparison.CurrentCultureIgnoreCase)) && x.Deleted == false
&& x.IssueStatus != IssueStatus.ResolvedIssue);
}
else
{
myIssues =
issues.Where(
x =>
x.Issues.Any(i => i.UserReported.Equals(Username, StringComparison.CurrentCultureIgnoreCase)) && x.Deleted == false);
}
}
else // Looks like the user is not an admin and there is no settings set.
{
var issuesModels = issues as IssuesModel[] ?? issues.ToArray();
myIssues = issuesModels.Where(x => x.Deleted == false);
if (!showResolved)
{
myIssues = issuesModels.Where(x => x.IssueStatus != IssueStatus.ResolvedIssue);
}
}
return myIssues;
}
private async Task<Response> RemoveIssue(int issueId)
{
try
{
if (!Security.HasAnyPermissions(User, Permissions.Administrator, Permissions.ManageRequests))
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Sorry, you do not have the correct permissions to remove an issue." });
}
var issue = await IssuesService.GetAsync(issueId);
var request = await RequestService.GetAsync(issue.RequestId);
if (request.Id > 0)
{
request.IssueId = 0; // No issue;
var result = await RequestService.UpdateRequestAsync(request);
if (result)
{
await IssuesService.DeleteIssueAsync(issueId);
}
}
else
{
await IssuesService.DeleteIssueAsync(issueId);
}
return Response.AsJson(new JsonResponseModel { Result = true });
}
catch (Exception e)
{
Log.Error(e);
return Response.AsJson(new JsonResponseModel() { Result = false, Message = "Could not delete issue! Check the logs."});
}
}
private async Task<Negotiator> ChangeStatus(int issueId, IssueStatus status)
{
try
{
if (!Security.HasAnyPermissions(User, Permissions.Administrator, Permissions.ManageRequests))
{
return View["Index"];
}
var issue = await IssuesService.GetAsync(issueId);
issue.IssueStatus = status;
var result = await IssuesService.UpdateIssueAsync(issue);
return result ? await Details(issueId) : View["Index"];
}
catch (Exception e)
{
Log.Error(e);
return View["Index"];
}
}
private async Task<Negotiator> ClearIssue(int issueId, IssueState state)
{
if (!Security.HasAnyPermissions(User, Permissions.Administrator, Permissions.ManageRequests))
{
return View["Index"];
}
var issue = await IssuesService.GetAsync(issueId);
var toRemove = issue.Issues.FirstOrDefault(x => x.Issue == state);
issue.Issues.Remove(toRemove);
var result = await IssuesService.UpdateIssueAsync(issue);
return result ? await Details(issueId) : View["Index"];
}
private async Task<Response> AddNote(int requestId, string noteArea, IssueState state)
{
if (!Security.HasAnyPermissions(User, Permissions.Administrator, Permissions.ManageRequests))
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Sorry, you do not have the correct permissions to add a note." });
}
var issue = await IssuesService.GetAsync(requestId);
if (issue == null)
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Issue does not exist to add a note!" });
}
var toAddNote = issue.Issues.FirstOrDefault(x => x.Issue == state);
if (toAddNote != null)
{
issue.Issues.Remove(toAddNote);
toAddNote.AdminNote = noteArea;
issue.Issues.Add(toAddNote);
}
var result = await IssuesService.UpdateIssueAsync(issue);
return Response.AsJson(result
? new JsonResponseModel { Result = true }
: new JsonResponseModel { Result = false, Message = "Could not update the notes, please try again or check the logs" });
}
/// <summary>
/// Orders the issues descending by the <see cref="IssueState"/>.
/// </summary>
/// <param name="issues">The issues.</param>
/// <returns></returns>
private IssuesModel Order(IssuesModel issues)
{
issues.Issues = issues.Issues.OrderByDescending(x => x.Issue).ToList();
return issues;
}
}
}

View file

@ -0,0 +1,126 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: LandingPageModule.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Threading.Tasks;
using Nancy;
using Nancy.Linker;
using Ombi.Api.Interfaces;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.UI.Models;
using ISecurityExtensions = Ombi.Core.ISecurityExtensions;
namespace Ombi.UI.Modules
{
public class LandingPageModule : BaseModule
{
public LandingPageModule(ISettingsService<PlexRequestSettings> settingsService, ISettingsService<LandingPageSettings> landing,
ISettingsService<PlexSettings> ps, IPlexApi pApi, IResourceLinker linker, ISecurityExtensions security, ISettingsService<EmbySettings> emby,
IEmbyApi embyApi) : base("landing", settingsService, security)
{
LandingSettings = landing;
PlexSettings = ps;
PlexApi = pApi;
Linker = linker;
EmbySettings = emby;
EmbyApi = embyApi;
Get["LandingPageIndex","/", true] = async (x, ct) =>
{
var s = await LandingSettings.GetSettingsAsync();
if (!s.BeforeLogin && string.IsNullOrEmpty(Username)) //We are signed in
{
var url = Linker.BuildRelativeUri(Context, "SearchIndex").ToString();
return Response.AsRedirect(url);
}
var model = new LandingPageViewModel
{
Enabled = s.Enabled,
Id = s.Id,
EnabledNoticeTime = s.EnabledNoticeTime,
NoticeEnable = s.NoticeEnable,
NoticeEnd = s.NoticeEnd,
NoticeMessage = s.NoticeMessage,
NoticeStart = s.NoticeStart,
ContinueUrl = s.BeforeLogin ? $"userlogin" : $"search"
};
return View["Landing/Index", model];
};
Get["/status", true] = async (x, ct) => await CheckStatus();
}
private ISettingsService<LandingPageSettings> LandingSettings { get; }
private ISettingsService<PlexSettings> PlexSettings { get; }
private ISettingsService<EmbySettings> EmbySettings { get; }
private IPlexApi PlexApi { get; }
private IEmbyApi EmbyApi { get; }
private IResourceLinker Linker { get; }
private async Task<Response> CheckStatus()
{
var plexSettings = await PlexSettings.GetSettingsAsync();
if (plexSettings.Enable)
{
if (string.IsNullOrEmpty(plexSettings.PlexAuthToken) || string.IsNullOrEmpty(plexSettings.Ip))
{
return Response.AsJson(false);
}
try
{
var status = PlexApi.GetStatus(plexSettings.PlexAuthToken, plexSettings.FullUri);
return Response.AsJson(status != null);
}
catch (Exception)
{
return Response.AsJson(false);
}
}
var emby = await EmbySettings.GetSettingsAsync();
if (emby.Enable)
{
if (string.IsNullOrEmpty(emby.AdministratorId) || string.IsNullOrEmpty(emby.Ip))
{
return Response.AsJson(false);
}
try
{
var status = EmbyApi.GetSystemInformation(emby.ApiKey, emby.FullUri);
return Response.AsJson(status?.Version != null);
}
catch (Exception)
{
return Response.AsJson(false);
}
}
return Response.AsJson(false);
}
}
}

View file

@ -0,0 +1,151 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: LayoutModule.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Linq;
using System.Threading.Tasks;
using Nancy;
using Nancy.Responses;
using NLog;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Core.StatusChecker;
using Ombi.Core.Users;
using Ombi.Helpers;
using Ombi.Services.Interfaces;
using Ombi.Services.Jobs;
using Ombi.UI.Models;
using ISecurityExtensions = Ombi.Core.ISecurityExtensions;
namespace Ombi.UI.Modules
{
public class LayoutModule : BaseAuthModule
{
public LayoutModule(ICacheProvider provider, ISettingsService<PlexRequestSettings> pr, ISettingsService<SystemSettings> settings, IJobRecord rec, ISecurityExtensions security, IUserHelper helper) : base("layout", pr, security)
{
Cache = provider;
SystemSettings = settings;
Job = rec;
UserHelper = helper;
Get["/", true] = async (x,ct) => await CheckLatestVersion();
Get["/cacher", true] = async (x,ct) => await CacherRunning();
Get["/gravatar"] = x => GetGravatarImage();
}
private ICacheProvider Cache { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
private ISettingsService<SystemSettings> SystemSettings { get; }
private IJobRecord Job { get; }
private IUserHelper UserHelper { get; }
private async Task<Response> CheckLatestVersion()
{
try
{
if (!IsAdmin)
{
return Response.AsJson(new JsonUpdateAvailableModel { UpdateAvailable = false });
}
#if DEBUG
return Response.AsJson(new JsonUpdateAvailableModel { UpdateAvailable = false });
#endif
var checker = new StatusChecker(SystemSettings);
var release = await Cache.GetOrSetAsync(CacheKeys.LastestProductVersion, async() => await checker.GetStatus(), 30);
return Response.AsJson(release.UpdateAvailable
? new JsonUpdateAvailableModel { UpdateAvailable = true}
: new JsonUpdateAvailableModel { UpdateAvailable = false });
}
catch (Exception e)
{
Log.Warn("Exception Thrown when attempting to check the status");
Log.Warn(e);
return Response.AsJson(new JsonUpdateAvailableModel { UpdateAvailable = false });
}
}
private async Task<Response> CacherRunning()
{
try
{
var jobs = await Job.GetJobsAsync();
// Check to see if any are running
var runningJobs = jobs.Where(x => x.Running);
// We only want the cachers
var cacherJobs = runningJobs.Where(x =>
x.Name.Equals(JobNames.CpCacher)
|| x.Name.Equals(JobNames.EpisodeCacher)
|| x.Name.Equals(JobNames.PlexChecker)
|| x.Name.Equals(JobNames.SonarrCacher)
|| x.Name.Equals(JobNames.SrCacher)
|| x.Name.Equals(JobNames.PlexCacher)
|| x.Name.Equals(JobNames.WatcherCacher));
return Response.AsJson(cacherJobs.Any()
? new { CurrentlyRunning = true, IsAdmin}
: new { CurrentlyRunning = false, IsAdmin });
}
catch (Exception e)
{
Log.Warn("Exception Thrown when attempting to check the status");
Log.Warn(e);
return Response.AsJson(new { CurrentlyRunning = false, IsAdmin });
}
}
private Response GetGravatarImage()
{
if (LoggedIn)
{
var user = UserHelper.GetUser(Username);
var hashed = StringHasher.CalcuateMd5Hash(user.EmailAddress);
if (string.IsNullOrEmpty(hashed))
{
return Response.AsJson(new JsonResponseModel
{
Result = false
});
}
return
Response.AsJson(new JsonResponseModel
{
Result = true,
Message = $"https://www.gravatar.com/avatar/{hashed}"
});
}
else
{
return Response.AsJson(new JsonResponseModel {Result = false});
}
}
}
}

View file

@ -0,0 +1,189 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: LoginModule.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Dynamic;
using System.Security;
using Nancy;
using Nancy.Extensions;
using Nancy.Linker;
using Nancy.Responses.Negotiation;
using Nancy.Security;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Helpers;
using Ombi.Helpers.Permissions;
using Ombi.Store;
using Ombi.Store.Repository;
using Ombi.UI.Authentication;
using Ombi.UI.Models;
using ISecurityExtensions = Ombi.Core.ISecurityExtensions;
namespace Ombi.UI.Modules
{
public class LoginModule : BaseModule
{
public LoginModule(ISettingsService<PlexRequestSettings> pr, ICustomUserMapper m, IResourceLinker linker, IRepository<UserLogins> userLoginRepo, ISecurityExtensions security)
: base(pr, security)
{
UserMapper = m;
Get["LocalLogin","/login"] = _ =>
{
if (LoggedIn)
{
var url = linker.BuildRelativeUri(Context, "SearchIndex");
return Response.AsRedirect(url.ToString());
}
dynamic model = new ExpandoObject();
model.Redirect = Request.Query.redirect.Value ?? string.Empty;
model.Errored = Request.Query.error.HasValue;
var adminCreated = UserMapper.DoUsersExist();
model.AdminExists = adminCreated;
return View["Index", model];
};
Get["/logout"] = x =>
{
if (Session[SessionKeys.UsernameKey] != null)
{
Session.Delete(SessionKeys.UsernameKey);
}
return CustomModuleExtensions.LogoutAndRedirect(this, !string.IsNullOrEmpty(BaseUrl) ? $"~/{BaseUrl}/" : "~/");
};
Post["/login"] = x =>
{
var username = (string)Request.Form.Username;
var password = (string)Request.Form.Password;
var dtOffset = (int)Request.Form.DateTimeOffset;
var redirect = (string)Request.Form.Redirect;
var userId = UserMapper.ValidateUser(username, password);
if (userId == null)
{
return
Context.GetRedirect(!string.IsNullOrEmpty(BaseUrl)
? $"~/{BaseUrl}/login?error=true&username=" + username
: "~/login?error=true&username=" + username);
}
DateTime? expiry = null;
if (Request.Form.RememberMe.HasValue)
{
expiry = DateTime.Now.AddDays(7);
}
Session[SessionKeys.UsernameKey] = username;
Session[SessionKeys.ClientDateTimeOffsetKey] = dtOffset;
if (redirect.Contains("userlogin"))
{
redirect = !string.IsNullOrEmpty(BaseUrl) ? $"/{BaseUrl}/search" : "/search";
}
userLoginRepo.Insert(new UserLogins
{
LastLoggedIn = DateTime.UtcNow,
Type = UserType.LocalUser,
UserId = userId.ToString()
});
return CustomModuleExtensions.LoginAndRedirect(this,userId.Value, expiry, redirect);
};
Get["/register"] = x =>
{
{
dynamic model = new ExpandoObject();
model.Errored = Request.Query.error.HasValue;
return View["Register", model];
}
};
Post["/register"] = x =>
{
var username = (string)Request.Form.Username;
var exists = UserMapper.DoUsersExist();
if (exists)
{
return
Context.GetRedirect(!string.IsNullOrEmpty(BaseUrl)
? $"~/{BaseUrl}/register?error=true"
: "~/register?error=true");
}
var userId = UserMapper.CreateUser(username, Request.Form.Password, EnumHelper<Permissions>.All(), 0);
Session[SessionKeys.UsernameKey] = username;
return CustomModuleExtensions.LoginAndRedirect(this, (Guid)userId);
};
Get["/changepassword"] = _ => ChangePassword();
Post["/changepassword"] = _ => ChangePasswordPost();
}
private ICustomUserMapper UserMapper { get; }
private Negotiator ChangePassword()
{
this.RequiresAuthentication();
return View["ChangePassword"];
}
private Response ChangePasswordPost()
{
var username = Context.CurrentUser.UserName;
var oldPass = Request.Form.OldPassword;
var newPassword = Request.Form.NewPassword;
var newPasswordAgain = Request.Form.NewPasswordAgain;
if (string.IsNullOrEmpty(oldPass) || string.IsNullOrEmpty(newPassword) ||
string.IsNullOrEmpty(newPasswordAgain))
{
return Response.AsJson(new JsonResponseModel { Message = "Please fill in all fields", Result = false });
}
if (!newPassword.Equals(newPasswordAgain))
{
return Response.AsJson(new JsonResponseModel { Message = "The passwords do not match", Result = false });
}
try
{
var result = UserMapper.UpdatePassword(username, oldPass, newPassword);
if (result)
{
return Response.AsJson(new JsonResponseModel { Message = "Password has been changed!", Result = true });
}
return Response.AsJson(new JsonResponseModel { Message = "Could not update the password in the database", Result = false });
}
catch (SecurityException e)
{
return Response.AsJson(new JsonResponseModel { Message = e.ToString(), Result = false });
}
}
}
}

View file

@ -0,0 +1,602 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: RequestsModule.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Nancy;
using Nancy.Responses.Negotiation;
using NLog;
using Ombi.Api.Interfaces;
using Ombi.Api.Models.Sonarr;
using Ombi.Core;
using Ombi.Core.Models;
using Ombi.Core.SettingModels;
using Ombi.Helpers;
using Ombi.Helpers.Analytics;
using Ombi.Helpers.Permissions;
using Ombi.Services.Interfaces;
using Ombi.Services.Notification;
using Ombi.Store;
using Ombi.UI.Models;
using Ombi.UI.Models.Admin;
using Ombi.UI.Models.Requests;
using Action = Ombi.Helpers.Analytics.Action;
using ISecurityExtensions = Ombi.Core.ISecurityExtensions;
namespace Ombi.UI.Modules
{
public class RequestsModule : BaseAuthModule
{
public RequestsModule(
IRequestService service,
ISettingsService<PlexRequestSettings> prSettings,
ISettingsService<PlexSettings> plex,
INotificationService notify,
ISettingsService<SonarrSettings> sonarrSettings,
ISettingsService<SickRageSettings> sickRageSettings,
ISettingsService<CouchPotatoSettings> cpSettings,
ICouchPotatoApi cpApi,
ISonarrApi sonarrApi,
ISickRageApi sickRageApi,
ICacheProvider cache,
IAnalytics an,
IPlexNotificationEngine engine,
IEmbyNotificationEngine embyEngine,
ISecurityExtensions security,
ISettingsService<CustomizationSettings> customSettings,
ISettingsService<EmbySettings> embyS,
ISettingsService<RadarrSettings> radarr,
IRadarrApi radarrApi) : base("requests", prSettings, security)
{
Service = service;
PrSettings = prSettings;
PlexSettings = plex;
NotificationService = notify;
SonarrSettings = sonarrSettings;
SickRageSettings = sickRageSettings;
CpSettings = cpSettings;
SonarrApi = sonarrApi;
SickRageApi = sickRageApi;
CpApi = cpApi;
Cache = cache;
Analytics = an;
PlexNotificationEngine = engine;
EmbyNotificationEngine = embyEngine;
CustomizationSettings = customSettings;
EmbySettings = embyS;
Radarr = radarr;
RadarrApi = radarrApi;
Get["/", true] = async (x, ct) => await LoadRequests();
Get["/movies", true] = async (x, ct) => await GetMovies();
Get["/tvshows", true] = async (c, ct) => await GetTvShows();
Get["/albums", true] = async (x, ct) => await GetAlbumRequests();
Post["/delete", true] = async (x, ct) => await DeleteRequest((int)Request.Form.id);
Post["/reportissue", true] = async (x, ct) => await ReportIssue((int)Request.Form.requestId, (IssueState)(int)Request.Form.issue, null);
Post["/reportissuecomment", true] = async (x, ct) => await ReportIssue((int)Request.Form.requestId, IssueState.Other, (string)Request.Form.commentArea);
Post["/clearissues", true] = async (x, ct) => await ClearIssue((int)Request.Form.Id);
Post["/changeavailability", true] = async (x, ct) => await ChangeRequestAvailability((int)Request.Form.Id, (bool)Request.Form.Available);
Post["/changeRootFoldertv", true] = async (x, ct) => await ChangeRootFolder(RequestType.TvShow, (int)Request.Form.requestId, (int)Request.Form.rootFolderId);
Post["/changeRootFoldermovie", true] = async (x, ct) => await ChangeRootFolder(RequestType.Movie, (int)Request.Form.requestId, (int)Request.Form.rootFolderId);
Get["/UpdateFilters", true] = async (x, ct) => await GetFilterAndSortSettings();
}
private static Logger Log = LogManager.GetCurrentClassLogger();
private IRequestService Service { get; }
private IAnalytics Analytics { get; }
private INotificationService NotificationService { get; }
private ISettingsService<PlexRequestSettings> PrSettings { get; }
private ISettingsService<PlexSettings> PlexSettings { get; }
private ISettingsService<SonarrSettings> SonarrSettings { get; }
private ISettingsService<SickRageSettings> SickRageSettings { get; }
private ISettingsService<CouchPotatoSettings> CpSettings { get; }
private ISettingsService<CustomizationSettings> CustomizationSettings { get; }
private ISettingsService<RadarrSettings> Radarr { get; }
private ISettingsService<EmbySettings> EmbySettings { get; }
private ISonarrApi SonarrApi { get; }
private IRadarrApi RadarrApi { get; }
private ISickRageApi SickRageApi { get; }
private ICouchPotatoApi CpApi { get; }
private ICacheProvider Cache { get; }
private INotificationEngine PlexNotificationEngine { get; }
private INotificationEngine EmbyNotificationEngine { get; }
private async Task<Negotiator> LoadRequests()
{
var settings = await PrSettings.GetSettingsAsync();
var custom = await CustomizationSettings.GetSettingsAsync();
return View["Index", new RequestsIndexViewModel { CustomizationSettings = custom, PlexRequestSettings = settings }];
}
private async Task<Response> GetMovies()
{
var allRequests = await Service.GetAllAsync();
allRequests = allRequests.Where(x => x.Type == RequestType.Movie);
var dbMovies = allRequests.ToList();
if (Security.HasPermissions(User, Permissions.UsersCanViewOnlyOwnRequests) && !IsAdmin)
{
dbMovies = dbMovies.Where(x => x.UserHasRequested(Username)).ToList();
}
List<QualityModel> qualities = new List<QualityModel>();
var rootFolders = new List<RootFolderModel>();
var radarr = await Radarr.GetSettingsAsync();
if (IsAdmin)
{
try
{
var cpSettings = await CpSettings.GetSettingsAsync();
if (cpSettings.Enabled)
{
try
{
var result = await Cache.GetOrSetAsync(CacheKeys.CouchPotatoQualityProfiles, async () =>
{
return
await Task.Run(() => CpApi.GetProfiles(cpSettings.FullUri, cpSettings.ApiKey))
.ConfigureAwait(false);
});
if (result != null)
{
qualities =
result.list.Select(x => new QualityModel { Id = x._id, Name = x.label }).ToList();
}
}
catch (Exception e)
{
Log.Info(e);
}
}
if (radarr.Enabled)
{
var rootFoldersResult = await Cache.GetOrSetAsync(CacheKeys.RadarrRootFolders, async () =>
{
return await Task.Run(() => RadarrApi.GetRootFolders(radarr.ApiKey, radarr.FullUri));
});
rootFolders =
rootFoldersResult.Select(
x => new RootFolderModel { Id = x.id.ToString(), Path = x.path, FreeSpace = x.freespace })
.ToList();
var result = await Cache.GetOrSetAsync(CacheKeys.RadarrQualityProfiles, async () =>
{
return await Task.Run(() => RadarrApi.GetProfiles(radarr.ApiKey, radarr.FullUri));
});
qualities = result.Select(x => new QualityModel { Id = x.id.ToString(), Name = x.name }).ToList();
}
}
catch (Exception e)
{
Log.Error(e);
}
}
var canManageRequest = Security.HasAnyPermissions(User, Permissions.Administrator, Permissions.ManageRequests);
var allowViewUsers = Security.HasAnyPermissions(User, Permissions.Administrator, Permissions.ViewUsers);
var viewModel = dbMovies.Select(movie => new RequestViewModel
{
ProviderId = movie.ProviderId,
Type = movie.Type,
Status = movie.Status,
ImdbId = movie.ImdbId,
Id = movie.Id,
PosterPath = movie.PosterPath,
ReleaseDate = movie.ReleaseDate,
ReleaseDateTicks = movie.ReleaseDate.Ticks,
RequestedDate = movie.RequestedDate,
Released = DateTime.Now > movie.ReleaseDate,
RequestedDateTicks = DateTimeHelper.OffsetUTCDateTime(movie.RequestedDate, DateTimeOffset).Ticks,
Approved = movie.Available || movie.Approved,
Title = movie.Title,
Overview = movie.Overview,
RequestedUsers = canManageRequest || allowViewUsers ? movie.AllUsers.ToArray() : new string[] { },
ReleaseYear = movie.ReleaseDate.Year.ToString(),
Available = movie.Available,
Admin = canManageRequest,
IssueId = movie.IssueId,
Denied = movie.Denied,
DeniedReason = movie.DeniedReason,
Qualities = qualities.ToArray(),
HasRootFolders = rootFolders.Any(),
RootFolders = rootFolders.ToArray(),
CurrentRootPath = radarr.Enabled ? GetRootPath(movie.RootFolderSelected, radarr).Result : null
}).ToList();
return Response.AsJson(viewModel);
}
private async Task<Response> GetTvShows()
{
var requests = await Service.GetAllAsync();
requests = requests.Where(x => x.Type == RequestType.TvShow);
var dbTv = requests;
if (Security.HasPermissions(User, Permissions.UsersCanViewOnlyOwnRequests) && !IsAdmin)
{
dbTv = dbTv.Where(x => x.UserHasRequested(Username)).ToList();
}
IEnumerable<QualityModel> qualities = new List<QualityModel>();
IEnumerable<RootFolderModel> rootFolders = new List<RootFolderModel>();
var sonarrSettings = await SonarrSettings.GetSettingsAsync();
if (IsAdmin)
{
try
{
if (sonarrSettings.Enabled)
{
var result = await Cache.GetOrSetAsync(CacheKeys.SonarrQualityProfiles, async () =>
{
return await Task.Run(() => SonarrApi.GetProfiles(sonarrSettings.ApiKey, sonarrSettings.FullUri));
});
qualities = result.Select(x => new QualityModel { Id = x.id.ToString(), Name = x.name }).ToList();
var rootFoldersResult = await Cache.GetOrSetAsync(CacheKeys.SonarrRootFolders, async () =>
{
return await Task.Run(() => SonarrApi.GetRootFolders(sonarrSettings.ApiKey, sonarrSettings.FullUri));
});
rootFolders = rootFoldersResult.Select(x => new RootFolderModel { Id = x.id.ToString(), Path = x.path, FreeSpace = x.freespace }).ToList();
}
else
{
var sickRageSettings = await SickRageSettings.GetSettingsAsync();
if (sickRageSettings.Enabled)
{
qualities = sickRageSettings.Qualities.Select(x => new QualityModel { Id = x.Key, Name = x.Value }).ToList();
}
}
}
catch (Exception e)
{
Log.Info(e);
}
}
var canManageRequest = Security.HasAnyPermissions(User, Permissions.Administrator, Permissions.ManageRequests);
var allowViewUsers = Security.HasAnyPermissions(User, Permissions.Administrator, Permissions.ViewUsers);
var viewModel = dbTv.Select(tv => new RequestViewModel
{
ProviderId = tv.ProviderId,
Type = tv.Type,
Status = tv.Status,
ImdbId = tv.ImdbId,
Id = tv.Id,
PosterPath = tv.PosterPath?.Contains("http:") ?? false ? tv.PosterPath?.Replace("http:", "https:") : tv.PosterPath ?? string.Empty, // We make the poster path https on request, but this is just incase
ReleaseDate = tv.ReleaseDate,
ReleaseDateTicks = tv.ReleaseDate.Ticks,
RequestedDate = tv.RequestedDate,
RequestedDateTicks = DateTimeHelper.OffsetUTCDateTime(tv.RequestedDate, DateTimeOffset).Ticks,
Released = DateTime.Now > tv.ReleaseDate,
Approved = tv.Available || tv.Approved,
Title = tv.Title,
Overview = tv.Overview,
RequestedUsers = canManageRequest || allowViewUsers ? tv.AllUsers.ToArray() : new string[] { },
ReleaseYear = tv.ReleaseDate.Year.ToString(),
Available = tv.Available,
Admin = canManageRequest,
IssueId = tv.IssueId,
Denied = tv.Denied,
DeniedReason = tv.DeniedReason,
TvSeriesRequestType = tv.SeasonsRequested,
Qualities = qualities.ToArray(),
Episodes = tv.Episodes.ToArray(),
RootFolders = rootFolders.ToArray(),
HasRootFolders = rootFolders.Any(),
CurrentRootPath = sonarrSettings.Enabled ? GetRootPath(tv.RootFolderSelected, sonarrSettings).Result : null
}).ToList();
return Response.AsJson(viewModel);
}
private async Task<string> GetRootPath(int pathId, SonarrSettings sonarrSettings)
{
var rootFoldersResult = await Cache.GetOrSetAsync(CacheKeys.SonarrRootFolders, async () =>
{
return await Task.Run(() => SonarrApi.GetRootFolders(sonarrSettings.ApiKey, sonarrSettings.FullUri));
});
foreach (var r in rootFoldersResult.Where(r => r.id == pathId))
{
return r.path;
}
int outRoot;
var defaultPath = int.TryParse(sonarrSettings.RootPath, out outRoot);
if (defaultPath)
{
// Return default path
return rootFoldersResult.FirstOrDefault(x => x.id.Equals(outRoot))?.path ?? string.Empty;
}
else
{
return rootFoldersResult.FirstOrDefault()?.path ?? string.Empty;
}
}
private async Task<string> GetRootPath(int pathId, RadarrSettings radarrSettings)
{
var rootFoldersResult = await Cache.GetOrSetAsync(CacheKeys.RadarrRootFolders, async () =>
{
return await Task.Run(() => RadarrApi.GetRootFolders(radarrSettings.ApiKey, radarrSettings.FullUri));
});
foreach (var r in rootFoldersResult.Where(r => r.id == pathId))
{
return r.path;
}
int outRoot;
var defaultPath = int.TryParse(radarrSettings.RootPath, out outRoot);
if (defaultPath)
{
// Return default path
return rootFoldersResult.FirstOrDefault(x => x.id.Equals(outRoot))?.path ?? string.Empty;
}
else
{
return rootFoldersResult.FirstOrDefault()?.path ?? string.Empty;
}
}
private async Task<Response> GetAlbumRequests()
{
var dbAlbum = await Service.GetAllAsync();
dbAlbum = dbAlbum.Where(x => x.Type == RequestType.Album);
if (Security.HasPermissions(User, Permissions.UsersCanViewOnlyOwnRequests) && !IsAdmin)
{
dbAlbum = dbAlbum.Where(x => x.UserHasRequested(Username));
}
var canManageRequest = Security.HasAnyPermissions(User, Permissions.Administrator, Permissions.ManageRequests);
var viewModel = dbAlbum.Select(album =>
{
return new RequestViewModel
{
ProviderId = album.ProviderId,
Type = album.Type,
Status = album.Status,
ImdbId = album.ImdbId,
Id = album.Id,
PosterPath = album.PosterPath,
ReleaseDate = album.ReleaseDate,
ReleaseDateTicks = album.ReleaseDate.Ticks,
RequestedDate = album.RequestedDate,
RequestedDateTicks = DateTimeHelper.OffsetUTCDateTime(album.RequestedDate, DateTimeOffset).Ticks,
Released = DateTime.Now > album.ReleaseDate,
Approved = album.Available || album.Approved,
Title = album.Title,
Overview = album.Overview,
RequestedUsers = canManageRequest ? album.AllUsers.ToArray() : new string[] { },
ReleaseYear = album.ReleaseDate.Year.ToString(),
Available = album.Available,
Admin = canManageRequest,
IssueId = album.IssueId,
Denied = album.Denied,
DeniedReason = album.DeniedReason,
TvSeriesRequestType = album.SeasonsRequested,
MusicBrainzId = album.MusicBrainzId,
ArtistName = album.ArtistName
};
}).ToList();
return Response.AsJson(viewModel);
}
private async Task<Response> DeleteRequest(int requestid)
{
if (!Security.HasAnyPermissions(User, Permissions.Administrator, Permissions.ManageRequests))
{
return Response.AsJson(new JsonResponseModel { Result = true });
}
Analytics.TrackEventAsync(Category.Requests, Action.Delete, "Delete Request", Username, CookieHelper.GetAnalyticClientId(Cookies));
var currentEntity = await Service.GetAsync(requestid);
await Service.DeleteRequestAsync(currentEntity);
return Response.AsJson(new JsonResponseModel { Result = true });
}
/// <summary>
/// Reports the issue.
/// Comment can be null if the <c>IssueState != Other</c>
/// </summary>
/// <param name="requestId">The request identifier.</param>
/// <param name="issue">The issue.</param>
/// <param name="comment">The comment.</param>
/// <returns></returns>
private async Task<Response> ReportIssue(int requestId, IssueState issue, string comment)
{
if (!Security.HasPermissions(User, Permissions.ReportIssue))
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Sorry, you do not have the correct permissions to report an issue." });
}
var originalRequest = await Service.GetAsync(requestId);
if (originalRequest == null)
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not add issue, please try again or contact the administrator!" });
}
originalRequest.Issues = issue;
originalRequest.OtherMessage = !string.IsNullOrEmpty(comment)
? $"{Username} - {comment}"
: string.Empty;
var result = await Service.UpdateRequestAsync(originalRequest);
var model = new NotificationModel
{
User = Username,
NotificationType = NotificationType.Issue,
Title = originalRequest.Title,
DateTime = DateTime.Now,
Body = issue == IssueState.Other ? comment : issue.ToString().ToCamelCaseWords(),
ImgSrc = originalRequest.Type == RequestType.Movie ? $"https://image.tmdb.org/t/p/w300/{originalRequest.PosterPath}" : originalRequest.PosterPath
};
await NotificationService.Publish(model);
return Response.AsJson(result
? new JsonResponseModel { Result = true }
: new JsonResponseModel { Result = false, Message = "Could not add issue, please try again or contact the administrator!" });
}
private async Task<Response> ClearIssue(int requestId)
{
if (!Security.HasAnyPermissions(User, Permissions.Administrator, Permissions.ManageRequests))
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Sorry, you do not have the correct permissions to clear an issue." });
}
var originalRequest = await Service.GetAsync(requestId);
if (originalRequest == null)
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Request does not exist to clear it!" });
}
originalRequest.Issues = IssueState.None;
originalRequest.OtherMessage = string.Empty;
var result = await Service.UpdateRequestAsync(originalRequest);
return Response.AsJson(result
? new JsonResponseModel { Result = true }
: new JsonResponseModel { Result = false, Message = "Could not clear issue, please try again or check the logs" });
}
private async Task<Response> ChangeRequestAvailability(int requestId, bool available)
{
if (!Security.HasAnyPermissions(User, Permissions.Administrator, Permissions.ManageRequests))
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Sorry, you do not have the correct permissions to change a request." });
}
Analytics.TrackEventAsync(Category.Requests, Action.Update, available ? "Make request available" : "Make request unavailable", Username, CookieHelper.GetAnalyticClientId(Cookies));
var originalRequest = await Service.GetAsync(requestId);
if (originalRequest == null)
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Request does not exist to change the availability!" });
}
originalRequest.Available = available;
var result = await Service.UpdateRequestAsync(originalRequest);
var plexSettings = await PlexSettings.GetSettingsAsync();
if (plexSettings.Enable)
{
await
PlexNotificationEngine.NotifyUsers(originalRequest,
available ? NotificationType.RequestAvailable : NotificationType.RequestDeclined);
}
var embySettings = await EmbySettings.GetSettingsAsync();
if (embySettings.Enable)
{
await EmbyNotificationEngine.NotifyUsers(originalRequest,
available ? NotificationType.RequestAvailable : NotificationType.RequestDeclined);
}
return Response.AsJson(result
? new { Result = true, Available = available, Message = string.Empty }
: new { Result = false, Available = false, Message = "Could not update the availability, please try again or check the logs" });
}
private async Task<Response> GetFilterAndSortSettings()
{
var s = await CustomizationSettings.GetSettingsAsync();
var sortVal = EnumHelper<SortOptions>.GetDisplayValue((SortOptions)s.DefaultSort);
var filterVal = EnumHelper<FilterOptions>.GetDisplayValue((FilterOptions)s.DefaultFilter);
var vm = new
{
DefaultSort = sortVal,
DefaultFilter = filterVal
};
return Response.AsJson(vm);
}
private async Task<Response> ChangeRootFolder(RequestType type, int id, int rootFolderId)
{
var rootFolders = new List<SonarrRootFolder>();
if (type == RequestType.TvShow)
{
// Get all root folders
var settings = await SonarrSettings.GetSettingsAsync();
rootFolders = SonarrApi.GetRootFolders(settings.ApiKey, settings.FullUri);
}
else
{
var settings = await Radarr.GetSettingsAsync();
rootFolders = RadarrApi.GetRootFolders(settings.ApiKey, settings.FullUri);
}
// Get Request
var allRequests = await Service.GetAllAsync();
var request = allRequests.FirstOrDefault(x => x.Id == id);
if (request == null)
{
return Response.AsJson(new JsonResponseModel { Result = false });
}
foreach (var folder in rootFolders)
{
if (folder.id.Equals(rootFolderId))
{
request.RootFolderSelected = folder.id;
break;
}
}
await Service.UpdateRequestAsync(request);
return Response.AsJson(new JsonResponseModel { Result = true });
}
}
}

View file

@ -0,0 +1,64 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2017 Jamie Rees
// File: SearchExtensionModule.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System.Threading.Tasks;
using Nancy;
using Ombi.Api.Interfaces;
using Ombi.Core;
using Ombi.Core.SettingModels;
namespace Ombi.UI.Modules
{
public class SearchExtensionModule : BaseAuthModule
{
public SearchExtensionModule(ISettingsService<PlexRequestSettings> pr, ISecurityExtensions security, INetflixApi netflix) : base("searchextension",pr, security)
{
NetflixApi = netflix;
Get["/netflix/{searchTerm}", true] = async (x, ctx) => await Netflix(x.searchTerm);
}
private INetflixApi NetflixApi { get; }
public async Task<Response> Netflix(string title)
{
await Task.Yield();
var result = NetflixApi.CheckNetflix(title);
if (!string.IsNullOrEmpty(result.Message))
{
return Response.AsJson(new { Result = false });
}
return Response.AsJson(new { Result = true, NetflixId = result.ShowId });
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,790 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: UserLoginModule.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Nancy;
using Nancy.Extensions;
using Nancy.Linker;
using NLog;
using Ombi.Api;
using Ombi.Api.Interfaces;
using Ombi.Api.Models.Emby;
using Ombi.Api.Models.Plex;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Core.Users;
using Ombi.Helpers;
using Ombi.Helpers.Analytics;
using Ombi.Helpers.Permissions;
using Ombi.Store;
using Ombi.Store.Models;
using Ombi.Store.Models.Emby;
using Ombi.Store.Models.Plex;
using Ombi.Store.Repository;
using Ombi.UI.Authentication;
using ISecurityExtensions = Ombi.Core.ISecurityExtensions;
namespace Ombi.UI.Modules
{
public class UserLoginModule : BaseModule
{
public UserLoginModule(ISettingsService<AuthenticationSettings> auth, IPlexApi api, ISettingsService<PlexSettings> plexSettings, ISettingsService<PlexRequestSettings> pr,
ISettingsService<LandingPageSettings> lp, IAnalytics a, IResourceLinker linker, IRepository<UserLogins> userLogins, IExternalUserRepository<PlexUsers> plexUsers, ICustomUserMapper custom,
ISecurityExtensions security, ISettingsService<UserManagementSettings> userManagementSettings, IEmbyApi embyApi, ISettingsService<EmbySettings> emby, IExternalUserRepository<EmbyUsers> embyU,
IUserHelper userHelper)
: base("userlogin", pr, security)
{
AuthService = auth;
LandingPageSettings = lp;
Analytics = a;
PlexApi = api;
PlexSettings = plexSettings;
Linker = linker;
UserLogins = userLogins;
PlexUserRepository = plexUsers;
CustomUserMapper = custom;
UserManagementSettings = userManagementSettings;
EmbySettings = emby;
EmbyApi = embyApi;
EmbyUserRepository = embyU;
UserHelper = userHelper;
Post["/", true] = async (x, ct) => await LoginUser();
Get["/logout"] = x => Logout();
Get["UserLoginIndex", "/", true] = async (x, ct) =>
{
if (Request.Query["landing"] == null)
{
var s = await LandingPageSettings.GetSettingsAsync();
if (s.Enabled)
{
if (s.BeforeLogin) // Before login
{
if (string.IsNullOrEmpty(Username))
{
// They are not logged in
return
Context.GetRedirect(Linker.BuildRelativeUri(Context, "LandingPageIndex").ToString());
}
return Context.GetRedirect(Linker.BuildRelativeUri(Context, "SearchIndex").ToString());
}
// After login
if (string.IsNullOrEmpty(Username))
{
// Not logged in yet
return Context.GetRedirect(Linker.BuildRelativeUri(Context, "UserLoginIndex").ToString() + "?landing");
}
// Send them to landing
var landingUrl = Linker.BuildRelativeUri(Context, "LandingPageIndex").ToString();
return Context.GetRedirect(landingUrl);
}
}
if (!string.IsNullOrEmpty(Username) || IsAdmin)
{
var url = Linker.BuildRelativeUri(Context, "SearchIndex").ToString();
return Response.AsRedirect(url);
}
var settings = await AuthService.GetSettingsAsync();
return View["Username", settings];
};
Post["/login", true] = async (x, ct) => await UsernameLogin();
Post["/password", true] = async (x, ct) => await PasswordLogin();
}
private ISettingsService<AuthenticationSettings> AuthService { get; }
private ISettingsService<LandingPageSettings> LandingPageSettings { get; }
private ISettingsService<PlexSettings> PlexSettings { get; }
private ISettingsService<EmbySettings> EmbySettings { get; }
private IPlexApi PlexApi { get; }
private IEmbyApi EmbyApi { get; }
private IResourceLinker Linker { get; }
private IAnalytics Analytics { get; }
private IRepository<UserLogins> UserLogins { get; }
private IExternalUserRepository<PlexUsers> PlexUserRepository { get; }
private IExternalUserRepository<EmbyUsers> EmbyUserRepository { get; }
private ICustomUserMapper CustomUserMapper { get; }
private ISettingsService<UserManagementSettings> UserManagementSettings { get; }
private IUserHelper UserHelper { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
private async Task<Response> UsernameLogin()
{
var username = Request.Form.username.Value;
var dateTimeOffset = Request.Form.DateTimeOffset;
var loginGuid = Guid.Empty;
var settings = await AuthService.GetSettingsAsync();
if (string.IsNullOrWhiteSpace(username) || IsUserInDeniedList(username, settings))
{
return Response.AsJson(new { result = false, message = Resources.UI.UserLogin_IncorrectUserPass });
}
var plexSettings = await PlexSettings.GetSettingsAsync();
var embySettings = await EmbySettings.GetSettingsAsync();
var authenticated = false;
var isOwner = false;
var userId = string.Empty;
EmbyUser embyUser = null;
if (plexSettings.Enable)
{
if (settings.UserAuthentication) // Check against the users in Plex
{
try
{
Log.Debug("Need to auth");
authenticated = CheckIfUserIsInPlexFriends(username, plexSettings.PlexAuthToken);
if (authenticated)
{
userId = GetUserIdIsInPlexFriends(username, plexSettings.PlexAuthToken);
}
if (CheckIfUserIsOwner(plexSettings.PlexAuthToken, username))
{
Log.Debug("User is the account owner");
authenticated = true;
isOwner = true;
userId = GetOwnerId(plexSettings.PlexAuthToken, username);
}
Log.Debug("Friends list result = {0}", authenticated);
}
catch (Exception)
{
return Response.AsJson(new {result = false, message = Resources.UI.UserLogin_IncorrectUserPass});
}
}
else if (!settings.UserAuthentication) // No auth, let them pass!
{
authenticated = true;
}
}
if (embySettings.Enable)
{
if (settings.UserAuthentication) // Check against the users in Plex
{
Log.Debug("Need to auth");
authenticated = CheckIfEmbyUser(username, embySettings);
if (authenticated)
{
embyUser = GetEmbyUser(username, embySettings);
userId = embyUser?.Id;
}
if (embyUser?.Policy?.IsAdministrator ?? false)
{
Log.Debug("User is the account owner");
authenticated = true;
isOwner = true;
}
Log.Debug("Friends list result = {0}", authenticated);
}
else if (!settings.UserAuthentication) // No auth, let them pass!
{
authenticated = true;
}
}
UsersModel dbUser = await IsDbuser(username);
if (dbUser != null) // in the db?
{
var perms = (Permissions)dbUser.Permissions;
authenticated = true;
isOwner = perms.HasFlag(Permissions.Administrator);
userId = dbUser.UserGuid;
}
if (settings.UsePassword || isOwner || Security.HasPermissions(username, Permissions.Administrator))
{
Session[SessionKeys.UserLoginName] = username;
var path = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Views", "UserLogin");
var file = System.IO.Directory.GetFiles(path).FirstOrDefault(x => x.Contains("Password.cshtml"));
var html = File.ReadAllText(file);
return Response.AsJson(new { result = true, usePassword = true, html });
}
if (!authenticated)
{
return Response.AsJson(new { result = false, message = Resources.UI.UserLogin_IncorrectUserPass });
}
var result = await AuthenticationSetup(userId, username, dateTimeOffset, loginGuid, isOwner, plexSettings.Enable, embySettings.Enable);
var landingSettings = await LandingPageSettings.GetSettingsAsync();
if (landingSettings.Enabled)
{
if (!landingSettings.BeforeLogin) // After Login
{
var uri = Linker.BuildRelativeUri(Context, "LandingPageIndex");
if (loginGuid != Guid.Empty)
{
return CustomModuleExtensions.LoginAndRedirect(this, result.LoginGuid, null, uri.ToString());
}
return Response.AsRedirect(uri.ToString());
}
}
var retVal = Linker.BuildRelativeUri(Context, "SearchIndex");
if (result.LoginGuid != Guid.Empty)
{
return CustomModuleExtensions.LoginAndRedirect(this, result.LoginGuid, null, retVal.ToString());
}
return Response.AsJson(new { result = true, url = retVal.ToString() });
}
private async Task<PlexUsers> IsPlexUser(string username)
{
var plexLocalUsers = await PlexUserRepository.GetAllAsync();
var plexLocal = plexLocalUsers.FirstOrDefault(x => x.Username == username);
return plexLocal;
}
private async Task<UsersModel> IsDbuser(string username)
{
var localUsers = await CustomUserMapper.GetUsersAsync();
var dbUser = localUsers.FirstOrDefault(x => x.UserName == username);
return dbUser;
}
private async Task<Response> PasswordLogin()
{
var password = Request.Form.password.Value;
if (string.IsNullOrEmpty(password))
{
return Response.AsJson(new { result = false, message = Resources.UI.UserLogin_IncorrectUserPass });
}
var dateTimeOffset = Request.Form.DateTimeOffset;
var loginGuid = Guid.Empty;
var settings = await AuthService.GetSettingsAsync();
var username = Session[SessionKeys.UserLoginName].ToString();
var authenticated = false;
var isOwner = false;
var userId = string.Empty;
var plexSettings = await PlexSettings.GetSettingsAsync();
var embySettings = await EmbySettings.GetSettingsAsync();
// attempt local login first as it has the least amount of overhead
userId = CustomUserMapper.ValidateUser(username, password)?.ToString();
if (userId != null)
{
authenticated = true;
}
else if (userId == null && plexSettings.Enable)
{
if (settings.UserAuthentication) // Authenticate with Plex
{
Log.Debug("Need to auth and also provide pass");
var signedIn = (PlexAuthentication) PlexApi.SignIn(username, password);
if (signedIn.user?.authentication_token != null)
{
Log.Debug("Correct credentials, checking if the user is account owner or in the friends list");
if (CheckIfUserIsOwner(plexSettings.PlexAuthToken, signedIn.user?.username))
{
Log.Debug("User is the account owner");
authenticated = true;
isOwner = true;
}
else
{
authenticated = CheckIfUserIsInPlexFriends(username, plexSettings.PlexAuthToken);
Log.Debug("Friends list result = {0}", authenticated);
}
userId = signedIn.user.uuid;
}
}
}
else if (userId == null && embySettings.Enable)
{
if (settings.UserAuthentication) // Authenticate with Emby
{
Log.Debug("Need to auth and also provide pass");
EmbyUser signedIn = null;
try
{
signedIn = (EmbyUser)EmbyApi.LogIn(username, password, embySettings.ApiKey, embySettings.FullUri);
}
catch (Exception e)
{
Log.Error(e);
}
if (signedIn != null)
{
Log.Debug("Correct credentials, checking if the user is account owner or in the friends list");
if (signedIn?.Policy?.IsAdministrator ?? false)
{
Log.Debug("User is the account owner");
authenticated = true;
isOwner = true;
}
else
{
authenticated = CheckIfEmbyUser(username, embySettings);
Log.Debug("Friends list result = {0}", authenticated);
}
userId = signedIn?.Id;
}
}
}
if (!authenticated)
{
return Response.AsJson(new { result = false, message = Resources.UI.UserLogin_IncorrectUserPass });
}
var m = await AuthenticationSetup(userId, username, dateTimeOffset, loginGuid, isOwner, plexSettings.Enable, embySettings.Enable);
var landingSettings = await LandingPageSettings.GetSettingsAsync();
if (landingSettings.Enabled)
{
if (!landingSettings.BeforeLogin) // After Login
{
var uri = Linker.BuildRelativeUri(Context, "LandingPageIndex");
if (m.LoginGuid != Guid.Empty)
{
return CustomModuleExtensions.LoginAndRedirect(this, m.LoginGuid, null, uri.ToString());
}
return Response.AsRedirect(uri.ToString());
}
}
var retVal = Linker.BuildRelativeUri(Context, "SearchIndex");
if (m.LoginGuid != Guid.Empty)
{
return CustomModuleExtensions.LoginAndRedirect(this, m.LoginGuid, null, retVal.ToString());
}
return Response.AsJson(new { result = true, url = retVal.ToString() });
}
private async Task<Response> LoginUser()
{
var userId = string.Empty;
var loginGuid = Guid.Empty;
var dateTimeOffset = Request.Form.DateTimeOffset;
var username = Request.Form.username.Value;
Log.Debug("Username \"{0}\" attempting to login", username);
if (string.IsNullOrWhiteSpace(username))
{
Session["TempMessage"] = Resources.UI.UserLogin_IncorrectUserPass;
var uri = Linker.BuildRelativeUri(Context, "UserLoginIndex");
return Response.AsRedirect(uri.ToString());
}
var authenticated = false;
var isOwner = false;
var settings = await AuthService.GetSettingsAsync();
var plexSettings = await PlexSettings.GetSettingsAsync();
if (IsUserInDeniedList(username, settings))
{
Log.Debug("User is in denied list, not allowing them to authenticate");
Session["TempMessage"] = Resources.UI.UserLogin_IncorrectUserPass;
var uri = Linker.BuildRelativeUri(Context, "UserLoginIndex");
return Response.AsRedirect(uri.ToString());
}
var password = string.Empty;
if (settings.UsePassword)
{
Log.Debug("Using password");
password = Request.Form.password.Value;
}
var localUsers = await CustomUserMapper.GetUsersAsync();
var plexLocalUsers = await PlexUserRepository.GetAllAsync();
if (settings.UserAuthentication && settings.UsePassword) // Authenticate with Plex
{
Log.Debug("Need to auth and also provide pass");
var signedIn = (PlexAuthentication)PlexApi.SignIn(username, password);
if (signedIn.user?.authentication_token != null)
{
Log.Debug("Correct credentials, checking if the user is account owner or in the friends list");
if (CheckIfUserIsOwner(plexSettings.PlexAuthToken, signedIn.user?.username))
{
Log.Debug("User is the account owner");
authenticated = true;
isOwner = true;
}
else
{
authenticated = CheckIfUserIsInPlexFriends(username, plexSettings.PlexAuthToken);
Log.Debug("Friends list result = {0}", authenticated);
}
userId = signedIn.user.uuid;
}
}
else if (settings.UserAuthentication) // Check against the users in Plex
{
Log.Debug("Need to auth");
authenticated = CheckIfUserIsInPlexFriends(username, plexSettings.PlexAuthToken);
if (authenticated)
{
userId = GetUserIdIsInPlexFriends(username, plexSettings.PlexAuthToken);
}
if (CheckIfUserIsOwner(plexSettings.PlexAuthToken, username))
{
Log.Debug("User is the account owner");
authenticated = true;
isOwner = true;
userId = GetOwnerId(plexSettings.PlexAuthToken, username);
}
Log.Debug("Friends list result = {0}", authenticated);
}
else if (!settings.UserAuthentication) // No auth, let them pass!
{
Log.Debug("No need to auth");
authenticated = true;
}
if (authenticated)
{
UserLogins.Insert(new UserLogins { UserId = userId, Type = UserType.PlexUser, LastLoggedIn = DateTime.UtcNow });
Log.Debug("We are authenticated! Setting session.");
// Add to the session (Used in the BaseModules)
Session[SessionKeys.UsernameKey] = (string)username;
Session[SessionKeys.ClientDateTimeOffsetKey] = (int)dateTimeOffset;
var plexLocal = plexLocalUsers.FirstOrDefault(x => x.Username == username);
if (plexLocal != null)
{
loginGuid = Guid.Parse(plexLocal.LoginId);
}
var dbUser = localUsers.FirstOrDefault(x => x.UserName == username);
if (dbUser != null)
{
loginGuid = Guid.Parse(dbUser.UserGuid);
}
if (loginGuid != Guid.Empty)
{
if (!settings.UserAuthentication)// Do not need to auth make admin use login screen for now TODO remove this
{
if (dbUser != null)
{
var perms = (Permissions)dbUser.Permissions;
if (perms.HasFlag(Permissions.Administrator))
{
var uri = Linker.BuildRelativeUri(Context, "UserLoginIndex");
Session["TempMessage"] = Resources.UI.UserLogin_AdminUsePassword;
return Response.AsRedirect(uri.ToString());
}
}
if (plexLocal != null)
{
var perms = (Permissions)plexLocal.Permissions;
if (perms.HasFlag(Permissions.Administrator))
{
var uri = Linker.BuildRelativeUri(Context, "UserLoginIndex");
Session["TempMessage"] = Resources.UI.UserLogin_AdminUsePassword;
return Response.AsRedirect(uri.ToString());
}
}
}
}
if (loginGuid == Guid.Empty && settings.UserAuthentication)
{
var defaultSettings = UserManagementSettings.GetSettings();
loginGuid = Guid.NewGuid();
var defaultPermissions = (Permissions)UserManagementHelper.GetPermissions(defaultSettings);
if (isOwner)
{
// If we are the owner, add the admin permission.
if (!defaultPermissions.HasFlag(Permissions.Administrator))
{
defaultPermissions += (int)Permissions.Administrator;
}
}
// Looks like we still don't have an entry, so this user does not exist
await PlexUserRepository.InsertAsync(new PlexUsers
{
PlexUserId = userId,
UserAlias = string.Empty,
Permissions = (int)defaultPermissions,
Features = UserManagementHelper.GetPermissions(defaultSettings),
Username = username,
EmailAddress = string.Empty, // We don't have it, we will get it on the next scheduled job run (in 30 mins)
LoginId = loginGuid.ToString()
});
}
}
if (!authenticated)
{
var uri = Linker.BuildRelativeUri(Context, "UserLoginIndex");
Session["TempMessage"] = Resources.UI.UserLogin_IncorrectUserPass;
return Response.AsRedirect(uri.ToString());
}
var landingSettings = await LandingPageSettings.GetSettingsAsync();
if (landingSettings.Enabled)
{
if (!landingSettings.BeforeLogin)
{
var uri = Linker.BuildRelativeUri(Context, "LandingPageIndex");
if (loginGuid != Guid.Empty)
{
return CustomModuleExtensions.LoginAndRedirect(this, loginGuid, null, uri.ToString());
}
return Response.AsRedirect(uri.ToString());
}
}
var retVal = Linker.BuildRelativeUri(Context, "SearchIndex");
if (loginGuid != Guid.Empty)
{
return CustomModuleExtensions.LoginAndRedirect(this, loginGuid, null, retVal.ToString());
}
return Response.AsRedirect(retVal.ToString());
}
private class LoginModel
{
public Guid LoginGuid { get; set; }
public string UserId { get; set; }
}
private async Task<LoginModel> AuthenticationSetup(string userId, string username, int dateTimeOffset, Guid loginGuid, bool isOwner, bool plex, bool emby)
{
var m = new LoginModel();
var settings = await AuthService.GetSettingsAsync();
var localUsers = await CustomUserMapper.GetUsersAsync();
var plexLocalUsers = await PlexUserRepository.GetAllAsync();
var embyLocalUsers = await EmbyUserRepository.GetAllAsync();
var localUser = false;
Log.Debug("We are authenticated! Setting session.");
// Add to the session (Used in the BaseModules)
Session[SessionKeys.UsernameKey] = username;
Session[SessionKeys.ClientDateTimeOffsetKey] = dateTimeOffset;
if (plex)
{
var plexLocal = plexLocalUsers.FirstOrDefault(x => x.Username == username);
if (plexLocal != null)
{
loginGuid = Guid.Parse(plexLocal.LoginId);
}
}
if (emby)
{
var embyLocal = embyLocalUsers.FirstOrDefault(x => x.Username == username);
if (embyLocal != null)
{
loginGuid = Guid.Parse(embyLocal.LoginId);
}
}
var dbUser = localUsers.FirstOrDefault(x => x.UserName == username);
if (dbUser != null)
{
loginGuid = Guid.Parse(dbUser.UserGuid);
localUser = true;
}
if (loginGuid == Guid.Empty && settings.UserAuthentication)
{
var defaultSettings = UserManagementSettings.GetSettings();
loginGuid = Guid.NewGuid();
var defaultPermissions = (Permissions)UserManagementHelper.GetPermissions(defaultSettings);
if (isOwner)
{
// If we are the owner, add the admin permission.
if (!defaultPermissions.HasFlag(Permissions.Administrator))
{
defaultPermissions += (int)Permissions.Administrator;
}
}
if (plex)
{
// Looks like we still don't have an entry, so this user does not exist
await PlexUserRepository.InsertAsync(new PlexUsers
{
PlexUserId = userId,
UserAlias = string.Empty,
Permissions = (int) defaultPermissions,
Features = UserManagementHelper.GetPermissions(defaultSettings),
Username = username,
EmailAddress = string.Empty,
// We don't have it, we will get it on the next scheduled job run (in 30 mins)
LoginId = loginGuid.ToString()
});
}
if (emby)
{
await EmbyUserRepository.InsertAsync(new EmbyUsers
{
EmbyUserId = userId,
UserAlias = string.Empty,
Permissions = (int)defaultPermissions,
Features = UserManagementHelper.GetPermissions(defaultSettings),
Username = username,
EmailAddress = string.Empty,
LoginId = loginGuid.ToString()
});
}
}
m.LoginGuid = loginGuid;
m.UserId = userId;
var type = UserType.LocalUser;
if (localUser)
{
type = UserType.LocalUser;
}
else if (plex)
{
type = UserType.PlexUser;
}
else if (emby)
{
type = UserType.EmbyUser;;
}
if (string.IsNullOrEmpty(userId))
{
// It's possible we have no auth enabled meaning the userId is empty
// Let's find that user!
var user = UserHelper.GetUser(username);
userId = user?.UserId ?? string.Empty;
}
UserLogins.Insert(new UserLogins { UserId = userId, Type = type, LastLoggedIn = DateTime.UtcNow });
return m;
}
private Response Logout()
{
if (Session[SessionKeys.UsernameKey] != null)
{
Session.Delete(SessionKeys.UsernameKey);
}
return Context.GetRedirect(!string.IsNullOrEmpty(BaseUrl)
? $"~/{BaseUrl}/userlogin"
: "~/userlogin");
}
private bool CheckIfUserIsOwner(string authToken, string userName)
{
var userAccount = PlexApi.GetAccount(authToken);
if (userAccount == null)
{
return false;
}
return userAccount.Username != null && userAccount.Username.Equals(userName, StringComparison.CurrentCultureIgnoreCase);
}
private string GetOwnerId(string authToken, string userName)
{
var userAccount = PlexApi.GetAccount(authToken);
if (userAccount == null)
{
return string.Empty;
}
return userAccount.Id;
}
private bool CheckIfUserIsInPlexFriends(string username, string authToken)
{
var users = PlexApi.GetUsers(authToken);
var allUsers = users?.User?.Where(x => !string.IsNullOrEmpty(x.Title));
return allUsers != null && allUsers.Any(x => x.Title.Equals(username, StringComparison.CurrentCultureIgnoreCase));
}
private bool CheckIfEmbyUser(string username, EmbySettings s)
{
try
{
var users = EmbyApi.GetUsers(s.FullUri, s.ApiKey);
var allUsers = users?.Where(x => !string.IsNullOrEmpty(x.Name));
return allUsers != null && allUsers.Any(x => x.Name.Equals(username, StringComparison.CurrentCultureIgnoreCase));
}
catch (Exception e)
{
Log.Error(e);
return false;
}
}
private EmbyUser GetEmbyUser(string username, EmbySettings s)
{
try
{
var users = EmbyApi.GetUsers(s.FullUri, s.ApiKey);
var allUsers = users?.Where(x => !string.IsNullOrEmpty(x.Name));
return allUsers?.FirstOrDefault(x => x.Name.Equals(username, StringComparison.CurrentCultureIgnoreCase));
}
catch (Exception e)
{
Log.Error(e);
return null;
}
}
private string GetUserIdIsInPlexFriends(string username, string authToken)
{
var users = PlexApi.GetUsers(authToken);
var allUsers = users?.User?.Where(x => !string.IsNullOrEmpty(x.Title));
return allUsers?.Where(x => x.Title.Equals(username, StringComparison.CurrentCultureIgnoreCase)).Select(x => x.Id).FirstOrDefault();
}
private bool IsUserInDeniedList(string username, AuthenticationSettings settings)
{
return settings.DeniedUserList.Any(x => x.Equals(username, StringComparison.CurrentCultureIgnoreCase));
}
}
}

View file

@ -0,0 +1,640 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Nancy;
using Nancy.Extensions;
using Nancy.Responses.Negotiation;
using Newtonsoft.Json;
using Ombi.Api.Interfaces;
using Ombi.Api.Models.Emby;
using Ombi.Api.Models.Plex;
using Ombi.Core;
using Ombi.Core.Models;
using Ombi.Core.SettingModels;
using Ombi.Helpers;
using Ombi.Helpers.Analytics;
using Ombi.Helpers.Permissions;
using Ombi.Store;
using Ombi.Store.Models;
using Ombi.Store.Models.Emby;
using Ombi.Store.Models.Plex;
using Ombi.Store.Repository;
using Ombi.UI.Models;
using Ombi.UI.Models.UserManagement;
using Action = Ombi.Helpers.Analytics.Action;
using ISecurityExtensions = Ombi.Core.ISecurityExtensions;
namespace Ombi.UI.Modules
{
public class UserManagementModule : BaseModule
{
public UserManagementModule(ISettingsService<PlexRequestSettings> pr, ICustomUserMapper m, IPlexApi plexApi, ISettingsService<PlexSettings> plex, IRepository<UserLogins> userLogins, IExternalUserRepository<PlexUsers> plexRepo
, ISecurityExtensions security, IRequestService req, IAnalytics ana, ISettingsService<EmbySettings> embyService, IEmbyApi embyApi, IExternalUserRepository<EmbyUsers> embyRepo) : base("usermanagement", pr, security)
{
#if !DEBUG
Before += (ctx) => Security.AdminLoginRedirect(Permissions.Administrator, ctx);
#endif
UserMapper = m;
PlexApi = plexApi;
PlexSettings = plex;
UserLoginsRepo = userLogins;
PlexUsersRepository = plexRepo;
PlexRequestSettings = pr;
RequestService = req;
Analytics = ana;
EmbySettings = embyService;
EmbyApi = embyApi;
EmbyRepository = embyRepo;
Get["/"] = x => Load();
Get["/users", true] = async (x, ct) => await LoadUsers();
Post["/createuser", true] = async (x, ct) => await CreateUser();
Get["/local/{id}"] = x => LocalDetails((Guid)x.id);
Get["/plex/{id}", true] = async (x, ct) => await PlexDetails(x.id);
Get["/permissions"] = x => GetEnum<Permissions>();
Get["/features"] = x => GetEnum<Features>();
Post["/updateuser", true] = async (x, ct) => await UpdateUser();
Post["/deleteuser"] = x => DeleteUser();
}
private ICustomUserMapper UserMapper { get; }
private IPlexApi PlexApi { get; }
private ISettingsService<PlexSettings> PlexSettings { get; }
private IRepository<UserLogins> UserLoginsRepo { get; }
private IExternalUserRepository<PlexUsers> PlexUsersRepository { get; }
private IExternalUserRepository<EmbyUsers> EmbyRepository { get; }
private ISettingsService<PlexRequestSettings> PlexRequestSettings { get; }
private ISettingsService<EmbySettings> EmbySettings { get; }
private IRequestService RequestService { get; }
private IAnalytics Analytics { get; }
private IEmbyApi EmbyApi { get; }
private Negotiator Load()
{
return View["Index"];
}
private async Task<Response> LoadUsers()
{
var model = new List<UserManagementUsersViewModel>();
var plexSettings = await PlexSettings.GetSettingsAsync();
var embySettings = await EmbySettings.GetSettingsAsync();
if (plexSettings.Enable)
{
model.AddRange(await LoadPlexUsers());
}
if (embySettings.Enable)
{
model.AddRange(await LoadEmbyUsers());
}
model.AddRange(await LoadLocalUsers());
return Response.AsJson(model);
}
private async Task<Response> CreateUser()
{
Analytics.TrackEventAsync(Category.UserManagement, Action.Create, "Created User", Username, CookieHelper.GetAnalyticClientId(Cookies));
var body = Request.Body.AsString();
if (string.IsNullOrEmpty(body))
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not save user, invalid JSON body" });
}
var model = JsonConvert.DeserializeObject<UserManagementCreateModel>(body);
if (string.IsNullOrWhiteSpace(model.Username) || string.IsNullOrWhiteSpace(model.Password))
{
return Response.AsJson(new JsonResponseModel
{
Result = false,
Message = "Please enter in a valid Username and Password"
});
}
var users = await UserMapper.GetUsersAsync();
if (users.Any(x => x.UserName.Equals(model.Username, StringComparison.CurrentCultureIgnoreCase)))
{
return Response.AsJson(new JsonResponseModel
{
Result = false,
Message = $"A user with the username '{model.Username}' already exists"
});
}
var featuresVal = 0;
var permissionsVal = 0;
foreach (var feature in model.Features)
{
var f = (int)EnumHelper<Features>.GetValueFromName(feature);
featuresVal += f;
}
foreach (var permission in model.Permissions)
{
var f = (int)EnumHelper<Permissions>.GetValueFromName(permission);
permissionsVal += f;
}
var user = UserMapper.CreateUser(model.Username, model.Password, permissionsVal, featuresVal, new UserProperties { EmailAddress = model.EmailAddress });
if (user.HasValue)
{
return Response.AsJson(MapLocalUser(UserMapper.GetUser(user.Value), DateTime.MinValue));
}
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not save user" });
}
private async Task<Response> UpdateUser()
{
Analytics.TrackEventAsync(Category.UserManagement, Action.Update, "Updated User", Username, CookieHelper.GetAnalyticClientId(Cookies));
var body = Request.Body.AsString();
if (string.IsNullOrEmpty(body))
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not save user, invalid JSON body" });
}
var model = JsonConvert.DeserializeObject<UserManagementUpdateModel>(body);
if (string.IsNullOrWhiteSpace(model.Id))
{
return Response.AsJson(new JsonResponseModel
{
Result = true,
Message = "Couldn't find the user"
});
}
var permissionsValue = model.Permissions.Where(c => c.Selected).Sum(c => c.Value);
var featuresValue = model.Features.Where(c => c.Selected).Sum(c => c.Value);
Guid outId;
Guid.TryParse(model.Id, out outId);
var localUser = UserMapper.GetUser(outId);
// Update Local User
if (localUser != null)
{
localUser.Permissions = permissionsValue;
localUser.Features = featuresValue;
var currentProps = ByteConverterHelper.ReturnObject<UserProperties>(localUser.UserProperties);
// Let's check if the alias has changed, if so we need to change all the requests associated with this
await UpdateRequests(localUser.UserName, currentProps.UserAlias, model.Alias);
currentProps.UserAlias = model.Alias;
currentProps.EmailAddress = model.EmailAddress;
localUser.UserProperties = ByteConverterHelper.ReturnBytes(currentProps);
var user = UserMapper.EditUser(localUser);
var dbUser = UserLoginsRepo.GetAll().FirstOrDefault(x => x.UserId == user.UserGuid);
var retUser = MapLocalUser(user, dbUser?.LastLoggedIn ?? DateTime.MinValue);
return Response.AsJson(retUser);
}
var plexSettings = await PlexSettings.GetSettingsAsync();
if (plexSettings.Enable)
{
var plexDbUsers = await PlexUsersRepository.GetAllAsync();
var plexUsers = PlexApi.GetUsers(plexSettings.PlexAuthToken);
var plexDbUser = plexDbUsers.FirstOrDefault(x => x.PlexUserId == model.Id);
var plexUser = plexUsers.User.FirstOrDefault(x => x.Id == model.Id);
var userLogin = UserLoginsRepo.GetAll().FirstOrDefault(x => x.UserId == model.Id);
if (plexDbUser != null && plexUser != null)
{
// We have a user in the DB for this Plex Account
plexDbUser.Permissions = permissionsValue;
plexDbUser.Features = featuresValue;
await UpdateRequests(plexDbUser.Username, plexDbUser.UserAlias, model.Alias);
plexDbUser.UserAlias = model.Alias;
plexDbUser.EmailAddress = model.EmailAddress;
await PlexUsersRepository.UpdateAsync(plexDbUser);
var retUser = MapPlexUser(plexUser, plexDbUser, userLogin?.LastLoggedIn ?? DateTime.MinValue);
return Response.AsJson(retUser);
}
// So it could actually be the admin
var account = PlexApi.GetAccount(plexSettings.PlexAuthToken);
if (plexDbUser != null && account != null)
{
// We have a user in the DB for this Plex Account
plexDbUser.Permissions = permissionsValue;
plexDbUser.Features = featuresValue;
await UpdateRequests(plexDbUser.Username, plexDbUser.UserAlias, model.Alias);
plexDbUser.UserAlias = model.Alias;
await PlexUsersRepository.UpdateAsync(plexDbUser);
var retUser = MapPlexAdmin(account, plexDbUser, userLogin?.LastLoggedIn ?? DateTime.MinValue);
return Response.AsJson(retUser);
}
// We have a Plex Account but he's not in the DB
if (plexUser != null)
{
var user = new PlexUsers
{
Permissions = permissionsValue,
Features = featuresValue,
UserAlias = model.Alias,
PlexUserId = plexUser.Id,
EmailAddress = plexUser.Email,
Username = plexUser.Title,
LoginId = Guid.NewGuid().ToString()
};
await PlexUsersRepository.InsertAsync(user);
var retUser = MapPlexUser(plexUser, user, userLogin?.LastLoggedIn ?? DateTime.MinValue);
return Response.AsJson(retUser);
}
}
var embySettings = await EmbySettings.GetSettingsAsync();
if (embySettings.Enable)
{
var embyDbUsers = await EmbyRepository.GetAllAsync();
var embyUsers = EmbyApi.GetUsers(embySettings.FullUri, embySettings.ApiKey);
var selectedDbUser = embyDbUsers.FirstOrDefault(x => x.EmbyUserId == model.Id);
var embyUser = embyUsers.FirstOrDefault(x => x.Id == model.Id);
var userLogin = UserLoginsRepo.GetAll().FirstOrDefault(x => x.UserId == model.Id);
if (selectedDbUser != null && embyUser != null)
{
// We have a user in the DB for this Plex Account
selectedDbUser.Permissions = permissionsValue;
selectedDbUser.Features = featuresValue;
await UpdateRequests(selectedDbUser.Username, selectedDbUser.UserAlias, model.Alias);
selectedDbUser.UserAlias = model.Alias;
selectedDbUser.EmailAddress = model.EmailAddress;
await EmbyRepository.UpdateAsync(selectedDbUser);
var retUser = MapEmbyUser(embyUser, selectedDbUser, userLogin?.LastLoggedIn ?? DateTime.MinValue);
return Response.AsJson(retUser);
}
}
return null; // We should never end up here.
}
private async Task UpdateRequests(string username, string oldAlias, string newAlias)
{
var newUsername = string.IsNullOrEmpty(newAlias) ? username : newAlias; // User the username if we are clearing the alias
var olderUsername = string.IsNullOrEmpty(oldAlias) ? username : oldAlias;
// Let's check if the alias has changed, if so we need to change all the requests associated with this
if (!olderUsername.Equals(newUsername, StringComparison.CurrentCultureIgnoreCase))
{
var requests = await RequestService.GetAllAsync();
// Update all requests
var requestsWithThisUser = requests.Where(x => x.RequestedUsers.Contains(olderUsername)).ToList();
foreach (var r in requestsWithThisUser)
{
r.RequestedUsers.Remove(olderUsername); // Remove old
r.RequestedUsers.Add(newUsername); // Add new
}
if (requestsWithThisUser.Any())
{
RequestService.BatchUpdate(requestsWithThisUser);
}
}
}
private Response DeleteUser()
{
Analytics.TrackEventAsync(Category.UserManagement, Action.Delete, "Deleted User", Username, CookieHelper.GetAnalyticClientId(Cookies));
var body = Request.Body.AsString();
if (string.IsNullOrEmpty(body))
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Could not save user, invalid JSON body" });
}
var model = JsonConvert.DeserializeObject<DeleteUserViewModel>(body);
if (string.IsNullOrWhiteSpace(model.Id))
{
return Response.AsJson(new JsonResponseModel
{
Result = true,
Message = "Couldn't find the user"
});
}
UserMapper.DeleteUser(model.Id);
return Response.AsJson(new JsonResponseModel { Result = true });
}
private Response LocalDetails(Guid id)
{
var localUser = UserMapper.GetUser(id);
if (localUser != null)
{
return Response.AsJson(localUser);
}
return Nancy.Response.NoBody;
}
private async Task<Response> PlexDetails(string id)
{
var plexSettings = await PlexSettings.GetSettingsAsync();
if (!string.IsNullOrEmpty(plexSettings.PlexAuthToken))
{
//Get Plex Users
var plexUsers = PlexApi.GetUsers(plexSettings.PlexAuthToken);
var selectedUser = plexUsers.User?.FirstOrDefault(x => x.Id.ToString() == id);
if (selectedUser != null)
{
return Response.AsJson(selectedUser);
}
}
return Nancy.Response.NoBody;
}
/// <summary>
/// Returns all claims for the users.
/// </summary>
/// <returns></returns>
private Response GetEnum<T>()
{
var retVal = new List<CheckBox>();
foreach (var p in Enum.GetValues(typeof(T)))
{
var perm = (T)p;
var displayValue = EnumHelper<T>.GetDisplayValue(perm);
retVal.Add(new CheckBox { Name = displayValue, Selected = false, Value = (int)p });
}
return Response.AsJson(retVal);
}
private UserManagementUsersViewModel MapLocalUser(UsersModel user, DateTime lastLoggedIn)
{
var features = (Features)user.Features;
var permissions = (Permissions)user.Permissions;
var userProps = ByteConverterHelper.ReturnObject<UserProperties>(user.UserProperties);
var m = new UserManagementUsersViewModel
{
Id = user.UserGuid,
PermissionsFormattedString = permissions == 0 ? "None" : permissions.ToString(),
FeaturesFormattedString = features.ToString(),
Username = user.UserName,
Type = UserType.LocalUser,
EmailAddress = userProps.EmailAddress,
Alias = userProps.UserAlias,
LastLoggedIn = lastLoggedIn,
};
m.Permissions.AddRange(GetPermissions(permissions));
m.Features.AddRange(GetFeatures(features));
return m;
}
private UserManagementUsersViewModel MapPlexUser(UserFriends plexInfo, PlexUsers dbUser, DateTime lastLoggedIn)
{
var newUser = false;
if (dbUser == null)
{
newUser = true;
dbUser = new PlexUsers();
}
var features = (Features)dbUser?.Features;
var permissions = (Permissions)dbUser?.Permissions;
var m = new UserManagementUsersViewModel
{
Id = plexInfo.Id,
PermissionsFormattedString = newUser ? "Processing..." : (permissions == 0 ? "None" : permissions.ToString()),
FeaturesFormattedString = newUser ? "Processing..." : features.ToString(),
Username = plexInfo.Title,
Type = UserType.PlexUser,
EmailAddress = string.IsNullOrEmpty(plexInfo.Email) ? dbUser.EmailAddress : plexInfo.Email,
Alias = dbUser?.UserAlias ?? string.Empty,
LastLoggedIn = lastLoggedIn,
PlexInfo = new UserManagementPlexInformation
{
Thumb = plexInfo.Thumb
},
ManagedUser = string.IsNullOrEmpty(plexInfo.Username)
};
m.Permissions.AddRange(GetPermissions(permissions));
m.Features.AddRange(GetFeatures(features));
return m;
}
private UserManagementUsersViewModel MapEmbyUser(EmbyUser embyInfo, EmbyUsers dbUser, DateTime lastLoggedIn)
{
var newUser = false;
if (dbUser == null)
{
newUser = true;
dbUser = new EmbyUsers();
}
var features = (Features)dbUser?.Features;
var permissions = (Permissions)dbUser?.Permissions;
var m = new UserManagementUsersViewModel
{
Id = embyInfo.Id,
PermissionsFormattedString = newUser ? "Processing..." : (permissions == 0 ? "None" : permissions.ToString()),
FeaturesFormattedString = newUser ? "Processing..." : features.ToString(),
Username = embyInfo.Name,
Type = UserType.EmbyUser,
EmailAddress =dbUser.EmailAddress,
Alias = dbUser?.UserAlias ?? string.Empty,
LastLoggedIn = lastLoggedIn,
ManagedUser = false
};
m.Permissions.AddRange(GetPermissions(permissions));
m.Features.AddRange(GetFeatures(features));
return m;
}
private UserManagementUsersViewModel MapPlexAdmin(PlexAccount plexInfo, PlexUsers dbUser, DateTime lastLoggedIn)
{
var newUser = false;
if (dbUser == null)
{
newUser = true;
dbUser = new PlexUsers();
}
var features = (Features)dbUser?.Features;
var permissions = (Permissions)dbUser?.Permissions;
var m = new UserManagementUsersViewModel
{
Id = plexInfo.Id,
PermissionsFormattedString = newUser ? "Processing..." : (permissions == 0 ? "None" : permissions.ToString()),
FeaturesFormattedString = newUser ? "Processing..." : features.ToString(),
Username = plexInfo.Username,
Type = UserType.PlexUser,
EmailAddress = plexInfo.Email,
Alias = dbUser?.UserAlias ?? string.Empty,
LastLoggedIn = lastLoggedIn,
};
m.Permissions.AddRange(GetPermissions(permissions));
m.Features.AddRange(GetFeatures(features));
return m;
}
private List<CheckBox> GetPermissions(Permissions permissions)
{
var retVal = new List<CheckBox>();
// Add permissions
foreach (var p in Enum.GetValues(typeof(Permissions)))
{
var perm = (Permissions)p;
var displayValue = EnumHelper<Permissions>.GetDisplayValue(perm);
var pm = new CheckBox
{
Name = displayValue,
Selected = permissions.HasFlag(perm),
Value = (int)perm
};
retVal.Add(pm);
}
return retVal;
}
private List<CheckBox> GetFeatures(Features features)
{
var retVal = new List<CheckBox>();
// Add features
foreach (var p in Enum.GetValues(typeof(Features)))
{
var perm = (Features)p;
var displayValue = EnumHelper<Features>.GetDisplayValue(perm);
var pm = new CheckBox
{
Name = displayValue,
Selected = features.HasFlag(perm),
Value = (int)perm
};
retVal.Add(pm);
}
return retVal;
}
private async Task<IEnumerable<UserManagementUsersViewModel>> LoadLocalUsers()
{
var localUsers = await UserMapper.GetUsersAsync(); var userLogins = UserLoginsRepo.GetAll().ToList();
var model = new List<UserManagementUsersViewModel>();
foreach (var user in localUsers)
{
var userDb = userLogins.FirstOrDefault(x => x.UserId == user.UserGuid);
model.Add(MapLocalUser(user, userDb?.LastLoggedIn ?? DateTime.MinValue));
}
return model;
}
private async Task<IEnumerable<UserManagementUsersViewModel>> LoadPlexUsers()
{
var plexDbUsers = await PlexUsersRepository.GetAllAsync();
var model = new List<UserManagementUsersViewModel>();
var userLogins = UserLoginsRepo.GetAll().ToList();
var plexSettings = await PlexSettings.GetSettingsAsync();
if (!string.IsNullOrEmpty(plexSettings.PlexAuthToken))
{
//Get Plex Users
var plexUsers = PlexApi.GetUsers(plexSettings.PlexAuthToken);
if (plexUsers?.User != null)
{
foreach (var u in plexUsers.User)
{
var dbUser = plexDbUsers.FirstOrDefault(x => x.PlexUserId == u.Id);
var userDb = userLogins.FirstOrDefault(x => x.UserId == u.Id);
// We don't have the user in the database yet
if (dbUser == null)
{
model.Add(MapPlexUser(u, null, userDb?.LastLoggedIn ?? DateTime.MinValue));
}
else
{
// The Plex User is in the database
model.Add(MapPlexUser(u, dbUser, userDb?.LastLoggedIn ?? DateTime.MinValue));
}
}
}
// Also get the server admin
var account = PlexApi.GetAccount(plexSettings.PlexAuthToken);
if (account != null)
{
var dbUser = plexDbUsers.FirstOrDefault(x => x.PlexUserId == account.Id);
var userDb = userLogins.FirstOrDefault(x => x.UserId == account.Id);
model.Add(MapPlexAdmin(account, dbUser, userDb?.LastLoggedIn ?? DateTime.MinValue));
}
}
return model;
}
private async Task<IEnumerable<UserManagementUsersViewModel>> LoadEmbyUsers()
{
var embyDbUsers = await EmbyRepository.GetAllAsync();
var model = new List<UserManagementUsersViewModel>();
var userLogins = UserLoginsRepo.GetAll().ToList();
var embySettings = await EmbySettings.GetSettingsAsync();
if (!string.IsNullOrEmpty(embySettings.ApiKey))
{
//Get Plex Users
var embyUsers = EmbyApi.GetUsers(embySettings.FullUri, embySettings.ApiKey);
if (embyUsers != null)
{
foreach (var u in embyUsers)
{
var dbUser = embyDbUsers.FirstOrDefault(x => x.EmbyUserId == u.Id);
var userDb = userLogins.FirstOrDefault(x => x.UserId == u.Id);
// We don't have the user in the database yet
model.Add(dbUser == null
? MapEmbyUser(u, null, userDb?.LastLoggedIn ?? DateTime.MinValue)
: MapEmbyUser(u, dbUser, userDb?.LastLoggedIn ?? DateTime.MinValue));
}
}
}
return model;
}
}
}

View file

@ -0,0 +1,291 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: UserWizardModule.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Linq;
using System.Threading.Tasks;
using Nancy;
using Nancy.Extensions;
using Nancy.ModelBinding;
using Nancy.Validation;
using NLog;
using Ombi.Api.Interfaces;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Helpers;
using Ombi.Helpers.Analytics;
using Ombi.Helpers.Permissions;
using Ombi.UI.Authentication;
using Ombi.UI.Helpers;
using Ombi.UI.Models;
using ISecurityExtensions = Ombi.Core.ISecurityExtensions;
using Action = Ombi.Helpers.Analytics.Action;
namespace Ombi.UI.Modules
{
public class UserWizardModule : BaseModule
{
public UserWizardModule(ISettingsService<PlexRequestSettings> pr, ISettingsService<PlexSettings> plex,
IPlexApi plexApi,
ISettingsService<AuthenticationSettings> auth, ICustomUserMapper m, IAnalytics a,
ISecurityExtensions security, IEmbyApi embyApi,
ISettingsService<EmbySettings> embySettings) : base("wizard", pr, security)
{
PlexSettings = plex;
PlexApi = plexApi;
PlexRequestSettings = pr;
Auth = auth;
Mapper = m;
Analytics = a;
EmbySettings = embySettings;
EmbyApi = embyApi;
Get["/", true] = async (x, ct) =>
{
a.TrackEventAsync(Category.Wizard, Action.Start, "Started the wizard", Username,
CookieHelper.GetAnalyticClientId(Cookies));
var settings = await PlexRequestSettings.GetSettingsAsync();
if (settings.Wizard)
{
return Context.GetRedirect("~/search");
}
return View["Index"];
};
Post["/plexAuth"] = x => PlexAuth();
Post["/plex", true] = async (x, ct) => await Plex();
Post["/plexrequest", true] = async (x, ct) => await PlexRequest();
Post["/auth", true] = async (x, ct) => await Authentication();
Post["/createuser", true] = async (x, ct) => await CreateUser();
Post["/embyauth", true] = async (x, ct) => await EmbyAuth();
}
private ISettingsService<PlexSettings> PlexSettings { get; }
private IPlexApi PlexApi { get; }
private ISettingsService<PlexRequestSettings> PlexRequestSettings { get; }
private ISettingsService<AuthenticationSettings> Auth { get; }
private ICustomUserMapper Mapper { get; }
private IAnalytics Analytics { get; }
private IEmbyApi EmbyApi { get; }
private ISettingsService<EmbySettings> EmbySettings { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
private Response PlexAuth()
{
var user = this.Bind<PlexAuth>();
if (string.IsNullOrEmpty(user.username) || string.IsNullOrEmpty(user.password))
{
return
Response.AsJson(new JsonResponseModel
{
Result = false,
Message = "Please provide a valid username and password"
});
}
var model = PlexApi.SignIn(user.username, user.password);
if (model?.user == null)
{
return
Response.AsJson(new JsonResponseModel { Result = false, Message = "Incorrect username or password!" });
}
// Set the auth token in the session so we can use it in the next form
Session[SessionKeys.UserWizardPlexAuth] = model.user.authentication_token;
var servers = PlexApi.GetServer(model.user.authentication_token);
var firstServer = servers.Server.FirstOrDefault();
return
Response.AsJson(
new { Result = true, firstServer?.Port, Ip = firstServer?.LocalAddresses, firstServer?.Scheme });
}
private async Task<Response> Plex()
{
var form = this.Bind<PlexSettings>();
var valid = this.Validate(form);
if (!valid.IsValid)
{
return Response.AsJson(valid.SendJsonError());
}
form.PlexAuthToken = Session[SessionKeys.UserWizardPlexAuth].ToString();
// Set the auth token from the previous form
// Get the machine ID from the settings (This could have changed)
try
{
var servers = PlexApi.GetServer(form.PlexAuthToken);
var firstServer = servers.Server.FirstOrDefault(x => x.AccessToken == form.PlexAuthToken);
Session[SessionKeys.UserWizardMachineId] = firstServer?.MachineIdentifier;
form.MachineIdentifier = firstServer?.MachineIdentifier;
}
catch (Exception e)
{
// Probably bad settings, just continue
Log.Error(e);
}
var result = await PlexSettings.SaveSettingsAsync(form);
if (result)
{
return Response.AsJson(new JsonResponseModel { Result = true });
}
return
Response.AsJson(new JsonResponseModel
{
Result = false,
Message = "Could not save the settings to the database, please try again."
});
}
private async Task<Response> PlexRequest()
{
var form = this.Bind<PlexRequestSettings>();
var valid = this.Validate(form);
if (!valid.IsValid)
{
return Response.AsJson(valid.SendJsonError());
}
var currentSettings = await PlexRequestSettings.GetSettingsAsync();
currentSettings.SearchForMovies = form.SearchForMovies;
currentSettings.SearchForTvShows = form.SearchForTvShows;
currentSettings.SearchForMusic = form.SearchForMusic;
var result = await PlexRequestSettings.SaveSettingsAsync(currentSettings);
if (result)
{
return Response.AsJson(new { Result = true });
}
return
Response.AsJson(new JsonResponseModel
{
Result = false,
Message = "Could not save the settings to the database, please try again."
});
}
private async Task<Response> Authentication()
{
var form = this.Bind<AuthenticationSettings>();
var result = await Auth.SaveSettingsAsync(form);
if (result)
{
return Response.AsJson(new JsonResponseModel { Result = true });
}
return
Response.AsJson(new JsonResponseModel
{
Result = false,
Message = "Could not save the settings to the database, please try again."
});
}
private async Task<Response> CreateUser()
{
var username = (string)Request.Form.Username;
var userId = Mapper.CreateUser(username, Request.Form.Password,
EnumHelper<Permissions>.All() - (int)Permissions.ReadOnlyUser, 0);
Analytics.TrackEventAsync(Category.Wizard, Action.Finish, "Finished the wizard", username,
CookieHelper.GetAnalyticClientId(Cookies));
Session[SessionKeys.UsernameKey] = username;
// Destroy the Plex Auth Token
Session.Delete(SessionKeys.UserWizardPlexAuth);
// Update the settings so we know we have been through the wizard
var settings = await PlexRequestSettings.GetSettingsAsync();
settings.Wizard = true;
await PlexRequestSettings.SaveSettingsAsync(settings);
var baseUrl = string.IsNullOrEmpty(settings.BaseUrl) ? string.Empty : $"/{settings.BaseUrl}";
return CustomModuleExtensions.LoginAndRedirect(this, (Guid)userId, fallbackRedirectUrl: $"{baseUrl}/search");
}
private async Task<Response> EmbyAuth()
{
var ip = (string)Request.Form.Ip;
var port = (int)Request.Form.Port;
var apiKey = (string)Request.Form.ApiKey;
var ssl = (bool)Request.Form.Ssl;
var settings = new EmbySettings
{
ApiKey = apiKey,
Enable = true,
Ip = ip,
Port = port,
Ssl = ssl,
};
try
{
// Test that we can connect
var result = EmbyApi.GetUsers(settings.FullUri, apiKey);
if (result != null && result.Any())
{
settings.AdministratorId = result.FirstOrDefault(x => x.Policy.IsAdministrator)?.Id ?? string.Empty;
await EmbySettings.SaveSettingsAsync(settings);
return Response.AsJson(new JsonResponseModel
{
Result = true
});
}
}
catch (Exception e)
{
return Response.AsJson(new JsonResponseModel
{
Result = false,
Message = $"Could not connect to Emby, please check your settings. Error: {e.Message}"
});
}
return Response.AsJson(new JsonResponseModel
{
Result = false,
Message = "Could not connect to Emby, please check your settings."
});
}
}
}