mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-07-10 15:32:37 -07:00
Fixed the issue with user management, needed to implement our own authentication provider
This commit is contained in:
parent
63c2744336
commit
2a8927eb6d
46 changed files with 1132 additions and 565 deletions
|
@ -43,8 +43,8 @@ namespace PlexRequests.Core.Migration.Migrations
|
|||
[Migration(11000, "v1.10.0.0")]
|
||||
public class Version1100 : BaseMigration, IMigration
|
||||
{
|
||||
public Version1100(IUserRepository userRepo, IRequestService requestService, ISettingsService<LogSettings> log, IPlexApi plexApi, ISettingsService<PlexSettings> plexService, IRepository<PlexUsers> plexusers,
|
||||
ISettingsService<PlexRequestSettings> prSettings, ISettingsService<UserManagementSettings> umSettings)
|
||||
public Version1100(IUserRepository userRepo, IRequestService requestService, ISettingsService<LogSettings> log, IPlexApi plexApi, ISettingsService<PlexSettings> plexService, IPlexUserRepository plexusers,
|
||||
ISettingsService<PlexRequestSettings> prSettings, ISettingsService<UserManagementSettings> umSettings, ISettingsService<ScheduledJobsSettings> sjs)
|
||||
{
|
||||
UserRepo = userRepo;
|
||||
RequestService = requestService;
|
||||
|
@ -54,6 +54,7 @@ namespace PlexRequests.Core.Migration.Migrations
|
|||
PlexUsers = plexusers;
|
||||
PlexRequestSettings = prSettings;
|
||||
UserManagementSettings = umSettings;
|
||||
ScheduledJobSettings = sjs;
|
||||
}
|
||||
public int Version => 11000;
|
||||
private IUserRepository UserRepo { get; }
|
||||
|
@ -61,9 +62,10 @@ namespace PlexRequests.Core.Migration.Migrations
|
|||
private ISettingsService<LogSettings> Log { get; }
|
||||
private IPlexApi PlexApi { get; }
|
||||
private ISettingsService<PlexSettings> PlexSettings { get; }
|
||||
private IRepository<PlexUsers> PlexUsers { get; }
|
||||
private IPlexUserRepository PlexUsers { get; }
|
||||
private ISettingsService<PlexRequestSettings> PlexRequestSettings { get; }
|
||||
private ISettingsService<UserManagementSettings> UserManagementSettings { get; }
|
||||
private ISettingsService<ScheduledJobsSettings> ScheduledJobSettings { get; }
|
||||
|
||||
public void Start(IDbConnection con)
|
||||
{
|
||||
|
@ -74,10 +76,21 @@ namespace PlexRequests.Core.Migration.Migrations
|
|||
ResetLogLevel();
|
||||
UpdatePlexUsers();
|
||||
PopulateDefaultUserManagementSettings();
|
||||
UpdateScheduledJobs();
|
||||
|
||||
UpdateSchema(con, Version);
|
||||
}
|
||||
|
||||
private void UpdateScheduledJobs()
|
||||
{
|
||||
var settings = ScheduledJobSettings.GetSettings();
|
||||
|
||||
settings.PlexUserChecker = 24;
|
||||
settings.PlexContentCacher = 60;
|
||||
|
||||
ScheduledJobSettings.SaveSettings(settings);
|
||||
}
|
||||
|
||||
private void PopulateDefaultUserManagementSettings()
|
||||
{
|
||||
var plexRequestSettings = PlexRequestSettings.GetSettings();
|
||||
|
@ -147,6 +160,9 @@ namespace PlexRequests.Core.Migration.Migrations
|
|||
Permissions = permissions,
|
||||
Features = 0,
|
||||
UserAlias = string.Empty,
|
||||
EmailAddress = user.Email,
|
||||
Username = user.Username,
|
||||
LoginId = Guid.NewGuid().ToString()
|
||||
};
|
||||
|
||||
PlexUsers.Insert(m);
|
||||
|
@ -171,6 +187,8 @@ namespace PlexRequests.Core.Migration.Migrations
|
|||
|
||||
con.AlterTable("PlexUsers", "ADD", "Permissions", true, "INTEGER");
|
||||
con.AlterTable("PlexUsers", "ADD", "Features", true, "INTEGER");
|
||||
con.AlterTable("PlexUsers", "ADD", "Username", true, "VARCHAR(100)");
|
||||
con.AlterTable("PlexUsers", "ADD", "EmailAddress", true, "VARCHAR(100)");
|
||||
|
||||
//https://image.tmdb.org/t/p/w150/https://image.tmdb.org/t/p/w150//aqhAqttDq7zgsTaBHtCD8wmTk6k.jpg
|
||||
|
||||
|
|
|
@ -44,5 +44,6 @@ namespace PlexRequests.Core.SettingModels
|
|||
public string RecentlyAddedCron { get; set; }
|
||||
public int FaultQueueHandler { get; set; }
|
||||
public int PlexContentCacher { get; set; }
|
||||
public int PlexUserChecker { get; set; }
|
||||
}
|
||||
}
|
|
@ -39,5 +39,7 @@ namespace PlexRequests.Services.Jobs
|
|||
public const string EpisodeCacher = "Plex Episode Cacher";
|
||||
public const string RecentlyAddedEmail = "Recently Added Email Notification";
|
||||
public const string FaultQueueHandler = "Request Fault Queue Handler";
|
||||
public const string PlexUserChecker = "Plex User Checker";
|
||||
|
||||
}
|
||||
}
|
164
PlexRequests.Services/Jobs/PlexUserChecker.cs
Normal file
164
PlexRequests.Services/Jobs/PlexUserChecker.cs
Normal file
|
@ -0,0 +1,164 @@
|
|||
#region Copyright
|
||||
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: StoreCleanup.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 NLog;
|
||||
using PlexRequests.Api.Interfaces;
|
||||
using PlexRequests.Core;
|
||||
using PlexRequests.Core.SettingModels;
|
||||
using PlexRequests.Helpers;
|
||||
using PlexRequests.Helpers.Permissions;
|
||||
using PlexRequests.Services.Interfaces;
|
||||
using PlexRequests.Store.Models;
|
||||
using PlexRequests.Store.Repository;
|
||||
|
||||
using Quartz;
|
||||
|
||||
namespace PlexRequests.Services.Jobs
|
||||
{
|
||||
public class PlexUserChecker : IJob
|
||||
{
|
||||
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
|
||||
|
||||
public PlexUserChecker(IPlexUserRepository plexUsers, IPlexApi plexAPi, IJobRecord rec, ISettingsService<PlexSettings> plexSettings, ISettingsService<PlexRequestSettings> prSettings)
|
||||
{
|
||||
Repo = plexUsers;
|
||||
JobRecord = rec;
|
||||
PlexApi = plexAPi;
|
||||
PlexSettings = plexSettings;
|
||||
PlexRequestSettings = prSettings;
|
||||
}
|
||||
|
||||
private IJobRecord JobRecord { get; }
|
||||
private IPlexApi PlexApi { get; }
|
||||
private IPlexUserRepository Repo { get; }
|
||||
private ISettingsService<PlexSettings> PlexSettings { get; }
|
||||
private ISettingsService<PlexRequestSettings> PlexRequestSettings { get; }
|
||||
|
||||
|
||||
public void Execute(IJobExecutionContext context)
|
||||
{
|
||||
JobRecord.SetRunning(true, JobNames.PlexUserChecker);
|
||||
|
||||
try
|
||||
{
|
||||
var settings = PlexSettings.GetSettings();
|
||||
if (string.IsNullOrEmpty(settings.PlexAuthToken))
|
||||
{
|
||||
return;
|
||||
}
|
||||
var plexUsers = PlexApi.GetUsers(settings.PlexAuthToken);
|
||||
var prSettings = PlexRequestSettings.GetSettings();
|
||||
|
||||
var dbUsers = Repo.GetAll().ToList();
|
||||
foreach (var user in plexUsers.User)
|
||||
{
|
||||
var dbUser = dbUsers.FirstOrDefault(x => x.PlexUserId == user.Id);
|
||||
if (dbUser != null)
|
||||
{
|
||||
var needToUpdate = false;
|
||||
|
||||
// Do we need up update any info?
|
||||
if (dbUser.EmailAddress != user.Email)
|
||||
{
|
||||
dbUser.EmailAddress = user.Email;
|
||||
needToUpdate = true;
|
||||
}
|
||||
if (dbUser.Username != user.Username)
|
||||
{
|
||||
dbUser.Username = user.Username;
|
||||
needToUpdate = true;
|
||||
}
|
||||
|
||||
if (needToUpdate)
|
||||
{
|
||||
Repo.Update(dbUser);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
int permissions = 0;
|
||||
if (prSettings.SearchForMovies)
|
||||
{
|
||||
permissions = (int)Permissions.RequestMovie;
|
||||
}
|
||||
if (prSettings.SearchForTvShows)
|
||||
{
|
||||
permissions += (int)Permissions.RequestTvShow;
|
||||
}
|
||||
if (prSettings.SearchForMusic)
|
||||
{
|
||||
permissions += (int)Permissions.RequestMusic;
|
||||
}
|
||||
if (!prSettings.RequireMovieApproval)
|
||||
{
|
||||
permissions += (int)Permissions.AutoApproveMovie;
|
||||
}
|
||||
if (!prSettings.RequireTvShowApproval)
|
||||
{
|
||||
permissions += (int)Permissions.AutoApproveTv;
|
||||
}
|
||||
if (!prSettings.RequireMusicApproval)
|
||||
{
|
||||
permissions += (int)Permissions.AutoApproveAlbum;
|
||||
}
|
||||
|
||||
// Add report Issues
|
||||
|
||||
permissions += (int)Permissions.ReportIssue;
|
||||
|
||||
var m = new PlexUsers
|
||||
{
|
||||
PlexUserId = user.Id,
|
||||
Permissions = permissions,
|
||||
Features = 0,
|
||||
UserAlias = string.Empty,
|
||||
EmailAddress = user.Email,
|
||||
Username = user.Username,
|
||||
LoginId = Guid.NewGuid().ToString()
|
||||
};
|
||||
|
||||
Repo.Insert(m);
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
JobRecord.SetRunning(false, JobNames.PlexUserChecker);
|
||||
JobRecord.Record(JobNames.PlexUserChecker);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -93,6 +93,7 @@
|
|||
<Compile Include="Jobs\PlexEpisodeCacher.cs" />
|
||||
<Compile Include="Jobs\RecentlyAdded.cs" />
|
||||
<Compile Include="Jobs\StoreBackup.cs" />
|
||||
<Compile Include="Jobs\PlexUserChecker.cs" />
|
||||
<Compile Include="Jobs\StoreCleanup.cs" />
|
||||
<Compile Include="Jobs\CouchPotatoCacher.cs" />
|
||||
<Compile Include="Jobs\PlexAvailabilityChecker.cs" />
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
// ************************************************************************/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using Dapper.Contrib.Extensions;
|
||||
|
||||
namespace PlexRequests.Store.Models
|
||||
|
@ -36,5 +37,8 @@ namespace PlexRequests.Store.Models
|
|||
public string UserAlias { get; set; }
|
||||
public int Permissions { get; set; }
|
||||
public int Features { get; set; }
|
||||
public string Username { get; set; }
|
||||
public string EmailAddress { get; set; }
|
||||
public string LoginId { get; set; }
|
||||
}
|
||||
}
|
|
@ -88,6 +88,7 @@
|
|||
<Compile Include="Repository\SettingsJsonRepository.cs" />
|
||||
<Compile Include="Repository\RequestJsonRepository.cs" />
|
||||
<Compile Include="Repository\GenericRepository.cs" />
|
||||
<Compile Include="Repository\PlexUserRepository.cs" />
|
||||
<Compile Include="Repository\UserRepository.cs" />
|
||||
<Compile Include="RequestedModel.cs" />
|
||||
<Compile Include="UserEntity.cs" />
|
||||
|
|
117
PlexRequests.Store/Repository/PlexUserRepository.cs
Normal file
117
PlexRequests.Store/Repository/PlexUserRepository.cs
Normal file
|
@ -0,0 +1,117 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: UserRepository.cs
|
||||
// Created By:
|
||||
//
|
||||
// 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.Data;
|
||||
using System.Threading.Tasks;
|
||||
using Dapper;
|
||||
using Dapper.Contrib.Extensions;
|
||||
using PlexRequests.Helpers;
|
||||
using PlexRequests.Store.Models;
|
||||
|
||||
namespace PlexRequests.Store.Repository
|
||||
{
|
||||
public class PlexUserRepository : BaseGenericRepository<PlexUsers>, IPlexUserRepository
|
||||
{
|
||||
public PlexUserRepository(ISqliteConfiguration config, ICacheProvider cache) : base(config,cache)
|
||||
{
|
||||
DbConfig = config;
|
||||
Cache = cache;
|
||||
}
|
||||
|
||||
private ISqliteConfiguration DbConfig { get; }
|
||||
private ICacheProvider Cache { get; }
|
||||
private IDbConnection Db => DbConfig.DbConnection();
|
||||
|
||||
public PlexUsers GetUser(string userGuid)
|
||||
{
|
||||
var sql = @"SELECT * FROM PlexUsers
|
||||
WHERE PlexUserId = @UserGuid";
|
||||
return Db.QueryFirstOrDefault<PlexUsers>(sql, new {UserGuid = userGuid});
|
||||
}
|
||||
|
||||
public PlexUsers GetUserByUsername(string username)
|
||||
{
|
||||
var sql = @"SELECT * FROM PlexUsers
|
||||
WHERE Username = @UserName";
|
||||
return Db.QueryFirstOrDefault<PlexUsers>(sql, new {UserName = username});
|
||||
}
|
||||
|
||||
public async Task<PlexUsers> GetUserAsync(string userguid)
|
||||
{
|
||||
var sql = @"SELECT * FROM PlexUsers
|
||||
WHERE PlexUserId = @UserGuid";
|
||||
return await Db.QueryFirstOrDefaultAsync<PlexUsers>(sql, new {UserGuid = userguid});
|
||||
}
|
||||
|
||||
#region abstract implimentation
|
||||
[Obsolete]
|
||||
public override PlexUsers Get(string id)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
public override Task<PlexUsers> GetAsync(int id)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
public override PlexUsers Get(int id)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
public override Task<PlexUsers> GetAsync(string id)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
public interface IPlexUserRepository
|
||||
{
|
||||
PlexUsers GetUser(string userGuid);
|
||||
PlexUsers GetUserByUsername(string username);
|
||||
Task<PlexUsers> GetUserAsync(string userguid);
|
||||
IEnumerable<PlexUsers> Custom(Func<IDbConnection, IEnumerable<PlexUsers>> func);
|
||||
long Insert(PlexUsers entity);
|
||||
void Delete(PlexUsers entity);
|
||||
IEnumerable<PlexUsers> GetAll();
|
||||
bool UpdateAll(IEnumerable<PlexUsers> entity);
|
||||
bool Update(PlexUsers entity);
|
||||
Task<IEnumerable<PlexUsers>> GetAllAsync();
|
||||
Task<bool> UpdateAsync(PlexUsers users);
|
||||
Task<int> InsertAsync(PlexUsers users);
|
||||
}
|
||||
}
|
||||
|
|
@ -117,7 +117,10 @@ CREATE TABLE IF NOT EXISTS PlexUsers
|
|||
PlexUserId varchar(100) NOT NULL,
|
||||
UserAlias varchar(100) NOT NULL,
|
||||
Permissions INTEGER,
|
||||
Features INTEGER
|
||||
Features INTEGER,
|
||||
Username VARCHAR(100),
|
||||
EmailAddress VARCHAR(100),
|
||||
LoginId VARCHAR(100)
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS PlexUsers_Id ON PlexUsers (Id);
|
||||
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: CustomAuthenticationConfiguration.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.Cryptography;
|
||||
using PlexRequests.Store.Repository;
|
||||
|
||||
namespace PlexRequests.UI.Authentication
|
||||
{
|
||||
public class CustomAuthenticationConfiguration
|
||||
{
|
||||
internal const string DefaultRedirectQuerystringKey = "returnUrl";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the forms authentication query string key for storing the return url
|
||||
/// </summary>
|
||||
public string RedirectQuerystringKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the redirect url for pages that require authentication
|
||||
/// </summary>
|
||||
public string RedirectUrl { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the username/identifier mapper</summary>
|
||||
public IUserRepository LocalUserRepository { get; set; }
|
||||
|
||||
public IPlexUserRepository PlexUserRepository { get; set; }
|
||||
|
||||
/// <summary>Gets or sets RequiresSSL property</summary>
|
||||
/// <value>The flag that indicates whether SSL is required</value>
|
||||
public bool RequiresSSL { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether to redirect to login page during unauthorized access.
|
||||
/// </summary>
|
||||
public bool DisableRedirect { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the domain of the auth cookie</summary>
|
||||
public string Domain { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the path of the auth cookie</summary>
|
||||
public string Path { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the cryptography configuration</summary>
|
||||
public CryptographyConfiguration CryptographyConfiguration { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the configuration is valid or not.
|
||||
/// </summary>
|
||||
public virtual bool IsValid => (this.DisableRedirect || !string.IsNullOrEmpty(this.RedirectUrl)) && (this.LocalUserRepository != null && PlexUserRepository != null && this.CryptographyConfiguration != null) && (this.CryptographyConfiguration.EncryptionProvider != null && this.CryptographyConfiguration.HmacProvider != null);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="T:Nancy.Authentication.Forms.FormsAuthenticationConfiguration" /> class.
|
||||
/// </summary>
|
||||
public CustomAuthenticationConfiguration()
|
||||
: this(CryptographyConfiguration.Default)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="T:Nancy.Authentication.Forms.FormsAuthenticationConfiguration" /> class.
|
||||
/// </summary>
|
||||
/// <param name="cryptographyConfiguration">Cryptography configuration</param>
|
||||
public CustomAuthenticationConfiguration(CryptographyConfiguration cryptographyConfiguration)
|
||||
{
|
||||
this.CryptographyConfiguration = cryptographyConfiguration;
|
||||
this.RedirectQuerystringKey = "returnUrl";
|
||||
}
|
||||
}
|
||||
}
|
409
PlexRequests.UI/Authentication/CustomAuthenticationProvider.cs
Normal file
409
PlexRequests.UI/Authentication/CustomAuthenticationProvider.cs
Normal file
|
@ -0,0 +1,409 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: CustomAuthenticationProvider.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 Nancy;
|
||||
using Nancy.Authentication.Forms;
|
||||
using Nancy.Bootstrapper;
|
||||
using Nancy.Cookies;
|
||||
using Nancy.Cryptography;
|
||||
using Nancy.Extensions;
|
||||
using Nancy.Helpers;
|
||||
using Nancy.Security;
|
||||
using PlexRequests.Core;
|
||||
using PlexRequests.Helpers;
|
||||
|
||||
namespace PlexRequests.UI.Authentication
|
||||
{
|
||||
public class CustomAuthenticationProvider
|
||||
{
|
||||
private static string formsAuthenticationCookieName = "_ncfa";
|
||||
private static CustomAuthenticationConfiguration currentConfiguration;
|
||||
|
||||
/// <summary>Gets or sets the forms authentication cookie name</summary>
|
||||
public static string FormsAuthenticationCookieName
|
||||
{
|
||||
get
|
||||
{
|
||||
return CustomAuthenticationProvider.formsAuthenticationCookieName;
|
||||
}
|
||||
set
|
||||
{
|
||||
CustomAuthenticationProvider.formsAuthenticationCookieName = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Enables forms authentication for the application</summary>
|
||||
/// <param name="pipelines">Pipelines to add handlers to (usually "this")</param>
|
||||
/// <param name="configuration">Forms authentication configuration</param>
|
||||
public static void Enable(IPipelines pipelines, CustomAuthenticationConfiguration configuration)
|
||||
{
|
||||
if (pipelines == null)
|
||||
throw new ArgumentNullException("pipelines");
|
||||
if (configuration == null)
|
||||
throw new ArgumentNullException("configuration");
|
||||
if (!configuration.IsValid)
|
||||
throw new ArgumentException("Configuration is invalid", "configuration");
|
||||
CustomAuthenticationProvider.currentConfiguration = configuration;
|
||||
pipelines.BeforeRequest.AddItemToStartOfPipeline(CustomAuthenticationProvider.GetLoadAuthenticationHook(configuration));
|
||||
if (configuration.DisableRedirect)
|
||||
return;
|
||||
pipelines.AfterRequest.AddItemToEndOfPipeline(CustomAuthenticationProvider.GetRedirectToLoginHook(configuration));
|
||||
}
|
||||
|
||||
/// <summary>Enables forms authentication for a module</summary>
|
||||
/// <param name="module">Module to add handlers to (usually "this")</param>
|
||||
/// <param name="configuration">Forms authentication configuration</param>
|
||||
public static void Enable(INancyModule module, CustomAuthenticationConfiguration configuration)
|
||||
{
|
||||
if (module == null)
|
||||
throw new ArgumentNullException("module");
|
||||
if (configuration == null)
|
||||
throw new ArgumentNullException("configuration");
|
||||
if (!configuration.IsValid)
|
||||
throw new ArgumentException("Configuration is invalid", "configuration");
|
||||
module.RequiresAuthentication();
|
||||
CustomAuthenticationProvider.currentConfiguration = configuration;
|
||||
module.Before.AddItemToStartOfPipeline(CustomAuthenticationProvider.GetLoadAuthenticationHook(configuration));
|
||||
if (configuration.DisableRedirect)
|
||||
return;
|
||||
module.After.AddItemToEndOfPipeline(CustomAuthenticationProvider.GetRedirectToLoginHook(configuration));
|
||||
}
|
||||
/// <summary>
|
||||
/// Creates a response that sets the authentication cookie and redirects
|
||||
/// the user back to where they came from.
|
||||
/// </summary>
|
||||
/// <param name="context">Current context</param>
|
||||
/// <param name="userIdentifier">User identifier guid</param>
|
||||
/// <param name="cookieExpiry">Optional expiry date for the cookie (for 'Remember me')</param>
|
||||
/// <param name="fallbackRedirectUrl">Url to redirect to if none in the querystring</param>
|
||||
/// <returns>Nancy response with redirect.</returns>
|
||||
public static Response UserLoggedInRedirectResponse(NancyContext context, Guid userIdentifier, DateTime? cookieExpiry = null, string fallbackRedirectUrl = null)
|
||||
{
|
||||
var redirectUrl = fallbackRedirectUrl;
|
||||
|
||||
if (string.IsNullOrEmpty(redirectUrl))
|
||||
{
|
||||
redirectUrl = context.Request.Url.BasePath;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(redirectUrl))
|
||||
{
|
||||
redirectUrl = "/";
|
||||
}
|
||||
|
||||
string redirectQuerystringKey = GetRedirectQuerystringKey(currentConfiguration);
|
||||
|
||||
if (context.Request.Query[redirectQuerystringKey].HasValue)
|
||||
{
|
||||
var queryUrl = (string)context.Request.Query[redirectQuerystringKey];
|
||||
|
||||
if (context.IsLocalUrl(queryUrl))
|
||||
{
|
||||
redirectUrl = queryUrl;
|
||||
}
|
||||
}
|
||||
|
||||
var response = context.GetRedirect(redirectUrl);
|
||||
var authenticationCookie = BuildCookie(userIdentifier, cookieExpiry, currentConfiguration);
|
||||
response.WithCookie(authenticationCookie);
|
||||
|
||||
return response;
|
||||
}
|
||||
/// <summary>
|
||||
/// Logs the user in.
|
||||
/// </summary>
|
||||
/// <param name="userIdentifier">User identifier guid</param>
|
||||
/// <param name="cookieExpiry">Optional expiry date for the cookie (for 'Remember me')</param>
|
||||
/// <returns>Nancy response with status <see cref="HttpStatusCode.OK"/></returns>
|
||||
public static Response UserLoggedInResponse(Guid userIdentifier, DateTime? cookieExpiry = null)
|
||||
{
|
||||
var response =
|
||||
(Response)HttpStatusCode.OK;
|
||||
|
||||
var authenticationCookie =
|
||||
BuildCookie(userIdentifier, cookieExpiry, currentConfiguration);
|
||||
|
||||
response.WithCookie(authenticationCookie);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs the user out and redirects them to a URL
|
||||
/// </summary>
|
||||
/// <param name="context">Current context</param>
|
||||
/// <param name="redirectUrl">URL to redirect to</param>
|
||||
/// <returns>Nancy response</returns>
|
||||
public static Response LogOutAndRedirectResponse(NancyContext context, string redirectUrl)
|
||||
{
|
||||
var response = context.GetRedirect(redirectUrl);
|
||||
var authenticationCookie = BuildLogoutCookie(currentConfiguration);
|
||||
response.WithCookie(authenticationCookie);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs the user out.
|
||||
/// </summary>
|
||||
/// <returns>Nancy response</returns>
|
||||
public static Response LogOutResponse()
|
||||
{
|
||||
var response =
|
||||
(Response)HttpStatusCode.OK;
|
||||
|
||||
var authenticationCookie =
|
||||
BuildLogoutCookie(currentConfiguration);
|
||||
|
||||
response.WithCookie(authenticationCookie);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the pre request hook for loading the authenticated user's details
|
||||
/// from the cookie.
|
||||
/// </summary>
|
||||
/// <param name="configuration">Forms authentication configuration to use</param>
|
||||
/// <returns>Pre request hook delegate</returns>
|
||||
private static Func<NancyContext, Response> GetLoadAuthenticationHook(CustomAuthenticationConfiguration configuration)
|
||||
{
|
||||
if (configuration == null)
|
||||
{
|
||||
throw new ArgumentNullException("configuration");
|
||||
}
|
||||
|
||||
return context =>
|
||||
{
|
||||
var userGuid = GetAuthenticatedUserFromCookie(context, configuration);
|
||||
|
||||
if (userGuid != Guid.Empty)
|
||||
{
|
||||
var identity = new UserIdentity();
|
||||
|
||||
var plexUsers = configuration.PlexUserRepository.GetAll();
|
||||
var plexUser = plexUsers.FirstOrDefault(x => Guid.Parse(x.LoginId) == userGuid);
|
||||
|
||||
if (plexUser != null)
|
||||
{
|
||||
identity.UserName = plexUser.Username;
|
||||
}
|
||||
|
||||
var localUsers = configuration.LocalUserRepository.GetAll();
|
||||
|
||||
var localUser = localUsers.FirstOrDefault(x => Guid.Parse(x.UserGuid) == userGuid);
|
||||
if (localUser != null)
|
||||
{
|
||||
identity.UserName = localUser.UserName;
|
||||
}
|
||||
|
||||
context.CurrentUser = identity;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the post request hook for redirecting to the login page
|
||||
/// </summary>
|
||||
/// <param name="configuration">Forms authentication configuration to use</param>
|
||||
/// <returns>Post request hook delegate</returns>
|
||||
private static Action<NancyContext> GetRedirectToLoginHook(CustomAuthenticationConfiguration configuration)
|
||||
{
|
||||
return context =>
|
||||
{
|
||||
if (context.Response.StatusCode == HttpStatusCode.Unauthorized)
|
||||
{
|
||||
string redirectQuerystringKey = GetRedirectQuerystringKey(configuration);
|
||||
|
||||
context.Response = context.GetRedirect(
|
||||
string.Format("{0}?{1}={2}",
|
||||
configuration.RedirectUrl,
|
||||
redirectQuerystringKey,
|
||||
context.ToFullPath("~" + context.Request.Path + HttpUtility.UrlEncode(context.Request.Url.Query))));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the authenticated user GUID from the incoming request cookie if it exists
|
||||
/// and is valid.
|
||||
/// </summary>
|
||||
/// <param name="context">Current context</param>
|
||||
/// <param name="configuration">Current configuration</param>
|
||||
/// <returns>Returns user guid, or Guid.Empty if not present or invalid</returns>
|
||||
private static Guid GetAuthenticatedUserFromCookie(NancyContext context, CustomAuthenticationConfiguration configuration)
|
||||
{
|
||||
if (!context.Request.Cookies.ContainsKey(formsAuthenticationCookieName))
|
||||
{
|
||||
return Guid.Empty;
|
||||
}
|
||||
|
||||
var cookieValueEncrypted = context.Request.Cookies[formsAuthenticationCookieName];
|
||||
|
||||
if (string.IsNullOrEmpty(cookieValueEncrypted))
|
||||
{
|
||||
return Guid.Empty;
|
||||
}
|
||||
|
||||
var cookieValue = DecryptAndValidateAuthenticationCookie(cookieValueEncrypted, configuration);
|
||||
|
||||
Guid returnGuid;
|
||||
if (string.IsNullOrEmpty(cookieValue) || !Guid.TryParse(cookieValue, out returnGuid))
|
||||
{
|
||||
return Guid.Empty;
|
||||
}
|
||||
|
||||
return returnGuid;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build the forms authentication cookie
|
||||
/// </summary>
|
||||
/// <param name="userIdentifier">Authenticated user identifier</param>
|
||||
/// <param name="cookieExpiry">Optional expiry date for the cookie (for 'Remember me')</param>
|
||||
/// <param name="configuration">Current configuration</param>
|
||||
/// <returns>Nancy cookie instance</returns>
|
||||
private static INancyCookie BuildCookie(Guid userIdentifier, DateTime? cookieExpiry, CustomAuthenticationConfiguration configuration)
|
||||
{
|
||||
var cookieContents = EncryptAndSignCookie(userIdentifier.ToString(), configuration);
|
||||
|
||||
var cookie = new NancyCookie(formsAuthenticationCookieName, cookieContents, true, configuration.RequiresSSL, cookieExpiry);
|
||||
|
||||
if (!string.IsNullOrEmpty(configuration.Domain))
|
||||
{
|
||||
cookie.Domain = configuration.Domain;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(configuration.Path))
|
||||
{
|
||||
cookie.Path = configuration.Path;
|
||||
}
|
||||
|
||||
return cookie;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a cookie for logging a user out
|
||||
/// </summary>
|
||||
/// <param name="configuration">Current configuration</param>
|
||||
/// <returns>Nancy cookie instance</returns>
|
||||
private static INancyCookie BuildLogoutCookie(CustomAuthenticationConfiguration configuration)
|
||||
{
|
||||
var cookie = new NancyCookie(formsAuthenticationCookieName, String.Empty, true, configuration.RequiresSSL, DateTime.Now.AddDays(-1));
|
||||
|
||||
if (!string.IsNullOrEmpty(configuration.Domain))
|
||||
{
|
||||
cookie.Domain = configuration.Domain;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(configuration.Path))
|
||||
{
|
||||
cookie.Path = configuration.Path;
|
||||
}
|
||||
|
||||
return cookie;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encrypt and sign the cookie contents
|
||||
/// </summary>
|
||||
/// <param name="cookieValue">Plain text cookie value</param>
|
||||
/// <param name="configuration">Current configuration</param>
|
||||
/// <returns>Encrypted and signed string</returns>
|
||||
private static string EncryptAndSignCookie(string cookieValue, CustomAuthenticationConfiguration configuration)
|
||||
{
|
||||
var encryptedCookie = configuration.CryptographyConfiguration.EncryptionProvider.Encrypt(cookieValue);
|
||||
var hmacBytes = GenerateHmac(encryptedCookie, configuration);
|
||||
var hmacString = Convert.ToBase64String(hmacBytes);
|
||||
|
||||
return String.Format("{1}{0}", encryptedCookie, hmacString);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a hmac for the encrypted cookie string
|
||||
/// </summary>
|
||||
/// <param name="encryptedCookie">Encrypted cookie string</param>
|
||||
/// <param name="configuration">Current configuration</param>
|
||||
/// <returns>Hmac byte array</returns>
|
||||
private static byte[] GenerateHmac(string encryptedCookie, CustomAuthenticationConfiguration configuration)
|
||||
{
|
||||
return configuration.CryptographyConfiguration.HmacProvider.GenerateHmac(encryptedCookie);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrypt and validate an encrypted and signed cookie value
|
||||
/// </summary>
|
||||
/// <param name="cookieValue">Encrypted and signed cookie value</param>
|
||||
/// <param name="configuration">Current configuration</param>
|
||||
/// <returns>Decrypted value, or empty on error or if failed validation</returns>
|
||||
public static string DecryptAndValidateAuthenticationCookie(string cookieValue, CustomAuthenticationConfiguration configuration)
|
||||
{
|
||||
var hmacStringLength = Base64Helpers.GetBase64Length(configuration.CryptographyConfiguration.HmacProvider.HmacLength);
|
||||
|
||||
var encryptedCookie = cookieValue.Substring(hmacStringLength);
|
||||
var hmacString = cookieValue.Substring(0, hmacStringLength);
|
||||
|
||||
var encryptionProvider = configuration.CryptographyConfiguration.EncryptionProvider;
|
||||
|
||||
// Check the hmacs, but don't early exit if they don't match
|
||||
var hmacBytes = Convert.FromBase64String(hmacString);
|
||||
var newHmac = GenerateHmac(encryptedCookie, configuration);
|
||||
var hmacValid = HmacComparer.Compare(newHmac, hmacBytes, configuration.CryptographyConfiguration.HmacProvider.HmacLength);
|
||||
|
||||
var decrypted = encryptionProvider.Decrypt(encryptedCookie);
|
||||
|
||||
// Only return the decrypted result if the hmac was ok
|
||||
return hmacValid ? decrypted : string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the redirect query string key from <see cref="FormsAuthenticationConfiguration"/>
|
||||
/// </summary>
|
||||
/// <param name="configuration">The forms authentication configuration.</param>
|
||||
/// <returns>Redirect Querystring key</returns>
|
||||
private static string GetRedirectQuerystringKey(CustomAuthenticationConfiguration configuration)
|
||||
{
|
||||
string redirectQuerystringKey = null;
|
||||
|
||||
if (configuration != null)
|
||||
{
|
||||
redirectQuerystringKey = configuration.RedirectQuerystringKey;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(redirectQuerystringKey))
|
||||
{
|
||||
redirectQuerystringKey = CustomAuthenticationConfiguration.DefaultRedirectQuerystringKey;
|
||||
}
|
||||
|
||||
return redirectQuerystringKey;
|
||||
}
|
||||
}
|
||||
}
|
108
PlexRequests.UI/Authentication/CustomModuleExtensions.cs
Normal file
108
PlexRequests.UI/Authentication/CustomModuleExtensions.cs
Normal file
|
@ -0,0 +1,108 @@
|
|||
#region Copyright
|
||||
// /************************************************************************
|
||||
// Copyright (c) 2016 Jamie Rees
|
||||
// File: CustomModuleExtensions.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.Authentication.Forms;
|
||||
using Nancy.Extensions;
|
||||
|
||||
namespace PlexRequests.UI.Authentication
|
||||
{
|
||||
public static class CustomModuleExtensions
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Logs the user in and returns either an empty 200 response for ajax requests, or a redirect response for non-ajax. <seealso cref="M:Nancy.Extensions.RequestExtensions.IsAjaxRequest(Nancy.Request)" />
|
||||
/// </summary>
|
||||
/// <param name="module">Nancy module</param>
|
||||
/// <param name="userIdentifier">User identifier guid</param>
|
||||
/// <param name="cookieExpiry">Optional expiry date for the cookie (for 'Remember me')</param>
|
||||
/// <param name="fallbackRedirectUrl">Url to redirect to if none in the querystring</param>
|
||||
/// <returns>Nancy response with redirect if request was not ajax, otherwise with OK.</returns>
|
||||
public static Response Login(this INancyModule module, Guid userIdentifier, DateTime? cookieExpiry = null, string fallbackRedirectUrl = "/")
|
||||
{
|
||||
if (!module.Context.Request.IsAjaxRequest())
|
||||
return module.LoginAndRedirect(userIdentifier, cookieExpiry, fallbackRedirectUrl);
|
||||
return module.LoginWithoutRedirect(userIdentifier, cookieExpiry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs the user in with the given user guid and redirects.
|
||||
/// </summary>
|
||||
/// <param name="module">Nancy module</param>
|
||||
/// <param name="userIdentifier">User identifier guid</param>
|
||||
/// <param name="cookieExpiry">Optional expiry date for the cookie (for 'Remember me')</param>
|
||||
/// <param name="fallbackRedirectUrl">Url to redirect to if none in the querystring</param>
|
||||
/// <returns>Nancy response instance</returns>
|
||||
public static Response LoginAndRedirect(this INancyModule module, Guid userIdentifier, DateTime? cookieExpiry = null, string fallbackRedirectUrl = "/")
|
||||
{
|
||||
return CustomAuthenticationProvider.UserLoggedInRedirectResponse(module.Context, userIdentifier, cookieExpiry, fallbackRedirectUrl);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs the user in with the given user guid and returns ok response.
|
||||
/// </summary>
|
||||
/// <param name="module">Nancy module</param>
|
||||
/// <param name="userIdentifier">User identifier guid</param>
|
||||
/// <param name="cookieExpiry">Optional expiry date for the cookie (for 'Remember me')</param>
|
||||
/// <returns>Nancy response instance</returns>
|
||||
public static Response LoginWithoutRedirect(this INancyModule module, Guid userIdentifier, DateTime? cookieExpiry = null)
|
||||
{
|
||||
return CustomAuthenticationProvider.UserLoggedInResponse(userIdentifier, cookieExpiry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs the user out and returns either an empty 200 response for ajax requests, or a redirect response for non-ajax. <seealso cref="M:Nancy.Extensions.RequestExtensions.IsAjaxRequest(Nancy.Request)" />
|
||||
/// </summary>
|
||||
/// <param name="module">Nancy module</param>
|
||||
/// <param name="redirectUrl">URL to redirect to</param>
|
||||
/// <returns>Nancy response with redirect if request was not ajax, otherwise with OK.</returns>
|
||||
public static Response Logout(this INancyModule module, string redirectUrl)
|
||||
{
|
||||
if (!module.Context.Request.IsAjaxRequest())
|
||||
return CustomAuthenticationProvider.LogOutAndRedirectResponse(module.Context, redirectUrl);
|
||||
return CustomAuthenticationProvider.LogOutResponse();
|
||||
}
|
||||
|
||||
/// <summary>Logs the user out and redirects</summary>
|
||||
/// <param name="module">Nancy module</param>
|
||||
/// <param name="redirectUrl">URL to redirect to</param>
|
||||
/// <returns>Nancy response instance</returns>
|
||||
public static Response LogoutAndRedirect(this INancyModule module, string redirectUrl)
|
||||
{
|
||||
return CustomAuthenticationProvider.LogOutAndRedirectResponse(module.Context, redirectUrl);
|
||||
}
|
||||
|
||||
/// <summary>Logs the user out without a redirect</summary>
|
||||
/// <param name="module">Nancy module</param>
|
||||
/// <returns>Nancy response instance</returns>
|
||||
public static Response LogoutWithoutRedirect(this INancyModule module)
|
||||
{
|
||||
return CustomAuthenticationProvider.LogOutResponse();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -52,6 +52,7 @@ using PlexRequests.UI.Helpers;
|
|||
using Nancy.Json;
|
||||
|
||||
using Ninject;
|
||||
using PlexRequests.UI.Authentication;
|
||||
|
||||
namespace PlexRequests.UI
|
||||
{
|
||||
|
@ -92,13 +93,14 @@ namespace PlexRequests.UI
|
|||
var redirect = string.IsNullOrEmpty(baseUrl) ? "~/login" : $"~/{baseUrl}/login";
|
||||
|
||||
// Enable forms auth
|
||||
var formsAuthConfiguration = new FormsAuthenticationConfiguration
|
||||
var config = new CustomAuthenticationConfiguration
|
||||
{
|
||||
RedirectUrl = redirect,
|
||||
UserMapper = container.Get<IUserMapper>()
|
||||
PlexUserRepository = container.Get<IPlexUserRepository>(),
|
||||
LocalUserRepository = container.Get<IUserRepository>()
|
||||
};
|
||||
|
||||
FormsAuthentication.Enable(pipelines, formsAuthConfiguration);
|
||||
CustomAuthenticationProvider.Enable(pipelines, config);
|
||||
|
||||
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls;
|
||||
ServicePointManager.ServerCertificateValidationCallback +=
|
||||
|
|
|
@ -44,7 +44,8 @@ namespace PlexRequests.UI.Helpers
|
|||
{
|
||||
var userRepo = ServiceLocator.Instance.Resolve<IUserRepository>();
|
||||
var linker = ServiceLocator.Instance.Resolve<IResourceLinker>();
|
||||
return _security ?? (_security = new SecurityExtensions(userRepo, null, linker));
|
||||
var plex = ServiceLocator.Instance.Resolve<IPlexUserRepository>();
|
||||
return _security ?? (_security = new SecurityExtensions(userRepo, null, linker, plex));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
22
PlexRequests.UI/Helpers/ISecurityExtensions.cs
Normal file
22
PlexRequests.UI/Helpers/ISecurityExtensions.cs
Normal file
|
@ -0,0 +1,22 @@
|
|||
using System;
|
||||
using Nancy;
|
||||
using Nancy.Security;
|
||||
using PlexRequests.Helpers.Permissions;
|
||||
|
||||
namespace PlexRequests.UI.Helpers
|
||||
{
|
||||
public interface ISecurityExtensions
|
||||
{
|
||||
Response AdminLoginRedirect(Permissions perm, NancyContext context);
|
||||
bool DoesNotHavePermissions(Permissions perm, IUserIdentity currentUser);
|
||||
bool DoesNotHavePermissions(int perm, IUserIdentity currentUser);
|
||||
Func<NancyContext, Response> ForbiddenIfNot(Func<NancyContext, bool> test);
|
||||
bool HasAnyPermissions(IUserIdentity user, params Permissions[] perm);
|
||||
bool HasPermissions(IUserIdentity user, Permissions perm);
|
||||
Response HasPermissionsRedirect(Permissions perm, NancyContext context, string routeName, HttpStatusCode code);
|
||||
Func<NancyContext, Response> HttpStatusCodeIfNot(HttpStatusCode statusCode, Func<NancyContext, bool> test);
|
||||
bool IsLoggedIn(NancyContext context);
|
||||
bool IsNormalUser(NancyContext context);
|
||||
bool IsPlexUser(NancyContext context);
|
||||
}
|
||||
}
|
|
@ -26,32 +26,30 @@
|
|||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Nancy;
|
||||
using Nancy.Extensions;
|
||||
using Nancy.Linker;
|
||||
using Nancy.Responses;
|
||||
using Nancy.Security;
|
||||
using Ninject;
|
||||
using PlexRequests.Helpers.Permissions;
|
||||
using PlexRequests.Store.Repository;
|
||||
using PlexRequests.UI.Models;
|
||||
|
||||
namespace PlexRequests.UI.Helpers
|
||||
{
|
||||
public class SecurityExtensions
|
||||
public class SecurityExtensions : ISecurityExtensions
|
||||
{
|
||||
public SecurityExtensions(IUserRepository userRepository, NancyModule context, IResourceLinker linker)
|
||||
public SecurityExtensions(IUserRepository userRepository, NancyModule context, IResourceLinker linker, IPlexUserRepository plexUsers)
|
||||
{
|
||||
UserRepository = userRepository;
|
||||
Module = context;
|
||||
Linker = linker;
|
||||
PlexUsers = plexUsers;
|
||||
}
|
||||
|
||||
private IUserRepository UserRepository { get; }
|
||||
private NancyModule Module { get; }
|
||||
private IResourceLinker Linker { get; }
|
||||
private IPlexUserRepository PlexUsers { get; }
|
||||
|
||||
public bool IsLoggedIn(NancyContext context)
|
||||
{
|
||||
|
@ -99,11 +97,7 @@ namespace PlexRequests.UI.Helpers
|
|||
{
|
||||
return ForbiddenIfNot(ctx =>
|
||||
{
|
||||
var dbUser = UserRepository.GetUserByUsername(ctx.CurrentUser.UserName);
|
||||
|
||||
if (dbUser == null) return false;
|
||||
|
||||
var permissions = (Permissions)dbUser.Permissions;
|
||||
var permissions = GetPermissions(ctx.CurrentUser);
|
||||
var result = permissions.HasFlag((Permissions)perm);
|
||||
return !result;
|
||||
});
|
||||
|
@ -116,37 +110,21 @@ namespace PlexRequests.UI.Helpers
|
|||
|
||||
public bool DoesNotHavePermissions(Permissions perm, IUserIdentity currentUser)
|
||||
{
|
||||
var dbUser = UserRepository.GetUserByUsername(currentUser.UserName);
|
||||
|
||||
if (dbUser == null) return false;
|
||||
|
||||
var permissions = (Permissions)dbUser.Permissions;
|
||||
var permissions = GetPermissions(currentUser);
|
||||
var result = permissions.HasFlag(perm);
|
||||
return !result;
|
||||
}
|
||||
|
||||
public bool HasPermissions(IUserIdentity user, Permissions perm)
|
||||
{
|
||||
if (user == null) return false;
|
||||
|
||||
var dbUser = UserRepository.GetUserByUsername(user.UserName);
|
||||
|
||||
if (dbUser == null) return false;
|
||||
|
||||
var permissions = (Permissions)dbUser.Permissions;
|
||||
var result = permissions.HasFlag(perm);
|
||||
return result;
|
||||
var permissions = GetPermissions(user);
|
||||
return permissions.HasFlag(perm);
|
||||
}
|
||||
|
||||
public bool HasAnyPermissions(IUserIdentity user, params Permissions[] perm)
|
||||
{
|
||||
if (user == null) return false;
|
||||
var permissions = GetPermissions(user);
|
||||
|
||||
var dbUser = UserRepository.GetUserByUsername(user.UserName);
|
||||
|
||||
if (dbUser == null) return false;
|
||||
|
||||
var permissions = (Permissions)dbUser.Permissions;
|
||||
foreach (var p in perm)
|
||||
{
|
||||
var result = permissions.HasFlag(p);
|
||||
|
@ -165,13 +143,7 @@ namespace PlexRequests.UI.Helpers
|
|||
|
||||
var response = ForbiddenIfNot(ctx =>
|
||||
{
|
||||
if (ctx.CurrentUser == null) return false;
|
||||
|
||||
var dbUser = UserRepository.GetUserByUsername(ctx.CurrentUser.UserName);
|
||||
|
||||
if (dbUser == null) return false;
|
||||
|
||||
var permissions = (Permissions) dbUser.Permissions;
|
||||
var permissions = GetPermissions(ctx.CurrentUser);
|
||||
var result = permissions.HasFlag(perm);
|
||||
return result;
|
||||
});
|
||||
|
@ -228,5 +200,26 @@ namespace PlexRequests.UI.Helpers
|
|||
};
|
||||
}
|
||||
|
||||
private Permissions GetPermissions(IUserIdentity user)
|
||||
{
|
||||
if (user == null) return 0;
|
||||
|
||||
var dbUser = UserRepository.GetUserByUsername(user.UserName);
|
||||
|
||||
if (dbUser != null)
|
||||
{
|
||||
var permissions = (Permissions)dbUser.Permissions;
|
||||
return permissions;
|
||||
}
|
||||
|
||||
var plexUser = PlexUsers.GetUserByUsername(user.UserName);
|
||||
if (plexUser != null)
|
||||
{
|
||||
var permissions = (Permissions)plexUser.Permissions;
|
||||
return permissions;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -63,7 +63,8 @@ namespace PlexRequests.UI.Jobs
|
|||
{
|
||||
JobBuilder.Create<PlexAvailabilityChecker>().WithIdentity("PlexAvailabilityChecker", "Plex").Build(),
|
||||
JobBuilder.Create<PlexContentCacher>().WithIdentity("PlexContentCacher", "Plex").Build(),
|
||||
JobBuilder.Create<PlexEpisodeCacher>().WithIdentity("PlexEpisodeCacher", "Cache").Build(),
|
||||
JobBuilder.Create<PlexEpisodeCacher>().WithIdentity("PlexEpisodeCacher", "Plex").Build(),
|
||||
JobBuilder.Create<PlexUserChecker>().WithIdentity("PlexUserChecker", "Plex").Build(),
|
||||
JobBuilder.Create<SickRageCacher>().WithIdentity("SickRageCacher", "Cache").Build(),
|
||||
JobBuilder.Create<SonarrCacher>().WithIdentity("SonarrCacher", "Cache").Build(),
|
||||
JobBuilder.Create<CouchPotatoCacher>().WithIdentity("CouchPotatoCacher", "Cache").Build(),
|
||||
|
@ -159,6 +160,10 @@ namespace PlexRequests.UI.Jobs
|
|||
{
|
||||
s.PlexContentCacher = 60;
|
||||
}
|
||||
if (s.PlexUserChecker == 0)
|
||||
{
|
||||
s.PlexUserChecker = 24;
|
||||
}
|
||||
|
||||
var triggers = new List<ITrigger>();
|
||||
|
||||
|
@ -175,6 +180,13 @@ namespace PlexRequests.UI.Jobs
|
|||
.WithSimpleSchedule(x => x.WithIntervalInMinutes(s.PlexContentCacher).RepeatForever())
|
||||
.Build();
|
||||
|
||||
var plexUserChecker =
|
||||
TriggerBuilder.Create()
|
||||
.WithIdentity("PlexUserChecker", "Plex")
|
||||
.StartNow()
|
||||
.WithSimpleSchedule(x => x.WithIntervalInMinutes(s.PlexUserChecker).RepeatForever())
|
||||
.Build();
|
||||
|
||||
var srCacher =
|
||||
TriggerBuilder.Create()
|
||||
.WithIdentity("SickRageCacher", "Cache")
|
||||
|
@ -253,6 +265,7 @@ namespace PlexRequests.UI.Jobs
|
|||
triggers.Add(plexEpCacher);
|
||||
triggers.Add(fault);
|
||||
triggers.Add(plexCacher);
|
||||
triggers.Add(plexUserChecker);
|
||||
|
||||
return triggers;
|
||||
}
|
||||
|
|
|
@ -124,7 +124,8 @@ namespace PlexRequests.UI.Modules
|
|||
ICacheProvider cache, ISettingsService<SlackNotificationSettings> slackSettings,
|
||||
ISlackApi slackApi, ISettingsService<LandingPageSettings> lp,
|
||||
ISettingsService<ScheduledJobsSettings> scheduler, IJobRecord rec, IAnalytics analytics,
|
||||
ISettingsService<NotificationSettingsV2> notifyService, IRecentlyAdded recentlyAdded) : base("admin", prService)
|
||||
ISettingsService<NotificationSettingsV2> notifyService, IRecentlyAdded recentlyAdded
|
||||
, ISecurityExtensions security) : base("admin", prService, security)
|
||||
{
|
||||
PrService = prService;
|
||||
CpService = cpService;
|
||||
|
|
|
@ -35,13 +35,14 @@ using PlexRequests.Helpers.Permissions;
|
|||
using PlexRequests.Store;
|
||||
using PlexRequests.Store.Models;
|
||||
using PlexRequests.Store.Repository;
|
||||
using PlexRequests.UI.Helpers;
|
||||
using PlexRequests.UI.Models;
|
||||
|
||||
namespace PlexRequests.UI.Modules.Admin
|
||||
{
|
||||
public class FaultQueueModule : BaseModule
|
||||
{
|
||||
public FaultQueueModule(ISettingsService<PlexRequestSettings> settingsService, ICacheProvider cache, IRepository<RequestQueue> requestQueue) : base("admin", settingsService)
|
||||
public FaultQueueModule(ISettingsService<PlexRequestSettings> settingsService, ICacheProvider cache, IRepository<RequestQueue> requestQueue, ISecurityExtensions security) : base("admin", settingsService, security)
|
||||
{
|
||||
Cache = cache;
|
||||
RequestQueue = requestQueue;
|
||||
|
|
|
@ -38,13 +38,14 @@ using PlexRequests.Core.SettingModels;
|
|||
using PlexRequests.Core.StatusChecker;
|
||||
using PlexRequests.Helpers;
|
||||
using PlexRequests.Helpers.Permissions;
|
||||
using PlexRequests.UI.Helpers;
|
||||
using PlexRequests.UI.Models;
|
||||
|
||||
namespace PlexRequests.UI.Modules.Admin
|
||||
{
|
||||
public class SystemStatusModule : BaseModule
|
||||
{
|
||||
public SystemStatusModule(ISettingsService<PlexRequestSettings> settingsService, ICacheProvider cache, ISettingsService<SystemSettings> ss) : base("admin", settingsService)
|
||||
public SystemStatusModule(ISettingsService<PlexRequestSettings> settingsService, ICacheProvider cache, ISettingsService<SystemSettings> ss, ISecurityExtensions security) : base("admin", settingsService, security)
|
||||
{
|
||||
Cache = cache;
|
||||
SystemSettings = ss;
|
||||
|
|
|
@ -29,12 +29,13 @@ using Nancy.Responses.Negotiation;
|
|||
|
||||
using PlexRequests.Core;
|
||||
using PlexRequests.Core.SettingModels;
|
||||
using PlexRequests.UI.Helpers;
|
||||
|
||||
namespace PlexRequests.UI.Modules
|
||||
{
|
||||
public class ApiDocsModule : BaseModule
|
||||
{
|
||||
public ApiDocsModule(ISettingsService<PlexRequestSettings> pr) : base("apidocs", pr)
|
||||
public ApiDocsModule(ISettingsService<PlexRequestSettings> pr, ISecurityExtensions security) : base("apidocs", pr, security)
|
||||
{
|
||||
Get["/"] = x => Documentation();
|
||||
}
|
||||
|
|
|
@ -37,13 +37,14 @@ using Newtonsoft.Json;
|
|||
using PlexRequests.Core;
|
||||
using PlexRequests.Core.SettingModels;
|
||||
using PlexRequests.Store;
|
||||
using PlexRequests.UI.Helpers;
|
||||
using PlexRequests.UI.Models;
|
||||
|
||||
namespace PlexRequests.UI.Modules
|
||||
{
|
||||
public class ApiRequestModule : BaseApiModule
|
||||
{
|
||||
public ApiRequestModule(IRequestService service, ISettingsService<PlexRequestSettings> pr) : base("api", pr)
|
||||
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);
|
||||
|
|
|
@ -37,6 +37,7 @@ using Newtonsoft.Json;
|
|||
using PlexRequests.Core;
|
||||
using PlexRequests.Core.SettingModels;
|
||||
using PlexRequests.Helpers;
|
||||
using PlexRequests.UI.Helpers;
|
||||
|
||||
namespace PlexRequests.UI.Modules
|
||||
{
|
||||
|
@ -44,7 +45,7 @@ namespace PlexRequests.UI.Modules
|
|||
{
|
||||
public ApiSettingsModule(ISettingsService<PlexRequestSettings> pr, ISettingsService<AuthenticationSettings> auth,
|
||||
ISettingsService<PlexSettings> plexSettings, ISettingsService<CouchPotatoSettings> cp,
|
||||
ISettingsService<SonarrSettings> sonarr, ISettingsService<SickRageSettings> sr, ISettingsService<HeadphonesSettings> hp) : base("api", pr)
|
||||
ISettingsService<SonarrSettings> sonarr, ISettingsService<SickRageSettings> sr, ISettingsService<HeadphonesSettings> hp, ISecurityExtensions security) : base("api", pr, security)
|
||||
{
|
||||
Get["GetVersion", "/version"] = x => GetVersion();
|
||||
|
||||
|
|
|
@ -33,13 +33,14 @@ using Nancy.ModelBinding;
|
|||
using PlexRequests.Core;
|
||||
using PlexRequests.Core.SettingModels;
|
||||
using PlexRequests.Store;
|
||||
using PlexRequests.UI.Helpers;
|
||||
using PlexRequests.UI.Models;
|
||||
|
||||
namespace PlexRequests.UI.Modules
|
||||
{
|
||||
public class ApiUserModule : BaseApiModule
|
||||
{
|
||||
public ApiUserModule(ISettingsService<PlexRequestSettings> pr, ICustomUserMapper m) : base("api", pr)
|
||||
public ApiUserModule(ISettingsService<PlexRequestSettings> pr, ICustomUserMapper m, ISecurityExtensions security) : base("api", pr, security)
|
||||
{
|
||||
|
||||
Put["PutCredentials", "/credentials/{username}"] = x => ChangePassword(x);
|
||||
|
|
|
@ -47,7 +47,7 @@ namespace PlexRequests.UI.Modules
|
|||
{
|
||||
|
||||
public ApplicationTesterModule(ICouchPotatoApi cpApi, ISonarrApi sonarrApi, IPlexApi plexApi,
|
||||
ISickRageApi srApi, IHeadphonesApi hpApi, ISettingsService<PlexRequestSettings> pr) : base("test", pr)
|
||||
ISickRageApi srApi, IHeadphonesApi hpApi, ISettingsService<PlexRequestSettings> pr, ISecurityExtensions security) : base("test", pr, security)
|
||||
{
|
||||
this.RequiresAuthentication();
|
||||
|
||||
|
|
|
@ -52,7 +52,8 @@ namespace PlexRequests.UI.Modules
|
|||
|
||||
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) : base("approval", pr)
|
||||
ISettingsService<HeadphonesSettings> hpSettings, IHeadphonesApi hpApi, ISettingsService<PlexRequestSettings> pr, ITransientFaultQueue faultQueue
|
||||
, ISecurityExtensions security) : base("approval", pr, security)
|
||||
{
|
||||
this.RequiresAnyClaim(UserClaims.Admin, UserClaims.PowerUser);
|
||||
|
||||
|
@ -68,6 +69,7 @@ namespace PlexRequests.UI.Modules
|
|||
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);
|
||||
|
|
|
@ -33,18 +33,19 @@ using Nancy.Validation;
|
|||
using PlexRequests.Core;
|
||||
using PlexRequests.Core.SettingModels;
|
||||
using PlexRequests.Store;
|
||||
using PlexRequests.UI.Helpers;
|
||||
|
||||
namespace PlexRequests.UI.Modules
|
||||
{
|
||||
public abstract class BaseApiModule : BaseModule
|
||||
{
|
||||
protected BaseApiModule(ISettingsService<PlexRequestSettings> s) : base(s)
|
||||
protected BaseApiModule(ISettingsService<PlexRequestSettings> s, ISecurityExtensions security) : base(s,security)
|
||||
{
|
||||
Settings = s;
|
||||
Before += (ctx) => CheckAuth();
|
||||
}
|
||||
|
||||
protected BaseApiModule(string modulePath, ISettingsService<PlexRequestSettings> s) : base(modulePath, s)
|
||||
protected BaseApiModule(string modulePath, ISettingsService<PlexRequestSettings> s, ISecurityExtensions security) : base(modulePath, s, security)
|
||||
{
|
||||
Settings = s;
|
||||
Before += (ctx) => CheckAuth();
|
||||
|
|
|
@ -31,18 +31,19 @@ using Nancy.Extensions;
|
|||
using PlexRequests.UI.Models;
|
||||
using PlexRequests.Core;
|
||||
using PlexRequests.Core.SettingModels;
|
||||
using PlexRequests.UI.Helpers;
|
||||
|
||||
namespace PlexRequests.UI.Modules
|
||||
{
|
||||
public abstract class BaseAuthModule : BaseModule
|
||||
{
|
||||
protected BaseAuthModule(ISettingsService<PlexRequestSettings> pr) : base(pr)
|
||||
protected BaseAuthModule(ISettingsService<PlexRequestSettings> pr, ISecurityExtensions security) : base(pr,security)
|
||||
{
|
||||
PlexRequestSettings = pr;
|
||||
Before += (ctx) => CheckAuth();
|
||||
}
|
||||
|
||||
protected BaseAuthModule(string modulePath, ISettingsService<PlexRequestSettings> pr) : base(modulePath, pr)
|
||||
protected BaseAuthModule(string modulePath, ISettingsService<PlexRequestSettings> pr, ISecurityExtensions security) : base(modulePath, pr, security)
|
||||
{
|
||||
PlexRequestSettings = pr;
|
||||
Before += (ctx) => CheckAuth();
|
||||
|
|
|
@ -49,7 +49,7 @@ namespace PlexRequests.UI.Modules
|
|||
protected string BaseUrl { get; set; }
|
||||
|
||||
|
||||
protected BaseModule(ISettingsService<PlexRequestSettings> settingsService)
|
||||
protected BaseModule(ISettingsService<PlexRequestSettings> settingsService, ISecurityExtensions security)
|
||||
{
|
||||
|
||||
var settings = settingsService.GetSettings();
|
||||
|
@ -59,11 +59,12 @@ namespace PlexRequests.UI.Modules
|
|||
var modulePath = string.IsNullOrEmpty(baseUrl) ? string.Empty : baseUrl;
|
||||
|
||||
ModulePath = modulePath;
|
||||
Security = security;
|
||||
|
||||
Before += (ctx) => SetCookie();
|
||||
}
|
||||
|
||||
protected BaseModule(string modulePath, ISettingsService<PlexRequestSettings> settingsService)
|
||||
protected BaseModule(string modulePath, ISettingsService<PlexRequestSettings> settingsService, ISecurityExtensions security)
|
||||
{
|
||||
|
||||
var settings = settingsService.GetSettings();
|
||||
|
@ -73,6 +74,7 @@ namespace PlexRequests.UI.Modules
|
|||
var settingModulePath = string.IsNullOrEmpty(baseUrl) ? modulePath : $"{baseUrl}/{modulePath}";
|
||||
|
||||
ModulePath = settingModulePath;
|
||||
Security = security;
|
||||
|
||||
Before += (ctx) =>
|
||||
{
|
||||
|
@ -100,8 +102,9 @@ namespace PlexRequests.UI.Modules
|
|||
return _dateTimeOffset;
|
||||
}
|
||||
}
|
||||
private string _username;
|
||||
|
||||
|
||||
private string _username;
|
||||
protected string Username
|
||||
{
|
||||
get
|
||||
|
@ -110,7 +113,7 @@ namespace PlexRequests.UI.Modules
|
|||
{
|
||||
try
|
||||
{
|
||||
_username = Session[SessionKeys.UsernameKey].ToString();
|
||||
_username = User == null ? Session[SessionKeys.UsernameKey].ToString() : User.UserName;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
@ -132,32 +135,13 @@ namespace PlexRequests.UI.Modules
|
|||
return false;
|
||||
}
|
||||
|
||||
var userRepo = ServiceLocator.Instance.Resolve<IUserRepository>();
|
||||
|
||||
var user = userRepo.GetUserByUsername(Context?.CurrentUser?.UserName);
|
||||
|
||||
if (user == null) return false;
|
||||
|
||||
var permissions = (Permissions) user.Permissions;
|
||||
return permissions.HasFlag(Permissions.Administrator);
|
||||
return Security.HasPermissions(Context?.CurrentUser, Permissions.Administrator);
|
||||
}
|
||||
}
|
||||
|
||||
protected IUserIdentity User => Context?.CurrentUser;
|
||||
|
||||
protected SecurityExtensions Security
|
||||
{
|
||||
|
||||
get
|
||||
{
|
||||
var userRepo = ServiceLocator.Instance.Resolve<IUserRepository>();
|
||||
var linker = ServiceLocator.Instance.Resolve<IResourceLinker>();
|
||||
return _security ?? (_security = new SecurityExtensions(userRepo, this, linker));
|
||||
}
|
||||
}
|
||||
|
||||
private SecurityExtensions _security;
|
||||
|
||||
protected ISecurityExtensions Security { get; set; }
|
||||
|
||||
protected bool LoggedIn => Context?.CurrentUser != null;
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ namespace PlexRequests.UI.Modules
|
|||
{
|
||||
public class CultureModule : BaseModule
|
||||
{
|
||||
public CultureModule(ISettingsService<PlexRequestSettings> pr, IAnalytics a) : base("culture",pr)
|
||||
public CultureModule(ISettingsService<PlexRequestSettings> pr, IAnalytics a, ISecurityExtensions security) : base("culture",pr, security)
|
||||
{
|
||||
Analytics = a;
|
||||
|
||||
|
|
|
@ -7,12 +7,13 @@ using NLog;
|
|||
using PlexRequests.Core;
|
||||
using PlexRequests.Core.SettingModels;
|
||||
using PlexRequests.Helpers;
|
||||
using PlexRequests.UI.Helpers;
|
||||
|
||||
namespace PlexRequests.UI.Modules
|
||||
{
|
||||
public class DonationLinkModule : BaseAuthModule
|
||||
{
|
||||
public DonationLinkModule(ICacheProvider provider, ISettingsService<PlexRequestSettings> pr) : base("customDonation", pr)
|
||||
public DonationLinkModule(ICacheProvider provider, ISettingsService<PlexRequestSettings> pr, ISecurityExtensions security) : base("customDonation", pr, security)
|
||||
{
|
||||
Cache = provider;
|
||||
|
||||
|
|
|
@ -33,12 +33,13 @@ using Nancy.Responses;
|
|||
|
||||
using PlexRequests.Core;
|
||||
using PlexRequests.Core.SettingModels;
|
||||
using PlexRequests.UI.Helpers;
|
||||
|
||||
namespace PlexRequests.UI.Modules
|
||||
{
|
||||
public class IndexModule : BaseAuthModule
|
||||
{
|
||||
public IndexModule(ISettingsService<PlexRequestSettings> pr, ISettingsService<LandingPageSettings> l, IResourceLinker rl) : base(pr)
|
||||
public IndexModule(ISettingsService<PlexRequestSettings> pr, ISettingsService<LandingPageSettings> l, IResourceLinker rl, ISecurityExtensions security) : base(pr, security)
|
||||
{
|
||||
LandingPage = l;
|
||||
Linker = rl;
|
||||
|
|
|
@ -26,7 +26,7 @@ namespace PlexRequests.UI.Modules
|
|||
{
|
||||
public class IssuesModule : BaseAuthModule
|
||||
{
|
||||
public IssuesModule(ISettingsService<PlexRequestSettings> pr, IIssueService issueService, IRequestService request, INotificationService n) : base("issues", pr)
|
||||
public IssuesModule(ISettingsService<PlexRequestSettings> pr, IIssueService issueService, IRequestService request, INotificationService n, ISecurityExtensions security) : base("issues", pr, security)
|
||||
{
|
||||
IssuesService = issueService;
|
||||
RequestService = request;
|
||||
|
|
|
@ -33,6 +33,7 @@ using Nancy.Linker;
|
|||
using PlexRequests.Api.Interfaces;
|
||||
using PlexRequests.Core;
|
||||
using PlexRequests.Core.SettingModels;
|
||||
using PlexRequests.UI.Helpers;
|
||||
using PlexRequests.UI.Models;
|
||||
|
||||
namespace PlexRequests.UI.Modules
|
||||
|
@ -40,7 +41,7 @@ namespace PlexRequests.UI.Modules
|
|||
public class LandingPageModule : BaseModule
|
||||
{
|
||||
public LandingPageModule(ISettingsService<PlexRequestSettings> settingsService, ISettingsService<LandingPageSettings> landing,
|
||||
ISettingsService<PlexSettings> ps, IPlexApi pApi, IResourceLinker linker) : base("landing", settingsService)
|
||||
ISettingsService<PlexSettings> ps, IPlexApi pApi, IResourceLinker linker, ISecurityExtensions security) : base("landing", settingsService, security)
|
||||
{
|
||||
LandingSettings = landing;
|
||||
PlexSettings = ps;
|
||||
|
|
|
@ -38,13 +38,14 @@ using PlexRequests.Core.StatusChecker;
|
|||
using PlexRequests.Helpers;
|
||||
using PlexRequests.Services.Interfaces;
|
||||
using PlexRequests.Services.Jobs;
|
||||
using PlexRequests.UI.Helpers;
|
||||
using PlexRequests.UI.Models;
|
||||
|
||||
namespace PlexRequests.UI.Modules
|
||||
{
|
||||
public class LayoutModule : BaseAuthModule
|
||||
{
|
||||
public LayoutModule(ICacheProvider provider, ISettingsService<PlexRequestSettings> pr, ISettingsService<SystemSettings> settings, IJobRecord rec) : base("layout", pr)
|
||||
public LayoutModule(ICacheProvider provider, ISettingsService<PlexRequestSettings> pr, ISettingsService<SystemSettings> settings, IJobRecord rec, ISecurityExtensions security) : base("layout", pr, security)
|
||||
{
|
||||
Cache = provider;
|
||||
SystemSettings = settings;
|
||||
|
|
|
@ -31,7 +31,6 @@ using System;
|
|||
using System.Dynamic;
|
||||
using System.Security;
|
||||
using Nancy;
|
||||
using Nancy.Authentication.Forms;
|
||||
using Nancy.Extensions;
|
||||
using Nancy.Linker;
|
||||
using Nancy.Responses.Negotiation;
|
||||
|
@ -43,14 +42,17 @@ using PlexRequests.Helpers;
|
|||
using PlexRequests.Helpers.Permissions;
|
||||
using PlexRequests.Store;
|
||||
using PlexRequests.Store.Repository;
|
||||
using PlexRequests.UI.Authentication;
|
||||
using PlexRequests.UI.Helpers;
|
||||
using PlexRequests.UI.Models;
|
||||
using ModuleExtensions = Nancy.Authentication.Forms.ModuleExtensions;
|
||||
|
||||
namespace PlexRequests.UI.Modules
|
||||
{
|
||||
public class LoginModule : BaseModule
|
||||
{
|
||||
public LoginModule(ISettingsService<PlexRequestSettings> pr, ICustomUserMapper m, IResourceLinker linker, IRepository<UserLogins> userLoginRepo)
|
||||
: base(pr)
|
||||
public LoginModule(ISettingsService<PlexRequestSettings> pr, ICustomUserMapper m, IResourceLinker linker, IRepository<UserLogins> userLoginRepo, ISecurityExtensions security)
|
||||
: base(pr, security)
|
||||
{
|
||||
UserMapper = m;
|
||||
Get["LocalLogin","/login"] = _ =>
|
||||
|
@ -74,7 +76,7 @@ namespace PlexRequests.UI.Modules
|
|||
{
|
||||
Session.Delete(SessionKeys.UsernameKey);
|
||||
}
|
||||
return this.LogoutAndRedirect(!string.IsNullOrEmpty(BaseUrl) ? $"~/{BaseUrl}/" : "~/");
|
||||
return CustomModuleExtensions.LogoutAndRedirect(this, !string.IsNullOrEmpty(BaseUrl) ? $"~/{BaseUrl}/" : "~/");
|
||||
};
|
||||
|
||||
Post["/login"] = x =>
|
||||
|
@ -112,7 +114,7 @@ namespace PlexRequests.UI.Modules
|
|||
UserId = userId.ToString()
|
||||
});
|
||||
|
||||
return this.LoginAndRedirect(userId.Value, expiry, redirect);
|
||||
return CustomModuleExtensions.LoginAndRedirect(this,userId.Value, expiry, redirect);
|
||||
};
|
||||
|
||||
Get["/register"] = x =>
|
||||
|
@ -138,7 +140,7 @@ namespace PlexRequests.UI.Modules
|
|||
}
|
||||
var userId = UserMapper.CreateUser(username, Request.Form.Password, EnumHelper<Permissions>.All(), 0);
|
||||
Session[SessionKeys.UsernameKey] = username;
|
||||
return this.LoginAndRedirect((Guid)userId);
|
||||
return CustomModuleExtensions.LoginAndRedirect(this, (Guid)userId);
|
||||
};
|
||||
|
||||
Get["/changepassword"] = _ => ChangePassword();
|
||||
|
|
|
@ -1,453 +0,0 @@
|
|||
#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.Linq;
|
||||
|
||||
using Nancy;
|
||||
using Nancy.Responses.Negotiation;
|
||||
using Nancy.Security;
|
||||
|
||||
using PlexRequests.Core;
|
||||
using PlexRequests.Core.SettingModels;
|
||||
using PlexRequests.Services.Interfaces;
|
||||
using PlexRequests.Services.Notification;
|
||||
using PlexRequests.Store;
|
||||
using PlexRequests.UI.Models;
|
||||
using PlexRequests.Helpers;
|
||||
using PlexRequests.UI.Helpers;
|
||||
using System.Collections.Generic;
|
||||
using PlexRequests.Api.Interfaces;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using NLog;
|
||||
|
||||
using PlexRequests.Core.Models;
|
||||
using PlexRequests.Helpers.Analytics;
|
||||
|
||||
using Action = PlexRequests.Helpers.Analytics.Action;
|
||||
|
||||
namespace PlexRequests.UI.Modules
|
||||
{
|
||||
public class RequestsBetaModule : BaseAuthModule
|
||||
{
|
||||
public RequestsBetaModule(
|
||||
IRequestService service,
|
||||
ISettingsService<PlexRequestSettings> prSettings,
|
||||
ISettingsService<RequestSettings> requestSettings,
|
||||
ISettingsService<PlexSettings> plex,
|
||||
INotificationService notify,
|
||||
ISettingsService<SonarrSettings> sonarrSettings,
|
||||
ISettingsService<SickRageSettings> sickRageSettings,
|
||||
ISettingsService<CouchPotatoSettings> cpSettings,
|
||||
ICouchPotatoApi cpApi,
|
||||
ISonarrApi sonarrApi,
|
||||
ISickRageApi sickRageApi,
|
||||
ICacheProvider cache,
|
||||
IAnalytics an) : base("requestsbeta", prSettings)
|
||||
{
|
||||
Service = service;
|
||||
PrSettings = prSettings;
|
||||
PlexSettings = plex;
|
||||
NotificationService = notify;
|
||||
SonarrSettings = sonarrSettings;
|
||||
SickRageSettings = sickRageSettings;
|
||||
CpSettings = cpSettings;
|
||||
SonarrApi = sonarrApi;
|
||||
SickRageApi = sickRageApi;
|
||||
CpApi = cpApi;
|
||||
Cache = cache;
|
||||
Analytics = an;
|
||||
|
||||
Get["/"] = x => LoadRequests();
|
||||
Get["/plexrequestsettings", true] = async (x, ct) => await GetPlexRequestSettings();
|
||||
Get["/requestsettings", true] = async (x, ct) => await GetRequestSettings();
|
||||
Get["/movies", true] = async (x, ct) => await GetMovies();
|
||||
Get["/movies/{searchTerm}", true] = async (x, ct) => await GetMovies((string)x.searchTerm);
|
||||
|
||||
|
||||
// Everything below is not being used in the beta page
|
||||
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<RequestSettings> RequestSettings { 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 Negotiator LoadRequests()
|
||||
{
|
||||
return View["Index"];
|
||||
}
|
||||
|
||||
private async Task<Response> GetPlexRequestSettings()
|
||||
{
|
||||
return Response.AsJson(await PrSettings.GetSettingsAsync());
|
||||
}
|
||||
|
||||
private async Task<Response> GetRequestSettings()
|
||||
{
|
||||
return Response.AsJson(await RequestSettings.GetSettingsAsync());
|
||||
}
|
||||
|
||||
private async Task<Response> GetMovies(string searchTerm = null, bool approved = false, bool notApproved = false,
|
||||
bool available = false, bool notAvailable = false, bool released = false, bool notReleased = false)
|
||||
{
|
||||
var dbMovies = await FilterMovies(searchTerm, approved, notApproved, available, notAvailable, released, notReleased);
|
||||
var qualities = await GetQualityProfiles();
|
||||
var model = MapMoviesToView(dbMovies.ToList(), qualities);
|
||||
|
||||
return Response.AsJson(model);
|
||||
}
|
||||
|
||||
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 (settings.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 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 = IsAdmin ? tv.AllUsers.ToArray() : new string[] { },
|
||||
ReleaseYear = tv.ReleaseDate.Year.ToString(),
|
||||
Available = tv.Available,
|
||||
Admin = IsAdmin,
|
||||
IssueId = tv.IssueId,
|
||||
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 (settings.UsersCanViewOnlyOwnRequests && !IsAdmin)
|
||||
{
|
||||
dbAlbum = dbAlbum.Where(x => x.UserHasRequested(Username));
|
||||
}
|
||||
|
||||
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 = IsAdmin ? album.AllUsers.ToArray() : new string[] { },
|
||||
ReleaseYear = album.ReleaseDate.Year.ToString(),
|
||||
Available = album.Available,
|
||||
Admin = IsAdmin,
|
||||
IssueId = album.IssueId,
|
||||
TvSeriesRequestType = album.SeasonsRequested,
|
||||
MusicBrainzId = album.MusicBrainzId,
|
||||
ArtistName = album.ArtistName
|
||||
|
||||
};
|
||||
}).ToList();
|
||||
|
||||
return Response.AsJson(viewModel);
|
||||
}
|
||||
|
||||
private async Task<Response> DeleteRequest(int requestid)
|
||||
{
|
||||
this.RequiresAnyClaim(UserClaims.Admin, UserClaims.PowerUser);
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
this.RequiresAnyClaim(UserClaims.Admin, UserClaims.PowerUser);
|
||||
|
||||
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)
|
||||
{
|
||||
this.RequiresAnyClaim(UserClaims.Admin, UserClaims.PowerUser);
|
||||
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);
|
||||
return Response.AsJson(result
|
||||
? new { Result = true, Available = available, Message = string.Empty }
|
||||
: new { Result = false, Available = false, Message = "Could not update the availability, please try again or check the logs" });
|
||||
}
|
||||
|
||||
private List<RequestViewModel> MapMoviesToView(List<RequestedModel> dbMovies, List<QualityModel> qualities)
|
||||
{
|
||||
return 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 = IsAdmin ? movie.AllUsers.ToArray() : new string[] { },
|
||||
ReleaseYear = movie.ReleaseDate.Year.ToString(),
|
||||
Available = movie.Available,
|
||||
Admin = IsAdmin,
|
||||
IssueId = movie.IssueId,
|
||||
Qualities = qualities.ToArray()
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
private async Task<List<QualityModel>> GetQualityProfiles()
|
||||
{
|
||||
var 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
return qualities;
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<RequestedModel>> FilterMovies(string searchTerm = null, bool approved = false, bool notApproved = false,
|
||||
bool available = false, bool notAvailable = false, bool released = false, bool notReleased = false)
|
||||
{
|
||||
var settings = PrSettings.GetSettings();
|
||||
var allRequests = await Service.GetAllAsync();
|
||||
allRequests = allRequests.Where(x => x.Type == RequestType.Movie);
|
||||
|
||||
var dbMovies = allRequests;
|
||||
|
||||
if (settings.UsersCanViewOnlyOwnRequests && !IsAdmin)
|
||||
{
|
||||
dbMovies = dbMovies.Where(x => x.UserHasRequested(Username));
|
||||
}
|
||||
|
||||
// Filter the movies on the search term
|
||||
if (!string.IsNullOrEmpty(searchTerm))
|
||||
{
|
||||
dbMovies = dbMovies.Where(x => x.Title.Contains(searchTerm));
|
||||
}
|
||||
|
||||
if (approved)
|
||||
{
|
||||
dbMovies = dbMovies.Where(x => x.Approved);
|
||||
}
|
||||
|
||||
if (notApproved)
|
||||
{
|
||||
dbMovies = dbMovies.Where(x => !x.Approved);
|
||||
}
|
||||
|
||||
if (available)
|
||||
{
|
||||
dbMovies = dbMovies.Where(x => x.Available);
|
||||
}
|
||||
|
||||
if (notAvailable)
|
||||
{
|
||||
dbMovies = dbMovies.Where(x => !x.Available);
|
||||
}
|
||||
|
||||
if (released)
|
||||
{
|
||||
dbMovies = dbMovies.Where(x => DateTime.Now > x.ReleaseDate);
|
||||
}
|
||||
|
||||
if (notReleased)
|
||||
{
|
||||
dbMovies = dbMovies.Where(x => DateTime.Now < x.ReleaseDate);
|
||||
}
|
||||
|
||||
return dbMovies;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -48,6 +48,7 @@ using NLog;
|
|||
using PlexRequests.Core.Models;
|
||||
using PlexRequests.Helpers.Analytics;
|
||||
using PlexRequests.Helpers.Permissions;
|
||||
using PlexRequests.UI.Helpers;
|
||||
using Action = PlexRequests.Helpers.Analytics.Action;
|
||||
|
||||
namespace PlexRequests.UI.Modules
|
||||
|
@ -67,7 +68,8 @@ namespace PlexRequests.UI.Modules
|
|||
ISickRageApi sickRageApi,
|
||||
ICacheProvider cache,
|
||||
IAnalytics an,
|
||||
INotificationEngine engine) : base("requests", prSettings)
|
||||
INotificationEngine engine,
|
||||
ISecurityExtensions security) : base("requests", prSettings, security)
|
||||
{
|
||||
Service = service;
|
||||
PrSettings = prSettings;
|
||||
|
|
|
@ -82,8 +82,8 @@ namespace PlexRequests.UI.Modules
|
|||
ICouchPotatoCacher cpCacher, ISonarrCacher sonarrCacher, ISickRageCacher sickRageCacher, IPlexApi plexApi,
|
||||
ISettingsService<PlexSettings> plexService, ISettingsService<AuthenticationSettings> auth,
|
||||
IRepository<UsersToNotify> u, ISettingsService<EmailNotificationSettings> email,
|
||||
IIssueService issue, IAnalytics a, IRepository<RequestLimit> rl, ITransientFaultQueue tfQueue, IRepository<PlexContent> content)
|
||||
: base("search", prSettings)
|
||||
IIssueService issue, IAnalytics a, IRepository<RequestLimit> rl, ITransientFaultQueue tfQueue, IRepository<PlexContent> content, ISecurityExtensions security)
|
||||
: base("search", prSettings, security)
|
||||
{
|
||||
Auth = auth;
|
||||
PlexService = plexService;
|
||||
|
|
|
@ -44,17 +44,19 @@ using PlexRequests.Helpers;
|
|||
using PlexRequests.Helpers.Analytics;
|
||||
using PlexRequests.Store;
|
||||
using PlexRequests.Store.Repository;
|
||||
using PlexRequests.UI.Authentication;
|
||||
using PlexRequests.UI.Helpers;
|
||||
using PlexRequests.UI.Models;
|
||||
using ModuleExtensions = Nancy.Authentication.Forms.ModuleExtensions;
|
||||
|
||||
|
||||
using Action = PlexRequests.Helpers.Analytics.Action;
|
||||
|
||||
namespace PlexRequests.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) : base("userlogin", pr)
|
||||
ISettingsService<LandingPageSettings> lp, IAnalytics a, IResourceLinker linker, IRepository<UserLogins> userLogins, IPlexUserRepository plexUsers, ICustomUserMapper custom, ISecurityExtensions security)
|
||||
: base("userlogin", pr, security)
|
||||
{
|
||||
AuthService = auth;
|
||||
LandingPageSettings = lp;
|
||||
|
@ -63,6 +65,8 @@ namespace PlexRequests.UI.Modules
|
|||
PlexSettings = plexSettings;
|
||||
Linker = linker;
|
||||
UserLogins = userLogins;
|
||||
PlexUserRepository = plexUsers;
|
||||
CustomUserMapper = custom;
|
||||
|
||||
Get["UserLoginIndex", "/", true] = async (x, ct) =>
|
||||
{
|
||||
|
@ -86,12 +90,15 @@ namespace PlexRequests.UI.Modules
|
|||
private IResourceLinker Linker { get; }
|
||||
private IAnalytics Analytics { get; }
|
||||
private IRepository<UserLogins> UserLogins { get; }
|
||||
private IPlexUserRepository PlexUserRepository { get; }
|
||||
private ICustomUserMapper CustomUserMapper { 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);
|
||||
|
@ -122,6 +129,9 @@ namespace PlexRequests.UI.Modules
|
|||
password = Request.Form.password.Value;
|
||||
}
|
||||
|
||||
var localUsers = await CustomUserMapper.GetUsersAsync();
|
||||
var plexLocalUsers = await PlexUserRepository.GetAllAsync();
|
||||
|
||||
|
||||
if (settings.UserAuthentication && settings.UsePassword) // Authenticate with Plex
|
||||
{
|
||||
|
@ -172,6 +182,18 @@ namespace PlexRequests.UI.Modules
|
|||
// 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 (!authenticated)
|
||||
|
@ -188,10 +210,20 @@ namespace PlexRequests.UI.Modules
|
|||
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());
|
||||
}
|
||||
|
||||
|
|
|
@ -17,13 +17,15 @@ using PlexRequests.Helpers.Permissions;
|
|||
using PlexRequests.Store;
|
||||
using PlexRequests.Store.Models;
|
||||
using PlexRequests.Store.Repository;
|
||||
using PlexRequests.UI.Helpers;
|
||||
using PlexRequests.UI.Models;
|
||||
|
||||
namespace PlexRequests.UI.Modules
|
||||
{
|
||||
public class UserManagementModule : BaseModule
|
||||
{
|
||||
public UserManagementModule(ISettingsService<PlexRequestSettings> pr, ICustomUserMapper m, IPlexApi plexApi, ISettingsService<PlexSettings> plex, IRepository<UserLogins> userLogins, IRepository<PlexUsers> plexRepo) : base("usermanagement", pr)
|
||||
public UserManagementModule(ISettingsService<PlexRequestSettings> pr, ICustomUserMapper m, IPlexApi plexApi, ISettingsService<PlexSettings> plex, IRepository<UserLogins> userLogins, IPlexUserRepository plexRepo
|
||||
, ISecurityExtensions security) : base("usermanagement", pr, security)
|
||||
{
|
||||
#if !DEBUG
|
||||
Before += (ctx) => Security.AdminLoginRedirect(Permissions.Administrator, ctx);
|
||||
|
@ -51,7 +53,7 @@ namespace PlexRequests.UI.Modules
|
|||
private IPlexApi PlexApi { get; }
|
||||
private ISettingsService<PlexSettings> PlexSettings { get; }
|
||||
private IRepository<UserLogins> UserLoginsRepo { get; }
|
||||
private IRepository<PlexUsers> PlexUsersRepository { get; }
|
||||
private IPlexUserRepository PlexUsersRepository { get; }
|
||||
private ISettingsService<PlexRequestSettings> PlexRequestSettings { get; }
|
||||
|
||||
private Negotiator Load()
|
||||
|
@ -112,11 +114,21 @@ namespace PlexRequests.UI.Modules
|
|||
{
|
||||
return Response.AsJson(new JsonResponseModel
|
||||
{
|
||||
Result = true,
|
||||
Result = false,
|
||||
Message = "Please enter in a valid Username and Password"
|
||||
});
|
||||
}
|
||||
|
||||
var users = UserMapper.GetUsers();
|
||||
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;
|
||||
|
||||
|
@ -213,7 +225,10 @@ namespace PlexRequests.UI.Modules
|
|||
Permissions = permissionsValue,
|
||||
Features = featuresValue,
|
||||
UserAlias = model.Alias,
|
||||
PlexUserId = plexUser.Id
|
||||
PlexUserId = plexUser.Id,
|
||||
EmailAddress = plexUser.Email,
|
||||
Username = plexUser.Username,
|
||||
LoginId = Guid.NewGuid().ToString()
|
||||
};
|
||||
|
||||
await PlexUsersRepository.InsertAsync(user);
|
||||
|
|
|
@ -41,6 +41,7 @@ using PlexRequests.Core.SettingModels;
|
|||
using PlexRequests.Helpers;
|
||||
using PlexRequests.Helpers.Analytics;
|
||||
using PlexRequests.Helpers.Permissions;
|
||||
using PlexRequests.UI.Authentication;
|
||||
using PlexRequests.UI.Helpers;
|
||||
using PlexRequests.UI.Models;
|
||||
|
||||
|
@ -51,7 +52,7 @@ namespace PlexRequests.UI.Modules
|
|||
public class UserWizardModule : BaseModule
|
||||
{
|
||||
public UserWizardModule(ISettingsService<PlexRequestSettings> pr, ISettingsService<PlexSettings> plex, IPlexApi plexApi,
|
||||
ISettingsService<AuthenticationSettings> auth, ICustomUserMapper m, IAnalytics a) : base("wizard", pr)
|
||||
ISettingsService<AuthenticationSettings> auth, ICustomUserMapper m, IAnalytics a, ISecurityExtensions security) : base("wizard", pr, security)
|
||||
{
|
||||
PlexSettings = plex;
|
||||
PlexApi = plexApi;
|
||||
|
@ -200,7 +201,7 @@ namespace PlexRequests.UI.Modules
|
|||
|
||||
var baseUrl = string.IsNullOrEmpty(settings.BaseUrl) ? string.Empty : $"/{settings.BaseUrl}";
|
||||
|
||||
return this.LoginAndRedirect((Guid)userId, fallbackRedirectUrl: $"{baseUrl}/search");
|
||||
return CustomModuleExtensions.LoginAndRedirect(this,(Guid)userId, fallbackRedirectUrl: $"{baseUrl}/search");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -38,6 +38,7 @@ using PlexRequests.Helpers;
|
|||
using PlexRequests.Services.Interfaces;
|
||||
using PlexRequests.Services.Notification;
|
||||
using PlexRequests.Store;
|
||||
using PlexRequests.UI.Helpers;
|
||||
|
||||
namespace PlexRequests.UI.NinjectModules
|
||||
{
|
||||
|
@ -59,6 +60,8 @@ namespace PlexRequests.UI.NinjectModules
|
|||
Bind<INotificationEngine>().To<NotificationEngine>();
|
||||
|
||||
Bind<IStatusChecker>().To<StatusChecker>();
|
||||
|
||||
Bind<ISecurityExtensions>().To<SecurityExtensions>();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -48,6 +48,7 @@ namespace PlexRequests.UI.NinjectModules
|
|||
Bind<IJobRecord>().To<JobRecord>();
|
||||
|
||||
Bind<IUserRepository>().To<UserRepository>();
|
||||
Bind<IPlexUserRepository>().To<PlexUserRepository>();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -203,6 +203,9 @@
|
|||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Authentication\CustomAuthenticationConfiguration.cs" />
|
||||
<Compile Include="Authentication\CustomAuthenticationProvider.cs" />
|
||||
<Compile Include="Authentication\CustomModuleExtensions.cs" />
|
||||
<Compile Include="Bootstrapper.cs" />
|
||||
<Compile Include="Helpers\BaseUrlHelper.cs" />
|
||||
<Compile Include="Helpers\ContravariantBindingResolver.cs" />
|
||||
|
@ -212,6 +215,7 @@
|
|||
<Compile Include="Helpers\EmptyViewBase.cs" />
|
||||
<Compile Include="Helpers\AngularViewBase.cs" />
|
||||
<Compile Include="Helpers\HtmlSecurityHelper.cs" />
|
||||
<Compile Include="Helpers\ISecurityExtensions.cs" />
|
||||
<Compile Include="Helpers\SecurityExtensions.cs" />
|
||||
<Compile Include="Helpers\ServiceLocator.cs" />
|
||||
<Compile Include="Helpers\Themes.cs" />
|
||||
|
@ -257,7 +261,6 @@
|
|||
<Compile Include="Modules\DonationLinkModule.cs" />
|
||||
<Compile Include="Modules\IssuesModule.cs" />
|
||||
<Compile Include="Modules\LandingPageModule.cs" />
|
||||
<Compile Include="Modules\RequestsBetaModule.cs" />
|
||||
<Compile Include="Modules\LayoutModule.cs" />
|
||||
<Compile Include="Modules\UserWizardModule.cs" />
|
||||
<Compile Include="NinjectModules\ApiModule.cs" />
|
||||
|
|
|
@ -32,6 +32,16 @@
|
|||
<input type="text" class="form-control form-control-custom " id="PlexAvailabilityChecker" name="PlexAvailabilityChecker" value="@Model.PlexAvailabilityChecker">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="PlexContentCacher" class="control-label">Plex Content Cacher (min)</label>
|
||||
<input type="text" class="form-control form-control-custom " id="PlexContentCacher" name="PlexContentCacher" value="@Model.PlexContentCacher">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="PlexUserChecker" class="control-label">Plex User Checker (hours)</label>
|
||||
<input type="text" class="form-control form-control-custom " id="PlexUserChecker" name="PlexContentCacher" value="@Model.PlexUserChecker">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="CouchPotatoCacher" class="control-label">Couch Potato Cacher (min)</label>
|
||||
<input type="text" class="form-control form-control-custom " id="CouchPotatoCacher" name="CouchPotatoCacher" value="@Model.CouchPotatoCacher">
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue