User perms

This commit is contained in:
Jamie.Rees 2016-10-28 17:24:45 +01:00
parent 624b32d926
commit 6d1eef9154
17 changed files with 524 additions and 104 deletions

View file

@ -22,7 +22,7 @@ namespace PlexRequests.Core.Migration
public void MigrateToLatest() public void MigrateToLatest()
{ {
var con = Db.DbConnection(); var con = Db.DbConnection();
var versions = GetMigrations().OrderBy(x => x.Key); var versions = GetMigrations();
var dbVersion = con.GetVersionInfo().OrderByDescending(x => x.Version).FirstOrDefault(); var dbVersion = con.GetVersionInfo().OrderByDescending(x => x.Version).FirstOrDefault();
if (dbVersion == null) if (dbVersion == null)

View file

@ -0,0 +1,58 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: Version1100.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.Data;
using PlexRequests.Store;
namespace PlexRequests.Core.Migration.Migrations
{
[Migration(11000, "v1.10.0.0")]
public class Version1100 : BaseMigration, IMigration
{
public Version1100()
{
}
public int Version => 11000;
public void Start(IDbConnection con)
{
UpdateDb(con);
UpdateSchema(con, Version);
}
private void UpdateDb(IDbConnection con)
{
// Create the two new columns
con.AlterTable("Users", "ADD", "Permissions", true, "INTEGER");
con.AlterTable("Users", "ADD", "Features", true, "INTEGER");
}
}
}

View file

@ -65,6 +65,7 @@
<Compile Include="MigrationAttribute.cs" /> <Compile Include="MigrationAttribute.cs" />
<Compile Include="MigrationRunner.cs" /> <Compile Include="MigrationRunner.cs" />
<Compile Include="Migrations\BaseMigration.cs" /> <Compile Include="Migrations\BaseMigration.cs" />
<Compile Include="Migrations\Version1100.cs" />
<Compile Include="Migrations\Version195.cs" /> <Compile Include="Migrations\Version195.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>

View file

@ -36,6 +36,7 @@ using Nancy.Security;
using PlexRequests.Core.Models; using PlexRequests.Core.Models;
using PlexRequests.Helpers; using PlexRequests.Helpers;
using PlexRequests.Helpers.Permissions;
using PlexRequests.Store; using PlexRequests.Store;
using PlexRequests.Store.Repository; using PlexRequests.Store.Repository;
@ -118,6 +119,27 @@ namespace PlexRequests.Core
return new Guid(userRecord.UserGuid); return new Guid(userRecord.UserGuid);
} }
public Guid? CreateUser(string username, string password, int permissions, int features, UserProperties properties = null)
{
var salt = PasswordHasher.GenerateSalt();
var userModel = new UsersModel
{
UserName = username,
UserGuid = Guid.NewGuid().ToString(),
Salt = salt,
Hash = PasswordHasher.ComputeHash(password, salt),
UserProperties = ByteConverterHelper.ReturnBytes(properties ?? new UserProperties()),
Permissions = permissions,
Features = features,
Claims = new byte[] {0}
};
Repo.Insert(userModel);
var userRecord = Repo.Get(userModel.UserGuid);
return new Guid(userRecord.UserGuid);
}
public void DeleteUser(string userId) public void DeleteUser(string userId)
{ {
var user = Repo.Get(userId); var user = Repo.Get(userId);
@ -187,6 +209,9 @@ namespace PlexRequests.Core
public interface ICustomUserMapper public interface ICustomUserMapper
{ {
Guid? CreateUser(string username, string password, string[] claims, UserProperties props); Guid? CreateUser(string username, string password, string[] claims, UserProperties props);
Guid? CreateUser(string username, string password, int permissions, int features,
UserProperties properties = null);
IEnumerable<string> GetAllClaims(); IEnumerable<string> GetAllClaims();
IEnumerable<UsersModel> GetUsers(); IEnumerable<UsersModel> GetUsers();
Task<IEnumerable<UsersModel>> GetUsersAsync(); Task<IEnumerable<UsersModel>> GetUsersAsync();

View file

@ -0,0 +1,54 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: EnumExtensions.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
namespace PlexRequests.Helpers
{
public static class EnumExtensions
{
public static IEnumerable<Enum> GetUniqueFlags(this Enum flags)
{
ulong flag = 1;
foreach (var value in Enum.GetValues(flags.GetType()).Cast<Enum>())
{
var bits = Convert.ToUInt64(value);
while (flag < bits)
{
flag <<= 1;
}
if (flag == bits && flags.HasFlag(value))
{
yield return value;
}
}
}
}
}

View file

@ -0,0 +1,112 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: EnumHelper.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Reflection;
namespace PlexRequests.Helpers
{
public static class EnumHelper<T>
{
public static IList<T> GetValues(Enum value)
{
return value.GetType().GetFields(BindingFlags.Static | BindingFlags.Public).Select(fi => (T) Enum.Parse(value.GetType(), fi.Name, false)).ToList();
}
public static T Parse(string value)
{
return (T)Enum.Parse(typeof(T), value, true);
}
public static IList<string> GetNames(Enum value)
{
return value.GetType().GetFields(BindingFlags.Static | BindingFlags.Public).Select(fi => fi.Name).ToList();
}
public static IList<string> GetDisplayValues(Enum value)
{
return GetNames(value).Select(obj => GetDisplayValue(Parse(obj))).ToList();
}
private static string LookupResource(Type resourceManagerProvider, string resourceKey)
{
foreach (var staticProperty in resourceManagerProvider.GetProperties(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public))
{
if (staticProperty.PropertyType == typeof(System.Resources.ResourceManager))
{
System.Resources.ResourceManager resourceManager = (System.Resources.ResourceManager)staticProperty.GetValue(null, null);
return resourceManager.GetString(resourceKey);
}
}
return resourceKey; // Fallback with the key name
}
public static string GetDisplayValue(T value)
{
var fieldInfo = value.GetType().GetField(value.ToString());
var descriptionAttributes = fieldInfo.GetCustomAttributes(
typeof(DisplayAttribute), false) as DisplayAttribute[];
if (descriptionAttributes[0].ResourceType != null)
return LookupResource(descriptionAttributes[0].ResourceType, descriptionAttributes[0].Name);
if (descriptionAttributes == null) return string.Empty;
return (descriptionAttributes.Length > 0) ? descriptionAttributes[0].Name : value.ToString();
}
public static T GetValueFromName(string name)
{
var type = typeof(T);
if (!type.IsEnum) throw new InvalidOperationException();
foreach (var field in type.GetFields())
{
var attribute = Attribute.GetCustomAttribute(field,
typeof(DisplayAttribute)) as DisplayAttribute;
if (attribute != null)
{
if (attribute.Name == name)
{
return (T)field.GetValue(null);
}
}
else
{
if (field.Name == name)
return (T)field.GetValue(null);
}
}
throw new ArgumentOutOfRangeException(nameof(name));
}
}
}

View file

@ -0,0 +1,42 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: Features.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.ComponentModel.DataAnnotations;
namespace PlexRequests.Helpers.Permissions
{
[Flags]
public enum Features
{
[Display(Name = "Newsletter")]
Newsletter = 1,
[Display(Name = "Recently Added Notification")]
RecentlyAddedNotification = 2,
}
}

View file

@ -0,0 +1,51 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: Permissions.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.ComponentModel.DataAnnotations;
namespace PlexRequests.Helpers.Permissions
{
[Flags]
public enum Permissions
{
[Display(Name = "Access Administration Settings")]
AdminSettings = 1,
[Display(Name = "Request Movie")]
RequestMovie = 2,
[Display(Name = "Request TV Show")]
RequestTvShow = 4,
[Display(Name = "Request Music")]
RequestMusic = 8,
[Display(Name = "Report Issue")]
ReportIssue = 16
}
}

View file

@ -48,6 +48,7 @@
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Runtime.Caching" /> <Reference Include="System.Runtime.Caching" />
<Reference Include="System.Web" /> <Reference Include="System.Web" />
@ -69,6 +70,7 @@
<Compile Include="ByteConverterHelper.cs" /> <Compile Include="ByteConverterHelper.cs" />
<Compile Include="CookieHelper.cs" /> <Compile Include="CookieHelper.cs" />
<Compile Include="DateTimeHelper.cs" /> <Compile Include="DateTimeHelper.cs" />
<Compile Include="EnumHelper.cs" />
<Compile Include="Exceptions\ApiRequestException.cs" /> <Compile Include="Exceptions\ApiRequestException.cs" />
<Compile Include="Exceptions\ApplicationSettingsException.cs" /> <Compile Include="Exceptions\ApplicationSettingsException.cs" />
<Compile Include="HtmlRemover.cs" /> <Compile Include="HtmlRemover.cs" />
@ -78,6 +80,9 @@
<Compile Include="MemoryCacheProvider.cs" /> <Compile Include="MemoryCacheProvider.cs" />
<Compile Include="ObjectCopier.cs" /> <Compile Include="ObjectCopier.cs" />
<Compile Include="PasswordHasher.cs" /> <Compile Include="PasswordHasher.cs" />
<Compile Include="EnumExtensions.cs" />
<Compile Include="Permissions\Features.cs" />
<Compile Include="Permissions\Permissions.cs" />
<Compile Include="PlexHelper.cs" /> <Compile Include="PlexHelper.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SerializerSettings.cs" /> <Compile Include="SerializerSettings.cs" />

View file

@ -7,8 +7,9 @@ CREATE TABLE IF NOT EXISTS Users
UserName varchar(50) NOT NULL, UserName varchar(50) NOT NULL,
Salt BLOB NOT NULL, Salt BLOB NOT NULL,
Hash BLOB NOT NULL, Hash BLOB NOT NULL,
Claims BLOB NOT NULL, UserProperties BLOB,
UserProperties BLOB Permissions INTEGER,
Features INTEGER
); );
CREATE TABLE IF NOT EXISTS UserLogins CREATE TABLE IF NOT EXISTS UserLogins

View file

@ -57,12 +57,13 @@ namespace PlexRequests.Store
} }
} }
public static void AddColumn(this IDbConnection connection, string tableName, string alterType, string newColumn, bool allowNulls, string dataType) public static void AlterTable(this IDbConnection connection, string tableName, string alterType, string newColumn, bool allowNulls, string dataType)
{ {
connection.Open(); connection.Open();
var result = connection.Query<TableInfo>($"PRAGMA table_info({tableName});"); var result = connection.Query<TableInfo>($"PRAGMA table_info({tableName});");
if (result.Any(x => x.name == newColumn)) if (result.Any(x => x.name == newColumn))
{ {
connection.Close();
return; return;
} }

View file

@ -35,7 +35,10 @@ namespace PlexRequests.Store
{ {
public byte[] Hash { get; set; } public byte[] Hash { get; set; }
public byte[] Salt { get; set; } public byte[] Salt { get; set; }
[Obsolete]
public byte[] Claims { get; set; } public byte[] Claims { get; set; }
public byte[] UserProperties { get; set; } public byte[] UserProperties { get; set; }
public int Permissions { get; set; }
public int Features { get; set; }
} }
} }

View file

@ -4,10 +4,14 @@
$scope.user = {}; // The local user $scope.user = {}; // The local user
$scope.users = []; // list of users $scope.users = []; // list of users
$scope.claims = []; // List of claims
$scope.features = []; // List of features
$scope.permissions = []; // List of permissions
$scope.selectedUser = {}; // User on the right side $scope.selectedUser = {}; // User on the right side
$scope.selectedClaims = {};
$scope.selectedFeatures = {};
$scope.selectedPermissions = {};
$scope.minDate = "0001-01-01T00:00:00.0000000+00:00"; $scope.minDate = "0001-01-01T00:00:00.0000000+00:00";
@ -44,11 +48,16 @@
}); });
}; };
// Get the claims and populate the create dropdown // Get the permissions and features and populate the create dropdown
$scope.getClaims = function () { $scope.getFeaturesPermissions = function () {
userManagementService.getClaims() userManagementService.getFeatures()
.then(function (data) { .then(function (data) {
$scope.claims = data.data; $scope.features = data.data;
});
userManagementService.getPermissions()
.then(function (data) {
$scope.permissions = data.data;
}); });
} }
@ -62,14 +71,14 @@
return; return;
} }
if (!$scope.selectedClaims) { if ($scope.selectedPermissions.length === 0) {
$scope.error.error = true; $scope.error.error = true;
$scope.error.errorMessage = "Please select a permission"; $scope.error.errorMessage = "Please select a permission";
generateNotify($scope.error.errorMessage, 'warning'); generateNotify($scope.error.errorMessage, 'warning');
return; return;
} }
userManagementService.addUser($scope.user, $scope.selectedClaims) userManagementService.addUser($scope.user, $scope.selectedPermissions, $scope.selectedFeatures)
.then(function (data) { .then(function (data) {
if (data.message) { if (data.message) {
$scope.error.error = true; $scope.error.error = true;
@ -77,27 +86,33 @@
} else { } else {
$scope.users.push(data.data); // Push the new user into the array to update the DOM $scope.users.push(data.data); // Push the new user into the array to update the DOM
$scope.user = {}; $scope.user = {};
$scope.selectedClaims = {}; $scope.selectedPermissions = {}; // Clear the checkboxes
$scope.claims.forEach(function (entry) { $scope.selectedFeatures = {};
$scope.features.forEach(function (entry) {
entry.selected = false; entry.selected = false;
}); });
$scope.permissions.forEach(function (entry) {
entry.selected = false;
});
} }
}); });
}; };
$scope.hasClaim = function (claim) { // Watch the checkboxes for updates (Creating a user)
var claims = $scope.selectedUser.claimsArray; $scope.$watch('features|filter:{selected:true}',
var result = claims.some(function (item) {
return item === claim.name;
});
return result;
};
$scope.$watch('claims|filter:{selected:true}',
function (nv) { function (nv) {
$scope.selectedClaims = nv.map(function (claim) { $scope.selectedFeatures = nv.map(function (f) {
return claim.name; return f.name;
});
},
true);
$scope.$watch('permissions|filter:{selected:true}',
function (nv) {
$scope.selectedPermissions = nv.map(function (f) {
return f.name;
}); });
}, },
true); true);
@ -105,10 +120,15 @@
$scope.updateUser = function () { $scope.updateUser = function () {
var u = $scope.selectedUser; var u = $scope.selectedUser;
userManagementService.updateUser(u.id, u.claimsItem, u.alias, u.emailAddress) userManagementService.updateUser(u.id, u.permissions, u.alias, u.emailAddress)
.then(function (data) { .then(function (data) {
if (data) { if (data) {
$scope.selectedUser = data; $scope.selectedUser = data;
if (open) {
open = false;
$("#wrapper").toggleClass("toggled");
}
return successCallback("Updated User", "success"); return successCallback("Updated User", "success");
} }
}); });
@ -118,7 +138,7 @@
var u = $scope.selectedUser; var u = $scope.selectedUser;
var result = userManagementService.deleteUser(u.id); var result = userManagementService.deleteUser(u.id);
result.success(function(data) { result.success(function (data) {
if (data.result) { if (data.result) {
removeUser(u.id, true); removeUser(u.id, true);
return successCallback("Deleted User", "success"); return successCallback("Deleted User", "success");
@ -138,7 +158,7 @@
// On page load // On page load
$scope.init = function () { $scope.init = function () {
$scope.getUsers(); $scope.getUsers();
$scope.getClaims(); $scope.getFeaturesPermissions();
return; return;
} }
@ -157,5 +177,5 @@
}; };
angular.module('PlexRequests').controller('userManagementController', ["$scope", "userManagementService","moment", controller]); angular.module('PlexRequests').controller('userManagementController', ["$scope", "userManagementService", "moment", controller]);
}()); }());

View file

@ -8,27 +8,31 @@
return $http.get('/usermanagement/users'); return $http.get('/usermanagement/users');
}; };
var addUser = function (user, claims) { var addUser = function (user, permissions, features) {
if (!user || claims.length === 0) { if (!user || permissions.length === 0) {
return null; return null;
} }
return $http({ return $http({
url: '/usermanagement/createuser', url: '/usermanagement/createuser',
method: "POST", method: "POST",
data: { username: user.username, password: user.password, claims: claims, email: user.email } data: { username: user.username, password: user.password, permissions: permissions, features : features, email: user.email }
}); });
} }
var getClaims = function () { var getFeatures = function () {
return $http.get('/usermanagement/claims'); return $http.get('/usermanagement/features');
} }
var updateUser = function (id, claims, alias, email) { var getPermissions = function () {
return $http.get('/usermanagement/permissions');
}
var updateUser = function (id, permissions, alias, email) {
return $http({ return $http({
url: '/usermanagement/updateUser', url: '/usermanagement/updateUser',
method: "POST", method: "POST",
data: { id: id, claims: claims, alias: alias, emailAddress: email } data: { id: id, permissions: permissions, alias: alias, emailAddress: email }
}); });
} }
@ -43,7 +47,8 @@
return { return {
getUsers: getUsers, getUsers: getUsers,
addUser: addUser, addUser: addUser,
getClaims: getClaims, getFeatures: getFeatures,
getPermissions: getPermissions,
updateUser: updateUser, updateUser: updateUser,
deleteUser: deleteUser deleteUser: deleteUser
}; };

View file

@ -10,17 +10,20 @@ namespace PlexRequests.UI.Models
public UserManagementUsersViewModel() public UserManagementUsersViewModel()
{ {
PlexInfo = new UserManagementPlexInformation(); PlexInfo = new UserManagementPlexInformation();
Permissions = new List<CheckBox>();
Features = new List<CheckBox>();
} }
public string Username { get; set; } public string Username { get; set; }
public string Claims { get; set; } public string FeaturesFormattedString { get; set; }
public string PermissionsFormattedString { get; set; }
public string Id { get; set; } public string Id { get; set; }
public string Alias { get; set; } public string Alias { get; set; }
public UserType Type { get; set; } public UserType Type { get; set; }
public string EmailAddress { get; set; } public string EmailAddress { get; set; }
public UserManagementPlexInformation PlexInfo { get; set; } public UserManagementPlexInformation PlexInfo { get; set; }
public string[] ClaimsArray { get; set; }
public List<UserManagementUpdateModel.ClaimsModel> ClaimsItem { get; set; }
public DateTime LastLoggedIn { get; set; } public DateTime LastLoggedIn { get; set; }
public List<CheckBox> Permissions { get; set; }
public List<CheckBox> Features { get; set; }
} }
public class UserManagementPlexInformation public class UserManagementPlexInformation
@ -33,6 +36,13 @@ namespace PlexRequests.UI.Models
public List<UserManagementPlexServers> Servers { get; set; } public List<UserManagementPlexServers> Servers { get; set; }
} }
public class CheckBox
{
public string Name { get; set; }
public int Value { get; set; }
public bool Selected { get; set; }
}
public class UserManagementPlexServers public class UserManagementPlexServers
{ {
public int Id { get; set; } public int Id { get; set; }
@ -50,8 +60,10 @@ namespace PlexRequests.UI.Models
public string Username { get; set; } public string Username { get; set; }
[JsonProperty("password")] [JsonProperty("password")]
public string Password { get; set; } public string Password { get; set; }
[JsonProperty("claims")] [JsonProperty("permissions")]
public string[] Claims { get; set; } public List<string> Permissions { get; set; }
[JsonProperty("features")]
public List<string> Features { get; set; }
[JsonProperty("email")] [JsonProperty("email")]
public string EmailAddress { get; set; } public string EmailAddress { get; set; }
@ -61,20 +73,10 @@ namespace PlexRequests.UI.Models
{ {
[JsonProperty("id")] [JsonProperty("id")]
public string Id { get; set; } public string Id { get; set; }
[JsonProperty("claims")] [JsonProperty("permissions")]
public List<ClaimsModel> Claims { get; set; } public List<CheckBox> Permissions { get; set; }
public string Alias { get; set; } public string Alias { get; set; }
public string EmailAddress { get; set; } public string EmailAddress { get; set; }
public class ClaimsModel
{
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("selected")]
public bool Selected { get; set; }
}
} }
} }

View file

@ -13,6 +13,7 @@ using PlexRequests.Core;
using PlexRequests.Core.Models; using PlexRequests.Core.Models;
using PlexRequests.Core.SettingModels; using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers; using PlexRequests.Helpers;
using PlexRequests.Helpers.Permissions;
using PlexRequests.Store; using PlexRequests.Store;
using PlexRequests.Store.Repository; using PlexRequests.Store.Repository;
using PlexRequests.UI.Models; using PlexRequests.UI.Models;
@ -24,7 +25,7 @@ namespace PlexRequests.UI.Modules
public UserManagementModule(ISettingsService<PlexRequestSettings> pr, ICustomUserMapper m, IPlexApi plexApi, ISettingsService<PlexSettings> plex, IRepository<UserLogins> userLogins) : base("usermanagement", pr) public UserManagementModule(ISettingsService<PlexRequestSettings> pr, ICustomUserMapper m, IPlexApi plexApi, ISettingsService<PlexSettings> plex, IRepository<UserLogins> userLogins) : base("usermanagement", pr)
{ {
#if !DEBUG #if !DEBUG
this.RequiresClaims(UserClaims.Admin); this.RequiresAnyClaims(UserClaims.Admin);
#endif #endif
UserMapper = m; UserMapper = m;
PlexApi = plexApi; PlexApi = plexApi;
@ -37,7 +38,8 @@ namespace PlexRequests.UI.Modules
Post["/createuser"] = x => CreateUser(); Post["/createuser"] = x => CreateUser();
Get["/local/{id}"] = x => LocalDetails((Guid)x.id); Get["/local/{id}"] = x => LocalDetails((Guid)x.id);
Get["/plex/{id}", true] = async (x, ct) => await PlexDetails(x.id); Get["/plex/{id}", true] = async (x, ct) => await PlexDetails(x.id);
Get["/claims"] = x => GetClaims(); Get["/permissions"] = x => GetEnum<Permissions>();
Get["/features"] = x => GetEnum<Features>();
Post["/updateuser"] = x => UpdateUser(); Post["/updateuser"] = x => UpdateUser();
Post["/deleteuser"] = x => DeleteUser(); Post["/deleteuser"] = x => DeleteUser();
} }
@ -79,7 +81,7 @@ namespace PlexRequests.UI.Modules
Username = u.Username, Username = u.Username,
Type = UserType.PlexUser, Type = UserType.PlexUser,
Id = u.Id, Id = u.Id,
Claims = "Requestor", FeaturesFormattedString = "Requestor",
EmailAddress = u.Email, EmailAddress = u.Email,
PlexInfo = new UserManagementPlexInformation PlexInfo = new UserManagementPlexInformation
{ {
@ -110,7 +112,23 @@ namespace PlexRequests.UI.Modules
Message = "Please enter in a valid Username and Password" Message = "Please enter in a valid Username and Password"
}); });
} }
var user = UserMapper.CreateUser(model.Username, model.Password, model.Claims, new UserProperties { EmailAddress = model.EmailAddress });
var featuresVal = 0;
var permissionsVal = 0;
foreach (var feature in model.Features)
{
var f = (int)EnumHelper<Features>.GetValueFromName(feature);
featuresVal += f;
}
foreach (var permission in model.Permissions)
{
var f = (int)EnumHelper<Permissions>.GetValueFromName(permission);
permissionsVal += f;
}
var user = UserMapper.CreateUser(model.Username, model.Password, featuresVal, permissionsVal, new UserProperties { EmailAddress = model.EmailAddress });
if (user.HasValue) if (user.HasValue)
{ {
return Response.AsJson(MapLocalUser(UserMapper.GetUser(user.Value), DateTime.MinValue)); return Response.AsJson(MapLocalUser(UserMapper.GetUser(user.Value), DateTime.MinValue));
@ -137,20 +155,13 @@ namespace PlexRequests.UI.Modules
Message = "Couldn't find the user" Message = "Couldn't find the user"
}); });
} }
var claims = new List<string>(); var val = model.Permissions.Where(c => c.Selected).Sum(c => c.Value);
foreach (var c in model.Claims)
{
if (c.Selected)
{
claims.Add(c.Name);
}
}
var userFound = UserMapper.GetUser(new Guid(model.Id)); var userFound = UserMapper.GetUser(new Guid(model.Id));
userFound.Claims = ByteConverterHelper.ReturnBytes(claims.ToArray()); userFound.Permissions = val;
var currentProps = ByteConverterHelper.ReturnObject<UserProperties>(userFound.UserProperties); var currentProps = ByteConverterHelper.ReturnObject<UserProperties>(userFound.UserProperties);
currentProps.UserAlias = model.Alias; currentProps.UserAlias = model.Alias;
currentProps.EmailAddress = model.EmailAddress; currentProps.EmailAddress = model.EmailAddress;
@ -221,53 +232,69 @@ namespace PlexRequests.UI.Modules
/// Returns all claims for the users. /// Returns all claims for the users.
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
private Response GetClaims() private Response GetEnum<T>()
{ {
var retVal = new List<dynamic>(); var retVal = new List<CheckBox>();
var claims = UserMapper.GetAllClaims(); foreach (var p in Enum.GetValues(typeof(T)))
foreach (var c in claims)
{ {
retVal.Add(new { Name = c, Selected = false }); var perm = (T)p;
var displayValue = EnumHelper<T>.GetDisplayValue(perm);
retVal.Add(new CheckBox{ Name = displayValue, Selected = false, Value = (int)p });
} }
return Response.AsJson(retVal); return Response.AsJson(retVal);
} }
private UserManagementUsersViewModel MapLocalUser(UsersModel user, DateTime lastLoggedIn) private UserManagementUsersViewModel MapLocalUser(UsersModel user, DateTime lastLoggedIn)
{ {
var claims = ByteConverterHelper.ReturnObject<string[]>(user.Claims); var features = (Features) user.Features;
var claimsString = string.Join(", ", claims); var permissions = (Permissions) user.Permissions;
var userProps = ByteConverterHelper.ReturnObject<UserProperties>(user.UserProperties); var userProps = ByteConverterHelper.ReturnObject<UserProperties>(user.UserProperties);
var m = new UserManagementUsersViewModel var m = new UserManagementUsersViewModel
{ {
Id = user.UserGuid, Id = user.UserGuid,
Claims = claimsString, PermissionsFormattedString = permissions == 0 ? "None" : permissions.ToString(),
FeaturesFormattedString = features.ToString(),
Username = user.UserName, Username = user.UserName,
Type = UserType.LocalUser, Type = UserType.LocalUser,
EmailAddress = userProps.EmailAddress, EmailAddress = userProps.EmailAddress,
Alias = userProps.UserAlias, Alias = userProps.UserAlias,
ClaimsArray = claims, LastLoggedIn = lastLoggedIn,
ClaimsItem = new List<UserManagementUpdateModel.ClaimsModel>(),
LastLoggedIn = lastLoggedIn
}; };
// Add all of the current claims // Add permissions
foreach (var c in claims) foreach (var p in Enum.GetValues(typeof(Permissions)))
{ {
m.ClaimsItem.Add(new UserManagementUpdateModel.ClaimsModel { Name = c, Selected = true }); var perm = (Permissions)p;
var displayValue = EnumHelper<Permissions>.GetDisplayValue(perm);
var pm = new CheckBox
{
Name = displayValue,
Selected = permissions.HasFlag(perm),
Value = (int)perm
};
m.Permissions.Add(pm);
} }
var allClaims = UserMapper.GetAllClaims(); // Add features
foreach (var p in Enum.GetValues(typeof(Features)))
// Get me the current claims that the user does not have
var missingClaims = allClaims.Except(claims);
// Add them into the view
foreach (var missingClaim in missingClaims)
{ {
m.ClaimsItem.Add(new UserManagementUpdateModel.ClaimsModel { Name = missingClaim, Selected = false }); var perm = (Features)p;
var displayValue = EnumHelper<Features>.GetDisplayValue(perm);
var pm = new CheckBox
{
Name = displayValue,
Selected = features.HasFlag(perm),
Value = (int)perm
};
m.Features.Add(pm);
} }
return m; return m;
} }
} }

View file

@ -16,7 +16,10 @@
<strong>Email Address: </strong><span ng-bind="selectedUser.emailAddress"></span> <strong>Email Address: </strong><span ng-bind="selectedUser.emailAddress"></span>
</div> </div>
<div> <div>
<strong>Permissions: </strong><span ng-bind="selectedUser.claims"></span> <strong>Permissions: </strong><span ng-bind="selectedUser.permissionsFormattedString"></span>
</div>
<div>
<strong>Features: </strong><span ng-bind="selectedUser.featuresFormattedString"></span>
</div> </div>
<div> <div>
<strong>User Type: </strong><span ng-bind="selectedUser.type === 1 ? 'Local User' : 'Plex User'"></span> <strong>User Type: </strong><span ng-bind="selectedUser.type === 1 ? 'Local User' : 'Plex User'"></span>
@ -28,9 +31,9 @@
<strong>Modify Roles:</strong> <strong>Modify Roles:</strong>
<!--Load all claims--> <!--Load all claims-->
<div class="checkbox" ng-repeat="claim in selectedUser.claimsItem"> <div class="checkbox" ng-repeat="p in selectedUser.permissions">
<input id="claimCheckboxEdit_{{$id}}" class="checkbox-custom" name="selectedClaims[]" ng-checked="claim.selected" ng-model="claim.selected" type="checkbox" value="claim" /> <input id="permissionsCheckbox_{{$id}}" class="checkbox-custom" name="permissions[]" ng-checked="p.selected" ng-model="p.selected" type="checkbox" value="{{p.value}}" />
<label for="claimCheckboxEdit_{{$id}}">{{claim.name}}</label> <label for="permissionsCheckbox_{{$id}}">{{p.name}}</label>
</div> </div>
<strong>Email Address</strong> <strong>Email Address</strong>
@ -71,13 +74,23 @@
<div class="form-group"> <div class="form-group">
<input id="email" type="email" placeholder="email address" ng-model="user.email" class="form-control form-control-custom" /> <input id="email" type="email" placeholder="email address" ng-model="user.email" class="form-control form-control-custom" />
</div> </div>
<h3>Permissions: </h3>
<div class="checkbox" ng-repeat="claim in claims"> <div class="checkbox" ng-repeat="permission in permissions">
<input id="claimCheckbox_{{$id}}" class="checkbox-custom" name="selectedClaims[]" <input id="permission_{{$id}}" class="checkbox-custom" name="permission[]"
ng-checked="claim.selected" ng-model="claim.selected" type="checkbox" value="claim" /> ng-checked="permission.selected" ng-model="permission.selected" type="checkbox" value="{{permission.value}}" />
<label for="claimCheckbox_{{$id}}">{{claim.name}}</label> <label for="permission_{{$id}}">{{permission.name}}</label>
</div> </div>
<h3>Features: </h3>
<div class="checkbox" ng-repeat="f in features">
<input id="features_{{$id}}" class="checkbox-custom" name="f[]"
ng-checked="f.selected" ng-model="f.selected" type="checkbox" value="{{f.value}}" />
<label for="features_{{$id}}">{{f.name}}</label>
</div>
<input type="submit" class="btn btn-success-outline" value="Add" /> <input type="submit" class="btn btn-success-outline" value="Add" />
</form> </form>
@ -119,7 +132,7 @@
</a> </a>
</th> </th>
<th> <th>
Roles Permissions
</th> </th>
<th> <th>
<a href="#" ng-click="sortType = 'type'; sortReverse = !sortReverse"> <a href="#" ng-click="sortType = 'type'; sortReverse = !sortReverse">
@ -150,7 +163,7 @@
{{u.emailAddress}} {{u.emailAddress}}
</td> </td>
<td> <td>
{{u.claims}} {{u.permissionsFormattedString}}
</td> </td>
<td> <td>
{{u.type === 1 ? 'Local User' : 'Plex User'}} {{u.type === 1 ? 'Local User' : 'Plex User'}}