duankanjian4642 2012-08-03 16:35
浏览 47
已采纳

在PHP中实现位掩码或关系ACL

I think PHP people are familiar with the E_ALL and various other bitmask constants from the error_reporting() function. They are number constants, example: E_ALL means 32676 and E_NOTICE means 8. I can say that I want all errors but notice shown, and I do this by passing E_ALL & ~E_NOTICE as the argument of error_reporting(). But essentially, I tell it 32759 which is 32767 - 8.

These bitmasks are taking their values from the output set of f(x) = 2^x function, and doing add and subtract arithmetics on these values, we can finetune what errors to get.

I am thinking about a more configurable access control thing to be implemented into my framework-thingy. For this, I wish to set the user's bitmask would have the same method of adding number values together, the problem is that I don't know how to pull this off, how to check against the requested and the present value: Does the user have rights to access foobar?.

And the other problem is scalability. I might only have 31 unique bits (as 2^32 reaches the too big and unmaintainable state), and it would be hard to migrate if I need to undergo (not really planned right now) this barrier? My other thought for access control would be setting up a table which links together the user.id and the integer value of the access bit he has.

To wrap it up, Which of the following two options is a better solution?:

  • Use one field in the user's table to store bitmasks, and then fetch the requested bit from it when needed.
  • Have a set of access flags defined in a table and have a relation table which links the user to the access, exploiting the awesomeness of relational databases to our needs.

I was researching about the second method, but I don't know which one would be the best to use.

  • 写回答

2条回答 默认 最新

  • dsakktdog498483070 2012-08-08 10:57
    关注

    Bitmasks

    The original problem with bitmasks is that they go against the conventions of data modelling (expressed in another answer here, with further reading here and here). A 4-byte signed integer might only contain 31 different values (represented in integer as 2 147 483 648) and calculation is hard with them. There have been various questions on Stack Overflow before discussing this topic, which I used to get a sense of implementation of how bistmasks would work.

    Testing also revealed that working with bitmasks is hard. It needs a sense to understand the bitwise operators, and migration becomes basically impossible. (By impossible I mean that, at first, implementing bitmasks seemed a good thing to do, but after all it turned out it requires too much expends compared to the benefit it could give in return.) Take a basic portal-like website... I mean no. Take Stack Overflow as an example, and how much unique privileges it has. I have actually tried to make a count but lost myself in complexity, but the amount we would need is way close, if not already over the said barrier of 31 unique values. There is a very likely chance that an update on the project which alters bitmask meanings result in a long need of recalculation, and it is a lot more error prone if confronted by database errors.

    While I don't have the exact, pinpoint timing figures, using bitmasks felt slower than ACL. Comparing database sizes, memory and storage footprint is bigger and we have less chance to exploit the relational database and indexing capabilities. For user permissions on a website, bitmasks are a no-go, if we have other methods we might use.

    There are various systems where bitmasks do works, namely MaNGOS (from which I originally came up with the idea), where item_template and various other template tables define flags which makes use of bitmasks. But the bottom line is that in these tables, the values are unlikely to ever change, the calculation is read-only, contrary to websites where users arbitrarily obtain and lose privileges.

    Example code

    define('U_NIL', 0);
    define('U_EXEC', 1);
    define('U_WRIT', 2);
    define('U_READ', 4);
    
    $our_perm = 7;
    $req_perm = U_EXEC | U_WRIT | U_READ;
    var_dump( ($our_perm & $req_perm) == $req_perm ); # Will be bool(true)
    
    $our_perm = 3;
    var_dump( ... The same thing ...); # Will be bool(false)
    
    $our_perm = U_READ;
    $req_perm = U_READ | U_WRIT;
    var_dumo(...); # Will be bool(false)
    
    $our_perm = U_READ | U_WRIT | U_EXEC;
    $req_perm = U_READ;
    var_dump(...); # Will be bool(true)
    

    Etcetera.

    I will spare you the lines of code because various other questions describe the method aptly, in a way I will never be able to describe it. Bitmasks seem to be nice, bitmasks seem to be exotic, but there is no point letting them settle in production.

    ACL

    The other option which was described in the original question was to exploit the relational database and the SQL language to set up the permissions in the database. We will need to create two more tables into our database:

    CREATE TABLE `perm_relation` (
        `row_id` int(10) NOT NULL AUTO_INCREMENT,
        `user_id` int(10) NOT NULL,
        `perm_id` int(10) NOT NULL,
        PRIMARY KEY (`row_id`),
        UNIQUE `permission` (`user_id`, `perm_id`)
    ) ENGINE=InnoDB;
    
    CREATE TABLE `permission` (
        `id` int(10) NOT NULL AUTO_INCREMENT,
        `name` varchar(64) NOT NULL,
        PRIMARY KEY (`row_id`)
    ) ENGINE=InnoDB;
    

    perm_relation.perm_id will be the foreign key pointing to permission.id, and perm_relation.user_id will be our relation to users.id. After this, it's only a matter of how we put together our programming logic.

    SELECT row_id FROM perm_relation
    WHERE perm_id = (SELECT id FROM permission WHERE name = "U_SUPERADMIN")
    AND user_id = CURRENT_USER_ID;
    

    Using this method we bind towards greater compatibility, quicker execution and easier migration. It takes only a neck of time to add new permissions, both as defining new ones and grating some to arbitrary users. Deletion is just as easy and it takes only a small SQL query to optimize our tables to remove orphan entires (for example: users having permissions given to them without related permission defined in the system).

    Because we are implementing this system into a PHP environment, we can assume that there will be some sort of administrative page with many features relying on this permission list. An example of listing users filtered by the permission they have would be one example. In this context, ACL is a lot better than bitmasks, because we can take further exploits of JOIN statements... and even beyond.

    The conclusion is that bitmasks and the relational pattern has different purposes. Bitmasks are too much and very bulk for a user permission system, while relational tables would be an overkill in the MaNGOS example mentioned above.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

悬赏问题

  • ¥20 西门子S7-Graph,S7-300,梯形图
  • ¥50 用易语言http 访问不了网页
  • ¥50 safari浏览器fetch提交数据后数据丢失问题
  • ¥15 matlab不知道怎么改,求解答!!
  • ¥15 永磁直线电机的电流环pi调不出来
  • ¥15 用stata实现聚类的代码
  • ¥15 请问paddlehub能支持移动端开发吗?在Android studio上该如何部署?
  • ¥20 docker里部署springboot项目,访问不到扬声器
  • ¥15 netty整合springboot之后自动重连失效
  • ¥15 悬赏!微信开发者工具报错,求帮改