• 【源码笔记】BlogEngine.Net 中的权限管理


           BlogEngine.Net 是个功能点很全面的开源博客系统,容易安装和实现定制,开放接口支持TrackBack,可以定义主题配置数据源等等。可谓五脏俱全,这里先记录一下它基于Membership的权限管理(一般只说到角色就没了)。

          Membership是.net2.0的时候就出来了,现在的最新版本是Identity(微软已经将这个Asp.net项目开源 https://github.com/aspnet/Identity  )。权限管理就是处理用户、角色、和具体权限的关系。用户和角色是多对多的关系,角色和权限也是多对多的关系。 用户通过拥有角色来间接获得权限。但为什么要使用Membership呢,我们可以在数据库中建几张表就可以搞定这些关系了,因为想用Asp.Net自带的账户管理,比自己实现的要安全方便。废话不多说了,切入正题。

          

    一、MembershipProvider 用户/账户管理

    功能:用户注册,登陆,账户管理

           Membership是基于Provider实现,在Asp.Net中到处可以见到Provider的身影。MembershipProvider是一个抽象类,主要负责给Membership提供用户账户验证方面的方法。BlogEngine实现了XmlMembershipProvider和DbMembershipProvider。再通过Webconfig的配置来决定启用哪一种MembershipProvider。

    1. 以XmlMembershipProvider为例,比较重要的一些方法是CreateUser,ValidateUser,ChangePassword 等。

    (完整的源码可以去官网下载,这里不列出了) 

    public class XmlMembershipProvider : MembershipProvider
        {
           //....
          
             /// <summary>
            /// Creates the user.
            /// </summary>
            /// <param name="username">The username.</param>
            /// <param name="password">The password.</param>
            /// <param name="email">The email.</param>
            /// <param name="passwordQuestion">The password question.</param>
            /// <param name="passwordAnswer">The password answer.</param>
            /// <param name="approved">if set to <c>true</c> [approved].</param>
            /// <param name="providerUserKey">The provider user key.</param>
            /// <param name="status">The status.</param>
            /// <returns>A Membership User.</returns>
            public override MembershipUser CreateUser(
                string username, 
                string password, 
                string email, 
                string passwordQuestion, 
                string passwordAnswer, 
                bool approved, 
                object providerUserKey, 
                out MembershipCreateStatus status)
            {
                this.ReadMembershipDataStore();
    
                if (this.users[Blog.CurrentInstance.Id].ContainsKey(username))
                {
                    throw new NotSupportedException("The username is already in use. Please choose another username.");
                }
    
                var doc = new XmlDocument();
                doc.Load(XmlFullyQualifiedPath);
    
                XmlNode xmlUserRoot = doc.CreateElement("User");
                XmlNode xmlUserName = doc.CreateElement("UserName");
                XmlNode xmlPassword = doc.CreateElement("Password");
                XmlNode xmlEmail = doc.CreateElement("Email");
                XmlNode xmlLastLoginTime = doc.CreateElement("LastLoginTime");
    
                xmlUserName.InnerText = username;
    
                string passwordPrep = this.passwordFormat == MembershipPasswordFormat.Hashed ? Utils.HashPassword(password) : password;
    
                xmlPassword.InnerText = passwordPrep;
    
                xmlEmail.InnerText = email;
                xmlLastLoginTime.InnerText = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
    
                xmlUserRoot.AppendChild(xmlUserName);
                xmlUserRoot.AppendChild(xmlPassword);
                xmlUserRoot.AppendChild(xmlEmail);
                xmlUserRoot.AppendChild(xmlLastLoginTime);
    
                doc.SelectSingleNode("Users").AppendChild(xmlUserRoot);
                doc.Save(XmlFullyQualifiedPath);
    
                status = MembershipCreateStatus.Success;
                var user = new MembershipUser(
                    this.Name, 
                    username, 
                    username, 
                    email, 
                    passwordQuestion, 
                    passwordPrep, 
                    approved, 
                    false, 
                    DateTime.Now, 
                    DateTime.Now, 
                    DateTime.Now, 
                    DateTime.Now, 
                    DateTime.MaxValue);
                this.users[Blog.CurrentInstance.Id].Add(username, user);
                return user;
            }
    
            /// <summary>
            /// Removes a user from the membership data source.
            /// </summary>
            /// <param name="username">The name of the user to delete.</param>
            /// <param name="deleteAllRelatedData">true to delete data related to the user from the database; false to leave data related to the user in the database.</param>
            /// <returns>
            /// true if the user was successfully deleted; otherwise, false.
            /// </returns>
            public override bool DeleteUser(string username, bool deleteAllRelatedData)
            {
                this.ReadMembershipDataStore();
    
                var doc = new XmlDocument();
                doc.Load(XmlFullyQualifiedPath);
    
                foreach (XmlNode node in
                    doc.GetElementsByTagName("User").Cast<XmlNode>().Where(node => node.ChildNodes[0].InnerText.Equals(username, StringComparison.OrdinalIgnoreCase)))
                {
                    doc.SelectSingleNode("Users").RemoveChild(node);
                    doc.Save(XmlFullyQualifiedPath);
                    this.users[Blog.CurrentInstance.Id].Remove(username);
                    return true;
                }
    
                return false;
            }
      /// <summary>
            /// Processes a request to update the password for a membership user.
            /// </summary>
            /// <param name="username">The user to update the password for.</param>
            /// <param name="oldPassword">The current password for the specified user.</param>
            /// <param name="newPassword">The new password for the specified user.</param>
            /// <returns>
            /// true if the password was updated successfully; otherwise, false.
            /// </returns>
            public override bool ChangePassword(string username, string oldPassword, string newPassword)
            {
                var doc = new XmlDocument();
                doc.Load(XmlFullyQualifiedPath);
                var nodes = doc.GetElementsByTagName("User");
                foreach (XmlNode node in nodes)
                {
                    if (!node["UserName"].InnerText.Equals(username, StringComparison.OrdinalIgnoreCase))
                    {
                        continue;
                    }
    
                    if (!this.CheckPassword(node["Password"].InnerText, oldPassword))
                    {
                        continue;
                    }
                    
                    string passwordPrep = this.passwordFormat == MembershipPasswordFormat.Hashed ? Utils.HashPassword(newPassword) : newPassword;
    
                    node["Password"].InnerText = passwordPrep;
                    doc.Save(XmlFullyQualifiedPath);
    
                    this.users = null;
                    this.ReadMembershipDataStore();
                    return true;
                }
    
                return false;
            }
    //......
       }
    View Code

    2.webconfig配置:

     在system.web目录下。通过defaultProvider来指定。

     <membership defaultProvider="XmlMembershipProvider">
          <providers>
            <clear />
            <add name="XmlMembershipProvider" type="BlogEngine.Core.Providers.XmlMembershipProvider, BlogEngine.Core" description="XML membership provider" passwordFormat="Hashed" />
            <add name="SqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider" connectionStringName="BlogEngine" applicationName="BlogEngine" />
            <add name="DbMembershipProvider" type="BlogEngine.Core.Providers.DbMembershipProvider, BlogEngine.Core" passwordFormat="Hashed" connectionStringName="BlogEngine" />
          </providers>
        </membership>

     这里看到的SqlMembershipProvider是在.net2.0中就自带的一个Provider。

     3.那这样就可以在我们的AccountController中调用了。

    [HttpPost]
            [AllowAnonymous]
            [ValidateAntiForgeryToken]
            public ActionResult Register(RegisterModel model)
            {
                if (ModelState.IsValid)
                {
                    // 尝试注册用户
                    try
                    {
                        Membership.CreateUser(model.UserName, model.Password, model.Email);
                        FormsAuthentication.SetAuthCookie(model.UserName, false);
                        return RedirectToAction("Index", "Home");
                    }
                    catch (MembershipCreateUserException e)
                    {
                        ModelState.AddModelError("", ErrorCodeToString(e.StatusCode));
                    }
                }
                // 如果我们进行到这一步时某个地方出错,则重新显示表单
                return View(model);
            }

    另外还封装了一个UsersRepository,并通过API的方式供外部使用。

     public class UsersRepository : IUsersRepository
        {
            /// <summary>
            /// Post list
            /// </summary>
            /// <param name="filter">Filter expression</param>
            /// <param name="order">Order expression</param>
            /// <param name="skip">Records to skip</param>
            /// <param name="take">Records to take</param>
            /// <returns>List of users</returns>
            public IEnumerable<BlogUser> Find(int take = 10, int skip = 0, string filter = "", string order = "")
            {
                if (!Security.IsAuthorizedTo(BlogEngine.Core.Rights.AccessAdminPages))
                    throw new System.UnauthorizedAccessException();
    
                var users = new List<BlogUser>();
                int count;
                var userCollection = Membership.Provider.GetAllUsers(0, 999, out count);
                var members = userCollection.Cast<MembershipUser>().ToList();
    
                foreach (var m in members)
                {
                    users.Add(new BlogUser { 
                        IsChecked = false, 
                        UserName = m.UserName, 
                        Email = m.Email,
                        Profile = GetProfile(m.UserName),
                        Roles = GetRoles(m.UserName)
                    });
                }
    
                var query = users.AsQueryable().Where(filter);
    
                // if take passed in as 0, return all
                if (take == 0) take = users.Count;
    
                return query.OrderBy(order).Skip(skip).Take(take);
            }
    
            /// <summary>
            /// Get single post
            /// </summary>
            /// <param name="id">User id</param>
            /// <returns>User object</returns>
            public BlogUser FindById(string id)
            {
                if (!Security.IsAuthorizedTo(BlogEngine.Core.Rights.AccessAdminPages))
                    throw new System.UnauthorizedAccessException();
    
                var users = new List<BlogUser>();
                int count;
                var userCollection = Membership.Provider.GetAllUsers(0, 999, out count);
                var members = userCollection.Cast<MembershipUser>().ToList();
    
                foreach (var m in members)
                {
                    users.Add(new BlogUser
                    {
                        IsChecked = false,
                        UserName = m.UserName,
                        Email = m.Email,
                        Profile = GetProfile(m.UserName),
                        Roles = GetRoles(m.UserName)
                    });
                }
                return users.AsQueryable().Where("UserName.ToLower() == "" + id.ToLower() + """).FirstOrDefault();
            }
    
            /// <summary>
            /// Add new user
            /// </summary>
            /// <param name="user">Blog user</param>
            /// <returns>Saved user</returns>
            public BlogUser Add(BlogUser user)
            {
                if (!Security.IsAuthorizedTo(BlogEngine.Core.Rights.CreateNewUsers))
                    throw new System.UnauthorizedAccessException();
    
                if (user == null || string.IsNullOrEmpty(user.UserName)
                    || string.IsNullOrEmpty(user.Email) || string.IsNullOrEmpty(user.Password))
                {
                    throw new ApplicationException("Error adding new user; Missing required fields");
                }
    
                if (!Security.IsAuthorizedTo(Rights.CreateNewUsers))
                    throw new ApplicationException("Not authorized");
    
                // create user
                var usr = Membership.CreateUser(user.UserName, user.Password, user.Email);
                if (usr == null)
                    throw new ApplicationException("Error creating new user");
    
                UpdateUserProfile(user);
    
                UpdateUserRoles(user);
    
                user.Password = "";
                return user;
            }
    
            /// <summary>
            /// Update user
            /// </summary>
            /// <param name="user">User to update</param>
            /// <returns>True on success</returns>
            public bool Update(BlogUser user)
            {
                if (!Security.IsAuthorizedTo(BlogEngine.Core.Rights.EditOwnUser))
                    throw new System.UnauthorizedAccessException();
    
                if (user == null || string.IsNullOrEmpty(user.UserName) || string.IsNullOrEmpty(user.Email))
                    throw new ApplicationException("Error adding new user; Missing required fields");
    
                if (!Security.IsAuthorizedTo(Rights.EditOwnUser))
                    throw new ApplicationException("Not authorized");
    
                // update user
                var usr = Membership.GetUser(user.UserName);
    
                if (usr == null)
                    return false;
    
                usr.Email = user.Email;
                Membership.UpdateUser(usr);
    
                UpdateUserProfile(user);
    
                UpdateUserRoles(user);
    
                return true;
            }
    
            /// <summary>
            /// Save user profile
            /// </summary>
            /// <param name="user">Blog user</param>
            /// <returns>True on success</returns>
            public bool SaveProfile(BlogUser user)
            {
                return UpdateUserProfile(user);
            }
    
            /// <summary>
            /// Delete user
            /// </summary>
            /// <param name="id">User ID</param>
            /// <returns>True on success</returns>
            public bool Remove(string id){
                if (string.IsNullOrEmpty(id))
                    return false;
    
                if (!Security.IsAuthorizedTo(BlogEngine.Core.Rights.DeleteUserSelf))
                    throw new System.UnauthorizedAccessException();
    
                bool isSelf = id.Equals(Security.CurrentUser.Identity.Name, StringComparison.OrdinalIgnoreCase);
    
                if (isSelf && !Security.IsAuthorizedTo(Rights.DeleteUserSelf))
                    throw new ApplicationException("Not authorized");
    
                else if (!isSelf && !Security.IsAuthorizedTo(Rights.DeleteUsersOtherThanSelf))
                    throw new ApplicationException("Not authorized");
    
                // Last check - it should not be possible to remove the last use who has the right to Add and/or Edit other user accounts. If only one of such a 
                // user remains, that user must be the current user, and can not be deleted, as it would lock the user out of the BE environment, left to fix
                // it in XML or SQL files / commands. See issue 11990
                bool adminsExist = false;
                MembershipUserCollection users = Membership.GetAllUsers();
                foreach (MembershipUser user in users)
                {
                    string[] roles = Roles.GetRolesForUser(user.UserName);
    
                    // look for admins other than 'id' 
                    if (!id.Equals(user.UserName, StringComparison.OrdinalIgnoreCase) && (Right.HasRight(Rights.EditOtherUsers, roles) || Right.HasRight(Rights.CreateNewUsers, roles)))
                    {
                        adminsExist = true;
                        break;
                    }
                }
    
                if (!adminsExist)
                    throw new ApplicationException("Can not delete last admin");
    
                string[] userRoles = Roles.GetRolesForUser(id);
    
                try
                {
                    if (userRoles.Length > 0)
                    {
                        Roles.RemoveUsersFromRoles(new string[] { id }, userRoles);
                    }
    
                    Membership.DeleteUser(id);
    
                    var pf = AuthorProfile.GetProfile(id);
                    if (pf != null)
                    {
                        BlogEngine.Core.Providers.BlogService.DeleteProfile(pf);
                    }
                }
                catch (Exception ex)
                {
                    Utils.Log("Error deleting user", ex.Message);
                    return false;
                }
                return true;
            }
    
            #region Private methods
    
            static Profile GetProfile(string id)
            {
                if (!Utils.StringIsNullOrWhitespace(id))
                {
                    var pf = AuthorProfile.GetProfile(id);
                    if (pf == null)
                    {
                        pf = new AuthorProfile(id);
                        pf.Birthday = DateTime.Parse("01/01/1900");
                        pf.DisplayName = id;
                        pf.EmailAddress = Utils.GetUserEmail(id);
                        pf.FirstName = id;
                        pf.Private = true;
                        pf.Save();
                    }
                    
                    return new Profile { 
                        AboutMe = string.IsNullOrEmpty(pf.AboutMe) ? "" : pf.AboutMe,
                        Birthday = pf.Birthday.ToShortDateString(),
                        CityTown = string.IsNullOrEmpty(pf.CityTown) ? "" : pf.CityTown,
                        Country = string.IsNullOrEmpty(pf.Country) ? "" : pf.Country,
                        DisplayName = pf.DisplayName,
                        EmailAddress = pf.EmailAddress,
                        PhoneFax = string.IsNullOrEmpty(pf.PhoneFax) ? "" : pf.PhoneFax,
                        FirstName = string.IsNullOrEmpty(pf.FirstName) ? "" : pf.FirstName,
                        Private = pf.Private,
                        LastName = string.IsNullOrEmpty(pf.LastName) ? "" : pf.LastName,
                        MiddleName = string.IsNullOrEmpty(pf.MiddleName) ? "" : pf.MiddleName,
                        PhoneMobile = string.IsNullOrEmpty(pf.PhoneMobile) ? "" : pf.PhoneMobile,
                        PhoneMain = string.IsNullOrEmpty(pf.PhoneMain) ? "" : pf.PhoneMain,
                        PhotoUrl = string.IsNullOrEmpty(pf.PhotoUrl) ? "" : pf.PhotoUrl.Replace(""", ""),
                        RegionState = string.IsNullOrEmpty(pf.RegionState) ? "" : pf.RegionState
                    };
                }
                return null;
            }
    
            static List<Data.Models.RoleItem> GetRoles(string id)
            {
                var roles = new List<Data.Models.RoleItem>();
                var userRoles = new List<Data.Models.RoleItem>();
    
                roles.AddRange(System.Web.Security.Roles.GetAllRoles().Select(r => new Data.Models.RoleItem { RoleName = r, IsSystemRole = Security.IsSystemRole(r) }));
                roles.Sort((r1, r2) => string.Compare(r1.RoleName, r2.RoleName));
    
                foreach (var r in roles)
                {
                    if (System.Web.Security.Roles.IsUserInRole(id, r.RoleName))
                    {
                        userRoles.Add(r);
                    }
                }
                return userRoles;
            }
    
            static bool UpdateUserProfile(BlogUser user)
            {
                if (user == null || string.IsNullOrEmpty(user.UserName))
                    return false;
    
                var pf = AuthorProfile.GetProfile(user.UserName) 
                    ?? new AuthorProfile(user.UserName);
                try
                {
                    pf.DisplayName = user.Profile.DisplayName;
                    pf.FirstName = user.Profile.FirstName;
                    pf.MiddleName = user.Profile.MiddleName;
                    pf.LastName = user.Profile.LastName;
                    pf.EmailAddress = user.Email; // user.Profile.EmailAddress;
    
                    DateTime date;
                    if (user.Profile.Birthday.Length == 0)
                        user.Profile.Birthday = "1/1/1001";
    
                    if (DateTime.TryParse(user.Profile.Birthday, out date))
                        pf.Birthday = date;
    
                    pf.PhotoUrl = user.Profile.PhotoUrl.Replace(""", "");
                    pf.Private = user.Profile.Private;
    
                    pf.PhoneMobile = user.Profile.PhoneMobile;
                    pf.PhoneMain = user.Profile.PhoneMain;
                    pf.PhoneFax = user.Profile.PhoneFax;
    
                    pf.CityTown = user.Profile.CityTown;
                    pf.RegionState = user.Profile.RegionState;
                    pf.Country = user.Profile.Country;
                    pf.AboutMe = user.Profile.AboutMe;
    
                    pf.Save();
                    UpdateProfileImage(pf);
                }
                catch (Exception ex)
                {
                    Utils.Log("Error editing profile", ex);
                    return false;
                }
                return true;
            }
    
            static bool UpdateUserRoles(BlogUser user)
            {
                try
                {
                    // remove all user roles and add only checked
                    string[] currentRoles = Roles.GetRolesForUser(user.UserName);
                    if (currentRoles.Length > 0)
                        Roles.RemoveUserFromRoles(user.UserName, currentRoles);
    
                    if (user.Roles.Count > 0)
                    {
                        string[] roles = user.Roles.Where(ur => ur.IsChecked).Select(r => r.RoleName).ToArray();
    
                        if(roles.Length > 0)
                            Roles.AddUsersToRoles(new string[] { user.UserName }, roles);
                        else
                            Roles.AddUsersToRoles(new string[] { user.UserName }, new string[] { BlogConfig.AnonymousRole });
                    }
                    return true;
                }
                catch (Exception ex)
                {
                    Utils.Log("Error updating user roles", ex);
                    return false;
                }
            }
    
            /// <summary>
            /// Remove any existing profile images
            /// </summary>
            /// <param name="profile">User profile</param>
            static void UpdateProfileImage(AuthorProfile profile)
            {
                var dir = BlogEngine.Core.Providers.BlogService.GetDirectory("/avatars");
    
                if(string.IsNullOrEmpty(profile.PhotoUrl))
                {
                    foreach (var f in dir.Files)
                    {
                        var dot = f.Name.IndexOf(".");
                        var img = dot > 0 ? f.Name.Substring(0, dot) : f.Name;
                        if (profile.UserName == img)
                        {
                            f.Delete();
                        }
                    }
                }
                else
                {
                    foreach (var f in dir.Files)
                    {
                        var dot = f.Name.IndexOf(".");
                        var img = dot > 0 ? f.Name.Substring(0, dot) : f.Name;
                        // delete old profile image saved with different name
                        // for example was admin.jpg and now admin.png
                        if (profile.UserName == img && f.Name != profile.PhotoUrl.Replace(""", ""))
                        {
                            f.Delete();
                        }
                    }
                }
            }
    
            #endregion
        }
    View Code
    unity.RegisterType<UsersController>();
    unity.RegisterType<IUsersRepository, UsersRepository>(new    HierarchicalLifetimeManager());
    //......
    public class UsersController : ApiController
    {
        readonly IUsersRepository repository;
    
        public UsersController(IUsersRepository repository)
        {
            this.repository = repository;
        }
         //..........
    }
    View Code

    最后的结构图如下: 

    二、RoleProvider 角色管理

    功能:提供用户角色的管理、验证相关方法。

       同上,BlogEngine提供了DbRoleProvider和XmlRoleProvider。而且通过配置文件加入了系统角色。在BlogConfig.cs文件中可以看到,他提供了三个系统角色,管理员,匿名用户和编辑。

    #region AdministratorRole
    
            private static string _administrativeRole;
    
            /// <summary>
            ///     The role that has administrator persmissions
            /// </summary>
            public static string AdministratorRole
            {
                get
                {
                    return _administrativeRole ?? (_administrativeRole = WebConfigurationManager.AppSettings["BlogEngine.AdminRole"] ?? "administrators");
                }
            }
            #endregion
    
            #region AnonymousRole
    
            private static string _anonymousRole;
    
            /// <summary>
            /// The role that represents all non-authenticated users.
            /// </summary>
            public static string AnonymousRole
            {
                get
                {
                    return _anonymousRole ?? (_anonymousRole = WebConfigurationManager.AppSettings["BlogEngine.AnonymousRole"] ?? "Anonymous");
                }
            }
    
            #endregion
    
            #region EditorsRole
    
            private static string _editorsRole;
    
            /// <summary>
            /// The role that represents all non-authenticated users.
            /// </summary>
            public static string EditorsRole
            {
                get
                {
                    return _editorsRole ?? (_editorsRole = WebConfigurationManager.AppSettings["BlogEngine.EditorsRole"] ?? "Editors");
                }
            }
    
            #endregion
    View Code

    在Web.config的AppSettings的节点可以看到,且这样可以比较方便的修改默认名称。

     <add key="BlogEngine.AdminRole" value="Administrators" />
        <!-- The name of the role for anonymous(non-authenticated) users. -->
        <add key="BlogEngine.AnonymousRole" value="Anonymous" />
        <!-- The name of the role for Editors -->
        <add key="BlogEngine.EditorsRole" value="Editors" />

     1.以XmlRoleProvider为例。(先不必纠结代码中Blog.CurrentInstance.Id)

     public class XmlRoleProvider : RoleProvider
        {
    //...............
     public override void AddUsersToRoles(string[] usernames, string[] roleNames)
            {
                ReadRoleDataStore();
    
                var currentRoles = new List<string>(this.GetAllRoles());
                if (usernames.Length != 0 && roleNames.Length != 0)
                {
                    foreach (var rolename in roleNames.Where(rolename => !currentRoles.Contains(rolename) && !rolename.Equals(BlogConfig.AnonymousRole, StringComparison.OrdinalIgnoreCase)))
                    {
                        this.roles[Blog.CurrentInstance.Id].Add(new Role(rolename, new List<string>(usernames)));
                    }
    
                    foreach (var role in this.roles[Blog.CurrentInstance.Id])
                    {
                        var role1 = role;
                        foreach (var s in from name in roleNames
                                          where role1.Name.Equals(name, StringComparison.OrdinalIgnoreCase)
                                          from s in usernames
                                          where !role1.Users.Contains(s)
                                          select s)
                        {
                            role.Users.Add(s);
                        }
                    }
                }
    
                this.Save();
            }
    
            /// <summary>
            /// Adds a new role to the data source for the configured applicationName.
            /// </summary>
            /// <param name="roleName">
            /// The name of the role to create.
            /// </param>
            public override void CreateRole(string roleName)
            {
                ReadRoleDataStore();
    
                // This needs to be fixed. This will always return false.
                if (this.roles[Blog.CurrentInstance.Id].Contains(new Role(roleName)))
                {
                    return;
                }
    
                this.roles[Blog.CurrentInstance.Id].Add(new Role(roleName));
                this.Save();
            }
    }
    View Code

     一个角色可以包含多个用户。Role对象如下,便于存储。

    public class Role
        {
            #region Constructors and Destructors
    
            /// <summary>
            /// Initializes a new instance of the <see cref="Role"/> class.
            /// </summary>
            /// <param name="name">
            /// A name of the role.
            /// </param>
            public Role(string name) : this(name, new List<string>())
            {
            }
    
            /// <summary>
            ///     Initializes a new instance of the <see cref = "Role" /> class.
            /// </summary>
            public Role() : this(null, new List<string>())
            {
            }
    
            /// <summary>
            /// Initializes a new instance of the <see cref="Role"/> class.
            /// </summary>
            /// <param name="name">
            /// A name of the role.
            /// </param>
            /// <param name="userNames">
            /// A list of users in role.
            /// </param>
            public Role(string name, List<string> userNames)
            {
                if (userNames == null)
                {
                    throw new System.ArgumentNullException("userNames");
                }
                else
                {
                    this.Name = name;
                    this.Users = userNames;
                }
            }
    
            #endregion
    
            #region Properties
    
            /// <summary>
            ///     Gets or sets the name.
            /// </summary>
            /// <value>The name of the role.</value>
            public string Name { get; set; }
    
            /// <summary>
            ///     Gets the users.
            /// </summary>
            /// <value>The users.</value>
            public List<string> Users { get; private set; }
    
            #endregion
    
        }
    View Code

     生成的xml文档:每个角色下面有那些用户 一目了然。

    <?xml version="1.0" encoding="utf-8" standalone="yes"?>
    <roles>
      <role>
        <name>Administrators</name>
        <users>
          <user>Admin</user>
        </users>
      </role>
      <role>
        <name>Editors</name>
        <users />
      </role>
      <role>
        <name>Anonymous</name>
        <users />
      </role>
      <role>
        <name>COCO</name>
        <users>
          <user>stoneniqiu</user>
        </users>
      </role>
    </roles>

    但呈现在UI上的每一个Role转换成RoleItem(相当于一个视图模型)。

    /// <summary>
        /// Json friendly Role wrapper
        /// </summary>
        public class RoleItem
        {
            /// <summary>
            /// If checked in the UI(是否选中)
            /// </summary>
            public bool IsChecked { get; set; }
            /// <summary>
            /// Role Name
            /// </summary>
            public string RoleName { get; set; }
            /// <summary>
            /// Is System Role
            /// </summary>
            public bool IsSystemRole { get; set; }
        }
    View Code

    2.Web.config配置:

      在system.web的rolManager节点中。

    <roleManager defaultProvider="XmlRoleProvider" enabled="true" cacheRolesInCookie="false">
          <providers>
            <clear />
            <add name="XmlRoleProvider" type="BlogEngine.Core.Providers.XmlRoleProvider, BlogEngine.Core" description="XML role provider" />
            <add name="SqlRoleProvider" type="System.Web.Security.SqlRoleProvider" connectionStringName="BlogEngine" applicationName="BlogEngine" />
            <add name="DbRoleProvider" type="BlogEngine.Core.Providers.DbRoleProvider, BlogEngine.Core" connectionStringName="BlogEngine" />
          </providers>
        </roleManager>

    同样,有一个现成的SqlRoleProvider以供选择。 

     3.如MembershipProvider对应Membership一样,RoleProvider对应的是System.Web.Security.Roles。

      在RolesRepository中 通过Roles来操作。

     public class RolesRepository : IRolesRepository
        {
    //....
      public IEnumerable<RoleItem> Find(int take = 10, int skip = 0, string filter = "", string order = "")
            {
       
                var roles = new List<RoleItem>();
    
                if (string.IsNullOrEmpty(filter)) filter = "1 == 1";
                if (string.IsNullOrEmpty(order)) order = "RoleName";
    
                roles.AddRange(System.Web.Security.Roles.GetAllRoles().Select(r => new RoleItem 
                    { RoleName = r, IsSystemRole = Security.IsSystemRole(r) }));
    
                roles.Sort((r1, r2) => string.Compare(r1.RoleName, r2.RoleName));
    
                return roles;
            } 
    
          public RoleItem Add(Data.Models.RoleItem role)
            {
      
              try
                    {
                        Roles.CreateRole(role.RoleName);
                        return FindById(role.RoleName);
                    }
                    catch (Exception ex)
                    {
                        Utils.Log(string.Format("Error adding role", ex));
                        throw new ApplicationException("Error adding new role");
                    }
                 }
       //....
       }

    在界面中,可以方便的给我们的用户指定角色。

     3.最后封装在RolesRepository中,通过Api的方式公布了出去 图如下

    三、Right 权限管理

    功能:权限和角色的管理

     用户和角色都有自带的Provider。而权限没有,在BlogEngine中,定义了枚举类型Rights、权限类型RightCategory、以及特性RightDetailsAttribute

     public enum Rights
        {
    
            /// <summary>
            /// Represents a user that has no rights or permissions. This flag should not be used in combination with any other flag.
            /// </summary>
            /// <remarks>
            /// 
            /// This value isn't meant for public consumption.
            /// 
            /// </remarks>
            None = 0,
    
            #region Misc
    
            /// <summary>
            /// A user is allowed to view exception messages.
            /// </summary>
            [RightDetails(Category = RightCategory.General)]
            ViewDetailedErrorMessages,
    
            /// <summary>
            /// A user is allowed to access administration pages.
            /// Typically, a blog where self-registration is allowed
            /// would restrict this right from guest users.
            /// </summary>
            [RightDetails(Category = RightCategory.General)]
            AccessAdminPages,
    
            /// <summary>
            /// A user is allowed to access admin settings pages.
            /// </summary>
            [RightDetails(Category = RightCategory.General)]
            AccessAdminSettingsPages,
    
            /// <summary>
            /// A user is allowed to manage widgets.
            /// </summary>
            [RightDetails(Category = RightCategory.General)]
            ManageWidgets,
    
            #endregion
    
            #region "Comments"
         //.............
    
    [AttributeUsage(AttributeTargets.Field, AllowMultiple=false, Inherited=false)]
        public sealed class RightDetailsAttribute : Attribute
        {
            /// <summary>
            /// Default constructor.
            /// </summary>
            public RightDetailsAttribute()
            {
    
            }
    
            #region "Properties"
    
            /// <summary>
            /// Key for grabbing a description from a resource file.
            /// </summary>
            public string DescriptionResourceLabelKey { get; set; }
    
            /// <summary>
            /// Key for grabbing a name from a resource file.
            /// </summary>
            public string NameResourceLabelKey { get; set; }
    
            /// <summary>
            /// The category a Right is for.
            /// </summary>
            public RightCategory Category { get; set; }
    
            #endregion
    
        }
    
        /// <summary>
        /// Categories for Rights.
        /// </summary>
        public enum RightCategory
        {
            /// <summary>
            /// No category
            /// </summary>
            None,
    
            /// <summary>
            /// General category
            /// </summary>
            General,
    
            /// <summary>
            /// Comments category
            /// </summary>
            Comments,
    
            /// <summary>
            /// Pages category
            /// </summary>
            Pages,
    
            /// <summary>
            /// Post category
            /// </summary>
            Posts,
    
            /// <summary>
            /// Users category
            /// </summary>
            Users,
    
            /// <summary>
            /// Roles
            /// </summary>
            Roles
        }
    }
    View Code

    1.权限管理的核心类是Right,实现了IHttpModule接口,提供静态的验证方法,同时又是作为一个存储模型(即和其他模型一样按照用户指定的方式存储,不像用户和角色需要配置)。这个类稍微有点复杂,刚开始看,容易搞晕。做几点说明。

      1).Fields里面有很多集合,主要是rihtsByRole(每个角色有哪些权限) rightsbyName(权限名称对应的Right对象集合)rightsByFlag(枚举类型的权限对应的Right对象集合)。allRightInstances(所有Right实例) _rolesWithRight(拥有当前权限的角色)

      2) 以上集合是多在静态构造函数中初始化,可以不必在意EnsureBlogInstanceDataLoaded 函数。

      3) RefreshAllRights初始化角色和权限。比如AdministratorRole 默认拥有所有权限都是在这里完成的。

     public sealed class Right : IHttpModule
        {
    
            #region "Static"
    
            #region "Fields"
    
            // These dictionaries would probably be better condensed into something else.
    
            private static readonly object staticLockObj = new Object();
    
    
            private static readonly ReadOnlyCollection<Rights> rightFlagValues;
            private static readonly ReadOnlyCollection<Right> allRightInstances;
    
            // This is a static collection so that there's no need to constantly remake a new empty collection
            // when a user has no rights.
            private static readonly ReadOnlyCollection<Right> noRights = new ReadOnlyCollection<Right>(new List<Right>());
    
            // Once rightsByFlag is set it should not be changed ever.
            private static readonly Dictionary<Rights, Right> rightsByFlag = new Dictionary<Rights, Right>();
            private static readonly Dictionary<string, Right> rightsByName = new Dictionary<string, Right>(StringComparer.OrdinalIgnoreCase);
            private static readonly Dictionary<Guid, Dictionary<string, HashSet<Right>>> rightsByRole = new Dictionary<Guid, Dictionary<string, HashSet<Right>>>();
    
            #endregion
    
            #region "IHttpModule"
    
            /// <summary>
            /// Initializes a module and prepares it to handle requests.
            /// </summary>
            /// <param name="context">An <see cref="T:System.Web.HttpApplication"/> that provides access to the methods, properties, and events common to all application objects within an ASP.NET application</param>
            public void Init(HttpApplication context)
            {
                context.BeginRequest += ContextBeginRequest;
            }
    
            /// <summary>
            /// Handles the BeginRequest event of the context control.
            /// </summary>
            /// <param name="sender">
            /// The source of the event.
            /// </param>
            /// <param name="e">
            /// The <see cref="System.EventArgs"/> instance containing the event data.
            /// </param>
            private static void ContextBeginRequest(object sender, EventArgs e)
            {
                //var context = ((HttpApplication)sender).Context;
    
                EnsureBlogInstanceDataLoaded();
            }
    
            /// <summary>
            /// Disposes of the resources (other than memory) used by the module that implements <see cref="T:System.Web.IHttpModule"/>.
            /// </summary>
            public void Dispose()
            {
                // Nothing to dispose
            }
    
            #endregion
    
            static Right()
            {
                // Initialize the various dictionaries to their starting state.
    
                var flagType = typeof(Rights);
                rightFlagValues = Enum.GetValues(flagType).Cast<Rights>().ToList().AsReadOnly();
    
                var adminRole = BlogEngine.Core.BlogConfig.AdministratorRole;
    
                var allRights = new List<Right>();
    
                // Create a Right instance for each value in the Rights enum.
                foreach (var flag in rightFlagValues)
                {
                    Rights curFlag = (Rights)flag;
                    var flagName = Enum.GetName(flagType, curFlag);
                    var curRight = new Right(curFlag, flagName);
    
                    allRights.Add(curRight);
    
                    // Use the Add function so if there are multiple flags with the same
                    // value they can be caught quickly at runtime.
                    rightsByFlag.Add(curFlag, curRight);
    
                    rightsByName.Add(flagName, curRight);
                }
    
                allRightInstances = allRights.AsReadOnly();
    
                EnsureBlogInstanceDataLoaded();
    
                Blog.Saved += (s, e) =>
                {
                    if (e.Action == SaveAction.Delete)
                    {
                        Blog blog = s as Blog;
                        if (blog != null)
                        {
                            // remove deleted blog from static 'rightsByRole'
    
                            if (rightsByRole != null && rightsByRole.ContainsKey(blog.Id))
                                rightsByRole.Remove(blog.Id);
    
                            // remove deleted blog from _readOnlyRoles/_rolesWithRight from
                            // each of the Right instances.
                            for (int i = 0; i < allRightInstances.Count; i++)
                            {
                                if (allRightInstances[i]._readOnlyRoles.ContainsKey(blog.Id))
                                    allRightInstances[i]._readOnlyRoles.Remove(blog.Id);
    
                                if (allRightInstances[i]._rolesWithRight.ContainsKey(blog.Id))
                                    allRightInstances[i]._rolesWithRight.Remove(blog.Id);
                            }
                        }
                    }
                };
            }
    
            #region "Methods"
    
            /// <summary>
            /// Method that should be called any time Rights are changed and saved.
            /// </summary>
            public static void RefreshAllRights()
            {
    
                var flagType = typeof(Rights);
    
                lock (staticLockObj)
                {
                    RightsByRole.Clear();
    
                    var allRoles = new HashSet<string>(System.Web.Security.Roles.GetAllRoles(), StringComparer.OrdinalIgnoreCase);
    
                    foreach (var role in allRoles)
                    {
                        var curRole = PrepareRoleName(role);
                        RightsByRole.Add(curRole, new HashSet<Right>());
                        allRoles.Add(curRole);
                    }
    
                    var adminRole = BlogConfig.AdministratorRole;
                    var anonymousRole = BlogConfig.AnonymousRole;
                    var editorsRole = BlogConfig.EditorsRole;
    
                    foreach (var right in GetAllRights())
                    {
                        // Clear the existing roles so any newly-deleted
                        // roles are removed from the list.
                        right.ClearRoles();
                        if (right.Flag != Rights.None)
                        {
                            right.AddRole(adminRole);
                        }
                    }
    
                    foreach (var pair in BlogEngine.Core.Providers.BlogService.FillRights())
                    {
                        // Ignore any values that are invalid. This is bound to happen
                        // during updates if a value gets renamed or removed.
                        if (Right.RightExists(pair.Key))
                        {
                            var key = GetRightByName(pair.Key);
    
                            foreach (var role in pair.Value)
                            {
                                var curRole = PrepareRoleName(role);
    
                                // Ignore any roles that are added that don't exist.
                                if (allRoles.Contains(curRole))
                                {
                                    key.AddRole(curRole);
                                    Right.RightsByRole[curRole].Add(key);
                                }
                            }
                        }
                    }
    
                    // Note: To reset right/roles to the defaults, the data store can be
                    // cleared out (delete rights.xml or clear DB table).  Then these
                    // defaults will be setup.
    
                    bool defaultsAdded = false;
    
                    // Check that the anonymous role is set up properly. If no rights
                    // are found, then the defaults need to be set.
                    if (!GetRights(anonymousRole).Any())
                    {
                        List<Rights> defaultRoleRights = GetDefaultRights(anonymousRole);
                        foreach (Rights rights in defaultRoleRights)
                        {
                            Right.rightsByFlag[rights].AddRole(anonymousRole);
                        }
    
                        defaultsAdded = true;
                    }
    
                    // Check that the editor role is set up properly. If no rights
                    // are found, then the defaults need to be set.
                    if (!GetRights(editorsRole).Any())
                    {
                        List<Rights> defaultRoleRights = GetDefaultRights(editorsRole);
                        foreach (Rights rights in defaultRoleRights)
                        {
                            Right.rightsByFlag[rights].AddRole(editorsRole);
                        }
    
                        defaultsAdded = true;
                    }
    
                    // This check is for autocreating the rights for the Administrator role.
                    foreach (KeyValuePair<Rights, Right> kvp in rightsByFlag)
                    {
                        if (kvp.Key != Rights.None)
                        {
                            kvp.Value.AddRole(adminRole);
    
                            // could set defaultsAdded to true if the right doesn't already
                            // have the adminRole in it.  since the admin always gets all
                            // rights and they cannot be removed, we simply grant the admin
                            // all rights without the need to persist that.
                        }
                    }
    
                    if (defaultsAdded)
                    {
                        BlogEngine.Core.Providers.BlogService.SaveRights();
                    }
                }
    
            }
    
            /// <summary>
            /// Gets the list of default rights for the given role name.
            /// </summary>
            /// <param name="roleName">The role for which we are obtaining rights.</param>
            /// <returns>If the role is found, a list of the appropriate rights. Otherwise, an empty list of rights.</returns>
            public static List<Rights> GetDefaultRights(string roleName)
            {
                if (string.IsNullOrEmpty(roleName)) { return new List<Rights>(); }
    
                if (roleName.Equals(BlogConfig.EditorsRole, StringComparison.OrdinalIgnoreCase))
                {
                    return new List<Rights>()
                    {
                        Rights.AccessAdminPages,
                        Rights.CreateComments,
                        Rights.ViewPublicComments,
                        Rights.ViewPublicPosts,
                        Rights.ViewPublicPages,
                        Rights.ViewRatingsOnPosts,
                        Rights.SubmitRatingsOnPosts,
                        Rights.ViewUnmoderatedComments,
                        Rights.ModerateComments,
                        Rights.ViewUnpublishedPages,
                        Rights.ViewUnpublishedPosts,
                        Rights.DeleteOwnPosts,
                        Rights.PublishOwnPosts,
                        Rights.CreateNewPages,
                        Rights.CreateNewPosts,
                        Rights.EditOwnPages,
                        Rights.EditOwnPosts,
                        Rights.EditOwnUser
                    };
                }
                else if (roleName.Equals(BlogConfig.AnonymousRole, StringComparison.OrdinalIgnoreCase))
                {
                    return new List<Rights>()
                    {
                        Rights.CreateComments,
                        Rights.ViewPublicComments,
                        Rights.ViewPublicPosts,
                        Rights.ViewPublicPages,
                        Rights.ViewRatingsOnPosts,
                        Rights.SubmitRatingsOnPosts
                    };
                }
    
                return new List<Rights>();
            }
    
            /// <summary>
            /// Handles updating Role name changes, so Role names tied to Rights stay in sync.
            /// </summary>
            /// <param name="oldname">The old Role name.</param>
            /// <param name="newname">The new Role name.</param>
            public static void OnRenamingRole(string oldname, string newname)
            {
                IEnumerable<Right> rightsWithRole = Right.GetRights(oldname);
                if (rightsWithRole.Any())
                {
                    foreach (Right right in rightsWithRole)
                    {
                        right.RemoveRole(oldname);
                        right.AddRole(newname);
                    }
    
                    BlogEngine.Core.Providers.BlogService.SaveRights();
                }
            }
    
            /// <summary>
            /// Handles removing Roles tied to Rights when a Role will be deleted.
            /// </summary>
            /// <param name="roleName"></param>
            public static void OnRoleDeleting(string roleName)
            {
                IEnumerable<Right> rightsWithRole = Right.GetRights(roleName);
                if (rightsWithRole.Any())
                {
                    foreach (Right right in rightsWithRole)
                    {
                        right.RemoveRole(roleName);
                    }
    
                    BlogEngine.Core.Providers.BlogService.SaveRights();
                }
            }
    
            /// <summary>
            /// Call this method for verifying role names and then trimming the string.
            /// </summary>
            /// <param name="roleName"></param>
            /// <returns></returns>
            private static string PrepareRoleName(string roleName)
            {
                if (Utils.StringIsNullOrWhitespace(roleName))
                {
                    throw new ArgumentNullException("roleName");
                }
                else
                {
                    return roleName.Trim();
                }
            }
    
            /// <summary>
            /// Returns an IEnumerable of all of the Rights that exist on BlogEngine.
            /// </summary>
            /// <returns></returns>
            public static IEnumerable<Right> GetAllRights()
            {
                return Right.allRightInstances;
            }
    
            /// <summary>
            /// Returns a Right instance based on its name.
            /// </summary>
            /// <param name="rightName"></param>
            /// <returns></returns>
            public static Right GetRightByName(string rightName)
            {
                if (Utils.StringIsNullOrWhitespace(rightName))
                {
                    throw new ArgumentNullException("rightName");
                }
                else
                {
                    Right right = null;
                    if (rightsByName.TryGetValue(rightName.Trim(), out right))
                    {
                        return right;
                    }
                    else
                    {
                        throw new KeyNotFoundException("No Right exists by the name '" + rightName + "'");
                    }
                }
            }
    
            /// <summary>
            /// Returns a Right instance based on the flag.
            /// </summary>
            /// <param name="flag"></param>
            /// <returns></returns>
            public static Right GetRightByFlag(Rights flag)
            {
    
                Right right = null;
                if (rightsByFlag.TryGetValue(flag, out right))
                {
                    return right;
                }
                else
                {
                    throw new KeyNotFoundException("Unable to find a corresponding right for the given flag");
                }
    
            }
    
            private static IEnumerable<Right> GetRightsInternal(string roleName)
            {
                roleName = PrepareRoleName(roleName);
                if (RightsByRole.ContainsKey(roleName))
                    return RightsByRole[roleName];
                else
                    return new HashSet<Right>();
            }
    
            /// <summary>
            /// Returns an IEnumerable of Rights that are in the given role.
            /// </summary>
            /// <param name="roleName"></param>
            /// <returns></returns>
            public static IEnumerable<Right> GetRights(string roleName)
            {
                return GetRightsInternal(roleName).ToList().AsReadOnly();
            }
    
            /// <summary>
            /// Returns an IEnumerable of Rights that are in all of the given roles.
            /// </summary>
            /// <param name="roles"></param>
            /// <returns></returns>
            public static IEnumerable<Right> GetRights(IEnumerable<string> roles)
            {
                if (roles == null)
                {
                    throw new ArgumentNullException("roles");
                }
                else if (!roles.Any())
                {
                    return noRights;
                }
                else
                {
                    var rights = new List<Right>();
    
                    foreach (var role in roles)
                    {
                        rights.AddRange(GetRightsInternal(role));
                    }
    
                    return rights.Distinct().ToList().AsReadOnly();
                }
            }
    
            /// <summary>
            /// Gets whether or not a Right exists within any of the given roles.
            /// </summary>
            /// <param name="right"></param>
            /// <param name="roles"></param>
            /// <returns>
            /// 
            /// Use this method instead of GetRights().Contains() as it'll be
            /// much faster than having to create a new collection of Right instances each time.
            /// 
            /// </returns>
            public static bool HasRight(Rights right, IEnumerable<string> roles)
            {
                if (roles == null)
                {
                    throw new ArgumentNullException("roles");
                }
                else if (!roles.Any())
                {
                    return false;
                }
                else
                {
                    var validRoles = GetRightByFlag(right).Roles;
                    if (roles.Count() == 1)
                    {
                        // This is faster than intersecting, so this is
                        // special cased.
                        return validRoles.Contains(roles.First(), StringComparer.OrdinalIgnoreCase);
                    }
                    else
                    {
                        return validRoles.Intersect(roles, StringComparer.OrdinalIgnoreCase).Any();
                    }
                }
            }
    
            /// <summary>
            /// Checks to see if a Right exists by the given name.
            /// </summary>
            /// <param name="rightName"></param>
            /// <returns></returns>
            public static bool RightExists(string rightName)
            {
                return rightsByName.ContainsKey(rightName);
            }
    
            #endregion
    
            #endregion
    
            #region "Instance"
    
            #region "Fields and Constants"
    
            private readonly object instanceLockObj = new Object();
    
            private readonly Dictionary<Guid, ReadOnlyCollection<string>> _readOnlyRoles;
            private readonly Dictionary<Guid, List<string>> _rolesWithRight;
    
    
            #endregion
    
            #region "Constructor"
            /// <summary>
            /// Private constructor for creating a Right instance.
            /// </summary>
            /// <param name="Right"></param>
            /// <param name="RightEnumName"></param>
            private Right(Rights Right, string RightEnumName)
            {
                _flag = Right;
                _name = RightEnumName;
                _rolesWithRight = new Dictionary<Guid, List<string>>();
                _readOnlyRoles = new Dictionary<Guid, ReadOnlyCollection<string>>();
            }
    
            // empty constructor so Right can be an HttpModule.
            private Right() { }
    
            #endregion
    
            #region "Properties"
    
            private static void EnsureBlogInstanceDataLoaded()
            {
                Blog blog = Blog.CurrentInstance;
    
                // either all the right instances will be setup for the current blog instance, or none
                // of them will be.  check just the first one to see if it is setup for the current
                // blog instance.
    
                if (!allRightInstances[0]._readOnlyRoles.ContainsKey(blog.Id))
                {
                    for (int i = 0; i < allRightInstances.Count; i++)
                    {
                        allRightInstances[i]._rolesWithRight[blog.Id] = new List<string>();
                        allRightInstances[i]._readOnlyRoles[blog.Id] = new ReadOnlyCollection<string>(allRightInstances[i]._rolesWithRight[blog.Id]);
                    }
                }
    
                if (!rightsByRole.ContainsKey(blog.Id))
                {
                    // touch RightsByRole to make sure data for current blog instance is loaded
                    // in the static rightsByRole.
                    var rr = RightsByRole;
                }
            }
    
    
            private List<string> RolesWithRight
            {
                get
                {
                    return _rolesWithRight[Blog.CurrentInstance.Id];
                }
            }
    
            private ReadOnlyCollection<string> ReadOnlyRoles
            {
                get
                {
                    return _readOnlyRoles[Blog.CurrentInstance.Id];
                }
            }
    
            private static Dictionary<string, HashSet<Right>> RightsByRole
            {
                get
                {
                    Blog blog = Blog.CurrentInstance;
    
                    if (!rightsByRole.ContainsKey(blog.Id))
                    {
                        lock (staticLockObj)
                        {
                            if (!rightsByRole.ContainsKey(blog.Id))
                            {
                                rightsByRole[blog.Id] = new Dictionary<string, HashSet<Right>>(StringComparer.OrdinalIgnoreCase);
                                InitRightForBlogInstance();
                            }
                        }
                    }
    
                    return rightsByRole[blog.Id];
                }
            }
    
            private static void InitRightForBlogInstance()
            {
                // Make sure the Administrator role exists with the Role provider.
                if (!System.Web.Security.Roles.RoleExists(BlogConfig.AdministratorRole))
                {
                    System.Web.Security.Roles.CreateRole(BlogConfig.AdministratorRole);
    
                    // if no one is in the admin role, and there is a user named "admin", add that user
                    // to the role.
                    if (System.Web.Security.Roles.GetUsersInRole(BlogConfig.AdministratorRole).Length == 0)
                    {
                        System.Web.Security.MembershipUser membershipUser = System.Web.Security.Membership.GetUser("Admin");
                        if (membershipUser != null)
                        {
                            System.Web.Security.Roles.AddUsersToRoles(new string[] { membershipUser.UserName }, new string[] { BlogConfig.AdministratorRole });
                        }
                    }
                }
    
                // Make sure the Anonymous role exists with the Role provider.
                if (!System.Web.Security.Roles.RoleExists(BlogConfig.AnonymousRole))
                {
                    // Users shouldn't actually be in the anonymous role, since the role is specifically for people who aren't users.
                    System.Web.Security.Roles.CreateRole(BlogConfig.AnonymousRole);
                }
    
                // Make sure the Editors role exists with the Role provider.
                if (!System.Web.Security.Roles.RoleExists(BlogConfig.EditorsRole))
                {
                    System.Web.Security.Roles.CreateRole(BlogConfig.EditorsRole);
                }
    
                var adminRole = BlogConfig.AdministratorRole;
    
                RefreshAllRights();
            }
    
            // These should use attributes to set up the basic part. Perhaps DisplayNameAttribute
            // for getting a label key that can be translated appropriately. 
    
            //public string ResourceLabelKey
            //{
            //    get
            //    {
            //        return _resourceLabelKey;
            //    }
            //}
            //private readonly string _resourceLabelKey;
    
            /// <summary>
            /// Returns a display-friendly version of this Right's name.
            /// </summary>
            public string DisplayName
            {
                get { return Utils.FormatIdentifierForDisplay(Name); }
            }
    
            /// <summary>
            /// Returns the empty string.
            /// </summary>
            public string Description
            {
                get { return string.Empty; }
            }
    
            /// <summary>
            /// Gets the Right value for this Right instance.
            /// </summary>
            public Rights Flag
            {
                get
                {
                    return _flag;
                }
            }
            private readonly Rights _flag;
    
            /// <summary>
            /// Gets the name of this right.
            /// </summary>
            /// <remarks>
            /// 
            /// This returns the string name of the Flag enum that this instance represents.
            /// 
            /// This value should be the one that's serialized to the provider's data store as
            /// it's far less likely to change than the numerical value.
            /// 
            /// </remarks>
            public string Name
            {
                get { return _name; }
            }
            private readonly string _name;
    
            /// <summary>
            /// Gets the Roles that currently have this Right.
            /// </summary>
            /// <remarks>
            /// This returns a read only wrapper around the internal roles list. The Roles list is not allowed
            /// to be altered anywhere. Changes to the list need to go through the proper channels.
            /// </remarks>
            public IEnumerable<string> Roles
            {
                get { return ReadOnlyRoles; }
            }
    
    
            #endregion
    
            #region "Methods"
    
            /// <summary>
            /// Adds a role to the list of roles that have this Right.
            /// </summary>
            /// <param name="roleName"></param>
            /// <returns>True if the role doesn't already exist in the list of roles. Otherwise, false.</returns>
            /// <remarks>
            /// 
            /// Use this method specifically to add roles to the internal list. This lets us keep track
            /// of what's added to it.
            /// 
            /// </remarks>
            public bool AddRole(string roleName)
            {
                roleName = PrepareRoleName(roleName);
    
                lock (instanceLockObj)
                {
                    if (!Roles.Contains(roleName, StringComparer.OrdinalIgnoreCase))
                    {
                        RolesWithRight.Add(roleName);
                        return true;
                    }
                    else
                    {
                        return false;
                    }
                }
            }
    
            /// <summary>
            /// Removes a Role from the collection of roles that allow this Right.
            /// </summary>
            /// <param name="roleName"></param>
            /// <returns>Returns true if the role was removed, false otherwise.</returns>
            /// <remarks>
            /// 
            /// Use this method specifically to remove roles from the internal list. This lets us keep track
            /// of what's removed from it.
            /// 
            /// </remarks>
            public bool RemoveRole(string roleName)
            {
    
                roleName = PrepareRoleName(roleName);
    
                if (roleName.Equals(BlogConfig.AdministratorRole, StringComparison.OrdinalIgnoreCase))
                {
                    throw new System.Security.SecurityException("Rights can not be removed from the administrative role");
                }
                lock (instanceLockObj)
                {
                    return RolesWithRight.Remove(roleName);
                }
            }
    
            /// <summary>
            /// Clears all the roles in the roles list. This is only meant to be used during the static RefreshAllRoles method.
            /// </summary>
            private void ClearRoles()
            {
                lock (instanceLockObj)
                {
                    RolesWithRight.Clear();
                }
            }
    
            #endregion
    
            #endregion
    
        }
    
    
    }
    View Code

    2.权限的验证

     在BlogEngine.Net中,封装了一个Security类,用于在仓库中验证。(代码没有贴全,源码在文章最下方)

      public partial class Security : IHttpModule
        {
       //........
       public static IEnumerable<Right> CurrentUserRights()
            {
                return Right.GetRights(Security.GetCurrentUserRoles());
            }
    //验证当前用户是否拥有权限
      public static bool IsAuthorizedTo(AuthorizationCheck authCheck, IEnumerable<Rights> rights)
            {
        //....
        }
    //....
        }
    View Code

    然后在仓库中进行验证。诸如此类。

      public RoleItem FindById(string id)
            {
                if (!Security.IsAuthorizedTo(BlogEngine.Core.Rights.ViewRoles))
                    throw new System.UnauthorizedAccessException();
      //...
      }

    那在MVC中,可以用Filter.

     public class RightsAuthorizeAttribute : ActionFilterAttribute
        {
            public Rights ValidRights { get; set; }
    
            public RightsAuthorizeAttribute(Rights rights)
            {
                ValidRights = rights;
            }
    
            public override void OnActionExecuting(ActionExecutingContext filterContext)
            {
                if (!Security.IsAuthorizedTo(ValidRights))
                {
                    filterContext.Result = new RedirectResult("~/Unauthorized.html");
                }
            }
        }
    View Code

    放在指定的Action上面,没有权限的人访问就会跳转到指示页面。

      [RightsAuthorize(Rights.EditProduct)]
            public ViewResult Edit(string name)
            {
                var file = _repository.Products.FirstOrDefault(n => n.Name == name);
                return View(file);
            }

    3.权限存取。

      权限是依附于角色而存在的,BlogEngine定义了Group和Permission两个视图模型。Group表示一个角色拥有哪些权限。Permission表示的就是权限名(在UI上不是直接用Right类)。

     public class Group
        {
            /// <summary>
            /// Empty constructor needed for serialization
            /// </summary>
            public Group() { }
            /// <summary>
            /// Constractor
            /// </summary>
            /// <param name="title">Role title</param>
            public Group(string title)
            {
                Title = title;
                if (Permissions == null)
                    Permissions = new List<Permission>();
            }
            /// <summary>
            /// Role title
            /// </summary>
            public string Title { get; set; }
            /// <summary>
            /// List of rights
            /// </summary>
            public List<Permission> Permissions { get; set; }
        }
    
        /// <summary>
        /// Permission
        /// </summary>
        public class Permission
        {
            /// <summary>
            /// Right Id
            /// </summary>
            public string Id { get; set; }
            /// <summary>
            /// Title
            /// </summary>
            public string Title { get; set; }
            /// <summary>
            /// Checked if right allowed for the role
            /// </summary>
            public bool IsChecked { get; set; }
        }
    View Code

     在 RolesRepository中进行转换。BlogEngine的数据存储也是基于Provider模式,默认是xml格式存储。

     public bool SaveRights(List<Data.Models.Group> rights, string id)
            {
                if (!Security.IsAuthorizedTo(Rights.EditRoles))
                {
                    throw new System.UnauthorizedAccessException();
                }
                else if (Utils.StringIsNullOrWhitespace(id))
                {
                    throw new ApplicationException("Invalid role name");
                }
                else if (rights == null)
                {
                    throw new ApplicationException("Rights can not be null");
                }
                else
                {
                    var rightsCollection = new Dictionary<string, bool>();
    
                    foreach (var g in rights)
                    {
                        foreach (var r in g.Permissions)
                        {
                            if (r.IsChecked)
                            {
                                rightsCollection.Add(r.Id, r.IsChecked);
                            }
                        }
                    }
                    foreach (var right in Right.GetAllRights())
                    {
                        if (right.Flag != Rights.None)
                        {
                            if (rightsCollection.ContainsKey(right.Name))
                            {
                                right.AddRole(id);
                            }
                            else
                            {
                                right.RemoveRole(id);
                            }
                        }
                    }
                    BlogEngine.Core.Providers.BlogService.SaveRights();
                    return true;
                }
    }
    View Code

     配置界面:

    保存成xml后的格式。

    <?xml version="1.0" encoding="utf-8" standalone="yes"?>
    <rights>
      <right name="None" />
      <right name="ViewDetailedErrorMessages">
        <role name="Administrators" />
      </right>
      <right name="AccessAdminPages">
        <role name="Administrators" />
        <role name="Editors" />
      </right>
    ....
    </rights>

    结构图大致如下:如果我们使用默认的MembershipProvider和RoleProvider,也可以这样加入我们的权限管理。

    小结:文章有点长了,花了不少时间。这只是对知识的一个梳理,并不是要推荐用这种方式做权限管理,分享一下BlogEngine实现的方式。所以园友们各取所需。希望对你有帮助。tsk!

    BlogEngine.Net源码:http://blogengine.codeplex.com/downloads/get/772826 

    1.参考文章 Membership 三部曲  http://www.cnblogs.com/jesse2013/p/membership.html 
  • 相关阅读:
    iptables命令参数简介
    在linux下开启IP转发的方法
    Linux配置IP路由
    NAT转换
    JS实验案例
    Ubuntu kylin优麒麟root用户与静态网络设置
    非对称加密-RSA
    对称加密-DES
    DM5详解
    Visio的安装教程
  • 原文地址:https://www.cnblogs.com/stoneniqiu/p/3735011.html
Copyright © 2020-2023  润新知