diff --git a/Areas/admin/Controllers/baseController.cs b/Areas/admin/Controllers/baseController.cs index 7889f69..f6e941f 100644 --- a/Areas/admin/Controllers/baseController.cs +++ b/Areas/admin/Controllers/baseController.cs @@ -3,16 +3,32 @@ using System.Linq; using System.Web; using System.Web.Mvc; +using System.Web.Security; namespace BuildFeed.Areas.admin.Controllers { - [Authorize(Users = "hounsell")] public class baseController : Controller { + [Authorize(Roles = "Administrators")] // GET: admin/base public ActionResult index() { return View(); } + + [Authorize(Users = "hounsell")] + public ActionResult setup() + { + if (!Roles.RoleExists("Administrators")) + { + Roles.CreateRole("Administrators"); + } + if (!Roles.IsUserInRole("hounsell", "Administrators")) + { + Roles.AddUserToRole("hounsell", "Administrators"); + } + + return RedirectToAction("index"); + } } } \ No newline at end of file diff --git a/Areas/admin/Controllers/usersController.cs b/Areas/admin/Controllers/usersController.cs index 69cde78..9f38907 100644 --- a/Areas/admin/Controllers/usersController.cs +++ b/Areas/admin/Controllers/usersController.cs @@ -8,7 +8,7 @@ namespace BuildFeed.Areas.admin.Controllers { - [Authorize(Users = "hounsell")] + [Authorize(Roles = "Administrators")] public class usersController : Controller { // GET: admin/users @@ -17,6 +17,29 @@ public ActionResult index() return View(Membership.GetAllUsers().Cast().OrderByDescending(m => m.IsApproved).ThenBy(m => m.UserName)); } + public ActionResult admins() + { + List admins = new List(); + foreach(var m in Roles.GetUsersInRole("Administrators")) + { + admins.Add(Membership.GetUser(m)); + } + + return View(admins.OrderByDescending(m => m.UserName)); + } + + public ActionResult promote(string id) + { + Roles.AddUserToRole(id, "Administrators"); + return RedirectToAction("Index"); + } + + public ActionResult demote(string id) + { + Roles.RemoveUserFromRole(id, "Administrators"); + return RedirectToAction("Index"); + } + public ActionResult approve(Guid id) { var provider = (Membership.Provider as RedisMembershipProvider); diff --git a/Areas/admin/Views/base/index.cshtml b/Areas/admin/Views/base/index.cshtml index 1657a20..296a958 100644 --- a/Areas/admin/Views/base/index.cshtml +++ b/Areas/admin/Views/base/index.cshtml @@ -7,5 +7,9 @@
  • @Html.ActionLink("Manage users", "index", "users")
  • + @if (User.Identity.Name == "hounsell") + { +
  • @Html.ActionLink("Initial setup", "setup")
  • + }
diff --git a/Areas/admin/Views/users/admins.cshtml b/Areas/admin/Views/users/admins.cshtml new file mode 100644 index 0000000..734af2a --- /dev/null +++ b/Areas/admin/Views/users/admins.cshtml @@ -0,0 +1,42 @@ +@model IEnumerable + +@{ + ViewBag.Title = "Administrators | BuildFeed"; +} + +

Administrators

+ +
    +
  • @Html.ActionLink("User Administration", "index")
  • +
  • @Html.ActionLink("Return to Admin Panel", "index", "base")
  • +
+ + + + + + + + + @foreach (MembershipUser mu in Model) + { + + + + + + } + +
+ Username + + Email Address + + Last Login Time +
+ @Html.DisplayFor(modelItem => mu.UserName) + + @Html.DisplayFor(modelItem => mu.Email) + + @Html.DisplayFor(modelItem => mu.LastLoginDate) +
diff --git a/Areas/admin/Views/users/index.cshtml b/Areas/admin/Views/users/index.cshtml index ddcc3fa..e21f085 100644 --- a/Areas/admin/Views/users/index.cshtml +++ b/Areas/admin/Views/users/index.cshtml @@ -6,6 +6,11 @@

User Administration

+
    +
  • @Html.ActionLink("View Administrators", "admins")
  • +
  • @Html.ActionLink("Return to Admin Panel", "index", "base")
  • +
+
diff --git a/Auth/RedisRoleProvider.cs b/Auth/RedisRoleProvider.cs new file mode 100644 index 0000000..9dc3511 --- /dev/null +++ b/Auth/RedisRoleProvider.cs @@ -0,0 +1,244 @@ +using NServiceKit.DataAnnotations; +using NServiceKit.DesignPatterns.Model; +using NServiceKit.Redis; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Web.Security; +using Required = System.ComponentModel.DataAnnotations.RequiredAttribute; + +namespace BuildFeed.Auth +{ + public class RedisRoleProvider : RoleProvider + { + public override string ApplicationName + { + get { return ""; } + set { } + } + + public override void AddUsersToRoles(string[] usernames, string[] roleNames) + { + using (RedisClient rClient = new RedisClient(DatabaseConfig.Host, DatabaseConfig.Port, db: DatabaseConfig.Database)) + { + var client = rClient.As(); + var uClient = rClient.As(); + + List roles = new List(); + roles.AddRange(from r in client.GetAll() + where roleNames.Any(n => n == r.RoleName) + select r); + + List users = new List(); + users.AddRange(from u in uClient.GetAll() + where usernames.Any(n => n == u.UserName) + select u); + + for (int i = 0; i < roles.Count; i++) + { + List newUsers = new List(); + + if(roles[i].Users != null) + { + var usersToAdd = from u in users + where !roles[i].Users.Any(v => v == u.Id) + select u.Id; + + newUsers.AddRange(roles[i].Users); + + newUsers.AddRange(usersToAdd); + } + + roles[i].Users = newUsers.ToArray(); + } + + client.StoreAll(roles); + } + } + + public override void CreateRole(string roleName) + { + using (RedisClient rClient = new RedisClient(DatabaseConfig.Host, DatabaseConfig.Port, db: DatabaseConfig.Database)) + { + var client = rClient.As(); + + RedisRole rr = new RedisRole() + { + Id = Guid.NewGuid(), + RoleName = roleName + }; + + client.Store(rr); + } + } + + public override bool DeleteRole(string roleName, bool throwOnPopulatedRole) + { + using (RedisClient rClient = new RedisClient(DatabaseConfig.Host, DatabaseConfig.Port, db: DatabaseConfig.Database)) + { + var client = rClient.As(); + + var role = client.GetAll().SingleOrDefault(r => r.RoleName == roleName); + + if (role.Users.Length > 0 && throwOnPopulatedRole) + { + throw new Exception("This role still has users"); + } + + client.DeleteById(role.Id); + return true; + } + } + + public override string[] FindUsersInRole(string roleName, string usernameToMatch) + { + using (RedisClient rClient = new RedisClient(DatabaseConfig.Host, DatabaseConfig.Port, db: DatabaseConfig.Database)) + { + var client = rClient.As(); + var uClient = rClient.As(); + + var userIds = from r in client.GetAll() + where r.RoleName == roleName + from u in r.Users + select u; + + var users = uClient.GetByIds(userIds); + + return (from u in users + where u.UserName.Contains(usernameToMatch) + select u.UserName).ToArray(); + } + } + + public override string[] GetAllRoles() + { + using (RedisClient rClient = new RedisClient(DatabaseConfig.Host, DatabaseConfig.Port, db: DatabaseConfig.Database)) + { + var client = rClient.As(); + + return (from r in client.GetAll() + select r.RoleName).ToArray(); + } + } + + public override string[] GetRolesForUser(string username) + { + using (RedisClient rClient = new RedisClient(DatabaseConfig.Host, DatabaseConfig.Port, db: DatabaseConfig.Database)) + { + var client = rClient.As(); + var uClient = rClient.As(); + + var user = uClient.GetAll().SingleOrDefault(u => u.UserName == username); + + if (user == null) + { + throw new Exception("Username does not exist"); + } + + return (from r in client.GetAll() + where r.Users != null + where r.Users.Any(u => u == user.Id) + select r.RoleName).ToArray(); + } + } + + public override string[] GetUsersInRole(string roleName) + { + using (RedisClient rClient = new RedisClient(DatabaseConfig.Host, DatabaseConfig.Port, db: DatabaseConfig.Database)) + { + var client = rClient.As(); + var uClient = rClient.As(); + + var userIds = from r in client.GetAll() + where r.RoleName == roleName + from u in r.Users + select u; + + var users = uClient.GetByIds(userIds); + + return (from u in users + select u.UserName).ToArray(); + } + } + + public override bool IsUserInRole(string username, string roleName) + { + using (RedisClient rClient = new RedisClient(DatabaseConfig.Host, DatabaseConfig.Port, db: DatabaseConfig.Database)) + { + var client = rClient.As(); + var uClient = rClient.As(); + + var user = uClient.GetAll().SingleOrDefault(u => u.UserName == username); + + if (user == null) + { + throw new Exception(); + } + + var role = client.GetAll().SingleOrDefault(r => r.RoleName == roleName); + + if(role.Users == null) + { + return false; + } + + return role.Users.Any(u => u == user.Id); + } + } + + public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames) + { + using (RedisClient rClient = new RedisClient(DatabaseConfig.Host, DatabaseConfig.Port, db: DatabaseConfig.Database)) + { + var client = rClient.As(); + var uClient = rClient.As(); + + List roles = new List(); + roles.AddRange(from r in client.GetAll() + where roleNames.Any(n => n == r.RoleName) + select r); + + List users = new List(); + users.AddRange(from u in uClient.GetAll() + where usernames.Any(n => n == u.UserName) + select u); + + for (int i = 0; i < roles.Count; i++) + { + roles[i].Users = (from u in roles[i].Users + where !users.Any(v => v.Id == u) + select u).ToArray(); + } + + client.StoreAll(roles); + } + } + + public override bool RoleExists(string roleName) + { + using (RedisClient rClient = new RedisClient(DatabaseConfig.Host, DatabaseConfig.Port, db: DatabaseConfig.Database)) + { + var client = rClient.As(); + + return client.GetAll().Any(r => r.RoleName == roleName); + } + } + } + + [DataObject] + public class RedisRole : IHasId + { + [Key] + [Index] + public Guid Id { get; set; } + + [@Required] + [DisplayName("Role name")] + [Key] + public string RoleName { get; set; } + + public Guid[] Users { get; set; } + } +} \ No newline at end of file diff --git a/BuildFeed.csproj b/BuildFeed.csproj index 55b5024..132d4b0 100644 --- a/BuildFeed.csproj +++ b/BuildFeed.csproj @@ -22,7 +22,7 @@ /subscriptions/4af45631-0e5c-4253-9e38-d0c47f9c5b32/resourcegroups/Default-ApplicationInsights-CentralUS/providers/microsoft.insights/components/BuildFeed - a0e53051 + 8ba59b7f true @@ -66,9 +66,9 @@ ..\packages\Microsoft.Diagnostics.Instrumentation.Extensions.Intercept.0.12.0-build02810\lib\net40\Microsoft.Diagnostics.Instrumentation.Extensions.Intercept.dll - + False - ..\packages\Microsoft.Diagnostics.Tracing.EventSource.Redist.1.1.13-beta\lib\net40\Microsoft.Diagnostics.Tracing.EventSource.dll + ..\packages\Microsoft.Diagnostics.Tracing.EventSource.Redist.1.1.14-beta\lib\net45\Microsoft.Diagnostics.Tracing.EventSource.dll ..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.dll @@ -174,6 +174,7 @@ + @@ -192,6 +193,7 @@ + @@ -210,6 +212,7 @@ + @@ -287,6 +290,7 @@ + @@ -335,8 +339,10 @@ + +