Merge pull request #3 from tidusjar/dev

Merge latest changes.
pull/83/head
Shannon Barrett 8 years ago
commit b100fbe678

@ -64,7 +64,7 @@ namespace PlexRequests.Api
}; };
var options = new SonarrAddSeries(); var options = new SonarrAddSeries();
if (episodes == true) if (episodes)
{ {
options.addOptions = new AddOptions options.addOptions = new AddOptions
{ {

@ -31,7 +31,9 @@ namespace PlexRequests.Core.Models
{ {
public string Version { get; set; } public string Version { get; set; }
public bool UpdateAvailable { get; set; } public bool UpdateAvailable { get; set; }
public int ReleasesBehind { get; set; }
public string UpdateUri { get; set; } public string UpdateUri { get; set; }
public string DownloadUri { get; set; }
public string ReleaseNotes { get; set; }
public string ReleaseTitle { get; set; }
} }
} }

@ -33,6 +33,7 @@ namespace PlexRequests.Core.SettingModels
{ {
public class CouchPotatoSettings : Settings public class CouchPotatoSettings : Settings
{ {
public bool Enabled { get; set; }
public string Ip { get; set; } public string Ip { get; set; }
public int Port { get; set; } public int Port { get; set; }
public string ApiKey { get; set; } public string ApiKey { get; set; }

@ -70,7 +70,11 @@ namespace PlexRequests.Core
{ {
model.UpdateAvailable = true; model.UpdateAvailable = true;
model.UpdateUri = latestRelease.Result.HtmlUrl; model.UpdateUri = latestRelease.Result.HtmlUrl;
} }
model.ReleaseNotes = latestRelease.Result.Body;
model.DownloadUri = latestRelease.Result.Assets[0].BrowserDownloadUrl;
model.ReleaseTitle = latestRelease.Result.Name;
return model; return model;
} }

@ -26,11 +26,13 @@
#endregion #endregion
using System; using System;
using System.Linq; using System.Linq;
using System.Security;
using Nancy; using Nancy;
using Nancy.Authentication.Forms; using Nancy.Authentication.Forms;
using Nancy.Security; using Nancy.Security;
using PlexRequests.Helpers;
using PlexRequests.Store; using PlexRequests.Store;
namespace PlexRequests.Core namespace PlexRequests.Core
@ -44,7 +46,7 @@ namespace PlexRequests.Core
private static ISqliteConfiguration Db { get; set; } private static ISqliteConfiguration Db { get; set; }
public IUserIdentity GetUserFromIdentifier(Guid identifier, NancyContext context) public IUserIdentity GetUserFromIdentifier(Guid identifier, NancyContext context)
{ {
var repo = new UserRepository<UserModel>(Db); var repo = new UserRepository<UsersModel>(Db);
var user = repo.Get(identifier.ToString()); var user = repo.Get(identifier.ToString());
@ -61,35 +63,65 @@ namespace PlexRequests.Core
public static Guid? ValidateUser(string username, string password) public static Guid? ValidateUser(string username, string password)
{ {
var repo = new UserRepository<UserModel>(Db); var repo = new UserRepository<UsersModel>(Db);
var users = repo.GetAll(); var users = repo.GetAll();
var userRecord = users.FirstOrDefault(u => u.UserName.Equals(username, StringComparison.InvariantCultureIgnoreCase) && u.Password.Equals(password)); // TODO hashing
if (userRecord == null) foreach (var u in users)
{ {
return null; if (username == u.UserName)
{
var passwordMatch = PasswordHasher.VerifyPassword(password, u.Salt, u.Hash);
if (passwordMatch)
{
return new Guid(u.UserGuid);
}
}
} }
return null;
return new Guid(userRecord.User);
} }
public static bool DoUsersExist() public static bool DoUsersExist()
{ {
var repo = new UserRepository<UserModel>(Db); var repo = new UserRepository<UsersModel>(Db);
var users = repo.GetAll(); var users = repo.GetAll();
return users.Any(); return users.Any();
} }
public static Guid? CreateUser(string username, string password) public static Guid? CreateUser(string username, string password)
{ {
var repo = new UserRepository<UserModel>(Db); var repo = new UserRepository<UsersModel>(Db);
var salt = PasswordHasher.GenerateSalt();
var userModel = new UserModel { UserName = username, User = Guid.NewGuid().ToString(), Password = password }; var userModel = new UsersModel { UserName = username, UserGuid = Guid.NewGuid().ToString(), Salt = salt, Hash = PasswordHasher.ComputeHash(password, salt)};
repo.Insert(userModel); repo.Insert(userModel);
var userRecord = repo.Get(userModel.User); var userRecord = repo.Get(userModel.UserGuid);
return new Guid(userRecord.User); return new Guid(userRecord.UserGuid);
}
public static bool UpdateUser(string username, string oldPassword, string newPassword)
{
var repo = new UserRepository<UsersModel>(Db);
var users = repo.GetAll();
var userToChange = users.FirstOrDefault(x => x.UserName == username);
if (userToChange == null)
return false;
var passwordMatch = PasswordHasher.VerifyPassword(oldPassword, userToChange.Salt, userToChange.Hash);
if (!passwordMatch)
{
throw new SecurityException("Password does not match");
}
var newSalt = PasswordHasher.GenerateSalt();
var newHash = PasswordHasher.ComputeHash(newPassword, newSalt);
userToChange.Hash = newHash;
userToChange.Salt = newSalt;
return repo.Update(userToChange);
} }
} }
} }

@ -0,0 +1,50 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: AssemblyHelperTests.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.Diagnostics;
using NUnit.Framework;
namespace PlexRequests.Helpers.Tests
{
[TestFixture]
public class PasswordHasherTests
{
[Test]
public void TestHash()
{
var password = "abcdef";
var salt = PasswordHasher.GenerateSalt();
var hash = PasswordHasher.ComputeHash(password, salt);
Assert.That(hash, Is.Not.EqualTo(password));
var match = PasswordHasher.VerifyPassword(password, salt, hash);
Assert.That(match, Is.True);
}
}
}

@ -70,6 +70,7 @@
</Otherwise> </Otherwise>
</Choose> </Choose>
<ItemGroup> <ItemGroup>
<Compile Include="PasswordHasherTests.cs" />
<Compile Include="HtmlRemoverTests.cs" /> <Compile Include="HtmlRemoverTests.cs" />
<Compile Include="AssemblyHelperTests.cs" /> <Compile Include="AssemblyHelperTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />

@ -0,0 +1,69 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: PasswordHasher.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.Security.Cryptography;
namespace PlexRequests.Helpers
{
public static class PasswordHasher
{
// 24 = 192 bits
private const int SaltByteSize = 24;
private const int HashByteSize = 24;
private const int HasingIterationsCount = 10101;
public static byte[] ComputeHash(string password, byte[] salt, int iterations = HasingIterationsCount, int hashByteSize = HashByteSize)
{
var hashGenerator = new Rfc2898DeriveBytes(password, salt) { IterationCount = iterations };
return hashGenerator.GetBytes(hashByteSize);
}
public static byte[] GenerateSalt(int saltByteSize = SaltByteSize)
{
var saltGenerator = new RNGCryptoServiceProvider();
var salt = new byte[saltByteSize];
saltGenerator.GetBytes(salt);
return salt;
}
public static bool VerifyPassword(string password, byte[] passwordSalt, byte[] passwordHash)
{
var computedHash = ComputeHash(password, passwordSalt);
return AreHashesEqual(computedHash, passwordHash);
}
//Length constant verification - prevents timing attack
private static bool AreHashesEqual(IReadOnlyList<byte> firstHash, IReadOnlyList<byte> secondHash)
{
var minHashLength = firstHash.Count <= secondHash.Count ? firstHash.Count : secondHash.Count;
var xor = firstHash.Count ^ secondHash.Count;
for (var i = 0; i < minHashLength; i++)
xor |= firstHash[i] ^ secondHash[i];
return 0 == xor;
}
}
}

@ -53,6 +53,7 @@
<Compile Include="LoggingHelper.cs" /> <Compile Include="LoggingHelper.cs" />
<Compile Include="MemoryCacheProvider.cs" /> <Compile Include="MemoryCacheProvider.cs" />
<Compile Include="ObjectCopier.cs" /> <Compile Include="ObjectCopier.cs" />
<Compile Include="PasswordHasher.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SerializerSettings.cs" /> <Compile Include="SerializerSettings.cs" />
<Compile Include="StringCipher.cs" /> <Compile Include="StringCipher.cs" />

@ -62,12 +62,9 @@ namespace PlexRequests.Services.Notification
public static void Subscribe(INotification notification) public static void Subscribe(INotification notification)
{ {
Log.Trace("Subscribing Observer {0}", notification.NotificationName);
INotification notificationValue; INotification notificationValue;
if (Observers.TryGetValue(notification.NotificationName, out notificationValue)) if (Observers.TryGetValue(notification.NotificationName, out notificationValue))
{ {
Log.Trace("Observer {0} already exists", notification.NotificationName);
// Observer already exists
return; return;
} }

@ -70,6 +70,8 @@
<Compile Include="Repository\RequestJsonRepository.cs" /> <Compile Include="Repository\RequestJsonRepository.cs" />
<Compile Include="GenericRepository.cs" /> <Compile Include="GenericRepository.cs" />
<Compile Include="RequestedModel.cs" /> <Compile Include="RequestedModel.cs" />
<Compile Include="UserEntity.cs" />
<Compile Include="UsersModel.cs" />
<Compile Include="UserRepository.cs" /> <Compile Include="UserRepository.cs" />
<Compile Include="Sql.Designer.cs"> <Compile Include="Sql.Designer.cs">
<AutoGen>True</AutoGen> <AutoGen>True</AutoGen>
@ -77,7 +79,6 @@
<DependentUpon>Sql.resx</DependentUpon> <DependentUpon>Sql.resx</DependentUpon>
</Compile> </Compile>
<Compile Include="TableCreation.cs" /> <Compile Include="TableCreation.cs" />
<Compile Include="UserModel.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="sqlite3.dll"> <None Include="sqlite3.dll">

@ -1,11 +1,12 @@
--Any DB changes need to be made in this file. --Any DB changes need to be made in this file.
CREATE TABLE IF NOT EXISTS User CREATE TABLE IF NOT EXISTS Users
( (
Id INTEGER PRIMARY KEY AUTOINCREMENT, Id INTEGER PRIMARY KEY AUTOINCREMENT,
User varchar(50) NOT NULL , UserGuid varchar(50) NOT NULL ,
UserName varchar(50) NOT NULL, UserName varchar(50) NOT NULL,
Password varchar(100) NOT NULL Salt BLOB NOT NULL,
Hash BLOB NOT NULL
); );

@ -1,7 +1,7 @@
#region Copyright #region Copyright
// /************************************************************************ // /************************************************************************
// Copyright (c) 2016 Jamie Rees // Copyright (c) 2016 Jamie Rees
// File: UserModel.cs // File: UserEntity.cs
// Created By: Jamie Rees // Created By: Jamie Rees
// //
// Permission is hereby granted, free of charge, to any person obtaining // Permission is hereby granted, free of charge, to any person obtaining
@ -28,11 +28,12 @@ using Dapper.Contrib.Extensions;
namespace PlexRequests.Store namespace PlexRequests.Store
{ {
[Table("User")] public class UserEntity
public class UserModel : Entity
{ {
public string User { get; set; } [Key]
public int Id { get; set; }
public string UserName { get; set; } public string UserName { get; set; }
public string Password { get; set; } public string UserGuid { get; set; }
} }
} }

@ -32,14 +32,14 @@ using Dapper.Contrib.Extensions;
namespace PlexRequests.Store namespace PlexRequests.Store
{ {
public class UserRepository<T> : IRepository<T> where T : UserModel public class UserRepository<T> : IRepository<T> where T : UserEntity
{ {
public UserRepository(ISqliteConfiguration config) public UserRepository(ISqliteConfiguration config)
{ {
Config = config; Config = config;
} }
private ISqliteConfiguration Config { get; set; } private ISqliteConfiguration Config { get; }
public long Insert(T entity) public long Insert(T entity)
{ {
using (var cnn = Config.DbConnection()) using (var cnn = Config.DbConnection())
@ -65,7 +65,7 @@ namespace PlexRequests.Store
{ {
db.Open(); db.Open();
var result = db.GetAll<T>(); var result = db.GetAll<T>();
var selected = result.FirstOrDefault(x => x.User == id); var selected = result.FirstOrDefault(x => x.UserGuid == id);
return selected; return selected;
} }
} }

@ -0,0 +1,37 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: UserModel.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 Dapper.Contrib.Extensions;
namespace PlexRequests.Store
{
[Table("Users")]
public class UsersModel : UserEntity
{
public byte[] Hash { get; set; }
public byte[] Salt { get; set; }
}
}

@ -114,7 +114,7 @@ $(".theNoteSaveButton").click(function (e) {
var $form = $("#noteForm"); var $form = $("#noteForm");
var data = $form.serialize(); var data = $form.serialize();
$.ajax({ $.ajax({
type: $form.prop("method"), type: $form.prop("method"),
@ -198,7 +198,11 @@ $(document).on("click", ".approve", function (e) {
success: function (response) { success: function (response) {
if (checkJsonResponse(response)) { if (checkJsonResponse(response)) {
generateNotify("Success! Request Approved.", "success"); if (response.message) {
generateNotify(response.message, "success");
} else {
generateNotify("Success! Request Approved.", "success");
}
$("button[custom-button='" + buttonId + "']").remove(); $("button[custom-button='" + buttonId + "']").remove();
$("#" + buttonId + "notapproved").prop("class", "fa fa-check"); $("#" + buttonId + "notapproved").prop("class", "fa fa-check");
@ -227,7 +231,7 @@ $(document).on("click", ".clear", function (e) {
if (checkJsonResponse(response)) { if (checkJsonResponse(response)) {
generateNotify("Success! Issues Cleared.", "info"); generateNotify("Success! Issues Cleared.", "info");
$('#issueArea'+buttonId).html("<div>Issue: None</div>"); $('#issueArea' + buttonId).html("<div>Issue: None</div>");
} }
}, },
error: function (e) { error: function (e) {
@ -258,7 +262,7 @@ $(document).on("click", ".change", function (e) {
if (checkJsonResponse(response)) { if (checkJsonResponse(response)) {
generateNotify("Success! Availibility changed.", "info"); generateNotify("Success! Availibility changed.", "info");
var button = $("button[custom-availibility='" + buttonId + "']"); var button = $("button[custom-availibility='" + buttonId + "']");
var icon = $('#availableIcon'+buttonId); var icon = $('#availableIcon' + buttonId);
if (response.available) { if (response.available) {
button.text("Mark Unavailable"); button.text("Mark Unavailable");
@ -333,7 +337,8 @@ function buildRequestContext(result, type) {
issues: result.issues, issues: result.issues,
otherMessage: result.otherMessage, otherMessage: result.otherMessage,
requestId: result.id, requestId: result.id,
adminNote: result.adminNotes adminNote: result.adminNotes,
imdb: result.imdbId
}; };
return context; return context;

@ -139,7 +139,8 @@ function buildMovieContext(result) {
voteCount: result.voteCount, voteCount: result.voteCount,
voteAverage: result.voteAverage, voteAverage: result.voteAverage,
year: year, year: year,
type: "movie" type: "movie",
imdb: result.imdbId
}; };
return context; return context;
@ -154,7 +155,8 @@ function buildTvShowContext(result) {
title: result.seriesName, title: result.seriesName,
overview: result.overview, overview: result.overview,
year: year, year: year,
type: "tv" type: "tv",
imdb: result.imdbId
}; };
return context; return context;
} }

@ -27,6 +27,8 @@
using System.Dynamic; using System.Dynamic;
using System.Linq; using System.Linq;
using MarkdownSharp;
using Nancy; using Nancy;
using Nancy.Extensions; using Nancy.Extensions;
using Nancy.ModelBinding; using Nancy.ModelBinding;
@ -356,8 +358,15 @@ namespace PlexRequests.UI.Modules
Log.Trace(settings.DumpJson()); Log.Trace(settings.DumpJson());
var result = EmailService.SaveSettings(settings); var result = EmailService.SaveSettings(settings);
NotificationService.Subscribe(new EmailMessageNotification(EmailService)); if (settings.Enabled)
{
NotificationService.Subscribe(new EmailMessageNotification(EmailService));
}
else
{
NotificationService.UnSubscribe(new EmailMessageNotification(EmailService));
}
Log.Info("Saved email settings, result: {0}", result); Log.Info("Saved email settings, result: {0}", result);
return Response.AsJson(result return Response.AsJson(result
@ -369,6 +378,8 @@ namespace PlexRequests.UI.Modules
{ {
var checker = new StatusChecker(); var checker = new StatusChecker();
var status = checker.GetStatus(); var status = checker.GetStatus();
var md = new Markdown();
status.ReleaseNotes = md.Transform(status.ReleaseNotes);
return View["Status", status]; return View["Status", status];
} }
@ -389,8 +400,14 @@ namespace PlexRequests.UI.Modules
Log.Trace(settings.DumpJson()); Log.Trace(settings.DumpJson());
var result = PushbulletService.SaveSettings(settings); var result = PushbulletService.SaveSettings(settings);
if (settings.Enabled)
NotificationService.Subscribe(new PushbulletNotification(PushbulletApi, PushbulletService)); {
NotificationService.Subscribe(new PushbulletNotification(PushbulletApi, PushbulletService));
}
else
{
NotificationService.UnSubscribe(new PushbulletNotification(PushbulletApi, PushbulletService));
}
Log.Info("Saved email settings, result: {0}", result); Log.Info("Saved email settings, result: {0}", result);
return Response.AsJson(result return Response.AsJson(result

@ -121,12 +121,12 @@ namespace PlexRequests.UI.Modules
Log.Info("Sent successfully, Approving request now."); Log.Info("Sent successfully, Approving request now.");
request.Approved = true; request.Approved = true;
var requestResult = Service.UpdateRequest(request); var requestResult = Service.UpdateRequest(request);
Log.Trace("Approval result: {0}",requestResult); Log.Trace("Approval result: {0}", requestResult);
if (requestResult) if (requestResult)
{ {
return Response.AsJson(new JsonResponseModel { Result = true }); 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(new JsonResponseModel { Result = false, Message = "Updated Sonarr but could not approve it in PlexRequests :(" });
} }
return Response.AsJson(new JsonResponseModel return Response.AsJson(new JsonResponseModel
{ {
@ -177,6 +177,21 @@ namespace PlexRequests.UI.Modules
var cpSettings = CpService.GetSettings(); var cpSettings = CpService.GetSettings();
var cp = new CouchPotatoApi(); var cp = new CouchPotatoApi();
Log.Info("Adding movie to CP : {0}", request.Title); Log.Info("Adding movie to CP : {0}", request.Title);
if (!cpSettings.Enabled)
{
// Approve it
request.Approved = true;
// Update the record
var inserted = Service.UpdateRequest(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 = cp.AddMovie(request.ImdbId, cpSettings.ApiKey, request.Title, cpSettings.FullUri, cpSettings.ProfileId); var result = cp.AddMovie(request.ImdbId, cpSettings.ApiKey, request.Title, cpSettings.FullUri, cpSettings.ProfileId);
Log.Trace("Adding movie to CP result {0}", result); Log.Trace("Adding movie to CP result {0}", result);
if (result) if (result)
@ -188,7 +203,7 @@ namespace PlexRequests.UI.Modules
var inserted = Service.UpdateRequest(request); var inserted = Service.UpdateRequest(request);
return Response.AsJson(inserted return Response.AsJson(inserted
? new JsonResponseModel {Result = true} ? new JsonResponseModel { Result = true }
: new JsonResponseModel : new JsonResponseModel
{ {
Result = false, Result = false,
@ -239,7 +254,7 @@ namespace PlexRequests.UI.Modules
} }
if (r.Type == RequestType.TvShow) if (r.Type == RequestType.TvShow)
{ {
var sender = new TvSender(SonarrApi,SickRageApi); var sender = new TvSender(SonarrApi, SickRageApi);
var sr = SickRageSettings.GetSettings(); var sr = SickRageSettings.GetSettings();
var sonarr = SonarrSettings.GetSettings(); var sonarr = SonarrSettings.GetSettings();
if (sr.Enabled) if (sr.Enabled)
@ -289,7 +304,7 @@ namespace PlexRequests.UI.Modules
} }
private bool SendMovie(CouchPotatoSettings settings, RequestedModel r, ICouchPotatoApi cp) private bool SendMovie(CouchPotatoSettings settings, RequestedModel r, ICouchPotatoApi cp)
{ {
Log.Info("Adding movie to CP : {0}", r.Title); Log.Info("Adding movie to CP : {0}", r.Title);
var result = cp.AddMovie(r.ImdbId, settings.ApiKey, r.Title, settings.FullUri, settings.ProfileId); var result = cp.AddMovie(r.ImdbId, settings.ApiKey, r.Title, settings.FullUri, settings.ProfileId);
Log.Trace("Adding movie to CP result {0}", result); Log.Trace("Adding movie to CP result {0}", result);

@ -30,6 +30,8 @@ using System.Dynamic;
using Nancy; using Nancy;
using Nancy.Authentication.Forms; using Nancy.Authentication.Forms;
using Nancy.Extensions; using Nancy.Extensions;
using Nancy.Responses.Negotiation;
using Nancy.Security;
using PlexRequests.Core; using PlexRequests.Core;
using PlexRequests.UI.Models; using PlexRequests.UI.Models;
@ -81,7 +83,6 @@ namespace PlexRequests.UI.Modules
return View["Login/Register", model]; return View["Login/Register", model];
} }
}; };
Post["/register"] = x => Post["/register"] = x =>
@ -96,6 +97,30 @@ namespace PlexRequests.UI.Modules
Session[SessionKeys.UsernameKey] = username; Session[SessionKeys.UsernameKey] = username;
return this.LoginAndRedirect((Guid)userId); return this.LoginAndRedirect((Guid)userId);
}; };
Get["/changepassword"] = _ => ChangePassword();
Post["/changepassword"] = _ => ChangePasswordPost();
}
private Negotiator ChangePassword()
{
this.RequiresAuthentication();
return View["ChangePassword"];
}
private Negotiator ChangePasswordPost()
{
var username = Context.CurrentUser.UserName;
var oldPass = Request.Form.OldPassword;
var newPassword = Request.Form.NewPassword;
var newPasswordAgain = Request.Form.NewPasswordAgain;
if (!newPassword.Equals(newPasswordAgain))
{
}
var result = UserMapper.UpdateUser(username, oldPass, newPassword);
return View["ChangePassword"];
} }
} }
} }

@ -39,6 +39,7 @@ using PlexRequests.Api.Interfaces;
using PlexRequests.Core; using PlexRequests.Core;
using PlexRequests.Core.SettingModels; using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers; using PlexRequests.Helpers;
using PlexRequests.Helpers.Exceptions;
using PlexRequests.Services.Interfaces; using PlexRequests.Services.Interfaces;
using PlexRequests.Services.Notification; using PlexRequests.Services.Notification;
using PlexRequests.Store; using PlexRequests.Store;
@ -176,27 +177,24 @@ namespace PlexRequests.UI.Modules
} }
Log.Debug("movie with id {0} doesnt exists", movieId); Log.Debug("movie with id {0} doesnt exists", movieId);
var cpSettings = CpService.GetSettings();
if (cpSettings.ApiKey == null)
{
Log.Warn("CP apiKey is null");
return Response.AsJson(new JsonResponseModel { Result = false, Message = "CouchPotato is not yet configured, If you are the Admin, please log in." });
}
Log.Trace("Settings: ");
Log.Trace(cpSettings.DumpJson);
var movieApi = new TheMovieDbApi(); var movieApi = new TheMovieDbApi();
var movieInfo = movieApi.GetMovieInformation(movieId).Result; var movieInfo = movieApi.GetMovieInformation(movieId).Result;
Log.Trace("Getting movie info from TheMovieDb"); Log.Trace("Getting movie info from TheMovieDb");
Log.Trace(movieInfo.DumpJson); Log.Trace(movieInfo.DumpJson);
//#if !DEBUG
//#if !DEBUG try
if (CheckIfTitleExistsInPlex(movieInfo.Title, movieInfo.ReleaseDate?.Year.ToString())) {
if (CheckIfTitleExistsInPlex(movieInfo.Title, movieInfo.ReleaseDate?.Year.ToString()))
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{movieInfo.Title} is already in Plex!" });
}
}
catch (ApplicationSettingsException)
{ {
return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{movieInfo.Title} is already in Plex!" }); return Response.AsJson(new JsonResponseModel { Result = false, Message = $"We could not check if {movieInfo.Title} is in Plex, are you sure it's correctly setup?" });
} }
//#endif //#endif
var model = new RequestedModel var model = new RequestedModel
{ {
@ -219,6 +217,11 @@ namespace PlexRequests.UI.Modules
Log.Trace(settings.DumpJson()); Log.Trace(settings.DumpJson());
if (!settings.RequireApproval) if (!settings.RequireApproval)
{ {
var cpSettings = CpService.GetSettings();
Log.Trace("Settings: ");
Log.Trace(cpSettings.DumpJson);
Log.Info("Adding movie to CP (No approval required)"); Log.Info("Adding movie to CP (No approval required)");
var result = CouchPotatoApi.AddMovie(model.ImdbId, cpSettings.ApiKey, model.Title, cpSettings.FullUri, cpSettings.ProfileId); var result = CouchPotatoApi.AddMovie(model.ImdbId, cpSettings.ApiKey, model.Title, cpSettings.FullUri, cpSettings.ProfileId);
Log.Debug("Adding movie to CP result {0}", result); Log.Debug("Adding movie to CP result {0}", result);
@ -266,13 +269,19 @@ namespace PlexRequests.UI.Modules
var tvApi = new TvMazeApi(); var tvApi = new TvMazeApi();
var showInfo = tvApi.ShowLookupByTheTvDbId(showId); var showInfo = tvApi.ShowLookupByTheTvDbId(showId);
//#if !DEBUG
//#if !DEBUG try
if (CheckIfTitleExistsInPlex(showInfo.name, showInfo.premiered?.Substring(0, 4))) // Take only the year Format = 2014-01-01 {
if (CheckIfTitleExistsInPlex(showInfo.name, showInfo.premiered?.Substring(0, 4))) // Take only the year Format = 2014-01-01
{
return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{showInfo.name} is already in Plex!" });
}
}
catch (ApplicationSettingsException)
{ {
return Response.AsJson(new JsonResponseModel { Result = false, Message = $"{showInfo.name} is already in Plex!" }); return Response.AsJson(new JsonResponseModel { Result = false, Message = $"We could not check if {showInfo.name} is in Plex, are you sure it's correctly setup?" });
} }
//#endif //#endif
DateTime firstAir; DateTime firstAir;
DateTime.TryParse(showInfo.premiered, out firstAir); DateTime.TryParse(showInfo.premiered, out firstAir);
@ -290,7 +299,8 @@ namespace PlexRequests.UI.Modules
Approved = false, Approved = false,
RequestedBy = Session[SessionKeys.UsernameKey].ToString(), RequestedBy = Session[SessionKeys.UsernameKey].ToString(),
Issues = IssueState.None, Issues = IssueState.None,
LatestTv = latest LatestTv = latest,
ImdbId = showInfo.externals?.imdb ?? string.Empty
}; };

@ -73,6 +73,10 @@
<HintPath>..\packages\Humanizer.Core.2.0.1\lib\dotnet\Humanizer.dll</HintPath> <HintPath>..\packages\Humanizer.Core.2.0.1\lib\dotnet\Humanizer.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="MarkdownSharp, Version=0.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\MarkdownSharp.1.13.0.0\lib\35\MarkdownSharp.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> <Reference Include="Microsoft.Owin, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll</HintPath> <HintPath>..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll</HintPath>
<Private>True</Private> <Private>True</Private>
@ -328,6 +332,9 @@
<Content Include="Views\Admin\Sickrage.cshtml"> <Content Include="Views\Admin\Sickrage.cshtml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
<Content Include="Views\Login\ChangePassword.cshtml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<None Include="Web.Debug.config"> <None Include="Web.Debug.config">
<DependentUpon>web.config</DependentUpon> <DependentUpon>web.config</DependentUpon>
</None> </None>

@ -14,7 +14,20 @@
<form class="form-horizontal" method="POST" id="mainForm"> <form class="form-horizontal" method="POST" id="mainForm">
<fieldset> <fieldset>
<legend>CouchPotato Settings</legend> <legend>CouchPotato Settings</legend>
<div class="form-group">
<div class="checkbox">
<label>
@if (Model.Enabled)
{
<input type="checkbox" id="Enabled" name="Enabled" checked="checked"><text>Enabled</text>
}
else
{
<input type="checkbox" id="Enabled" name="Enabled"><text>Enabled</text>
}
</label>
</div>
</div>
<div class="form-group"> <div class="form-group">
<label for="Ip" class="control-label">CouchPotato Hostname or IP</label> <label for="Ip" class="control-label">CouchPotato Hostname or IP</label>
<div class=""> <div class="">

@ -14,7 +14,7 @@
<label class="control-label">Update Available: </label> <label class="control-label">Update Available: </label>
@if (Model.UpdateAvailable) @if (Model.UpdateAvailable)
{ {
<label class="control-label"><a href="@Model.UpdateUri" target="_blank"><i class="fa fa-check"></i> Click Here!</a></label> <label class="control-label"><a href="@Model.UpdateUri" target="_blank"><i class="fa fa-check"></i></a></label>
} }
else else
{ {
@ -23,6 +23,16 @@
</div> </div>
@if (Model.UpdateAvailable)
{
<h2>
<a href="@Model.DownloadUri">@Model.ReleaseTitle</a>
</h2>
<hr/>
<label>Release Notes:</label>
@Html.Raw(Model.ReleaseNotes)
}
</fieldset> </fieldset>
</div> </div>

@ -0,0 +1,9 @@
<form method="POST">
<br />
Old Password <input class="form-control" name="OldPassword" type="password" />
New Password <input class="form-control" name="NewPassword" type="password" />
New Password again <input class="form-control" name="NewPasswordAgain" type="password" />
<br />
<br />
<input class="btn btn-success-outline" type="submit" value="Change Password" />
</form>

@ -94,7 +94,7 @@
</div> </div>
<div class="col-sm-5 "> <div class="col-sm-5 ">
<div> <div>
<a href="https://www.themoviedb.org/{{type}}/{{id}}"> <a href="http://www.imdb.com/title/{{imdb}}/" targe="_blank">
<h4 class="request-title">{{title}} ({{year}})</h4> <h4 class="request-title">{{title}} ({{year}})</h4>
</a> </a>
<span class="label label-success">{{status}}</span> <span class="label label-success">{{status}}</span>

@ -76,7 +76,7 @@
</div> </div>
<div class="col-sm-5 "> <div class="col-sm-5 ">
<div> <div>
<a href="https://www.themoviedb.org/{{type}}/{{id}}"> <a href="http://www.imdb.com/title/{{imdb}}/" targe="_blank">
<h4>{{title}} ({{year}})</h4> <h4>{{title}} ({{year}})</h4>
</a> </a>
</div> </div>

@ -4,6 +4,7 @@
<package id="FluentScheduler" version="3.1.46" targetFramework="net46" /> <package id="FluentScheduler" version="3.1.46" targetFramework="net46" />
<package id="FluentValidation" version="6.2.1.0" targetFramework="net46" /> <package id="FluentValidation" version="6.2.1.0" targetFramework="net46" />
<package id="Humanizer.Core" version="2.0.1" targetFramework="net452" /> <package id="Humanizer.Core" version="2.0.1" targetFramework="net452" />
<package id="MarkdownSharp" version="1.13.0.0" targetFramework="net46" />
<package id="Microsoft.AspNet.Razor" version="2.0.30506.0" targetFramework="net452" /> <package id="Microsoft.AspNet.Razor" version="2.0.30506.0" targetFramework="net452" />
<package id="Microsoft.Owin" version="3.0.1" targetFramework="net452" /> <package id="Microsoft.Owin" version="3.0.1" targetFramework="net452" />
<package id="Microsoft.Owin.Host.HttpListener" version="3.0.1" targetFramework="net452" /> <package id="Microsoft.Owin.Host.HttpListener" version="3.0.1" targetFramework="net452" />

Loading…
Cancel
Save