├── DynamicRoleBasedAccess ├── Views │ ├── _ViewStart.cshtml │ ├── Home │ │ ├── About.cshtml │ │ ├── Contact.cshtml │ │ └── Index.cshtml │ ├── Account │ │ ├── ExternalLoginFailure.cshtml │ │ ├── ForgotPasswordConfirmation.cshtml │ │ ├── ConfirmEmail.cshtml │ │ ├── ResetPasswordConfirmation.cshtml │ │ ├── SendCode.cshtml │ │ ├── ForgotPassword.cshtml │ │ ├── _ExternalLoginsListPartial.cshtml │ │ ├── VerifyCode.cshtml │ │ ├── ExternalLoginConfirmation.cshtml │ │ ├── Register.cshtml │ │ ├── ResetPassword.cshtml │ │ └── Login.cshtml │ ├── Shared │ │ ├── Lockout.cshtml │ │ ├── Error.cshtml │ │ ├── _LoginPartial.cshtml │ │ └── _Layout.cshtml │ ├── Manage │ │ ├── AddPhoneNumber.cshtml │ │ ├── VerifyPhoneNumber.cshtml │ │ ├── SetPassword.cshtml │ │ ├── ChangePassword.cshtml │ │ ├── ManageLogins.cshtml │ │ └── Index.cshtml │ ├── Access │ │ ├── Index.cshtml │ │ └── Edit.cshtml │ ├── Web.config │ └── Role │ │ ├── Index.cshtml │ │ ├── Create.cshtml │ │ └── Edit.cshtml ├── favicon.ico ├── Global.asax ├── Scripts │ ├── _references.js │ ├── respond.min.js │ ├── jquery.validate.unobtrusive.min.js │ ├── respond.js │ └── jquery.bonsai.js ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.ttf │ └── glyphicons-halflings-regular.woff ├── App_Start │ ├── FilterConfig.cs │ ├── RouteConfig.cs │ ├── BundleConfig.cs │ ├── Startup.Auth.cs │ └── IdentityConfig.cs ├── Startup.cs ├── Global.asax.cs ├── Content │ ├── Site.css │ └── jquery.bonsai.css ├── Controllers │ ├── HomeController.cs │ ├── AccessController.cs │ ├── RoleController.cs │ └── ManageController.cs ├── Web.Debug.config ├── Web.Release.config ├── Properties │ └── AssemblyInfo.cs ├── Filters │ └── CustomAuthorizeAttribute.cs ├── Models │ ├── IdentityModels.cs │ ├── ManageViewModels.cs │ └── AccountViewModels.cs ├── packages.config ├── Web.config ├── Helpers │ └── ControllerHelper.cs ├── Project_Readme.html ├── Extensions │ └── HtmlExtensions.cs └── DynamicRoleBasedAccess.csproj ├── README.md ├── WebApplication2.sln.GhostDoc.xml ├── .gitignore └── LICENSE /DynamicRoleBasedAccess/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "~/Views/Shared/_Layout.cshtml"; 3 | } 4 | -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mo-esmp/DynamicRoleBasedAccess/HEAD/DynamicRoleBasedAccess/favicon.ico -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DynamicRoleBasedAccess 2 | Dynamic Role-Based access control in ASP.NET MVC 5 with ASP.NET Identity 2.0 sample application. 3 | -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Global.asax: -------------------------------------------------------------------------------- 1 | <%@ Application Codebehind="Global.asax.cs" Inherits="DynamicRoleBasedAccess.MvcApplication" Language="C#" %> 2 | -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Scripts/_references.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mo-esmp/DynamicRoleBasedAccess/HEAD/DynamicRoleBasedAccess/Scripts/_references.js -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mo-esmp/DynamicRoleBasedAccess/HEAD/DynamicRoleBasedAccess/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mo-esmp/DynamicRoleBasedAccess/HEAD/DynamicRoleBasedAccess/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mo-esmp/DynamicRoleBasedAccess/HEAD/DynamicRoleBasedAccess/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Views/Home/About.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.Title = "About"; 3 | } 4 |

@ViewBag.Title.

5 |

@ViewBag.Message

6 | 7 |

Use this area to provide additional information.

8 | -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Views/Account/ExternalLoginFailure.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.Title = "Login Failure"; 3 | } 4 | 5 |
6 |

@ViewBag.Title.

7 |

Unsuccessful login with service.

8 |
9 | -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Views/Shared/Lockout.cshtml: -------------------------------------------------------------------------------- 1 | @model System.Web.Mvc.HandleErrorInfo 2 | 3 | @{ 4 | ViewBag.Title = "Locked Out"; 5 | } 6 | 7 |
8 |

Locked out.

9 |

This account has been locked out, please try again later.

10 |
11 | -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Views/Account/ForgotPasswordConfirmation.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.Title = "Forgot Password Confirmation"; 3 | } 4 | 5 |
6 |

@ViewBag.Title.

7 |
8 |
9 |

10 | Please check your email to reset your password. 11 |

12 |
13 | 14 | -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/App_Start/FilterConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Web.Mvc; 2 | 3 | namespace DynamicRoleBasedAccess 4 | { 5 | public class FilterConfig 6 | { 7 | public static void RegisterGlobalFilters(GlobalFilterCollection filters) 8 | { 9 | filters.Add(new HandleErrorAttribute()); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Views/Account/ConfirmEmail.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.Title = "Confirm Email"; 3 | } 4 | 5 |

@ViewBag.Title.

6 |
7 |

8 | Thank you for confirming your email. Please @Html.ActionLink("Click here to Log in", "Login", "Account", routeValues: null, htmlAttributes: new { id = "loginLink" }) 9 |

10 |
11 | -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Startup.cs: -------------------------------------------------------------------------------- 1 | using DynamicRoleBasedAccess; 2 | using Microsoft.Owin; 3 | using Owin; 4 | 5 | [assembly: OwinStartup(typeof(Startup))] 6 | namespace DynamicRoleBasedAccess 7 | { 8 | public partial class Startup 9 | { 10 | public void Configuration(IAppBuilder app) 11 | { 12 | ConfigureAuth(app); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Views/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 | { 2 | Layout = null; 3 | } 4 | 5 | 6 | 7 | 8 | 9 | Error 10 | 11 | 12 |
13 |

Error.

14 |

An error occurred while processing your request.

15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Views/Account/ResetPasswordConfirmation.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.Title = "Reset password confirmation"; 3 | } 4 | 5 |
6 |

@ViewBag.Title.

7 |
8 |
9 |

10 | Your password has been reset. Please @Html.ActionLink("click here to log in", "Login", "Account", routeValues: null, htmlAttributes: new { id = "loginLink" }) 11 |

12 |
13 | -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Views/Home/Contact.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.Title = "Contact"; 3 | } 4 |

@ViewBag.Title.

5 |

@ViewBag.Message

6 | 7 |
8 | One Microsoft Way
9 | Redmond, WA 98052-6399
10 | P: 11 | 425.555.0100 12 |
13 | 14 |
15 | Support: Support@example.com
16 | Marketing: Marketing@example.com 17 |
-------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Global.asax.cs: -------------------------------------------------------------------------------- 1 | using System.Web.Mvc; 2 | using System.Web.Optimization; 3 | using System.Web.Routing; 4 | 5 | namespace DynamicRoleBasedAccess 6 | { 7 | public class MvcApplication : System.Web.HttpApplication 8 | { 9 | protected void Application_Start() 10 | { 11 | AreaRegistration.RegisterAllAreas(); 12 | FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); 13 | RouteConfig.RegisterRoutes(RouteTable.Routes); 14 | BundleConfig.RegisterBundles(BundleTable.Bundles); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/App_Start/RouteConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Web.Mvc; 2 | using System.Web.Routing; 3 | 4 | namespace DynamicRoleBasedAccess 5 | { 6 | public class RouteConfig 7 | { 8 | public static void RegisterRoutes(RouteCollection routes) 9 | { 10 | routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); 11 | 12 | routes.MapRoute( 13 | name: "Default", 14 | url: "{controller}/{action}/{id}", 15 | defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } 16 | ); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Content/Site.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 50px; 3 | padding-bottom: 20px; 4 | } 5 | 6 | /* Set padding to keep content from hitting the edges */ 7 | .body-content { 8 | padding-left: 15px; 9 | padding-right: 15px; 10 | } 11 | 12 | /* Override the default bootstrap behavior where horizontal description lists 13 | will truncate terms that are too long to fit in the left column 14 | */ 15 | .dl-horizontal dt { 16 | white-space: normal; 17 | } 18 | 19 | /* Set width on the form input elements since they're 100% wide by default */ 20 | input, 21 | select, 22 | textarea { 23 | max-width: 280px; 24 | } 25 | -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Content/jquery.bonsai.css: -------------------------------------------------------------------------------- 1 | .bonsai, 2 | .bonsai li { 3 | margin: 0; 4 | padding: 0; 5 | list-style: none; 6 | overflow: hidden; 7 | } 8 | 9 | .bonsai li { 10 | position: relative; 11 | padding-left: 1.3em; /* padding for the thumb */ 12 | } 13 | 14 | li .thumb { 15 | margin: -1px 0 0 -1em; /* negative margin into the padding of the li */ 16 | position: absolute; 17 | cursor: pointer; 18 | } 19 | 20 | li.has-children > .thumb:after { 21 | content: '▸'; 22 | } 23 | 24 | li.has-children.expanded > .thumb:after { 25 | content: '▾'; 26 | } 27 | 28 | li.collapsed > ol.bonsai { 29 | height: 0; 30 | overflow: hidden; 31 | } 32 | 33 | .bonsai .all, 34 | .bonsai .none { 35 | cursor: pointer; 36 | } -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Web.Mvc; 3 | 4 | namespace DynamicRoleBasedAccess.Controllers 5 | { 6 | [Description("Home")] 7 | public class HomeController : Controller 8 | { 9 | [Description("Index")] 10 | public ActionResult Index() 11 | { 12 | return View(); 13 | } 14 | 15 | [Description("About us")] 16 | public ActionResult About() 17 | { 18 | ViewBag.Message = "Your application description page."; 19 | 20 | return View(); 21 | } 22 | 23 | [Description("Contact us")] 24 | public ActionResult Contact() 25 | { 26 | ViewBag.Message = "Your contact page."; 27 | 28 | return View(); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Views/Account/SendCode.cshtml: -------------------------------------------------------------------------------- 1 | @model DynamicRoleBasedAccess.Models.SendCodeViewModel 2 | @{ 3 | ViewBag.Title = "Send"; 4 | } 5 | 6 |

@ViewBag.Title.

7 | 8 | @using (Html.BeginForm("SendCode", "Account", new { ReturnUrl = Model.ReturnUrl }, FormMethod.Post, new { @class = "form-horizontal", role = "form" })) { 9 | @Html.AntiForgeryToken() 10 | @Html.Hidden("rememberMe", @Model.RememberMe) 11 |

Send verification code

12 |
13 |
14 |
15 | Select Two-Factor Authentication Provider: 16 | @Html.DropDownListFor(model => model.SelectedProvider, Model.Providers) 17 | 18 |
19 |
20 | } 21 | 22 | @section Scripts { 23 | @Scripts.Render("~/bundles/jqueryval") 24 | } 25 | -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Views/Shared/_LoginPartial.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNet.Identity 2 | @if (Request.IsAuthenticated) 3 | { 4 | using (Html.BeginForm("LogOff", "Account", FormMethod.Post, new { id = "logoutForm", @class = "navbar-right" })) 5 | { 6 | @Html.AntiForgeryToken() 7 | 8 | 14 | } 15 | } 16 | else 17 | { 18 | 22 | } 23 | -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Views/Manage/AddPhoneNumber.cshtml: -------------------------------------------------------------------------------- 1 | @model DynamicRoleBasedAccess.Models.AddPhoneNumberViewModel 2 | @{ 3 | ViewBag.Title = "Phone Number"; 4 | } 5 | 6 |

@ViewBag.Title.

7 | 8 | @using (Html.BeginForm("AddPhoneNumber", "Manage", FormMethod.Post, new { @class = "form-horizontal", role = "form" })) 9 | { 10 | @Html.AntiForgeryToken() 11 |

Add a phone number

12 |
13 | @Html.ValidationSummary("", new { @class = "text-danger" }) 14 |
15 | @Html.LabelFor(m => m.Number, new { @class = "col-md-2 control-label" }) 16 |
17 | @Html.TextBoxFor(m => m.Number, new { @class = "form-control" }) 18 |
19 |
20 |
21 |
22 | 23 |
24 |
25 | } 26 | 27 | @section Scripts { 28 | @Scripts.Render("~/bundles/jqueryval") 29 | } 30 | -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Views/Account/ForgotPassword.cshtml: -------------------------------------------------------------------------------- 1 | @model DynamicRoleBasedAccess.Models.ForgotPasswordViewModel 2 | @{ 3 | ViewBag.Title = "Forgot your password?"; 4 | } 5 | 6 |

@ViewBag.Title.

7 | 8 | @using (Html.BeginForm("ForgotPassword", "Account", FormMethod.Post, new { @class = "form-horizontal", role = "form" })) 9 | { 10 | @Html.AntiForgeryToken() 11 |

Enter your email.

12 |
13 | @Html.ValidationSummary("", new { @class = "text-danger" }) 14 |
15 | @Html.LabelFor(m => m.Email, new { @class = "col-md-2 control-label" }) 16 |
17 | @Html.TextBoxFor(m => m.Email, new { @class = "form-control" }) 18 |
19 |
20 |
21 |
22 | 23 |
24 |
25 | } 26 | 27 | @section Scripts { 28 | @Scripts.Render("~/bundles/jqueryval") 29 | } 30 | -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Views/Access/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model IEnumerable 2 | @{ 3 | ViewBag.Title = "Access List"; 4 | } 5 | 6 |

Access List

7 | 8 | 9 | 10 | 13 | 16 | 17 | 18 | 19 | 20 | @foreach (var user in Model) 21 | { 22 | 23 | 24 | 30 | 33 | 34 | } 35 | 36 |
11 | @Html.DisplayNameFor(m => m.UserName) 12 | 14 | @Html.DisplayNameFor(m => m.Roles) 15 |
@Html.DisplayFor(m => user.UserName) 25 | @foreach (var role in user.Roles) 26 | { 27 | @Html.DisplayFor(m => role)   28 | } 29 | 31 | @Html.ActionLink("Edit", "Edit", new { id = user.UserId }) 32 |
37 | -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Views/Manage/VerifyPhoneNumber.cshtml: -------------------------------------------------------------------------------- 1 | @model DynamicRoleBasedAccess.Models.VerifyPhoneNumberViewModel 2 | @{ 3 | ViewBag.Title = "Verify Phone Number"; 4 | } 5 | 6 |

@ViewBag.Title.

7 | 8 | @using (Html.BeginForm("VerifyPhoneNumber", "Manage", FormMethod.Post, new { @class = "form-horizontal", role = "form" })) 9 | { 10 | @Html.AntiForgeryToken() 11 | @Html.Hidden("phoneNumber", @Model.PhoneNumber) 12 |

Enter verification code

13 |
@ViewBag.Status
14 |
15 | @Html.ValidationSummary("", new { @class = "text-danger" }) 16 |
17 | @Html.LabelFor(m => m.Code, new { @class = "col-md-2 control-label" }) 18 |
19 | @Html.TextBoxFor(m => m.Code, new { @class = "form-control" }) 20 |
21 |
22 |
23 |
24 | 25 |
26 |
27 | } 28 | 29 | @section Scripts { 30 | @Scripts.Render("~/bundles/jqueryval") 31 | } 32 | -------------------------------------------------------------------------------- /WebApplication2.sln.GhostDoc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | *.min.js 4 | jquery*.js 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | .\Help 17 | WebApplication2 18 | 19 | 20 | true 21 | false 22 | false 23 | false 24 | 25 | 26 | true 27 | false 28 | false 29 | false 30 | true 31 | true 32 | false 33 | 34 | 35 | true 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Views/Account/_ExternalLoginsListPartial.cshtml: -------------------------------------------------------------------------------- 1 | @model DynamicRoleBasedAccess.Models.ExternalLoginListViewModel 2 | @using Microsoft.Owin.Security 3 | 4 |

Use another service to log in.

5 |
6 | @{ 7 | var loginProviders = Context.GetOwinContext().Authentication.GetExternalAuthenticationTypes(); 8 | if (loginProviders.Count() == 0) { 9 |
10 |

11 | There are no external authentication services configured. See this article 12 | for details on setting up this ASP.NET application to support logging in via external services. 13 |

14 |
15 | } 16 | else { 17 | using (Html.BeginForm("ExternalLogin", "Account", new { ReturnUrl = Model.ReturnUrl })) { 18 | @Html.AntiForgeryToken() 19 |
20 |

21 | @foreach (AuthenticationDescription p in loginProviders) { 22 | 23 | } 24 |

25 |
26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Web.Debug.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 17 | 18 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/App_Start/BundleConfig.cs: -------------------------------------------------------------------------------- 1 | using System.Web.Optimization; 2 | 3 | namespace DynamicRoleBasedAccess 4 | { 5 | public class BundleConfig 6 | { 7 | // For more information on bundling, visit http://go.microsoft.com/fwlink/?LinkId=301862 8 | public static void RegisterBundles(BundleCollection bundles) 9 | { 10 | bundles.Add(new ScriptBundle("~/bundles/jquery").Include( 11 | "~/Scripts/jquery-{version}.js")); 12 | 13 | bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include( 14 | "~/Scripts/jquery.validate*")); 15 | 16 | // Use the development version of Modernizr to develop with and learn from. Then, when you're 17 | // ready for production, use the build tool at http://modernizr.com to pick only the tests you need. 18 | bundles.Add(new ScriptBundle("~/bundles/modernizr").Include( 19 | "~/Scripts/modernizr-*")); 20 | 21 | bundles.Add(new ScriptBundle("~/bundles/bootstrap").Include( 22 | "~/Scripts/bootstrap.js", 23 | "~/Scripts/respond.js")); 24 | 25 | bundles.Add(new StyleBundle("~/Content/css").Include( 26 | "~/Content/bootstrap.css", 27 | "~/Content/site.css")); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Web.Release.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 17 | 18 | 19 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Views/Account/VerifyCode.cshtml: -------------------------------------------------------------------------------- 1 | @model DynamicRoleBasedAccess.Models.VerifyCodeViewModel 2 | @{ 3 | ViewBag.Title = "Verify"; 4 | } 5 | 6 |

@ViewBag.Title.

7 | 8 | @using (Html.BeginForm("VerifyCode", "Account", new { ReturnUrl = Model.ReturnUrl }, FormMethod.Post, new { @class = "form-horizontal", role = "form" })) { 9 | @Html.AntiForgeryToken() 10 | @Html.Hidden("provider", @Model.Provider) 11 | @Html.Hidden("rememberMe", @Model.RememberMe) 12 |

Enter verification code

13 |
14 | @Html.ValidationSummary("", new { @class = "text-danger" }) 15 |
16 | @Html.LabelFor(m => m.Code, new { @class = "col-md-2 control-label" }) 17 |
18 | @Html.TextBoxFor(m => m.Code, new { @class = "form-control" }) 19 |
20 |
21 |
22 |
23 |
24 | @Html.CheckBoxFor(m => m.RememberBrowser) 25 | @Html.LabelFor(m => m.RememberBrowser) 26 |
27 |
28 |
29 |
30 |
31 | 32 |
33 |
34 | } 35 | 36 | @section Scripts { 37 | @Scripts.Render("~/bundles/jqueryval") 38 | } 39 | -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Views/Access/Edit.cshtml: -------------------------------------------------------------------------------- 1 | @model DynamicRoleBasedAccess.Models.EditUserRoleViewModel 2 | 3 | @{ 4 | ViewBag.Title = "Edit User Access"; 5 | } 6 | 7 | @section Header{ 8 | 11 | } 12 | 13 |

Edit User Access

14 | 15 | @using (Html.BeginForm("Edit", "Access", FormMethod.Post, new {@class = "form-horizontal", role = "form"})) 16 | { 17 | @Html.AntiForgeryToken() 18 | @Html.HiddenFor(m => m.UserId) 19 | @Html.HiddenFor(m => m.UserName) 20 | @Html.ValidationSummary() 21 |
22 | @Html.LabelFor(m => m.UserName, new {@class = "col-md-2 control-label"}) 23 |
24 | @Html.DisplayFor(m => m.UserName) 25 |
26 |
27 | 28 |
29 | 30 |
31 | @foreach (var role in Model.Roles) 32 | { 33 | 34 | } 35 |
36 |
37 | 38 |
39 |
40 | 41 |
42 |
43 | } -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Views/Manage/SetPassword.cshtml: -------------------------------------------------------------------------------- 1 | @model DynamicRoleBasedAccess.Models.SetPasswordViewModel 2 | @{ 3 | ViewBag.Title = "Create Password"; 4 | } 5 | 6 |

@ViewBag.Title.

7 |

8 | You do not have a local username/password for this site. Add a local 9 | account so you can log in without an external login. 10 |

11 | 12 | @using (Html.BeginForm("SetPassword", "Manage", FormMethod.Post, new { @class = "form-horizontal", role = "form" })) 13 | { 14 | @Html.AntiForgeryToken() 15 | 16 |

Create Local Login

17 |
18 | @Html.ValidationSummary("", new { @class = "text-danger" }) 19 |
20 | @Html.LabelFor(m => m.NewPassword, new { @class = "col-md-2 control-label" }) 21 |
22 | @Html.PasswordFor(m => m.NewPassword, new { @class = "form-control" }) 23 |
24 |
25 |
26 | @Html.LabelFor(m => m.ConfirmPassword, new { @class = "col-md-2 control-label" }) 27 |
28 | @Html.PasswordFor(m => m.ConfirmPassword, new { @class = "form-control" }) 29 |
30 |
31 |
32 |
33 | 34 |
35 |
36 | } 37 | @section Scripts { 38 | @Scripts.Render("~/bundles/jqueryval") 39 | } -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Views/Account/ExternalLoginConfirmation.cshtml: -------------------------------------------------------------------------------- 1 | @model DynamicRoleBasedAccess.Models.ExternalLoginConfirmationViewModel 2 | @{ 3 | ViewBag.Title = "Register"; 4 | } 5 |

@ViewBag.Title.

6 |

Associate your @ViewBag.LoginProvider account.

7 | 8 | @using (Html.BeginForm("ExternalLoginConfirmation", "Account", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post, new { @class = "form-horizontal", role = "form" })) 9 | { 10 | @Html.AntiForgeryToken() 11 | 12 |

Association Form

13 |
14 | @Html.ValidationSummary(true, "", new { @class = "text-danger" }) 15 |

16 | You've successfully authenticated with @ViewBag.LoginProvider. 17 | Please enter a user name for this site below and click the Register button to finish 18 | logging in. 19 |

20 |
21 | @Html.LabelFor(m => m.Email, new { @class = "col-md-2 control-label" }) 22 |
23 | @Html.TextBoxFor(m => m.Email, new { @class = "form-control" }) 24 | @Html.ValidationMessageFor(m => m.Email, "", new { @class = "text-danger" }) 25 |
26 |
27 |
28 |
29 | 30 |
31 |
32 | } 33 | 34 | @section Scripts { 35 | @Scripts.Render("~/bundles/jqueryval") 36 | } 37 | -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("DynamicRoleBasedAccess")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("DynamicRoleBasedAccess")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("53c66e69-bfa2-4bc0-9506-6e050354b60d")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Revision and Build Numbers 33 | // by using the '*' as shown below: 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Views/Account/Register.cshtml: -------------------------------------------------------------------------------- 1 | @model DynamicRoleBasedAccess.Models.RegisterViewModel 2 | @{ 3 | ViewBag.Title = "Register"; 4 | } 5 | 6 |

@ViewBag.Title.

7 | 8 | @using (Html.BeginForm("Register", "Account", FormMethod.Post, new { @class = "form-horizontal", role = "form" })) 9 | { 10 | @Html.AntiForgeryToken() 11 |

Create a new account.

12 |
13 | @Html.ValidationSummary("", new { @class = "text-danger" }) 14 |
15 | @Html.LabelFor(m => m.Email, new { @class = "col-md-2 control-label" }) 16 |
17 | @Html.TextBoxFor(m => m.Email, new { @class = "form-control" }) 18 |
19 |
20 |
21 | @Html.LabelFor(m => m.Password, new { @class = "col-md-2 control-label" }) 22 |
23 | @Html.PasswordFor(m => m.Password, new { @class = "form-control" }) 24 |
25 |
26 |
27 | @Html.LabelFor(m => m.ConfirmPassword, new { @class = "col-md-2 control-label" }) 28 |
29 | @Html.PasswordFor(m => m.ConfirmPassword, new { @class = "form-control" }) 30 |
31 |
32 |
33 |
34 | 35 |
36 |
37 | } 38 | 39 | @section Scripts { 40 | @Scripts.Render("~/bundles/jqueryval") 41 | } 42 | -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewBag.Title = "Home Page"; 3 | } 4 | 5 |
6 |

ASP.NET

7 |

ASP.NET is a free web framework for building great Web sites and Web applications using HTML, CSS and JavaScript.

8 |

Learn more »

9 |
10 | 11 |
12 |
13 |

Getting started

14 |

15 | ASP.NET MVC gives you a powerful, patterns-based way to build dynamic websites that 16 | enables a clean separation of concerns and gives you full control over markup 17 | for enjoyable, agile development. 18 |

19 |

Learn more »

20 |
21 |
22 |

Get more libraries

23 |

NuGet is a free Visual Studio extension that makes it easy to add, remove, and update libraries and tools in Visual Studio projects.

24 |

Learn more »

25 |
26 |
27 |

Web Hosting

28 |

You can easily find a web hosting company that offers the right mix of features and price for your applications.

29 |

Learn more »

30 |
31 |
-------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Views/Manage/ChangePassword.cshtml: -------------------------------------------------------------------------------- 1 | @model DynamicRoleBasedAccess.Models.ChangePasswordViewModel 2 | @{ 3 | ViewBag.Title = "Change Password"; 4 | } 5 | 6 |

@ViewBag.Title.

7 | 8 | @using (Html.BeginForm("ChangePassword", "Manage", FormMethod.Post, new { @class = "form-horizontal", role = "form" })) 9 | { 10 | @Html.AntiForgeryToken() 11 |

Change Password Form

12 |
13 | @Html.ValidationSummary("", new { @class = "text-danger" }) 14 |
15 | @Html.LabelFor(m => m.OldPassword, new { @class = "col-md-2 control-label" }) 16 |
17 | @Html.PasswordFor(m => m.OldPassword, new { @class = "form-control" }) 18 |
19 |
20 |
21 | @Html.LabelFor(m => m.NewPassword, new { @class = "col-md-2 control-label" }) 22 |
23 | @Html.PasswordFor(m => m.NewPassword, new { @class = "form-control" }) 24 |
25 |
26 |
27 | @Html.LabelFor(m => m.ConfirmPassword, new { @class = "col-md-2 control-label" }) 28 |
29 | @Html.PasswordFor(m => m.ConfirmPassword, new { @class = "form-control" }) 30 |
31 |
32 |
33 |
34 | 35 |
36 |
37 | } 38 | @section Scripts { 39 | @Scripts.Render("~/bundles/jqueryval") 40 | } -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Views/Account/ResetPassword.cshtml: -------------------------------------------------------------------------------- 1 | @model DynamicRoleBasedAccess.Models.ResetPasswordViewModel 2 | @{ 3 | ViewBag.Title = "Reset password"; 4 | } 5 | 6 |

@ViewBag.Title.

7 | 8 | @using (Html.BeginForm("ResetPassword", "Account", FormMethod.Post, new { @class = "form-horizontal", role = "form" })) 9 | { 10 | @Html.AntiForgeryToken() 11 |

Reset your password.

12 |
13 | @Html.ValidationSummary("", new { @class = "text-danger" }) 14 | @Html.HiddenFor(model => model.Code) 15 |
16 | @Html.LabelFor(m => m.Email, new { @class = "col-md-2 control-label" }) 17 |
18 | @Html.TextBoxFor(m => m.Email, new { @class = "form-control" }) 19 |
20 |
21 |
22 | @Html.LabelFor(m => m.Password, new { @class = "col-md-2 control-label" }) 23 |
24 | @Html.PasswordFor(m => m.Password, new { @class = "form-control" }) 25 |
26 |
27 |
28 | @Html.LabelFor(m => m.ConfirmPassword, new { @class = "col-md-2 control-label" }) 29 |
30 | @Html.PasswordFor(m => m.ConfirmPassword, new { @class = "form-control" }) 31 |
32 |
33 |
34 |
35 | 36 |
37 |
38 | } 39 | 40 | @section Scripts { 41 | @Scripts.Render("~/bundles/jqueryval") 42 | } 43 | -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | @ViewBag.Title - My ASP.NET Application 7 | @Styles.Render("~/Content/css") 8 | @RenderSection("Header", required: false) 9 | 10 | 11 | 33 |
34 | @RenderBody() 35 |
36 |
37 |

© @DateTime.Now.Year - My ASP.NET Application

38 |
39 |
40 | 41 | @Scripts.Render("~/bundles/jquery") 42 | @Scripts.Render("~/bundles/jqueryval") 43 | @Scripts.Render("~/bundles/bootstrap") 44 | @RenderSection("scripts", required: false) 45 | 46 | -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Views/Web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Views/Role/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model IEnumerable 2 | 3 | @{ 4 | ViewBag.Title = "Role List"; 5 | } 6 | 7 |

Role List

8 | @Html.ActionLink("Create Role", "Create", "Role") 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | @foreach (var role in Model) 20 | { 21 | 22 | 23 | 27 | 28 | } 29 | 30 |
13 | @Html.DisplayNameFor(m => m.Name) 14 |
@Html.DisplayFor(m => role.Name) 24 | @Html.ActionLink("Edit", "Edit", new { id = role.Id }) | 25 | @Html.ActionLink("Delete", "Delete", new {id = role.Id}, new { @class="delete-item" }) 26 |
31 | 32 | @section scripts { 33 | 59 | } 60 | -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Filters/CustomAuthorizeAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Web; 4 | using System.Web.Mvc; 5 | using DynamicRoleBasedAccess.Models; 6 | using Microsoft.AspNet.Identity.Owin; 7 | 8 | namespace DynamicRoleBasedAccess.Filters 9 | { 10 | public class CustomAuthorizeAttribute : AuthorizeAttribute 11 | { 12 | private string _requestControllerName; 13 | private string _requestedActionName; 14 | 15 | public override void OnAuthorization(AuthorizationContext filterContext) 16 | { 17 | _requestControllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName; 18 | _requestedActionName = filterContext.ActionDescriptor.ActionName; 19 | 20 | base.OnAuthorization(filterContext); 21 | } 22 | 23 | protected override bool AuthorizeCore(HttpContextBase httpContext) 24 | { 25 | if (httpContext == null) 26 | throw new ArgumentNullException("httpContext"); 27 | 28 | var user = httpContext.User; 29 | if (!user.Identity.IsAuthenticated) 30 | return false; 31 | 32 | var dbContext = httpContext.GetOwinContext().Get(); 33 | var roleAccess = from ra in dbContext.RoleAccesses 34 | let userId = dbContext.Users.FirstOrDefault(u => u.UserName == user.Identity.Name).Id 35 | let roleIds = dbContext.Roles.Where(r => r.Users.Any(u => u.UserId == userId)).Select(r => r.Id) 36 | where roleIds.Contains(ra.RoleId) 37 | select ra; 38 | 39 | if (roleAccess.Any(ra => 40 | ra.Controller.Equals(_requestControllerName, StringComparison.InvariantCultureIgnoreCase) && 41 | ra.Action.Equals(_requestedActionName, StringComparison.InvariantCultureIgnoreCase))) 42 | return true; 43 | 44 | return false; 45 | } 46 | 47 | protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) 48 | { 49 | if (filterContext.HttpContext.Request.IsAuthenticated) 50 | { 51 | filterContext.Result = new HttpStatusCodeResult(403); 52 | return; 53 | } 54 | 55 | base.HandleUnauthorizedRequest(filterContext); 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Models/IdentityModels.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Data.Entity; 3 | using System.Security.Claims; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNet.Identity; 6 | using Microsoft.AspNet.Identity.EntityFramework; 7 | 8 | namespace DynamicRoleBasedAccess.Models 9 | { 10 | // You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more. 11 | public class ApplicationUser : IdentityUser 12 | { 13 | public async Task GenerateUserIdentityAsync(UserManager manager) 14 | { 15 | // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType 16 | var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie); 17 | // Add custom user claims here 18 | return userIdentity; 19 | } 20 | } 21 | 22 | public class ApplicationRole : IdentityRole 23 | { 24 | public virtual ICollection RoleAccesses { get; set; } 25 | } 26 | 27 | public class RoleAccess 28 | { 29 | public int Id { get; set; } 30 | 31 | public string Controller { get; set; } 32 | 33 | public string Action { get; set; } 34 | 35 | public string RoleId { get; set; } 36 | 37 | public virtual ApplicationRole Role { get; set; } 38 | } 39 | 40 | public class ApplicationDbContext : IdentityDbContext 41 | { 42 | public ApplicationDbContext() 43 | : base("DefaultConnection", throwIfV1Schema: false) 44 | { 45 | } 46 | 47 | public static ApplicationDbContext Create() 48 | { 49 | return new ApplicationDbContext(); 50 | } 51 | 52 | public DbSet RoleAccesses { get; set; } 53 | 54 | protected override void OnModelCreating(DbModelBuilder modelBuilder) 55 | { 56 | modelBuilder.Entity().Property(ra => ra.Action) 57 | .IsUnicode(false).HasMaxLength(70).IsRequired(); 58 | modelBuilder.Entity().Property(ra => ra.Controller) 59 | .IsUnicode(false).HasMaxLength(70).IsRequired(); 60 | modelBuilder.Entity().HasRequired(ra => ra.Role) 61 | .WithMany(r => r.RoleAccesses) 62 | .HasForeignKey(ra => ra.RoleId); 63 | 64 | base.OnModelCreating(modelBuilder); 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Views/Account/Login.cshtml: -------------------------------------------------------------------------------- 1 | @using DynamicRoleBasedAccess.Models 2 | @model DynamicRoleBasedAccess.Models.LoginViewModel 3 | @{ 4 | ViewBag.Title = "Log in"; 5 | } 6 | 7 |

@ViewBag.Title.

8 |
9 |
10 |
11 | @using (Html.BeginForm("Login", "Account", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post, new { @class = "form-horizontal", role = "form" })) 12 | { 13 | @Html.AntiForgeryToken() 14 |

Use a local account to log in.

15 |
16 | @Html.ValidationSummary(true, "", new { @class = "text-danger" }) 17 |
18 | @Html.LabelFor(m => m.Email, new { @class = "col-md-2 control-label" }) 19 |
20 | @Html.TextBoxFor(m => m.Email, new { @class = "form-control" }) 21 | @Html.ValidationMessageFor(m => m.Email, "", new { @class = "text-danger" }) 22 |
23 |
24 |
25 | @Html.LabelFor(m => m.Password, new { @class = "col-md-2 control-label" }) 26 |
27 | @Html.PasswordFor(m => m.Password, new { @class = "form-control" }) 28 | @Html.ValidationMessageFor(m => m.Password, "", new { @class = "text-danger" }) 29 |
30 |
31 |
32 |
33 |
34 | @Html.CheckBoxFor(m => m.RememberMe) 35 | @Html.LabelFor(m => m.RememberMe) 36 |
37 |
38 |
39 |
40 |
41 | 42 |
43 |
44 |

45 | @Html.ActionLink("Register as a new user", "Register") 46 |

47 | @* Enable this once you have account confirmation enabled for password reset functionality 48 |

49 | @Html.ActionLink("Forgot your password?", "ForgotPassword") 50 |

*@ 51 | } 52 |
53 |
54 |
55 |
56 | @Html.Partial("_ExternalLoginsListPartial", new ExternalLoginListViewModel { ReturnUrl = ViewBag.ReturnUrl }) 57 |
58 |
59 |
60 | 61 | @section Scripts { 62 | @Scripts.Render("~/bundles/jqueryval") 63 | } -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Models/ManageViewModels.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel.DataAnnotations; 3 | using Microsoft.AspNet.Identity; 4 | using Microsoft.Owin.Security; 5 | 6 | namespace DynamicRoleBasedAccess.Models 7 | { 8 | public class IndexViewModel 9 | { 10 | public bool HasPassword { get; set; } 11 | public IList Logins { get; set; } 12 | public string PhoneNumber { get; set; } 13 | public bool TwoFactor { get; set; } 14 | public bool BrowserRemembered { get; set; } 15 | } 16 | 17 | public class ManageLoginsViewModel 18 | { 19 | public IList CurrentLogins { get; set; } 20 | public IList OtherLogins { get; set; } 21 | } 22 | 23 | public class FactorViewModel 24 | { 25 | public string Purpose { get; set; } 26 | } 27 | 28 | public class SetPasswordViewModel 29 | { 30 | [Required] 31 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] 32 | [DataType(DataType.Password)] 33 | [Display(Name = "New password")] 34 | public string NewPassword { get; set; } 35 | 36 | [DataType(DataType.Password)] 37 | [Display(Name = "Confirm new password")] 38 | [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] 39 | public string ConfirmPassword { get; set; } 40 | } 41 | 42 | public class ChangePasswordViewModel 43 | { 44 | [Required] 45 | [DataType(DataType.Password)] 46 | [Display(Name = "Current password")] 47 | public string OldPassword { get; set; } 48 | 49 | [Required] 50 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] 51 | [DataType(DataType.Password)] 52 | [Display(Name = "New password")] 53 | public string NewPassword { get; set; } 54 | 55 | [DataType(DataType.Password)] 56 | [Display(Name = "Confirm new password")] 57 | [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] 58 | public string ConfirmPassword { get; set; } 59 | } 60 | 61 | public class AddPhoneNumberViewModel 62 | { 63 | [Required] 64 | [Phone] 65 | [Display(Name = "Phone Number")] 66 | public string Number { get; set; } 67 | } 68 | 69 | public class VerifyPhoneNumberViewModel 70 | { 71 | [Required] 72 | [Display(Name = "Code")] 73 | public string Code { get; set; } 74 | 75 | [Required] 76 | [Phone] 77 | [Display(Name = "Phone Number")] 78 | public string PhoneNumber { get; set; } 79 | } 80 | 81 | public class ConfigureTwoFactorViewModel 82 | { 83 | public string SelectedProvider { get; set; } 84 | public ICollection Providers { get; set; } 85 | } 86 | } -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Views/Manage/ManageLogins.cshtml: -------------------------------------------------------------------------------- 1 | @model DynamicRoleBasedAccess.Models.ManageLoginsViewModel 2 | @using Microsoft.Owin.Security 3 | @{ 4 | ViewBag.Title = "Manage your external logins"; 5 | } 6 | 7 |

@ViewBag.Title.

8 | 9 |

@ViewBag.StatusMessage

10 | @{ 11 | var loginProviders = Context.GetOwinContext().Authentication.GetExternalAuthenticationTypes(); 12 | if (loginProviders.Count() == 0) { 13 |
14 |

15 | There are no external authentication services configured. See this article 16 | for details on setting up this ASP.NET application to support logging in via external services. 17 |

18 |
19 | } 20 | else 21 | { 22 | if (Model.CurrentLogins.Count > 0) 23 | { 24 |

Registered Logins

25 | 26 | 27 | @foreach (var account in Model.CurrentLogins) 28 | { 29 | 30 | 31 | 49 | 50 | } 51 | 52 |
@account.LoginProvider 32 | @if (ViewBag.ShowRemoveButton) 33 | { 34 | using (Html.BeginForm("RemoveLogin", "Manage")) 35 | { 36 | @Html.AntiForgeryToken() 37 |
38 | @Html.Hidden("loginProvider", account.LoginProvider) 39 | @Html.Hidden("providerKey", account.ProviderKey) 40 | 41 |
42 | } 43 | } 44 | else 45 | { 46 | @:   47 | } 48 |
53 | } 54 | if (Model.OtherLogins.Count > 0) 55 | { 56 | using (Html.BeginForm("LinkLogin", "Manage")) 57 | { 58 | @Html.AntiForgeryToken() 59 |
60 |

61 | @foreach (AuthenticationDescription p in Model.OtherLogins) 62 | { 63 | 64 | } 65 |

66 |
67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Views/Manage/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model DynamicRoleBasedAccess.Models.IndexViewModel 2 | @{ 3 | ViewBag.Title = "Manage"; 4 | } 5 | 6 |

@ViewBag.Title.

7 | 8 |

@ViewBag.StatusMessage

9 |
10 |

Change your account settings

11 |
12 |
13 |
Password:
14 |
15 | [ 16 | @if (Model.HasPassword) 17 | { 18 | @Html.ActionLink("Change your password", "ChangePassword") 19 | } 20 | else 21 | { 22 | @Html.ActionLink("Create", "SetPassword") 23 | } 24 | ] 25 |
26 |
External Logins:
27 |
28 | @Model.Logins.Count [ 29 | @Html.ActionLink("Manage", "ManageLogins") ] 30 |
31 | @* 32 | Phone Numbers can used as a second factor of verification in a two-factor authentication system. 33 | 34 | See this article 35 | for details on setting up this ASP.NET application to support two-factor authentication using SMS. 36 | 37 | Uncomment the following block after you have set up two-factor authentication 38 | *@ 39 | @* 40 |
Phone Number:
41 |
42 | @(Model.PhoneNumber ?? "None") [ 43 | @if (Model.PhoneNumber != null) 44 | { 45 | @Html.ActionLink("Change", "AddPhoneNumber") 46 | @:  |  47 | @Html.ActionLink("Remove", "RemovePhoneNumber") 48 | } 49 | else 50 | { 51 | @Html.ActionLink("Add", "AddPhoneNumber") 52 | } 53 | ] 54 |
55 | *@ 56 |
Two-Factor Authentication:
57 |
58 |

59 | There are no two-factor authentication providers configured. See this article 60 | for details on setting up this ASP.NET application to support two-factor authentication. 61 |

62 | @*@if (Model.TwoFactor) 63 | { 64 | using (Html.BeginForm("DisableTwoFactorAuthentication", "Manage", FormMethod.Post, new { @class = "form-horizontal", role = "form" })) 65 | { 66 | @Html.AntiForgeryToken() 67 | Enabled 68 | 69 | 70 | } 71 | } 72 | else 73 | { 74 | using (Html.BeginForm("EnableTwoFactorAuthentication", "Manage", FormMethod.Post, new { @class = "form-horizontal", role = "form" })) 75 | { 76 | @Html.AntiForgeryToken() 77 | Disabled 78 | 79 | 80 | } 81 | }*@ 82 |
83 |
84 |
85 | -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Controllers/AccessController.cs: -------------------------------------------------------------------------------- 1 | using DynamicRoleBasedAccess.Models; 2 | using Microsoft.AspNet.Identity.EntityFramework; 3 | using Microsoft.AspNet.Identity.Owin; 4 | using System.ComponentModel; 5 | using System.Data.Entity; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | using System.Web; 9 | using System.Web.Mvc; 10 | 11 | namespace DynamicRoleBasedAccess.Controllers 12 | { 13 | [Description("Access")] 14 | //[CustomAuthorize] 15 | public class AccessController : Controller 16 | { 17 | private ApplicationDbContext _dbContext; 18 | 19 | // GET: Access 20 | [Description("Access List")] 21 | public async Task Index() 22 | { 23 | _dbContext = HttpContext.GetOwinContext().Get(); 24 | var users = await (from u in _dbContext.Users 25 | select new 26 | { 27 | UserId = u.Id, 28 | u.UserName, 29 | Roles = _dbContext.Roles.Where(r => u.Roles.Any(ur => ur.RoleId == r.Id)).Select(r => r.Name) 30 | }).Select(ra => new UserRoleViewModel 31 | { 32 | UserId = ra.UserId, 33 | UserName = ra.UserName, 34 | Roles = ra.Roles 35 | }).ToListAsync(); 36 | 37 | return View(users); 38 | } 39 | 40 | // GET: Access/Edit 41 | [Description("Edit Access")] 42 | public async Task Edit(string id) 43 | { 44 | _dbContext = HttpContext.GetOwinContext().Get(); 45 | var user = await (from u in _dbContext.Users 46 | select new 47 | { 48 | UserId = u.Id, 49 | u.UserName, 50 | Roles = _dbContext.Roles.Where(r => u.Roles.Any(ur => ur.RoleId == r.Id)).Select(r => r.Name) 51 | }).SingleOrDefaultAsync(u => u.UserId == id); 52 | if (user == null) 53 | return HttpNotFound(); 54 | 55 | var roles = await _dbContext.Roles.ToListAsync(); 56 | 57 | var viewModel = new EditUserRoleViewModel 58 | { 59 | UserId = user.UserId, 60 | UserName = user.UserName, 61 | SelectedRoles = user.Roles, 62 | Roles = roles 63 | }; 64 | 65 | return View(viewModel); 66 | } 67 | 68 | // POST: Access/Edit 69 | [HttpPost] 70 | [ValidateAntiForgeryToken] 71 | public async Task Edit(EditUserRoleViewModel viewModel) 72 | { 73 | _dbContext = HttpContext.GetOwinContext().Get(); 74 | if (!ModelState.IsValid) 75 | { 76 | viewModel.Roles = await _dbContext.Roles.ToListAsync(); 77 | return View(viewModel); 78 | } 79 | 80 | var user = _dbContext.Users.Find(viewModel.UserId); 81 | user.Roles.Clear(); 82 | foreach (var roleId in viewModel.SelectedRoles) 83 | { 84 | user.Roles.Add(new IdentityUserRole { RoleId = roleId }); 85 | } 86 | await _dbContext.SaveChangesAsync(); 87 | 88 | return RedirectToAction("Index"); 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/App_Start/Startup.Auth.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using DynamicRoleBasedAccess.Models; 3 | using Microsoft.AspNet.Identity; 4 | using Microsoft.AspNet.Identity.Owin; 5 | using Microsoft.Owin; 6 | using Microsoft.Owin.Security.Cookies; 7 | using Owin; 8 | 9 | namespace DynamicRoleBasedAccess 10 | { 11 | public partial class Startup 12 | { 13 | // For more information on configuring authentication, please visit http://go.microsoft.com/fwlink/?LinkId=301864 14 | public void ConfigureAuth(IAppBuilder app) 15 | { 16 | // Configure the db context, user manager and signin manager to use a single instance per request 17 | app.CreatePerOwinContext(ApplicationDbContext.Create); 18 | app.CreatePerOwinContext(ApplicationUserManager.Create); 19 | app.CreatePerOwinContext(ApplicationSignInManager.Create); 20 | app.CreatePerOwinContext(ApplicationRoleManager.Create); 21 | 22 | // Enable the application to use a cookie to store information for the signed in user 23 | // and to use a cookie to temporarily store information about a user logging in with a third party login provider 24 | // Configure the sign in cookie 25 | app.UseCookieAuthentication(new CookieAuthenticationOptions 26 | { 27 | AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, 28 | LoginPath = new PathString("/Account/Login"), 29 | Provider = new CookieAuthenticationProvider 30 | { 31 | // Enables the application to validate the security stamp when the user logs in. 32 | // This is a security feature which is used when you change a password or add an external login to your account. 33 | OnValidateIdentity = SecurityStampValidator.OnValidateIdentity( 34 | validateInterval: TimeSpan.FromMinutes(30), 35 | regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager)) 36 | } 37 | }); 38 | app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie); 39 | 40 | // Enables the application to temporarily store user information when they are verifying the second factor in the two-factor authentication process. 41 | app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5)); 42 | 43 | // Enables the application to remember the second login verification factor such as phone or email. 44 | // Once you check this option, your second step of verification during the login process will be remembered on the device where you logged in from. 45 | // This is similar to the RememberMe option when you log in. 46 | app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie); 47 | 48 | // Uncomment the following lines to enable logging in with third party login providers 49 | //app.UseMicrosoftAccountAuthentication( 50 | // clientId: "", 51 | // clientSecret: ""); 52 | 53 | //app.UseTwitterAuthentication( 54 | // consumerKey: "", 55 | // consumerSecret: ""); 56 | 57 | //app.UseFacebookAuthentication( 58 | // appId: "", 59 | // appSecret: ""); 60 | 61 | //app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions() 62 | //{ 63 | // ClientId = "", 64 | // ClientSecret = "" 65 | //}); 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Views/Role/Create.cshtml: -------------------------------------------------------------------------------- 1 | @model DynamicRoleBasedAccess.Models.CreateRoleViewModel 2 | @{ 3 | ViewBag.Title = "Create Role"; 4 | int i = 0, j = 0; 5 | } 6 | @section header { 7 | @Styles.Render("~/Content/jquery.bonsai.css") 8 | } 9 | 10 |

Create Role

11 | 12 | @using (Html.BeginForm("Create", "Role", FormMethod.Post, new {@class = "form-horizontal", role = "form"})) 13 | { 14 | @Html.AntiForgeryToken() 15 |
16 | @Html.LabelFor(m => m.Name, new {@class = "col-md-2 control-label"}) 17 |
18 | @Html.TextBoxFor(m => m.Name, new {@class = "form-control"}) 19 | @Html.ValidationMessageFor(m => m.Name, null, new {@class = "danger"}) 20 |
21 |
22 | 23 |
24 | 25 |
26 |
    27 | @foreach (var controller in Model.Controllers) 28 | { 29 | string name; 30 | { 31 | name = string.IsNullOrWhiteSpace(controller.Description) ? controller.Name : controller.Description; 32 | } 33 |
  1. 34 | @name 35 | @if (controller.Actions.Any()) 36 | { 37 |
      38 | @foreach (var action in controller.Actions) 39 | { 40 | { 41 | name = string.IsNullOrWhiteSpace(action.Description) ? action.Name : action.Description; 42 | } 43 |
    • @name
    • 44 | j++; 45 | } 46 |
    47 | } 48 | @{ j = 0; } 49 |
  2. 50 | i++; 51 | } 52 |
53 | 54 |
55 |
56 | 57 |
58 |
59 | 60 |
61 |
62 | } 63 | @section scripts{ 64 | @Scripts.Render("~/Scripts/jquery.bonsai.js") 65 | 96 | } -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Views/Role/Edit.cshtml: -------------------------------------------------------------------------------- 1 | @model DynamicRoleBasedAccess.Models.EditRoleViewModel 2 | @{ 3 | ViewBag.Title = "Edit Role"; 4 | int i = 0, j = 0; 5 | } 6 | @section header { 7 | @Styles.Render("~/Content/jquery.bonsai.css") 8 | } 9 | 10 |

Edit Role

11 | 12 | @using (Html.BeginForm("Edit", "Role", FormMethod.Post, new {@class = "form-horizontal", role = "form"})) 13 | { 14 | @Html.AntiForgeryToken() 15 | @Html.HiddenFor(m => m.Id) 16 |
17 | @Html.LabelFor(m => m.Name, new {@class = "col-md-2 control-label"}) 18 |
19 | @Html.TextBoxFor(m => m.Name, new {@class = "form-control"}) 20 | @Html.ValidationMessageFor(m => m.Name, null, new {@class = "danger"}) 21 |
22 |
23 | 24 |
25 | 26 |
27 |
    28 | @foreach (var controller in Model.Controllers) 29 | { 30 | string name; 31 | { 32 | name = string.IsNullOrWhiteSpace(controller.Description) ? controller.Name : controller.Description; 33 | } 34 |
  1. r.Controller == controller.Name)) { data-checked='1' }> 35 | @name 36 | @if (controller.Actions.Any()) 37 | { 38 |
      39 | @foreach (var action in controller.Actions) 40 | { 41 | { 42 | name = string.IsNullOrWhiteSpace(action.Description) ? action.Name : action.Description; 43 | } 44 |
    • r.Controller == controller.Name && r.Action == action.Name )) { data-checked='1' } >@name
    • 45 | j++; 46 | } 47 |
    48 | } 49 | @{ j = 0; } 50 |
  2. 51 | i++; 52 | } 53 |
54 | 55 |
56 |
57 | 58 |
59 |
60 | 61 |
62 |
63 | } 64 | @section scripts{ 65 | @Scripts.Render("~/Scripts/jquery.bonsai.js") 66 | 97 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | 28 | # MSTest test Results 29 | [Tt]est[Rr]esult*/ 30 | [Bb]uild[Ll]og.* 31 | 32 | # NUNIT 33 | *.VisualState.xml 34 | TestResult.xml 35 | 36 | # Build Results of an ATL Project 37 | [Dd]ebugPS/ 38 | [Rr]eleasePS/ 39 | dlldata.c 40 | 41 | # DNX 42 | project.lock.json 43 | artifacts/ 44 | 45 | *_i.c 46 | *_p.c 47 | *_i.h 48 | *.ilk 49 | *.meta 50 | *.obj 51 | *.pch 52 | *.pdb 53 | *.pgc 54 | *.pgd 55 | *.rsp 56 | *.sbr 57 | *.tlb 58 | *.tli 59 | *.tlh 60 | *.tmp 61 | *.tmp_proj 62 | *.log 63 | *.vspscc 64 | *.vssscc 65 | .builds 66 | *.pidb 67 | *.svclog 68 | *.scc 69 | 70 | # Chutzpah Test files 71 | _Chutzpah* 72 | 73 | # Visual C++ cache files 74 | ipch/ 75 | *.aps 76 | *.ncb 77 | *.opensdf 78 | *.sdf 79 | *.cachefile 80 | 81 | # Visual Studio profiler 82 | *.psess 83 | *.vsp 84 | *.vspx 85 | 86 | # TFS 2012 Local Workspace 87 | $tf/ 88 | 89 | # Guidance Automation Toolkit 90 | *.gpState 91 | 92 | # ReSharper is a .NET coding add-in 93 | _ReSharper*/ 94 | *.[Rr]e[Ss]harper 95 | *.DotSettings.user 96 | 97 | # JustCode is a .NET coding add-in 98 | .JustCode 99 | 100 | # TeamCity is a build add-in 101 | _TeamCity* 102 | 103 | # DotCover is a Code Coverage Tool 104 | *.dotCover 105 | 106 | # NCrunch 107 | _NCrunch_* 108 | .*crunch*.local.xml 109 | 110 | # MightyMoose 111 | *.mm.* 112 | AutoTest.Net/ 113 | 114 | # Web workbench (sass) 115 | .sass-cache/ 116 | 117 | # Installshield output folder 118 | [Ee]xpress/ 119 | 120 | # DocProject is a documentation generator add-in 121 | DocProject/buildhelp/ 122 | DocProject/Help/*.HxT 123 | DocProject/Help/*.HxC 124 | DocProject/Help/*.hhc 125 | DocProject/Help/*.hhk 126 | DocProject/Help/*.hhp 127 | DocProject/Help/Html2 128 | DocProject/Help/html 129 | 130 | # Click-Once directory 131 | publish/ 132 | 133 | # Publish Web Output 134 | *.[Pp]ublish.xml 135 | *.azurePubxml 136 | ## TODO: Comment the next line if you want to checkin your 137 | ## web deploy settings but do note that will include unencrypted 138 | ## passwords 139 | #*.pubxml 140 | 141 | *.publishproj 142 | 143 | # NuGet Packages 144 | *.nupkg 145 | # The packages folder can be ignored because of Package Restore 146 | **/packages/* 147 | # except build/, which is used as an MSBuild target. 148 | !**/packages/build/ 149 | # Uncomment if necessary however generally it will be regenerated when needed 150 | #!**/packages/repositories.config 151 | 152 | # Windows Azure Build Output 153 | csx/ 154 | *.build.csdef 155 | 156 | # Windows Store app package directory 157 | AppPackages/ 158 | 159 | # Visual Studio cache files 160 | # files ending in .cache can be ignored 161 | *.[Cc]ache 162 | # but keep track of directories ending in .cache 163 | !*.[Cc]ache/ 164 | 165 | # Others 166 | ClientBin/ 167 | [Ss]tyle[Cc]op.* 168 | ~$* 169 | *~ 170 | *.dbmdl 171 | *.dbproj.schemaview 172 | *.pfx 173 | *.publishsettings 174 | node_modules/ 175 | orleans.codegen.cs 176 | 177 | # RIA/Silverlight projects 178 | Generated_Code/ 179 | 180 | # Backup & report files from converting an old project file 181 | # to a newer Visual Studio version. Backup files are not needed, 182 | # because we have git ;-) 183 | _UpgradeReport_Files/ 184 | Backup*/ 185 | UpgradeLog*.XML 186 | UpgradeLog*.htm 187 | 188 | # SQL Server files 189 | *.mdf 190 | *.ldf 191 | 192 | # Business Intelligence projects 193 | *.rdl.data 194 | *.bim.layout 195 | *.bim_*.settings 196 | 197 | # Microsoft Fakes 198 | FakesAssemblies/ 199 | 200 | # Node.js Tools for Visual Studio 201 | .ntvs_analysis.dat 202 | 203 | # Visual Studio 6 build log 204 | *.plg 205 | 206 | # Visual Studio 6 workspace options file 207 | *.opt 208 | 209 | # LightSwitch generated files 210 | GeneratedArtifacts/ 211 | _Pvt_Extensions/ 212 | ModelManifest.xml 213 | -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Web.config: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Helpers/ControllerHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Web.Mvc; 7 | 8 | namespace DynamicRoleBasedAccess.Helpers 9 | { 10 | /// 11 | /// A Utility class for MVC controllers. 12 | /// 13 | public class ControllerHelper 14 | { 15 | /// 16 | /// Gets the controllers name an description with their actions. 17 | /// 18 | /// The filter. 19 | /// Lazy<IEnumerable<ControllerDescription>>. 20 | public IEnumerable GetControllersNameAnDescription(string filter = null) 21 | { 22 | var assembly = Assembly.GetCallingAssembly(); 23 | var controllers = assembly.GetTypes() 24 | .Where(type => type.IsSubclassOf(typeof(Controller))) 25 | .ToList(); 26 | 27 | if (!string.IsNullOrWhiteSpace(filter)) 28 | controllers = controllers.Where(t => !t.Name.Contains(filter)).ToList(); 29 | 30 | var controllerList = ( 31 | from controller in controllers 32 | let attribute = (DescriptionAttribute)Attribute.GetCustomAttribute(controller, typeof(DescriptionAttribute)) 33 | let ctrlDescription = attribute == null ? "" : attribute.Description 34 | select new ControllerDescription 35 | { 36 | Name = controller.Name.Replace("Controller", ""), 37 | Description = ctrlDescription, 38 | Actions = GetActionList(controller) 39 | } 40 | ).ToList(); 41 | 42 | return controllerList; 43 | } 44 | 45 | /// 46 | /// Gets the action list. 47 | /// 48 | /// Type of the controller. 49 | /// IEnumerable<ActionDescription>. 50 | private static IEnumerable GetActionList(Type controllerType) 51 | { 52 | var actions = new ReflectedControllerDescriptor(controllerType).GetCanonicalActions().ToList(); 53 | 54 | var actionList = (from actionDescriptor in actions 55 | let attribute = actionDescriptor.GetCustomAttributes(typeof(DescriptionAttribute), false).LastOrDefault() as DescriptionAttribute 56 | let acnDescription = attribute == null ? "" : attribute.Description 57 | select new ActionDescription 58 | { 59 | Name = actionDescriptor.ActionName, 60 | Description = acnDescription 61 | }).ToList(); 62 | actionList = actionList.GroupBy(a => a.Name).Select(g => g.First()).ToList(); 63 | return actionList; 64 | } 65 | } 66 | 67 | /// 68 | /// Controller description class. 69 | /// 70 | public class ControllerDescription 71 | { 72 | /// 73 | /// Gets or sets the name of controller. 74 | /// 75 | /// The name. 76 | public string Name { get; set; } 77 | 78 | /// 79 | /// Gets or sets the description of controller. 80 | /// 81 | /// The description. 82 | public string Description { get; set; } 83 | 84 | /// 85 | /// Gets or sets the actions. 86 | /// 87 | /// The actions. 88 | public IEnumerable Actions { get; set; } 89 | } 90 | 91 | /// 92 | /// Action description class. 93 | /// 94 | public class ActionDescription 95 | { 96 | /// 97 | /// Gets or sets the name of action. 98 | /// 99 | /// The name. 100 | public string Name { get; set; } 101 | 102 | /// 103 | /// Gets or sets the description of action. 104 | /// 105 | /// The description. 106 | public string Description { get; set; } 107 | } 108 | } -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Scripts/respond.min.js: -------------------------------------------------------------------------------- 1 | /* NUGET: BEGIN LICENSE TEXT 2 | * 3 | * Microsoft grants you the right to use these script files for the sole 4 | * purpose of either: (i) interacting through your browser with the Microsoft 5 | * website or online service, subject to the applicable licensing or use 6 | * terms; or (ii) using the files as included with a Microsoft product subject 7 | * to that product's license terms. Microsoft reserves all other rights to the 8 | * files not expressly granted by Microsoft, whether by implication, estoppel 9 | * or otherwise. Insofar as a script file is dual licensed under GPL, 10 | * Microsoft neither took the code under GPL nor distributes it thereunder but 11 | * under the terms set out in this paragraph. All notices and licenses 12 | * below are for informational purposes only. 13 | * 14 | * NUGET: END LICENSE TEXT */ 15 | /*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas. Dual MIT/BSD license */ 16 | /*! NOTE: If you're already including a window.matchMedia polyfill via Modernizr or otherwise, you don't need this part */ 17 | window.matchMedia=window.matchMedia||(function(e,f){var c,a=e.documentElement,b=a.firstElementChild||a.firstChild,d=e.createElement("body"),g=e.createElement("div");g.id="mq-test-1";g.style.cssText="position:absolute;top:-100em";d.style.background="none";d.appendChild(g);return function(h){g.innerHTML='­';a.insertBefore(d,b);c=g.offsetWidth==42;a.removeChild(d);return{matches:c,media:h}}})(document); 18 | 19 | /*! Respond.js v1.2.0: min/max-width media query polyfill. (c) Scott Jehl. MIT/GPLv2 Lic. j.mp/respondjs */ 20 | (function(e){e.respond={};respond.update=function(){};respond.mediaQueriesSupported=e.matchMedia&&e.matchMedia("only all").matches;if(respond.mediaQueriesSupported){return}var w=e.document,s=w.documentElement,i=[],k=[],q=[],o={},h=30,f=w.getElementsByTagName("head")[0]||s,g=w.getElementsByTagName("base")[0],b=f.getElementsByTagName("link"),d=[],a=function(){var D=b,y=D.length,B=0,A,z,C,x;for(;B-1,minw:F.match(/\(min\-width:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:F.match(/\(max\-width:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}}j()},l,r,v=function(){var z,A=w.createElement("div"),x=w.body,y=false;A.style.cssText="position:absolute;font-size:1em;width:1em";if(!x){x=y=w.createElement("body");x.style.background="none"}x.appendChild(A);s.insertBefore(x,s.firstChild);z=A.offsetWidth;if(y){s.removeChild(x)}else{x.removeChild(A)}z=p=parseFloat(z);return z},p,j=function(I){var x="clientWidth",B=s[x],H=w.compatMode==="CSS1Compat"&&B||w.body[x]||B,D={},G=b[b.length-1],z=(new Date()).getTime();if(I&&l&&z-l-1?(p||v()):1)}if(!!J){J=parseFloat(J)*(J.indexOf(y)>-1?(p||v()):1)}if(!K.hasquery||(!A||!L)&&(A||H>=C)&&(L||H<=J)){if(!D[K.media]){D[K.media]=[]}D[K.media].push(k[K.rules])}}for(var E in q){if(q[E]&&q[E].parentNode===f){f.removeChild(q[E])}}for(var E in D){var M=w.createElement("style"),F=D[E].join("\n");M.type="text/css";M.media=E;f.insertBefore(M,G.nextSibling);if(M.styleSheet){M.styleSheet.cssText=F}else{M.appendChild(w.createTextNode(F))}q.push(M)}},n=function(x,z){var y=c();if(!y){return}y.open("GET",x,true);y.onreadystatechange=function(){if(y.readyState!=4||y.status!=200&&y.status!=304){return}z(y.responseText)};if(y.readyState==4){return}y.send(null)},c=(function(){var x=false;try{x=new XMLHttpRequest()}catch(y){x=new ActiveXObject("Microsoft.XMLHTTP")}return function(){return x}})();a();respond.update=a;function t(){j(true)}if(e.addEventListener){e.addEventListener("resize",t,false)}else{if(e.attachEvent){e.attachEvent("onresize",t)}}})(this); -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/App_Start/IdentityConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security.Claims; 3 | using System.Threading.Tasks; 4 | using DynamicRoleBasedAccess.Models; 5 | using Microsoft.AspNet.Identity; 6 | using Microsoft.AspNet.Identity.EntityFramework; 7 | using Microsoft.AspNet.Identity.Owin; 8 | using Microsoft.Owin; 9 | using Microsoft.Owin.Security; 10 | 11 | namespace DynamicRoleBasedAccess 12 | { 13 | public class EmailService : IIdentityMessageService 14 | { 15 | public Task SendAsync(IdentityMessage message) 16 | { 17 | // Plug in your email service here to send an email. 18 | return Task.FromResult(0); 19 | } 20 | } 21 | 22 | public class SmsService : IIdentityMessageService 23 | { 24 | public Task SendAsync(IdentityMessage message) 25 | { 26 | // Plug in your SMS service here to send a text message. 27 | return Task.FromResult(0); 28 | } 29 | } 30 | 31 | // Configure the application role manager which is used in this application. 32 | public class ApplicationRoleManager : RoleManager 33 | { 34 | public ApplicationRoleManager(IRoleStore store) 35 | : base(store) 36 | { 37 | } 38 | 39 | public static ApplicationRoleManager Create(IdentityFactoryOptions options, IOwinContext context) 40 | { 41 | return new ApplicationRoleManager(new RoleStore(context.Get())); 42 | } 43 | } 44 | 45 | // Configure the application user manager used in this application. UserManager is defined in ASP.NET Identity and is used by the application. 46 | public class ApplicationUserManager : UserManager 47 | { 48 | public ApplicationUserManager(IUserStore store) 49 | : base(store) 50 | { 51 | } 52 | 53 | public static ApplicationUserManager Create(IdentityFactoryOptions options, IOwinContext context) 54 | { 55 | var manager = new ApplicationUserManager(new UserStore(context.Get())); 56 | // Configure validation logic for usernames 57 | manager.UserValidator = new UserValidator(manager) 58 | { 59 | AllowOnlyAlphanumericUserNames = false, 60 | RequireUniqueEmail = true 61 | }; 62 | 63 | // Configure validation logic for passwords 64 | manager.PasswordValidator = new PasswordValidator 65 | { 66 | RequiredLength = 6, 67 | RequireNonLetterOrDigit = false, 68 | RequireDigit = true, 69 | RequireLowercase = true, 70 | RequireUppercase = false, 71 | }; 72 | 73 | // Configure user lockout defaults 74 | manager.UserLockoutEnabledByDefault = true; 75 | manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5); 76 | manager.MaxFailedAccessAttemptsBeforeLockout = 5; 77 | 78 | // Register two factor authentication providers. This application uses Phone and Emails as a step of receiving a code for verifying the user 79 | // You can write your own provider and plug it in here. 80 | manager.RegisterTwoFactorProvider("Phone Code", new PhoneNumberTokenProvider 81 | { 82 | MessageFormat = "Your security code is {0}" 83 | }); 84 | manager.RegisterTwoFactorProvider("Email Code", new EmailTokenProvider 85 | { 86 | Subject = "Security Code", 87 | BodyFormat = "Your security code is {0}" 88 | }); 89 | manager.EmailService = new EmailService(); 90 | manager.SmsService = new SmsService(); 91 | var dataProtectionProvider = options.DataProtectionProvider; 92 | if (dataProtectionProvider != null) 93 | { 94 | manager.UserTokenProvider = 95 | new DataProtectorTokenProvider(dataProtectionProvider.Create("ASP.NET Identity")); 96 | } 97 | return manager; 98 | } 99 | } 100 | 101 | // Configure the application sign-in manager which is used in this application. 102 | public class ApplicationSignInManager : SignInManager 103 | { 104 | public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager) 105 | : base(userManager, authenticationManager) 106 | { 107 | } 108 | 109 | public override Task CreateUserIdentityAsync(ApplicationUser user) 110 | { 111 | return user.GenerateUserIdentityAsync((ApplicationUserManager)UserManager); 112 | } 113 | 114 | public static ApplicationSignInManager Create(IdentityFactoryOptions options, IOwinContext context) 115 | { 116 | return new ApplicationSignInManager(context.GetUserManager(), context.Authentication); 117 | } 118 | } 119 | } -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Models/AccountViewModels.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.ComponentModel.DataAnnotations; 3 | using DynamicRoleBasedAccess.Helpers; 4 | using Microsoft.AspNet.Identity.EntityFramework; 5 | 6 | namespace DynamicRoleBasedAccess.Models 7 | { 8 | public class ExternalLoginConfirmationViewModel 9 | { 10 | [Required] 11 | [Display(Name = "Email")] 12 | public string Email { get; set; } 13 | } 14 | 15 | public class ExternalLoginListViewModel 16 | { 17 | public string ReturnUrl { get; set; } 18 | } 19 | 20 | public class SendCodeViewModel 21 | { 22 | public string SelectedProvider { get; set; } 23 | 24 | public ICollection Providers { get; set; } 25 | 26 | public string ReturnUrl { get; set; } 27 | 28 | public bool RememberMe { get; set; } 29 | } 30 | 31 | public class VerifyCodeViewModel 32 | { 33 | [Required] 34 | public string Provider { get; set; } 35 | 36 | [Required] 37 | [Display(Name = "Code")] 38 | public string Code { get; set; } 39 | 40 | public string ReturnUrl { get; set; } 41 | 42 | [Display(Name = "Remember this browser?")] 43 | public bool RememberBrowser { get; set; } 44 | 45 | public bool RememberMe { get; set; } 46 | } 47 | 48 | public class ForgotViewModel 49 | { 50 | [Required] 51 | [Display(Name = "Email")] 52 | public string Email { get; set; } 53 | } 54 | 55 | public class LoginViewModel 56 | { 57 | [Required] 58 | [Display(Name = "Email")] 59 | [EmailAddress] 60 | public string Email { get; set; } 61 | 62 | [Required] 63 | [DataType(DataType.Password)] 64 | [Display(Name = "Password")] 65 | public string Password { get; set; } 66 | 67 | [Display(Name = "Remember me?")] 68 | public bool RememberMe { get; set; } 69 | } 70 | 71 | public class RegisterViewModel 72 | { 73 | [Required] 74 | [EmailAddress] 75 | [Display(Name = "Email")] 76 | public string Email { get; set; } 77 | 78 | [Required] 79 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] 80 | [DataType(DataType.Password)] 81 | [Display(Name = "Password")] 82 | public string Password { get; set; } 83 | 84 | [DataType(DataType.Password)] 85 | [Display(Name = "Confirm password")] 86 | [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] 87 | public string ConfirmPassword { get; set; } 88 | } 89 | 90 | public class ResetPasswordViewModel 91 | { 92 | [Required] 93 | [EmailAddress] 94 | [Display(Name = "Email")] 95 | public string Email { get; set; } 96 | 97 | [Required] 98 | [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] 99 | [DataType(DataType.Password)] 100 | [Display(Name = "Password")] 101 | public string Password { get; set; } 102 | 103 | [DataType(DataType.Password)] 104 | [Display(Name = "Confirm password")] 105 | [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] 106 | public string ConfirmPassword { get; set; } 107 | 108 | public string Code { get; set; } 109 | } 110 | 111 | public class ForgotPasswordViewModel 112 | { 113 | [Required] 114 | [EmailAddress] 115 | [Display(Name = "Email")] 116 | public string Email { get; set; } 117 | } 118 | 119 | public class CreateRoleViewModel 120 | { 121 | [Required] 122 | [StringLength(256, ErrorMessage = "The {0} must be at least {2} characters long.")] 123 | public string Name { get; set; } 124 | 125 | public IEnumerable SelectedControllers { get; set; } 126 | 127 | public IEnumerable Controllers { get; set; } 128 | } 129 | 130 | public class EditRoleViewModel 131 | { 132 | public string Id { get; set; } 133 | 134 | [Required] 135 | [StringLength(256, ErrorMessage = "The {0} must be at least {2} characters long.")] 136 | public string Name { get; set; } 137 | 138 | public IEnumerable RoleAccesses { get; set; } 139 | 140 | public IEnumerable SelectedControllers { get; set; } 141 | 142 | public IEnumerable Controllers { get; set; } 143 | } 144 | 145 | public class UserRoleViewModel 146 | { 147 | public string UserId { get; set; } 148 | 149 | public string UserName { get; set; } 150 | 151 | public IEnumerable Roles { get; set; } 152 | } 153 | 154 | public class EditUserRoleViewModel 155 | { 156 | [Required] 157 | public string UserId { get; set; } 158 | 159 | public string UserName { get; set; } 160 | 161 | [Required] 162 | public IEnumerable SelectedRoles { get; set; } 163 | 164 | public IEnumerable Roles { get; set; } 165 | } 166 | } -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Project_Readme.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Your ASP.NET application 6 | 95 | 96 | 97 | 98 | 102 | 103 |
104 |
105 |

This application consists of:

106 |
    107 |
  • Sample pages showing basic nav between Home, About, and Contact
  • 108 |
  • Theming using Bootstrap
  • 109 |
  • Authentication, if selected, shows how to register and sign in
  • 110 |
  • ASP.NET features managed using NuGet
  • 111 |
112 |
113 | 114 | 131 | 132 |
133 |

Deploy

134 | 139 |
140 | 141 |
142 |

Get help

143 | 147 |
148 |
149 | 150 | 151 | -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Controllers/RoleController.cs: -------------------------------------------------------------------------------- 1 | using DynamicRoleBasedAccess.Helpers; 2 | using DynamicRoleBasedAccess.Models; 3 | using Microsoft.AspNet.Identity.Owin; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.ComponentModel; 7 | using System.Net; 8 | using System.Threading.Tasks; 9 | using System.Web; 10 | using System.Web.Mvc; 11 | 12 | namespace DynamicRoleBasedAccess.Controllers 13 | { 14 | public class RoleController : Controller 15 | { 16 | private ApplicationRoleManager _roleManager; 17 | private static Lazy> _controllerList = new Lazy>(); 18 | 19 | public RoleController() 20 | { 21 | } 22 | 23 | public RoleController(ApplicationRoleManager roleManager) 24 | { 25 | RoleManager = roleManager; 26 | } 27 | 28 | public ApplicationRoleManager RoleManager 29 | { 30 | get { return _roleManager ?? HttpContext.GetOwinContext().Get(); } 31 | private set { _roleManager = value; } 32 | } 33 | 34 | // GET: Role 35 | [Description("Role List")] 36 | public ActionResult Index() 37 | { 38 | var roles = RoleManager.Roles; 39 | return View(roles); 40 | } 41 | 42 | // GET: Role/Create 43 | [Description("Create Role")] 44 | public ActionResult Create() 45 | { 46 | var createRoleViewModel = new CreateRoleViewModel 47 | { 48 | Controllers = GetControllers() 49 | }; 50 | return View(createRoleViewModel); 51 | } 52 | 53 | // POST: ROle/Create 54 | [HttpPost] 55 | [ValidateAntiForgeryToken] 56 | public async Task Create(CreateRoleViewModel viewModel) 57 | { 58 | if (!ModelState.IsValid) 59 | { 60 | viewModel.Controllers = GetControllers(); 61 | return View(viewModel); 62 | } 63 | 64 | var role = new ApplicationRole 65 | { 66 | Name = viewModel.Name, 67 | RoleAccesses = new List() 68 | }; 69 | 70 | // 71 | foreach (var controller in viewModel.SelectedControllers) 72 | { 73 | foreach (var action in controller.Actions) 74 | { 75 | role.RoleAccesses.Add(new RoleAccess { Controller = controller.Name, Action = action.Name }); 76 | } 77 | } 78 | 79 | await RoleManager.CreateAsync(role); 80 | return RedirectToAction("Index"); 81 | } 82 | 83 | // GET: /Role/Edit 84 | [Description("Edit Role")] 85 | public async Task Edit(string id) 86 | { 87 | var role = await RoleManager.FindByIdAsync(id); 88 | if (role == null) 89 | return HttpNotFound(); 90 | 91 | var viewModel = new EditRoleViewModel 92 | { 93 | Id = role.Id, 94 | Name = role.Name, 95 | RoleAccesses = new List(role.RoleAccesses), 96 | Controllers = GetControllers() 97 | }; 98 | 99 | return View(viewModel); 100 | } 101 | 102 | // POST: /Role/Edit 103 | [HttpPost] 104 | [ValidateAntiForgeryToken] 105 | public async Task Edit(EditRoleViewModel viewModel) 106 | { 107 | if (!ModelState.IsValid) 108 | { 109 | viewModel.Controllers = GetControllers(); 110 | return View(viewModel); 111 | } 112 | 113 | var role = await RoleManager.FindByIdAsync(viewModel.Id); 114 | role.Name = viewModel.Name; 115 | var dbContext = HttpContext.GetOwinContext().Get(); 116 | dbContext.RoleAccesses.RemoveRange(role.RoleAccesses); 117 | 118 | foreach (var controller in viewModel.SelectedControllers) 119 | { 120 | foreach (var action in controller.Actions) 121 | { 122 | role.RoleAccesses.Add(new RoleAccess 123 | { 124 | Action = action.Name, 125 | Controller = controller.Name, 126 | RoleId = role.Id, 127 | Role = role, 128 | }); 129 | } 130 | } 131 | await RoleManager.UpdateAsync(role); 132 | await dbContext.SaveChangesAsync(); 133 | return RedirectToAction("Index"); 134 | } 135 | 136 | // GET: /Role/Delete 137 | [Description("Delete Role")] 138 | public async Task Delete(string id) 139 | { 140 | var role = await RoleManager.FindByIdAsync(id); 141 | if (role == null) 142 | { 143 | Response.StatusCode = (int)HttpStatusCode.NotFound; 144 | return Json(false, JsonRequestBehavior.AllowGet); 145 | } 146 | 147 | await RoleManager.DeleteAsync(role); 148 | return Json(true, JsonRequestBehavior.AllowGet); 149 | } 150 | 151 | private static IEnumerable GetControllers() 152 | { 153 | if (_controllerList.IsValueCreated) 154 | return _controllerList.Value; 155 | 156 | _controllerList = new Lazy>(() => new ControllerHelper().GetControllersNameAnDescription()); 157 | return _controllerList.Value; 158 | } 159 | } 160 | } -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Scripts/jquery.validate.unobtrusive.min.js: -------------------------------------------------------------------------------- 1 | /* NUGET: BEGIN LICENSE TEXT 2 | * 3 | * Microsoft grants you the right to use these script files for the sole 4 | * purpose of either: (i) interacting through your browser with the Microsoft 5 | * website or online service, subject to the applicable licensing or use 6 | * terms; or (ii) using the files as included with a Microsoft product subject 7 | * to that product's license terms. Microsoft reserves all other rights to the 8 | * files not expressly granted by Microsoft, whether by implication, estoppel 9 | * or otherwise. Insofar as a script file is dual licensed under GPL, 10 | * Microsoft neither took the code under GPL nor distributes it thereunder but 11 | * under the terms set out in this paragraph. All notices and licenses 12 | * below are for informational purposes only. 13 | * 14 | * NUGET: END LICENSE TEXT */ 15 | /* 16 | ** Unobtrusive validation support library for jQuery and jQuery Validate 17 | ** Copyright (C) Microsoft Corporation. All rights reserved. 18 | */ 19 | (function(a){var d=a.validator,b,e="unobtrusiveValidation";function c(a,b,c){a.rules[b]=c;if(a.message)a.messages[b]=a.message}function j(a){return a.replace(/^\s+|\s+$/g,"").split(/\s*,\s*/g)}function f(a){return a.replace(/([!"#$%&'()*+,./:;<=>?@\[\\\]^`{|}~])/g,"\\$1")}function h(a){return a.substr(0,a.lastIndexOf(".")+1)}function g(a,b){if(a.indexOf("*.")===0)a=a.replace("*.",b);return a}function m(c,e){var b=a(this).find("[data-valmsg-for='"+f(e[0].name)+"']"),d=b.attr("data-valmsg-replace"),g=d?a.parseJSON(d)!==false:null;b.removeClass("field-validation-valid").addClass("field-validation-error");c.data("unobtrusiveContainer",b);if(g){b.empty();c.removeClass("input-validation-error").appendTo(b)}else c.hide()}function l(e,d){var c=a(this).find("[data-valmsg-summary=true]"),b=c.find("ul");if(b&&b.length&&d.errorList.length){b.empty();c.addClass("validation-summary-errors").removeClass("validation-summary-valid");a.each(d.errorList,function(){a("
  • ").html(this.message).appendTo(b)})}}function k(d){var b=d.data("unobtrusiveContainer"),c=b.attr("data-valmsg-replace"),e=c?a.parseJSON(c):null;if(b){b.addClass("field-validation-valid").removeClass("field-validation-error");d.removeData("unobtrusiveContainer");e&&b.empty()}}function n(){var b=a(this),c="__jquery_unobtrusive_validation_form_reset";if(b.data(c))return;b.data(c,true);try{b.data("validator").resetForm()}finally{b.removeData(c)}b.find(".validation-summary-errors").addClass("validation-summary-valid").removeClass("validation-summary-errors");b.find(".field-validation-error").addClass("field-validation-valid").removeClass("field-validation-error").removeData("unobtrusiveContainer").find(">*").removeData("unobtrusiveContainer")}function i(b){var c=a(b),f=c.data(e),i=a.proxy(n,b),g=d.unobtrusive.options||{},h=function(e,d){var c=g[e];c&&a.isFunction(c)&&c.apply(b,d)};if(!f){f={options:{errorClass:g.errorClass||"input-validation-error",errorElement:g.errorElement||"span",errorPlacement:function(){m.apply(b,arguments);h("errorPlacement",arguments)},invalidHandler:function(){l.apply(b,arguments);h("invalidHandler",arguments)},messages:{},rules:{},success:function(){k.apply(b,arguments);h("success",arguments)}},attachValidation:function(){c.off("reset."+e,i).on("reset."+e,i).validate(this.options)},validate:function(){c.validate();return c.valid()}};c.data(e,f)}return f}d.unobtrusive={adapters:[],parseElement:function(b,h){var d=a(b),f=d.parents("form")[0],c,e,g;if(!f)return;c=i(f);c.options.rules[b.name]=e={};c.options.messages[b.name]=g={};a.each(this.adapters,function(){var c="data-val-"+this.name,i=d.attr(c),h={};if(i!==undefined){c+="-";a.each(this.params,function(){h[this]=d.attr(c+this)});this.adapt({element:b,form:f,message:i,params:h,rules:e,messages:g})}});a.extend(e,{__dummy__:true});!h&&c.attachValidation()},parse:function(c){var b=a(c),e=b.parents().addBack().filter("form").add(b.find("form")).has("[data-val=true]");b.find("[data-val=true]").each(function(){d.unobtrusive.parseElement(this,true)});e.each(function(){var a=i(this);a&&a.attachValidation()})}};b=d.unobtrusive.adapters;b.add=function(c,a,b){if(!b){b=a;a=[]}this.push({name:c,params:a,adapt:b});return this};b.addBool=function(a,b){return this.add(a,function(d){c(d,b||a,true)})};b.addMinMax=function(e,g,f,a,d,b){return this.add(e,[d||"min",b||"max"],function(b){var e=b.params.min,d=b.params.max;if(e&&d)c(b,a,[e,d]);else if(e)c(b,g,e);else d&&c(b,f,d)})};b.addSingleVal=function(a,b,d){return this.add(a,[b||"val"],function(e){c(e,d||a,e.params[b])})};d.addMethod("__dummy__",function(){return true});d.addMethod("regex",function(b,c,d){var a;if(this.optional(c))return true;a=(new RegExp(d)).exec(b);return a&&a.index===0&&a[0].length===b.length});d.addMethod("nonalphamin",function(c,d,b){var a;if(b){a=c.match(/\W/g);a=a&&a.length>=b}return a});if(d.methods.extension){b.addSingleVal("accept","mimtype");b.addSingleVal("extension","extension")}else b.addSingleVal("extension","extension","accept");b.addSingleVal("regex","pattern");b.addBool("creditcard").addBool("date").addBool("digits").addBool("email").addBool("number").addBool("url");b.addMinMax("length","minlength","maxlength","rangelength").addMinMax("range","min","max","range");b.addMinMax("minlength","minlength").addMinMax("maxlength","minlength","maxlength");b.add("equalto",["other"],function(b){var i=h(b.element.name),j=b.params.other,d=g(j,i),e=a(b.form).find(":input").filter("[name='"+f(d)+"']")[0];c(b,"equalTo",e)});b.add("required",function(a){(a.element.tagName.toUpperCase()!=="INPUT"||a.element.type.toUpperCase()!=="CHECKBOX")&&c(a,"required",true)});b.add("remote",["url","type","additionalfields"],function(b){var d={url:b.params.url,type:b.params.type||"GET",data:{}},e=h(b.element.name);a.each(j(b.params.additionalfields||b.element.name),function(i,h){var c=g(h,e);d.data[c]=function(){var d=a(b.form).find(":input").filter("[name='"+f(c)+"']");return d.is(":checkbox")?d.filter(":checked").val()||d.filter(":hidden").val()||"":d.is(":radio")?d.filter(":checked").val()||"":d.val()}});c(b,"remote",d)});b.add("password",["min","nonalphamin","regex"],function(a){a.params.min&&c(a,"minlength",a.params.min);a.params.nonalphamin&&c(a,"nonalphamin",a.params.nonalphamin);a.params.regex&&c(a,"regex",a.params.regex)});a(function(){d.unobtrusive.parse(document)})})(jQuery); -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Scripts/respond.js: -------------------------------------------------------------------------------- 1 | /* NUGET: BEGIN LICENSE TEXT 2 | * 3 | * Microsoft grants you the right to use these script files for the sole 4 | * purpose of either: (i) interacting through your browser with the Microsoft 5 | * website or online service, subject to the applicable licensing or use 6 | * terms; or (ii) using the files as included with a Microsoft product subject 7 | * to that product's license terms. Microsoft reserves all other rights to the 8 | * files not expressly granted by Microsoft, whether by implication, estoppel 9 | * or otherwise. Insofar as a script file is dual licensed under GPL, 10 | * Microsoft neither took the code under GPL nor distributes it thereunder but 11 | * under the terms set out in this paragraph. All notices and licenses 12 | * below are for informational purposes only. 13 | * 14 | * NUGET: END LICENSE TEXT */ 15 | /*! matchMedia() polyfill - Test a CSS media type/query in JS. Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas. Dual MIT/BSD license */ 16 | /*! NOTE: If you're already including a window.matchMedia polyfill via Modernizr or otherwise, you don't need this part */ 17 | window.matchMedia = window.matchMedia || (function(doc, undefined){ 18 | 19 | var bool, 20 | docElem = doc.documentElement, 21 | refNode = docElem.firstElementChild || docElem.firstChild, 22 | // fakeBody required for 23 | fakeBody = doc.createElement('body'), 24 | div = doc.createElement('div'); 25 | 26 | div.id = 'mq-test-1'; 27 | div.style.cssText = "position:absolute;top:-100em"; 28 | fakeBody.style.background = "none"; 29 | fakeBody.appendChild(div); 30 | 31 | return function(q){ 32 | 33 | div.innerHTML = '­'; 34 | 35 | docElem.insertBefore(fakeBody, refNode); 36 | bool = div.offsetWidth == 42; 37 | docElem.removeChild(fakeBody); 38 | 39 | return { matches: bool, media: q }; 40 | }; 41 | 42 | })(document); 43 | 44 | 45 | 46 | 47 | /*! Respond.js v1.2.0: min/max-width media query polyfill. (c) Scott Jehl. MIT/GPLv2 Lic. j.mp/respondjs */ 48 | (function( win ){ 49 | //exposed namespace 50 | win.respond = {}; 51 | 52 | //define update even in native-mq-supporting browsers, to avoid errors 53 | respond.update = function(){}; 54 | 55 | //expose media query support flag for external use 56 | respond.mediaQueriesSupported = win.matchMedia && win.matchMedia( "only all" ).matches; 57 | 58 | //if media queries are supported, exit here 59 | if( respond.mediaQueriesSupported ){ return; } 60 | 61 | //define vars 62 | var doc = win.document, 63 | docElem = doc.documentElement, 64 | mediastyles = [], 65 | rules = [], 66 | appendedEls = [], 67 | parsedSheets = {}, 68 | resizeThrottle = 30, 69 | head = doc.getElementsByTagName( "head" )[0] || docElem, 70 | base = doc.getElementsByTagName( "base" )[0], 71 | links = head.getElementsByTagName( "link" ), 72 | requestQueue = [], 73 | 74 | //loop stylesheets, send text content to translate 75 | ripCSS = function(){ 76 | var sheets = links, 77 | sl = sheets.length, 78 | i = 0, 79 | //vars for loop: 80 | sheet, href, media, isCSS; 81 | 82 | for( ; i < sl; i++ ){ 83 | sheet = sheets[ i ], 84 | href = sheet.href, 85 | media = sheet.media, 86 | isCSS = sheet.rel && sheet.rel.toLowerCase() === "stylesheet"; 87 | 88 | //only links plz and prevent re-parsing 89 | if( !!href && isCSS && !parsedSheets[ href ] ){ 90 | // selectivizr exposes css through the rawCssText expando 91 | if (sheet.styleSheet && sheet.styleSheet.rawCssText) { 92 | translate( sheet.styleSheet.rawCssText, href, media ); 93 | parsedSheets[ href ] = true; 94 | } else { 95 | if( (!/^([a-zA-Z:]*\/\/)/.test( href ) && !base) 96 | || href.replace( RegExp.$1, "" ).split( "/" )[0] === win.location.host ){ 97 | requestQueue.push( { 98 | href: href, 99 | media: media 100 | } ); 101 | } 102 | } 103 | } 104 | } 105 | makeRequests(); 106 | }, 107 | 108 | //recurse through request queue, get css text 109 | makeRequests = function(){ 110 | if( requestQueue.length ){ 111 | var thisRequest = requestQueue.shift(); 112 | 113 | ajax( thisRequest.href, function( styles ){ 114 | translate( styles, thisRequest.href, thisRequest.media ); 115 | parsedSheets[ thisRequest.href ] = true; 116 | makeRequests(); 117 | } ); 118 | } 119 | }, 120 | 121 | //find media blocks in css text, convert to style blocks 122 | translate = function( styles, href, media ){ 123 | var qs = styles.match( /@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi ), 124 | ql = qs && qs.length || 0, 125 | //try to get CSS path 126 | href = href.substring( 0, href.lastIndexOf( "/" )), 127 | repUrls = function( css ){ 128 | return css.replace( /(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g, "$1" + href + "$2$3" ); 129 | }, 130 | useMedia = !ql && media, 131 | //vars used in loop 132 | i = 0, 133 | j, fullq, thisq, eachq, eql; 134 | 135 | //if path exists, tack on trailing slash 136 | if( href.length ){ href += "/"; } 137 | 138 | //if no internal queries exist, but media attr does, use that 139 | //note: this currently lacks support for situations where a media attr is specified on a link AND 140 | //its associated stylesheet has internal CSS media queries. 141 | //In those cases, the media attribute will currently be ignored. 142 | if( useMedia ){ 143 | ql = 1; 144 | } 145 | 146 | 147 | for( ; i < ql; i++ ){ 148 | j = 0; 149 | 150 | //media attr 151 | if( useMedia ){ 152 | fullq = media; 153 | rules.push( repUrls( styles ) ); 154 | } 155 | //parse for styles 156 | else{ 157 | fullq = qs[ i ].match( /@media *([^\{]+)\{([\S\s]+?)$/ ) && RegExp.$1; 158 | rules.push( RegExp.$2 && repUrls( RegExp.$2 ) ); 159 | } 160 | 161 | eachq = fullq.split( "," ); 162 | eql = eachq.length; 163 | 164 | for( ; j < eql; j++ ){ 165 | thisq = eachq[ j ]; 166 | mediastyles.push( { 167 | media : thisq.split( "(" )[ 0 ].match( /(only\s+)?([a-zA-Z]+)\s?/ ) && RegExp.$2 || "all", 168 | rules : rules.length - 1, 169 | hasquery: thisq.indexOf("(") > -1, 170 | minw : thisq.match( /\(min\-width:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/ ) && parseFloat( RegExp.$1 ) + ( RegExp.$2 || "" ), 171 | maxw : thisq.match( /\(max\-width:[\s]*([\s]*[0-9\.]+)(px|em)[\s]*\)/ ) && parseFloat( RegExp.$1 ) + ( RegExp.$2 || "" ) 172 | } ); 173 | } 174 | } 175 | 176 | applyMedia(); 177 | }, 178 | 179 | lastCall, 180 | 181 | resizeDefer, 182 | 183 | // returns the value of 1em in pixels 184 | getEmValue = function() { 185 | var ret, 186 | div = doc.createElement('div'), 187 | body = doc.body, 188 | fakeUsed = false; 189 | 190 | div.style.cssText = "position:absolute;font-size:1em;width:1em"; 191 | 192 | if( !body ){ 193 | body = fakeUsed = doc.createElement( "body" ); 194 | body.style.background = "none"; 195 | } 196 | 197 | body.appendChild( div ); 198 | 199 | docElem.insertBefore( body, docElem.firstChild ); 200 | 201 | ret = div.offsetWidth; 202 | 203 | if( fakeUsed ){ 204 | docElem.removeChild( body ); 205 | } 206 | else { 207 | body.removeChild( div ); 208 | } 209 | 210 | //also update eminpx before returning 211 | ret = eminpx = parseFloat(ret); 212 | 213 | return ret; 214 | }, 215 | 216 | //cached container for 1em value, populated the first time it's needed 217 | eminpx, 218 | 219 | //enable/disable styles 220 | applyMedia = function( fromResize ){ 221 | var name = "clientWidth", 222 | docElemProp = docElem[ name ], 223 | currWidth = doc.compatMode === "CSS1Compat" && docElemProp || doc.body[ name ] || docElemProp, 224 | styleBlocks = {}, 225 | lastLink = links[ links.length-1 ], 226 | now = (new Date()).getTime(); 227 | 228 | //throttle resize calls 229 | if( fromResize && lastCall && now - lastCall < resizeThrottle ){ 230 | clearTimeout( resizeDefer ); 231 | resizeDefer = setTimeout( applyMedia, resizeThrottle ); 232 | return; 233 | } 234 | else { 235 | lastCall = now; 236 | } 237 | 238 | for( var i in mediastyles ){ 239 | var thisstyle = mediastyles[ i ], 240 | min = thisstyle.minw, 241 | max = thisstyle.maxw, 242 | minnull = min === null, 243 | maxnull = max === null, 244 | em = "em"; 245 | 246 | if( !!min ){ 247 | min = parseFloat( min ) * ( min.indexOf( em ) > -1 ? ( eminpx || getEmValue() ) : 1 ); 248 | } 249 | if( !!max ){ 250 | max = parseFloat( max ) * ( max.indexOf( em ) > -1 ? ( eminpx || getEmValue() ) : 1 ); 251 | } 252 | 253 | // if there's no media query at all (the () part), or min or max is not null, and if either is present, they're true 254 | if( !thisstyle.hasquery || ( !minnull || !maxnull ) && ( minnull || currWidth >= min ) && ( maxnull || currWidth <= max ) ){ 255 | if( !styleBlocks[ thisstyle.media ] ){ 256 | styleBlocks[ thisstyle.media ] = []; 257 | } 258 | styleBlocks[ thisstyle.media ].push( rules[ thisstyle.rules ] ); 259 | } 260 | } 261 | 262 | //remove any existing respond style element(s) 263 | for( var i in appendedEls ){ 264 | if( appendedEls[ i ] && appendedEls[ i ].parentNode === head ){ 265 | head.removeChild( appendedEls[ i ] ); 266 | } 267 | } 268 | 269 | //inject active styles, grouped by media type 270 | for( var i in styleBlocks ){ 271 | var ss = doc.createElement( "style" ), 272 | css = styleBlocks[ i ].join( "\n" ); 273 | 274 | ss.type = "text/css"; 275 | ss.media = i; 276 | 277 | //originally, ss was appended to a documentFragment and sheets were appended in bulk. 278 | //this caused crashes in IE in a number of circumstances, such as when the HTML element had a bg image set, so appending beforehand seems best. Thanks to @dvelyk for the initial research on this one! 279 | head.insertBefore( ss, lastLink.nextSibling ); 280 | 281 | if ( ss.styleSheet ){ 282 | ss.styleSheet.cssText = css; 283 | } 284 | else { 285 | ss.appendChild( doc.createTextNode( css ) ); 286 | } 287 | 288 | //push to appendedEls to track for later removal 289 | appendedEls.push( ss ); 290 | } 291 | }, 292 | //tweaked Ajax functions from Quirksmode 293 | ajax = function( url, callback ) { 294 | var req = xmlHttp(); 295 | if (!req){ 296 | return; 297 | } 298 | req.open( "GET", url, true ); 299 | req.onreadystatechange = function () { 300 | if ( req.readyState != 4 || req.status != 200 && req.status != 304 ){ 301 | return; 302 | } 303 | callback( req.responseText ); 304 | } 305 | if ( req.readyState == 4 ){ 306 | return; 307 | } 308 | req.send( null ); 309 | }, 310 | //define ajax obj 311 | xmlHttp = (function() { 312 | var xmlhttpmethod = false; 313 | try { 314 | xmlhttpmethod = new XMLHttpRequest(); 315 | } 316 | catch( e ){ 317 | xmlhttpmethod = new ActiveXObject( "Microsoft.XMLHTTP" ); 318 | } 319 | return function(){ 320 | return xmlhttpmethod; 321 | }; 322 | })(); 323 | 324 | //translate CSS 325 | ripCSS(); 326 | 327 | //expose update for re-running respond later on 328 | respond.update = ripCSS; 329 | 330 | //adjust on resize 331 | function callMedia(){ 332 | applyMedia( true ); 333 | } 334 | if( win.addEventListener ){ 335 | win.addEventListener( "resize", callMedia, false ); 336 | } 337 | else if( win.attachEvent ){ 338 | win.attachEvent( "onresize", callMedia ); 339 | } 340 | })(this); 341 | -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Extensions/HtmlExtensions.cs: -------------------------------------------------------------------------------- 1 | using DynamicRoleBasedAccess.Models; 2 | using Microsoft.AspNet.Identity.Owin; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Web; 7 | using System.Web.Mvc; 8 | using System.Web.Mvc.Html; 9 | using System.Web.Routing; 10 | 11 | namespace DynamicRoleBasedAccess.Extensions 12 | { 13 | /// 14 | /// Represents extension methods for HtmlHelper class. 15 | /// 16 | public static class HtmlExtensions 17 | { 18 | /// 19 | /// Returns an anchor element (a element) for the specified link text and action. 20 | /// 21 | /// 22 | /// 23 | /// An anchor element (a element). 24 | /// 25 | /// The HTML helper instance that this method extends.The inner text of the anchor element.The name of the action.The parameter is null or empty. 26 | public static MvcHtmlString SecureActionLink(this HtmlHelper htmlHelper, string linkText, string actionName) 27 | { 28 | return SecureActionLink(htmlHelper, linkText, actionName, null, new RouteValueDictionary(), new RouteValueDictionary()); 29 | } 30 | 31 | /// 32 | /// Returns an anchor element (a element) for the specified link text, action, and route values. 33 | /// 34 | /// 35 | /// 36 | /// An anchor element (a element). 37 | /// 38 | /// The HTML helper instance that this method extends.The inner text of the anchor element.The name of the action.An object that contains the parameters for a route. The parameters are retrieved through reflection by examining the properties of the object. The object is typically created by using object initializer syntax.The parameter is null or empty. 39 | public static MvcHtmlString SecureActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, object routeValues) 40 | { 41 | return SecureActionLink(htmlHelper, linkText, actionName, null, routeValues, (IDictionary)new RouteValueDictionary()); 42 | } 43 | 44 | /// 45 | /// Returns an anchor element (a element) for the specified link text, action, route values, and HTML attributes. 46 | /// 47 | /// 48 | /// 49 | /// An anchor element (a element). 50 | /// 51 | /// The HTML helper instance that this method extends.The inner text of the anchor element.The name of the action.An object that contains the parameters for a route. The parameters are retrieved through reflection by examining the properties of the object. The object is typically created by using object initializer syntax.An object that contains the HTML attributes for the element. The attributes are retrieved through reflection by examining the properties of the object. The object is typically created by using object initializer syntax.The parameter is null or empty. 52 | public static MvcHtmlString SecureActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, object routeValues, object htmlAttributes) 53 | { 54 | return SecureActionLink(htmlHelper, linkText, actionName, null, routeValues, htmlAttributes); 55 | } 56 | 57 | /// 58 | /// Returns an anchor element (a element) for the specified link text, action, and route values as a route value dictionary. 59 | /// 60 | /// 61 | /// 62 | /// An anchor element (a element). 63 | /// 64 | /// The HTML helper instance that this method extends.The inner text of the anchor element.The name of the action.An object that contains the parameters for a route.The parameter is null or empty. 65 | public static MvcHtmlString SecureActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, RouteValueDictionary routeValues) 66 | { 67 | return SecureActionLink(htmlHelper, linkText, actionName, null, routeValues, new RouteValueDictionary()); 68 | } 69 | 70 | /// 71 | /// Returns an anchor element (a element) for the specified link text, action, route values as a route value dictionary, and HTML attributes as a dictionary. 72 | /// 73 | /// 74 | /// 75 | /// An anchor element (a element). 76 | /// 77 | /// The HTML helper instance that this method extends.The inner text of the anchor element.The name of the action.An object that contains the parameters for a route.An object that contains the HTML attributes to set for the element.The parameter is null or empty. 78 | public static MvcHtmlString SecureActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, RouteValueDictionary routeValues, IDictionary htmlAttributes) 79 | { 80 | return SecureActionLink(htmlHelper, linkText, actionName, null, routeValues, htmlAttributes); 81 | } 82 | 83 | /// 84 | /// Returns an anchor element (a element) for the specified link text, action, and controller. 85 | /// 86 | /// 87 | /// 88 | /// An anchor element (a element). 89 | /// 90 | /// The HTML helper instance that this method extends.The inner text of the anchor element.The name of the action.The name of the controller.The parameter is null or empty. 91 | public static MvcHtmlString SecureActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, string controllerName) 92 | { 93 | return SecureActionLink(htmlHelper, linkText, actionName, controllerName, new RouteValueDictionary(), new RouteValueDictionary()); 94 | } 95 | 96 | /// 97 | /// Returns an anchor element (a element) for the specified link text, action, controller, route values, and HTML attributes. 98 | /// 99 | /// 100 | /// 101 | /// An anchor element (a element). 102 | /// 103 | /// The HTML helper instance that this method extends.The inner text of the anchor element.The name of the action.The name of the controller.An object that contains the parameters for a route. The parameters are retrieved through reflection by examining the properties of the object. The object is typically created by using object initializer syntax.An object that contains the HTML attributes to set for the element.The parameter is null or empty. 104 | public static MvcHtmlString SecureActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, string controllerName, object routeValues, object htmlAttributes) 105 | { 106 | //var url = htmlHelper.ActionLink(linkText, actionName, controllerName, routeValues, htmlAttributes); 107 | 108 | return CanAccess(htmlHelper, actionName, controllerName) 109 | ? htmlHelper.ActionLink(linkText, actionName, controllerName, routeValues, htmlAttributes) 110 | : new MvcHtmlString(string.Empty); 111 | } 112 | 113 | /// 114 | /// Returns an anchor element (a element) for the specified link text, action, controller, route values as a route value dictionary, and HTML attributes as a dictionary. 115 | /// 116 | /// 117 | /// 118 | /// An anchor element (a element). 119 | /// 120 | /// The HTML helper instance that this method extends.The inner text of the anchor element.The name of the action.The name of the controller.An object that contains the parameters for a route.An object that contains the HTML attributes to set for the element.The parameter is null or empty. 121 | public static MvcHtmlString SecureActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, string controllerName, RouteValueDictionary routeValues, IDictionary htmlAttributes) 122 | { 123 | return CanAccess(htmlHelper, actionName, controllerName) 124 | ? htmlHelper.ActionLink(linkText, actionName, controllerName, routeValues, htmlAttributes) 125 | : new MvcHtmlString(string.Empty); 126 | } 127 | 128 | /// 129 | /// Returns an anchor element (a element) for the specified link text, action, controller, protocol, host name, URL fragment, route values, and HTML attributes. 130 | /// 131 | /// 132 | /// 133 | /// An anchor element (a element). 134 | /// 135 | /// The HTML helper instance that this method extends.The inner text of the anchor element.The name of the action.The name of the controller.The protocol for the URL, such as "http" or "https".The host name for the URL.The URL fragment name (the anchor name).An object that contains the parameters for a route. The parameters are retrieved through reflection by examining the properties of the object. The object is typically created by using object initializer syntax.An object that contains the HTML attributes to set for the element.The parameter is null or empty. 136 | public static MvcHtmlString SecureActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, string controllerName, string protocol, string hostName, string fragment, object routeValues, object htmlAttributes) 137 | { 138 | return CanAccess(htmlHelper, actionName, controllerName) 139 | ? htmlHelper.ActionLink(linkText, actionName, controllerName, protocol, hostName, fragment, routeValues, htmlAttributes) 140 | : new MvcHtmlString(string.Empty); 141 | } 142 | 143 | /// 144 | /// Determines whether this instance can access the requested Controller and Action. 145 | /// 146 | /// The HTML helper. 147 | /// Name of the action. 148 | /// Name of the controller. 149 | /// true if this instance can access the specified HTML helper; otherwise, false. 150 | private static bool CanAccess(HtmlHelper htmlHelper, string actionName, string controllerName) 151 | { 152 | var httpContext = htmlHelper.ViewContext.HttpContext; 153 | var dbContext = httpContext.GetOwinContext().Get(); 154 | var user = httpContext.User; 155 | var roleAccess = from ra in dbContext.RoleAccesses 156 | let userId = dbContext.Users.FirstOrDefault(u => u.UserName == user.Identity.Name).Id 157 | let roleIds = dbContext.Roles.Where(r => r.Users.Any(u => u.UserId == userId)).Select(r => r.Id) 158 | where roleIds.Contains(ra.RoleId) 159 | select ra; 160 | 161 | if (string.IsNullOrWhiteSpace(controllerName)) 162 | controllerName = htmlHelper.ViewContext.Controller.ToString().Split('.').Last().Replace("Controller", ""); 163 | 164 | if (roleAccess.Any(ra => 165 | ra.Controller.Equals(controllerName, StringComparison.InvariantCultureIgnoreCase) && 166 | ra.Action.Equals(actionName, StringComparison.InvariantCultureIgnoreCase))) 167 | return true; 168 | 169 | return false; 170 | } 171 | } 172 | } -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Controllers/ManageController.cs: -------------------------------------------------------------------------------- 1 | using DynamicRoleBasedAccess.Filters; 2 | using DynamicRoleBasedAccess.Models; 3 | using Microsoft.AspNet.Identity; 4 | using Microsoft.AspNet.Identity.Owin; 5 | using Microsoft.Owin.Security; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | using System.Web; 9 | using System.Web.Mvc; 10 | 11 | namespace DynamicRoleBasedAccess.Controllers 12 | { 13 | [CustomAuthorize] 14 | public class ManageController : Controller 15 | { 16 | private ApplicationSignInManager _signInManager; 17 | private ApplicationUserManager _userManager; 18 | 19 | public ManageController() 20 | { 21 | } 22 | 23 | public ManageController(ApplicationUserManager userManager, ApplicationSignInManager signInManager) 24 | { 25 | UserManager = userManager; 26 | SignInManager = signInManager; 27 | } 28 | 29 | public ApplicationSignInManager SignInManager 30 | { 31 | get 32 | { 33 | return _signInManager ?? HttpContext.GetOwinContext().Get(); 34 | } 35 | private set 36 | { 37 | _signInManager = value; 38 | } 39 | } 40 | 41 | public ApplicationUserManager UserManager 42 | { 43 | get 44 | { 45 | return _userManager ?? HttpContext.GetOwinContext().GetUserManager(); 46 | } 47 | private set 48 | { 49 | _userManager = value; 50 | } 51 | } 52 | 53 | // 54 | // GET: /Manage/Index 55 | public async Task Index(ManageMessageId? message) 56 | { 57 | ViewBag.StatusMessage = 58 | message == ManageMessageId.ChangePasswordSuccess ? "Your password has been changed." 59 | : message == ManageMessageId.SetPasswordSuccess ? "Your password has been set." 60 | : message == ManageMessageId.SetTwoFactorSuccess ? "Your two-factor authentication provider has been set." 61 | : message == ManageMessageId.Error ? "An error has occurred." 62 | : message == ManageMessageId.AddPhoneSuccess ? "Your phone number was added." 63 | : message == ManageMessageId.RemovePhoneSuccess ? "Your phone number was removed." 64 | : ""; 65 | 66 | var userId = User.Identity.GetUserId(); 67 | var model = new IndexViewModel 68 | { 69 | HasPassword = HasPassword(), 70 | PhoneNumber = await UserManager.GetPhoneNumberAsync(userId), 71 | TwoFactor = await UserManager.GetTwoFactorEnabledAsync(userId), 72 | Logins = await UserManager.GetLoginsAsync(userId), 73 | BrowserRemembered = await AuthenticationManager.TwoFactorBrowserRememberedAsync(userId) 74 | }; 75 | return View(model); 76 | } 77 | 78 | // 79 | // POST: /Manage/RemoveLogin 80 | [HttpPost] 81 | [ValidateAntiForgeryToken] 82 | public async Task RemoveLogin(string loginProvider, string providerKey) 83 | { 84 | ManageMessageId? message; 85 | var result = await UserManager.RemoveLoginAsync(User.Identity.GetUserId(), new UserLoginInfo(loginProvider, providerKey)); 86 | if (result.Succeeded) 87 | { 88 | var user = await UserManager.FindByIdAsync(User.Identity.GetUserId()); 89 | if (user != null) 90 | { 91 | await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false); 92 | } 93 | message = ManageMessageId.RemoveLoginSuccess; 94 | } 95 | else 96 | { 97 | message = ManageMessageId.Error; 98 | } 99 | return RedirectToAction("ManageLogins", new { Message = message }); 100 | } 101 | 102 | // 103 | // GET: /Manage/AddPhoneNumber 104 | public ActionResult AddPhoneNumber() 105 | { 106 | return View(); 107 | } 108 | 109 | // 110 | // POST: /Manage/AddPhoneNumber 111 | [HttpPost] 112 | [ValidateAntiForgeryToken] 113 | public async Task AddPhoneNumber(AddPhoneNumberViewModel model) 114 | { 115 | if (!ModelState.IsValid) 116 | { 117 | return View(model); 118 | } 119 | // Generate the token and send it 120 | var code = await UserManager.GenerateChangePhoneNumberTokenAsync(User.Identity.GetUserId(), model.Number); 121 | if (UserManager.SmsService != null) 122 | { 123 | var message = new IdentityMessage 124 | { 125 | Destination = model.Number, 126 | Body = "Your security code is: " + code 127 | }; 128 | await UserManager.SmsService.SendAsync(message); 129 | } 130 | return RedirectToAction("VerifyPhoneNumber", new { PhoneNumber = model.Number }); 131 | } 132 | 133 | // 134 | // POST: /Manage/EnableTwoFactorAuthentication 135 | [HttpPost] 136 | [ValidateAntiForgeryToken] 137 | public async Task EnableTwoFactorAuthentication() 138 | { 139 | await UserManager.SetTwoFactorEnabledAsync(User.Identity.GetUserId(), true); 140 | var user = await UserManager.FindByIdAsync(User.Identity.GetUserId()); 141 | if (user != null) 142 | { 143 | await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false); 144 | } 145 | return RedirectToAction("Index", "Manage"); 146 | } 147 | 148 | // 149 | // POST: /Manage/DisableTwoFactorAuthentication 150 | [HttpPost] 151 | [ValidateAntiForgeryToken] 152 | public async Task DisableTwoFactorAuthentication() 153 | { 154 | await UserManager.SetTwoFactorEnabledAsync(User.Identity.GetUserId(), false); 155 | var user = await UserManager.FindByIdAsync(User.Identity.GetUserId()); 156 | if (user != null) 157 | { 158 | await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false); 159 | } 160 | return RedirectToAction("Index", "Manage"); 161 | } 162 | 163 | // 164 | // GET: /Manage/VerifyPhoneNumber 165 | public async Task VerifyPhoneNumber(string phoneNumber) 166 | { 167 | var code = await UserManager.GenerateChangePhoneNumberTokenAsync(User.Identity.GetUserId(), phoneNumber); 168 | // Send an SMS through the SMS provider to verify the phone number 169 | return phoneNumber == null ? View("Error") : View(new VerifyPhoneNumberViewModel { PhoneNumber = phoneNumber }); 170 | } 171 | 172 | // 173 | // POST: /Manage/VerifyPhoneNumber 174 | [HttpPost] 175 | [ValidateAntiForgeryToken] 176 | public async Task VerifyPhoneNumber(VerifyPhoneNumberViewModel model) 177 | { 178 | if (!ModelState.IsValid) 179 | { 180 | return View(model); 181 | } 182 | var result = await UserManager.ChangePhoneNumberAsync(User.Identity.GetUserId(), model.PhoneNumber, model.Code); 183 | if (result.Succeeded) 184 | { 185 | var user = await UserManager.FindByIdAsync(User.Identity.GetUserId()); 186 | if (user != null) 187 | { 188 | await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false); 189 | } 190 | return RedirectToAction("Index", new { Message = ManageMessageId.AddPhoneSuccess }); 191 | } 192 | // If we got this far, something failed, redisplay form 193 | ModelState.AddModelError("", "Failed to verify phone"); 194 | return View(model); 195 | } 196 | 197 | // 198 | // GET: /Manage/RemovePhoneNumber 199 | public async Task RemovePhoneNumber() 200 | { 201 | var result = await UserManager.SetPhoneNumberAsync(User.Identity.GetUserId(), null); 202 | if (!result.Succeeded) 203 | { 204 | return RedirectToAction("Index", new { Message = ManageMessageId.Error }); 205 | } 206 | var user = await UserManager.FindByIdAsync(User.Identity.GetUserId()); 207 | if (user != null) 208 | { 209 | await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false); 210 | } 211 | return RedirectToAction("Index", new { Message = ManageMessageId.RemovePhoneSuccess }); 212 | } 213 | 214 | // 215 | // GET: /Manage/ChangePassword 216 | public ActionResult ChangePassword() 217 | { 218 | return View(); 219 | } 220 | 221 | // 222 | // POST: /Manage/ChangePassword 223 | [HttpPost] 224 | [ValidateAntiForgeryToken] 225 | public async Task ChangePassword(ChangePasswordViewModel model) 226 | { 227 | if (!ModelState.IsValid) 228 | { 229 | return View(model); 230 | } 231 | var result = await UserManager.ChangePasswordAsync(User.Identity.GetUserId(), model.OldPassword, model.NewPassword); 232 | if (result.Succeeded) 233 | { 234 | var user = await UserManager.FindByIdAsync(User.Identity.GetUserId()); 235 | if (user != null) 236 | { 237 | await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false); 238 | } 239 | return RedirectToAction("Index", new { Message = ManageMessageId.ChangePasswordSuccess }); 240 | } 241 | AddErrors(result); 242 | return View(model); 243 | } 244 | 245 | // 246 | // GET: /Manage/SetPassword 247 | public ActionResult SetPassword() 248 | { 249 | return View(); 250 | } 251 | 252 | // 253 | // POST: /Manage/SetPassword 254 | [HttpPost] 255 | [ValidateAntiForgeryToken] 256 | public async Task SetPassword(SetPasswordViewModel model) 257 | { 258 | if (ModelState.IsValid) 259 | { 260 | var result = await UserManager.AddPasswordAsync(User.Identity.GetUserId(), model.NewPassword); 261 | if (result.Succeeded) 262 | { 263 | var user = await UserManager.FindByIdAsync(User.Identity.GetUserId()); 264 | if (user != null) 265 | { 266 | await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false); 267 | } 268 | return RedirectToAction("Index", new { Message = ManageMessageId.SetPasswordSuccess }); 269 | } 270 | AddErrors(result); 271 | } 272 | 273 | // If we got this far, something failed, redisplay form 274 | return View(model); 275 | } 276 | 277 | // 278 | // GET: /Manage/ManageLogins 279 | public async Task ManageLogins(ManageMessageId? message) 280 | { 281 | ViewBag.StatusMessage = 282 | message == ManageMessageId.RemoveLoginSuccess ? "The external login was removed." 283 | : message == ManageMessageId.Error ? "An error has occurred." 284 | : ""; 285 | var user = await UserManager.FindByIdAsync(User.Identity.GetUserId()); 286 | if (user == null) 287 | { 288 | return View("Error"); 289 | } 290 | var userLogins = await UserManager.GetLoginsAsync(User.Identity.GetUserId()); 291 | var otherLogins = AuthenticationManager.GetExternalAuthenticationTypes().Where(auth => userLogins.All(ul => auth.AuthenticationType != ul.LoginProvider)).ToList(); 292 | ViewBag.ShowRemoveButton = user.PasswordHash != null || userLogins.Count > 1; 293 | return View(new ManageLoginsViewModel 294 | { 295 | CurrentLogins = userLogins, 296 | OtherLogins = otherLogins 297 | }); 298 | } 299 | 300 | // 301 | // POST: /Manage/LinkLogin 302 | [HttpPost] 303 | [ValidateAntiForgeryToken] 304 | public ActionResult LinkLogin(string provider) 305 | { 306 | // Request a redirect to the external login provider to link a login for the current user 307 | return new AccountController.ChallengeResult(provider, Url.Action("LinkLoginCallback", "Manage"), User.Identity.GetUserId()); 308 | } 309 | 310 | // 311 | // GET: /Manage/LinkLoginCallback 312 | public async Task LinkLoginCallback() 313 | { 314 | var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync(XsrfKey, User.Identity.GetUserId()); 315 | if (loginInfo == null) 316 | { 317 | return RedirectToAction("ManageLogins", new { Message = ManageMessageId.Error }); 318 | } 319 | var result = await UserManager.AddLoginAsync(User.Identity.GetUserId(), loginInfo.Login); 320 | return result.Succeeded ? RedirectToAction("ManageLogins") : RedirectToAction("ManageLogins", new { Message = ManageMessageId.Error }); 321 | } 322 | 323 | protected override void Dispose(bool disposing) 324 | { 325 | if (disposing && _userManager != null) 326 | { 327 | _userManager.Dispose(); 328 | _userManager = null; 329 | } 330 | 331 | base.Dispose(disposing); 332 | } 333 | 334 | #region Helpers 335 | 336 | // Used for XSRF protection when adding external logins 337 | private const string XsrfKey = "XsrfId"; 338 | 339 | private IAuthenticationManager AuthenticationManager 340 | { 341 | get 342 | { 343 | return HttpContext.GetOwinContext().Authentication; 344 | } 345 | } 346 | 347 | private void AddErrors(IdentityResult result) 348 | { 349 | foreach (var error in result.Errors) 350 | { 351 | ModelState.AddModelError("", error); 352 | } 353 | } 354 | 355 | private bool HasPassword() 356 | { 357 | var user = UserManager.FindById(User.Identity.GetUserId()); 358 | if (user != null) 359 | { 360 | return user.PasswordHash != null; 361 | } 362 | return false; 363 | } 364 | 365 | private bool HasPhoneNumber() 366 | { 367 | var user = UserManager.FindById(User.Identity.GetUserId()); 368 | if (user != null) 369 | { 370 | return user.PhoneNumber != null; 371 | } 372 | return false; 373 | } 374 | 375 | public enum ManageMessageId 376 | { 377 | AddPhoneSuccess, 378 | ChangePasswordSuccess, 379 | SetTwoFactorSuccess, 380 | SetPasswordSuccess, 381 | RemoveLoginSuccess, 382 | RemovePhoneSuccess, 383 | Error 384 | } 385 | 386 | #endregion Helpers 387 | } 388 | } -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/DynamicRoleBasedAccess.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | 8 | 9 | 2.0 10 | {A967E089-2595-41CF-8FA3-1B8DCC6A806B} 11 | {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} 12 | Library 13 | Properties 14 | DynamicRoleBasedAccess 15 | DynamicRoleBasedAccess 16 | v4.5.2 17 | false 18 | true 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | true 27 | full 28 | false 29 | bin\ 30 | DEBUG;TRACE 31 | prompt 32 | 4 33 | 34 | 35 | pdbonly 36 | true 37 | bin\ 38 | TRACE 39 | prompt 40 | 4 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | True 64 | ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll 65 | 66 | 67 | 68 | 69 | 70 | 71 | True 72 | ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll 73 | 74 | 75 | True 76 | ..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll 77 | 78 | 79 | ..\packages\Microsoft.AspNet.Web.Optimization.1.1.3\lib\net40\System.Web.Optimization.dll 80 | 81 | 82 | True 83 | ..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll 84 | 85 | 86 | True 87 | ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll 88 | 89 | 90 | True 91 | ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll 92 | 93 | 94 | True 95 | ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll 96 | 97 | 98 | True 99 | ..\packages\WebGrease.1.5.2\lib\WebGrease.dll 100 | 101 | 102 | True 103 | ..\packages\Antlr.3.4.1.9004\lib\Antlr3.Runtime.dll 104 | 105 | 106 | 107 | 108 | ..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll 109 | 110 | 111 | ..\packages\EntityFramework.6.1.2\lib\net45\EntityFramework.dll 112 | 113 | 114 | ..\packages\EntityFramework.6.1.2\lib\net45\EntityFramework.SqlServer.dll 115 | 116 | 117 | ..\packages\Microsoft.AspNet.Identity.Core.2.2.0\lib\net45\Microsoft.AspNet.Identity.Core.dll 118 | 119 | 120 | ..\packages\Microsoft.AspNet.Identity.Owin.2.2.0\lib\net45\Microsoft.AspNet.Identity.Owin.dll 121 | 122 | 123 | ..\packages\Microsoft.AspNet.Identity.EntityFramework.2.2.0\lib\net45\Microsoft.AspNet.Identity.EntityFramework.dll 124 | 125 | 126 | ..\packages\Owin.1.0\lib\net40\Owin.dll 127 | 128 | 129 | ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll 130 | 131 | 132 | ..\packages\Microsoft.Owin.Host.SystemWeb.3.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll 133 | 134 | 135 | ..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll 136 | 137 | 138 | ..\packages\Microsoft.Owin.Security.Facebook.3.0.1\lib\net45\Microsoft.Owin.Security.Facebook.dll 139 | 140 | 141 | ..\packages\Microsoft.Owin.Security.Cookies.3.0.1\lib\net45\Microsoft.Owin.Security.Cookies.dll 142 | 143 | 144 | ..\packages\Microsoft.Owin.Security.OAuth.3.0.1\lib\net45\Microsoft.Owin.Security.OAuth.dll 145 | 146 | 147 | ..\packages\Microsoft.Owin.Security.Google.3.0.1\lib\net45\Microsoft.Owin.Security.Google.dll 148 | 149 | 150 | ..\packages\Microsoft.Owin.Security.Twitter.3.0.1\lib\net45\Microsoft.Owin.Security.Twitter.dll 151 | 152 | 153 | ..\packages\Microsoft.Owin.Security.MicrosoftAccount.3.0.1\lib\net45\Microsoft.Owin.Security.MicrosoftAccount.dll 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | Global.asax 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | Designer 204 | 205 | 206 | Web.config 207 | 208 | 209 | Web.config 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 10.0 257 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | True 270 | True 271 | 58192 272 | / 273 | http://localhost:58192/ 274 | False 275 | False 276 | 277 | 278 | False 279 | 280 | 281 | 282 | 283 | 289 | -------------------------------------------------------------------------------- /DynamicRoleBasedAccess/Scripts/jquery.bonsai.js: -------------------------------------------------------------------------------- 1 | (function ($) { 2 | $.fn.qubit = function (options) { 3 | return this.each(function () { 4 | new Qubit(this, options); 5 | }); 6 | }; 7 | var Qubit = function (el) { 8 | var self = this; 9 | this.scope = $(el); 10 | var handler = function (e) { 11 | if (!self.suspendListeners) { 12 | self.process(e.target); 13 | } 14 | }; 15 | this.scope.on('change', 'input[type=checkbox]', handler); 16 | // workaround for IE<10 17 | if (document.documentMode && document.documentMode <= 9) { 18 | this.scope.on('click', 'input[type=checkbox]:indeterminate', handler); 19 | } 20 | this.processParents(); 21 | }; 22 | Qubit.prototype = { 23 | itemSelector: 'li', 24 | process: function (checkbox) { 25 | checkbox = $(checkbox); 26 | var parentItems = checkbox.parentsUntil(this.scope, this.itemSelector); 27 | var self = this; 28 | try { 29 | this.suspendListeners = true; 30 | // all children inherit my state 31 | parentItems.eq(0).find('input[type=checkbox]') 32 | .filter(checkbox.prop('checked') ? ':not(:checked)' : ':checked') 33 | .each(function () { 34 | if (!$(this).parent().hasClass('hidden')) { 35 | self.setChecked($(this), checkbox.prop('checked')); 36 | } 37 | }) 38 | .trigger('change'); 39 | this.processParents(); 40 | } finally { 41 | this.suspendListeners = false; 42 | } 43 | }, 44 | processParents: function () { 45 | var self = this, changed = false; 46 | this.scope.find('input[type=checkbox]').each(function () { 47 | var $this = $(this); 48 | var parent = $this.closest(self.itemSelector); 49 | var children = parent.find('input[type=checkbox]').not($this); 50 | var numChecked = children.filter(function () { 51 | return $(this).prop('checked') || $(this).prop('indeterminate'); 52 | }).length; 53 | 54 | if (children.length) { 55 | if (numChecked === 0) { 56 | self.setChecked($this, false) && (changed = true); 57 | } 58 | else if (numChecked == children.length) { 59 | self.setChecked($this, true) && (changed = true); 60 | } 61 | else { 62 | self.setIndeterminate($this, true) && (changed = true); 63 | } 64 | } 65 | else { 66 | self.setIndeterminate($this, false) && (changed = true); 67 | } 68 | }); 69 | if (changed) this.processParents(); 70 | }, 71 | setChecked: function (checkbox, value, event) { 72 | var changed = false; 73 | if (checkbox.prop('indeterminate')) { 74 | checkbox.prop('indeterminate', false); 75 | changed = true; 76 | } 77 | if (checkbox.prop('checked') != value) { 78 | checkbox.prop('checked', value).trigger('change'); 79 | changed = true; 80 | } 81 | return changed; 82 | }, 83 | setIndeterminate: function (checkbox, value) { 84 | if (value) { 85 | checkbox.prop('checked', false); 86 | } 87 | if (checkbox.prop('indeterminate') != value) { 88 | checkbox.prop('indeterminate', value); 89 | return true; 90 | } 91 | } 92 | }; 93 | }(jQuery)); 94 | 95 | (function ($) { 96 | $.fn.bonsai = function (options) { 97 | var args = arguments; 98 | return this.each(function () { 99 | var bonsai = $(this).data('bonsai'); 100 | if (!bonsai) { 101 | bonsai = new Bonsai(this, options); 102 | $(this).data('bonsai', bonsai); 103 | } 104 | if (typeof options == 'string') { 105 | var method = options; 106 | return bonsai[method].apply(bonsai, [].slice.call(args, 1)); 107 | } 108 | }); 109 | }; 110 | $.bonsai = {}; 111 | $.bonsai.defaults = { 112 | expandAll: false, // expand all items 113 | expand: null, // optional function to expand an item 114 | collapse: null, // optional function to collapse an item 115 | addExpandAll: false, // add a link to expand all items 116 | addSelectAll: false, // add a link to select all checkboxes 117 | selectAllExclude: null, // a filter selector or function for selectAll 118 | idAttribute: 'id', // which attribute of the list items to use as an id 119 | 120 | // createInputs: create checkboxes or radio buttons for each list item 121 | // by setting createInputs to "checkbox" or "radio". 122 | // 123 | // The name and value for the inputs can be declared in the 124 | // markup using `data-name` and `data-value`. 125 | // 126 | // The name is inherited from parent items if not specified. 127 | // 128 | // Checked state can be indicated using `data-checked`. 129 | createInputs: false, 130 | // checkboxes: run qubit(this.options) on the root node (requires jquery.qubit) 131 | checkboxes: false, 132 | // handleDuplicateCheckboxes: update any other checkboxes that 133 | // have the same value 134 | handleDuplicateCheckboxes: false, 135 | // createRadioButtons: creates radio buttons for each list item. 136 | // 137 | // The name and value for the checkboxes can be declared in the 138 | // markup using `data-name` and `data-value`. 139 | // 140 | // The name is inherited from parent items if not specified. 141 | // 142 | // Checked state can be indicated using `data-checked`. 143 | createRadioButtons: false 144 | }; 145 | var Bonsai = function (el, options) { 146 | var self = this; 147 | options = options || {}; 148 | this.options = $.extend({}, $.bonsai.defaults, options); 149 | this.el = $(el).addClass('bonsai').data('bonsai', this); 150 | 151 | // store the scope in the options for child nodes 152 | if (!this.options.scope) { 153 | this.options.scope = this.el; 154 | } 155 | this.update(); 156 | if (this.isRootNode()) { 157 | if (this.options.createCheckboxes) this.createInputs = 'checkbox'; 158 | if (this.options.handleDuplicateCheckboxes) this.handleDuplicateCheckboxes(); 159 | if (this.options.checkboxes) this.el.qubit(this.options); 160 | if (this.options.addExpandAll) this.addExpandAllLink(); 161 | if (this.options.addSelectAll) this.addSelectAllLink(); 162 | this.el.on('click', '.thumb', function (ev) { 163 | self.toggle($(ev.currentTarget).closest('li')); 164 | }); 165 | } 166 | if (this.options.expandAll) this.expandAll(); 167 | }; 168 | Bonsai.prototype = { 169 | isRootNode: function () { 170 | return this.options.scope == this.el; 171 | }, 172 | listItem: function (id) { 173 | if (typeof id === 'object') return $(id); 174 | return this.el.find('[' + this.options.idAttribute + '="' + id + '"]'); 175 | }, 176 | toggle: function (listItem) { 177 | if (!$(listItem).hasClass('expanded')) { 178 | return this.expand(listItem); 179 | } 180 | else { 181 | return this.collapse(listItem); 182 | } 183 | }, 184 | expand: function (listItem) { 185 | return this.setExpanded(listItem, true); 186 | }, 187 | collapse: function (listItem) { 188 | return this.setExpanded(listItem, false); 189 | }, 190 | setExpanded: function (listItem, expanded) { 191 | var $li = this.listItem(listItem); 192 | if ($li.length > 1) { 193 | var self = this; 194 | $li.each(function () { 195 | self.setExpanded(this, expanded); 196 | }); 197 | return; 198 | } 199 | if (expanded) { 200 | if (!$li.data('subList')) return; 201 | $li = $($li).addClass('expanded').removeClass('collapsed'); 202 | $($li.data('subList')).css('height', 'auto'); 203 | } 204 | else { 205 | $li = $($li).addClass('collapsed') 206 | .removeClass('expanded'); 207 | $($li.data('subList')).height(0); 208 | } 209 | return $li; 210 | }, 211 | expandAll: function () { 212 | this.expand(this.el.find('li')); 213 | }, 214 | collapseAll: function () { 215 | this.collapse(this.el.find('li')); 216 | }, 217 | expandTo: function (listItem) { 218 | var self = this; 219 | var $li = this.listItem(listItem); 220 | $li.parents('li').each(function () { 221 | self.expand($(this)); 222 | }); 223 | return $li; 224 | }, 225 | update: function () { 226 | var self = this; 227 | // look for a nested list (if any) 228 | this.el.children().each(function () { 229 | var item = $(this); 230 | if (self.options.createInputs) self.insertInput(item); 231 | 232 | // insert a thumb if it doesn't already exist 233 | if (item.children().filter('.thumb').length == 0) { 234 | var thumb = $('
    '); 235 | item.prepend(thumb); 236 | } 237 | var subLists = item.children().filter('ol, ul'); 238 | item.toggleClass('has-children', subLists.find('li').length > 0); 239 | // if there is a child list 240 | subLists.each(function () { 241 | // that's not empty 242 | if ($('li', this).length == 0) { 243 | return; 244 | } 245 | // then this el has children 246 | item.data('subList', this); 247 | // collapse the nested list 248 | if (item.hasClass('expanded')) { 249 | self.expand(item); 250 | } 251 | else { 252 | self.collapse(item); 253 | } 254 | // handle any deeper nested lists 255 | var exists = !!$(this).data('bonsai'); 256 | $(this).bonsai(exists ? 'update' : self.options); 257 | }); 258 | }); 259 | 260 | this.expand = this.options.expand || this.expand; 261 | this.collapse = this.options.collapse || this.collapse; 262 | }, 263 | serialize: function () { 264 | var idAttr = this.options.idAttribute; 265 | return this.el.find('li').toArray().reduce(function (acc, li) { 266 | var $li = $(li); 267 | var id = $li.attr(idAttr); 268 | // only items with IDs can be serialized 269 | if (id) { 270 | var state = $li.hasClass('expanded') 271 | ? 'expanded' 272 | : ($li.hasClass('collapsed') ? 'collapsed' : null); 273 | if (state) acc[$li.hasClass('expanded') ? 'expanded' : 'collapsed'].push(id); 274 | } 275 | return acc; 276 | }, { expanded: [], collapsed: [], version: 2 }); 277 | }, 278 | restore: function (state) { 279 | var self = this; 280 | if (state.version > 1) { 281 | state.expanded.map(this.expand.bind(this)); 282 | state.collapsed.map(this.collapse.bind(this)); 283 | } 284 | else { 285 | Object.keys(state).forEach(function (id) { 286 | self.setExpanded(id, state[id] === 'expanded'); 287 | }); 288 | } 289 | }, 290 | insertInput: function (listItem) { 291 | var type = this.options.createInputs; 292 | if (listItem.find('> input[type=' + type + ']').length) return; 293 | var id = this.inputIdFor(listItem); 294 | var checkbox = $(' ' 296 | ); 297 | var children = listItem.children(); 298 | // get the first text node for the label 299 | var text = listItem.contents().filter(function () { 300 | return this.nodeType == 3; 301 | }).first(); 302 | checkbox.val(listItem.data('value')); 303 | checkbox.prop('checked', listItem.data('checked')) 304 | children.detach(); 305 | listItem.append(checkbox) 306 | .append( 307 | $('