All Sln changes

This commit is contained in:
tidusjar 2016-12-19 20:14:31 +00:00
commit 796f0fc188
615 changed files with 68 additions and 747 deletions

View file

@ -0,0 +1,114 @@
#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.Threading.Tasks;
using Nancy;
using Nancy.Responses.Negotiation;
using NLog;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Helpers;
using Ombi.Helpers.Permissions;
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) : base("admin", settingsService, security)
{
Before += (ctx) => Security.AdminLoginRedirect(Permissions.Administrator, ctx);
SettingsService = systemService;
StatusChecker = statusChecker;
Get["/about", true] = async (x,ct) => await Index();
Post["/about", true] = async (x,ct) => await ReportIssue();
}
private ISettingsService<SystemSettings> SettingsService { get; }
private IStatusChecker StatusChecker { get; }
private async Task<Negotiator> Index()
{
var vm = new AboutAdminViewModel();
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.ApplicationVersion = AssemblyHelper.GetFileVersion();
vm.Branch = EnumHelper<Branches>.GetDisplayValue(systemSettings.Branch);
vm.LogLevel = LogManager.Configuration.LoggingRules.FirstOrDefault(x => x.NameMatches("database"))?.Levels?.FirstOrDefault()?.Name ?? "Unknown";
return View["About", 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 result = await StatusChecker.ReportBug(title,body);
return Response.AsJson(new {result = true, url = result.HtmlUrl.ToString()});
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,72 @@
#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.Threading.Tasks;
using Nancy;
using Nancy.ModelBinding;
using Nancy.Responses.Negotiation;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Helpers.Permissions;
using Ombi.UI.Models;
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();
return View["customization", model];
}
private async Task<Response> Save()
{
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" });
}
}
}

View file

@ -0,0 +1,73 @@
#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.Linq;
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();
}
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.Message
}).ToList();
return View["RequestFaultQueue", model];
}
}
}

View file

@ -0,0 +1,149 @@
#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.Core;
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) : base("admin", settingsService, security)
{
Cache = cache;
SystemSettings = ss;
Analytics = a;
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();
}
private ICacheProvider Cache { get; }
private ISettingsService<SystemSettings> SystemSettings { get; }
private IAnalytics Analytics { 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>.GetDisplayValue(Branches.Stable),
Value = Branches.Stable,
Selected = settings.Branch == Branches.Stable
},
new BranchDropdown
{
Name = EnumHelper<Branches>.GetDisplayValue(Branches.EarlyAccessPreview),
Value = Branches.EarlyAccessPreview,
Selected = settings.Branch == Branches.EarlyAccessPreview
},
new BranchDropdown
{
Name = EnumHelper<Branches>.GetDisplayValue(Branches.Dev),
Value = Branches.Dev,
Selected = settings.Branch == Branches.Dev
},
};
return View["Status", settings];
}
private async Task<Response> Save()
{
var settings = this.Bind<SystemSettings>();
Analytics.TrackEventAsync(Category.Admin, Action.Update, $"Updated Branch {EnumHelper<Branches>.GetDisplayValue(settings.Branch)}", 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, "PlexRequests.Updater.exe");
if (!string.IsNullOrEmpty(lowered))
{
if (lowered.Contains("plexrequests.exe"))
{
lowered = lowered.Replace("plexrequests.exe", "");
}
}
var startArgs = string.IsNullOrEmpty(lowered) ? appPath : $"{lowered} Plexrequests.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 Plex Requests");
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 Plex Requests");
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,288 @@
#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) : base("test", pr, security)
{
this.RequiresAuthentication();
CpApi = cpApi;
SonarrApi = sonarrApi;
PlexApi = plexApi;
SickRageApi = srApi;
HeadphonesApi = hpApi;
Post["/cp"] = _ => CouchPotatoTest();
Post["/sonarr"] = _ => SonarrTest();
Post["/plex"] = _ => PlexTest();
Post["/sickrage"] = _ => SickRageTest();
Post["/headphones"] = _ => HeadphonesTest();
Post["/plexdb"] = _ => TestPlexDb();
}
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 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 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 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 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,524 @@
#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, ISettingsService<CouchPotatoSettings> cpService, ICouchPotatoApi cpApi, ISonarrApi sonarrApi,
ISettingsService<SonarrSettings> sonarrSettings, ISickRageApi srApi, ISettingsService<SickRageSettings> srSettings,
ISettingsService<HeadphonesSettings> hpSettings, IHeadphonesApi hpApi, ISettingsService<PlexRequestSettings> pr, ITransientFaultQueue faultQueue
, ISecurityExtensions security) : base("approval", pr, security)
{
Before += (ctx) => Security.AdminLoginRedirect(ctx, Permissions.Administrator,Permissions.ManageRequests);
Service = service;
CpService = cpService;
CpApi = cpApi;
SonarrApi = sonarrApi;
SonarrSettings = sonarrSettings;
SickRageApi = srApi;
SickRageSettings = srSettings;
HeadphonesSettings = hpSettings;
HeadphoneApi = hpApi;
FaultQueue = faultQueue;
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 static Logger Log = LogManager.GetCurrentClassLogger();
private ISettingsService<SonarrSettings> SonarrSettings { get; }
private ISettingsService<SickRageSettings> SickRageSettings { get; }
private ISettingsService<CouchPotatoSettings> CpService { get; }
private ISettingsService<HeadphonesSettings> HeadphonesSettings { get; }
private ISonarrApi SonarrApi { get; }
private ISickRageApi SickRageApi { get; }
private ICouchPotatoApi CpApi { get; }
private IHeadphonesApi HeadphoneApi { get; }
private ITransientFaultQueue FaultQueue { 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); // 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 cpSettings = await CpService.GetSettingsAsync();
Log.Info("Adding movie to CouchPotato : {0}", request.Title);
if (!cpSettings.Enabled)
{
// Approve it
request.Approved = true;
Log.Warn("We approved movie: {0} but could not add it to CouchPotato 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 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 result = CpApi.AddMovie(request.ImdbId, cpSettings.ApiKey, request.Title, cpSettings.FullUri, string.IsNullOrEmpty(qualityId) ? cpSettings.ProfileId : qualityId);
Log.Trace("Adding movie to CP result {0}", result);
if (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 to CouchPotato! 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 cpSettings = await CpService.GetSettingsAsync();
var updatedRequests = new List<RequestedModel>();
foreach (var r in requestedModels)
{
if (r.Type == RequestType.Movie)
{
if (cpSettings.Enabled)
{
var res = SendMovie(cpSettings, r, CpApi);
if (res)
{
r.Approved = true;
updatedRequests.Add(r);
}
else
{
Log.Error("Could not approve and send the movie {0} to couch potato!", r.Title);
}
}
else
{
r.Approved = true;
updatedRequests.Add(r);
}
}
if (r.Type == RequestType.TvShow)
{
var sender = new TvSenderOld(SonarrApi, SickRageApi); // 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" });
}
private bool SendMovie(CouchPotatoSettings settings, RequestedModel r, ICouchPotatoApi cp)
{
Log.Info("Adding movie to CP : {0}", r.Title);
var result = cp.AddMovie(r.ImdbId, settings.ApiKey, r.Title, settings.FullUri, settings.ProfileId);
Log.Trace("Adding movie to CP result {0}", result);
return result;
}
}
}

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,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,189 @@
#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 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 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 Session[SessionKeys.UsernameKey].ToString();
}
_username = username;
}
catch (Exception)
{
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;
protected string Culture { get; set; }
protected const string CultureCookieName = "_culture";
protected 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,
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,100 @@
#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) : base("landing", settingsService, security)
{
LandingSettings = landing;
PlexSettings = ps;
PlexApi = pApi;
Linker = linker;
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 IPlexApi PlexApi { get; }
private IResourceLinker Linker { get; }
private async Task<Response> CheckStatus()
{
var plexSettings = await PlexSettings.GetSettingsAsync();
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);
}
}
}
}

View file

@ -0,0 +1,119 @@
#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 NLog;
using Ombi.Core;
using Ombi.Core.SettingModels;
using Ombi.Core.StatusChecker;
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) : base("layout", pr, security)
{
Cache = provider;
SystemSettings = settings;
Job = rec;
Get["/", true] = async (x,ct) => await CheckLatestVersion();
Get["/cacher", true] = async (x,ct) => await CacherRunning();
}
private ICacheProvider Cache { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
private ISettingsService<SystemSettings> SystemSettings { get; }
private IJobRecord Job { 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));
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 });
}
}
}
}

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,412 @@
#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.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 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,
INotificationEngine engine,
ISecurityExtensions security) : 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;
NotificationEngine = engine;
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);
}
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 ISonarrApi SonarrApi { get; }
private ISickRageApi SickRageApi { get; }
private ICouchPotatoApi CpApi { get; }
private ICacheProvider Cache { get; }
private INotificationEngine NotificationEngine { get; }
private async Task<Negotiator> LoadRequests()
{
var settings = await PrSettings.GetSettingsAsync();
return View["Index", settings];
}
private async Task<Response> GetMovies()
{
var settings = PrSettings.GetSettings();
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>();
if (IsAdmin)
{
var cpSettings = CpSettings.GetSettings();
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);
}
}
}
var canManageRequest = Security.HasAnyPermissions(User, Permissions.Administrator, Permissions.ManageRequests);
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 ? 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()
}).ToList();
return Response.AsJson(viewModel);
}
private async Task<Response> GetTvShows()
{
var settingsTask = PrSettings.GetSettingsAsync();
var requests = await Service.GetAllAsync();
requests = requests.Where(x => x.Type == RequestType.TvShow);
var dbTv = requests;
var settings = await settingsTask;
if (Security.HasPermissions(User, Permissions.UsersCanViewOnlyOwnRequests) && !IsAdmin)
{
dbTv = dbTv.Where(x => x.UserHasRequested(Username)).ToList();
}
IEnumerable<QualityModel> qualities = new List<QualityModel>();
if (IsAdmin)
{
try
{
var sonarrSettings = await SonarrSettings.GetSettingsAsync();
if (sonarrSettings.Enabled)
{
var result = Cache.GetOrSetAsync(CacheKeys.SonarrQualityProfiles, async () =>
{
return await Task.Run(() => SonarrApi.GetProfiles(sonarrSettings.ApiKey, sonarrSettings.FullUri));
});
qualities = result.Result.Select(x => new QualityModel { Id = x.id.ToString(), Name = x.name }).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 viewModel = dbTv.Select(tv => new RequestViewModel
{
ProviderId = tv.ProviderId,
Type = tv.Type,
Status = tv.Status,
ImdbId = tv.ImdbId,
Id = tv.Id,
PosterPath = tv.PosterPath,
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 ? 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(),
}).ToList();
return Response.AsJson(viewModel);
}
private async Task<Response> GetAlbumRequests()
{
var settings = PrSettings.GetSettings();
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()
};
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 plexService = await PlexSettings.GetSettingsAsync();
await NotificationEngine.NotifyUsers(originalRequest, plexService.PlexAuthToken, 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" });
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,369 @@
#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.Linq;
using System.Threading.Tasks;
using Nancy;
using Nancy.Extensions;
using Nancy.Linker;
using NLog;
using Ombi.Api.Interfaces;
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.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, IPlexUserRepository plexUsers, ICustomUserMapper custom,
ISecurityExtensions security, ISettingsService<UserManagementSettings> userManagementSettings)
: base("userlogin", pr, security)
{
AuthService = auth;
LandingPageSettings = lp;
Analytics = a;
Api = api;
PlexSettings = plexSettings;
Linker = linker;
UserLogins = userLogins;
PlexUserRepository = plexUsers;
CustomUserMapper = custom;
UserManagementSettings = userManagementSettings;
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["Index", settings];
};
Post["/", true] = async (x, ct) => await LoginUser();
Get["/logout"] = x => Logout();
}
private ISettingsService<AuthenticationSettings> AuthService { get; }
private ISettingsService<LandingPageSettings> LandingPageSettings { get; }
private ISettingsService<PlexSettings> PlexSettings { get; }
private IPlexApi Api { get; }
private IResourceLinker Linker { get; }
private IAnalytics Analytics { get; }
private IRepository<UserLogins> UserLogins { get; }
private IPlexUserRepository PlexUserRepository { get; }
private ICustomUserMapper CustomUserMapper { get; }
private ISettingsService<UserManagementSettings> UserManagementSettings {get;}
private static Logger Log = LogManager.GetCurrentClassLogger();
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)Api.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 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 = Api.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 = Api.GetAccount(authToken);
if (userAccount == null)
{
return string.Empty;
}
return userAccount.Id;
}
private bool CheckIfUserIsInPlexFriends(string username, string authToken)
{
var users = Api.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 string GetUserIdIsInPlexFriends(string username, string authToken)
{
var users = Api.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,507 @@
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.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.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, IPlexUserRepository plexRepo
, ISecurityExtensions security, IRequestService req, IAnalytics ana) : 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;
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 IPlexUserRepository PlexUsersRepository { get; }
private ISettingsService<PlexRequestSettings> PlexRequestSettings { get; }
private IRequestService RequestService { get; }
private IAnalytics Analytics { get; }
private Negotiator Load()
{
return View["Index"];
}
private async Task<Response> LoadUsers()
{
var localUsers = await UserMapper.GetUsersAsync();
var plexDbUsers = await PlexUsersRepository.GetAllAsync();
var model = new List<UserManagementUsersViewModel>();
var userLogins = UserLoginsRepo.GetAll().ToList();
foreach (var user in localUsers)
{
var userDb = userLogins.FirstOrDefault(x => x.UserId == user.UserGuid);
model.Add(MapLocalUser(user, userDb?.LastLoggedIn ?? DateTime.MinValue));
}
var plexSettings = await PlexSettings.GetSettingsAsync();
if (!string.IsNullOrEmpty(plexSettings.PlexAuthToken))
{
//Get Plex Users
var plexUsers = PlexApi.GetUsers(plexSettings.PlexAuthToken);
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 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();
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;
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.Username,
LoginId = Guid.NewGuid().ToString()
};
await PlexUsersRepository.InsertAsync(user);
var retUser = MapPlexUser(plexUser, user, 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)
{
if (dbUser == null)
{
dbUser = new PlexUsers();
}
var features = (Features)dbUser?.Features;
var permissions = (Permissions)dbUser?.Permissions;
var m = new UserManagementUsersViewModel
{
Id = plexInfo.Id,
PermissionsFormattedString = permissions == 0 ? "None" : permissions.ToString(),
FeaturesFormattedString = features.ToString(),
Username = plexInfo.Username,
Type = UserType.PlexUser,
EmailAddress = plexInfo.Email,
Alias = dbUser?.UserAlias ?? string.Empty,
LastLoggedIn = lastLoggedIn,
PlexInfo = new UserManagementPlexInformation
{
Thumb = plexInfo.Thumb
},
};
m.Permissions.AddRange(GetPermissions(permissions));
m.Features.AddRange(GetFeatures(features));
return m;
}
private UserManagementUsersViewModel MapPlexAdmin(PlexAccount plexInfo, PlexUsers dbUser, DateTime lastLoggedIn)
{
if (dbUser == null)
{
dbUser = new PlexUsers();
}
var features = (Features)dbUser?.Features;
var permissions = (Permissions)dbUser?.Permissions;
var m = new UserManagementUsersViewModel
{
Id = plexInfo.Id,
PermissionsFormattedString = permissions == 0 ? "None" : permissions.ToString(),
FeaturesFormattedString = 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;
}
}
}

View file

@ -0,0 +1,203 @@
#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) : base("wizard", pr, security)
{
PlexSettings = plex;
PlexApi = plexApi;
PlexRequestSettings = pr;
Auth = auth;
Mapper = m;
Analytics = a;
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();
}
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 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;
}
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");
}
}
}