I recently (1 year ago) discovered Drupal through an internship at the Open
Web Solutions company (http://ows.fr).
As I really do like the drupal way of thinking which make it one of the most
powerful CMS over the world, I was missing a feature that could permit me to
have a fine-cut control over the user access : an access control based on the
drupal taxonomy, so i did create my own module to do that.
At first sight, you may think it's just another fork of already available module
on drupal.org like taxonomy_access or tac_lite. First one is a taxonomy control
access based on roles, second one is a taxonomy control access based on users. But
both of those modules miss the - according to me essential - inheritance notion :
if you have access to one term, you don't automatically have access to the children
nodes. My module does take care about inheritance and this way permit a powerfull
user access control.
Description:
For this description, I will try to illustrate the module with the following term
scheme:
1 (root) -> 2 -> 4
-> 5 -> 7
-> 3 -> 6
The basic idea of inheritance is : if you have access to one term then you may
access all children terms.
So I provided 4 different grant access for this module:
- grant_view
Which controls whether the user may view nodes associated to this term
- grant_update
Which controls whether the user may add nodes associated to this term or edit
them
- grant_delete
Which controls whether the user may delete nodes associated to this term
- grant_admin
Which is the most particular grant out of the four.
The last grant access (grant_admin) permits a user to "delegate" control of an access
over a term to another user : if a lambda use has the grant_admin over the term 2, he
may grant view to another user to terms 2, 4, 5 or 7.
So now you are familiar with the possible grant access, if the user lambda have
grant_view on the term 1, then he may view nodes associated with the term 1, 2, 3, 4,
5, 6 and/or 7. The only record in the database about the grant access is about the
term 1. To reach this goal in a reasonnable amount of time and with simple queries, I
used SQL functions in the DBMS. Availability of those functions is controlled each
time the module is invoked by the hook_init which permit not to break the module even
if the database is transfered to another dbms and if the user has forgot to backup
sql functions :).
Yet another feature of this module is the ability to restrain access to a subtree
while he still have access to the other part. For example, if i set grant_view = 1 to
the term 1 to the user lambda and grant_view = -1 to the term 2, then the user may view
terms 1, 3 and 6 while access to terms 2, 4, 5 and 7 would be denied. The default value
is 0 and it's the value which permits inheritance.
This module also provides a UI to administer the module, views and views handler for
specific fields. It is also tested with simpletest, some work on code coverage has been done.
This module could have multiple applications :
- An e-commerce website where a department supervisor may edit nodes associated to
his department or delegate rights to his employees
- An news website where the person in charge of the sport section may only create
articles in his own section or may delegate access to the golf area to one specific
person.
The module could be found
here : http://github.com/baloo/taxonomy-access-user/tarball/master
Thank you in Advance for accepting my CVS account request.
| Comment | File | Size | Author |
|---|---|---|---|
| #33 | taxonomy-access-user.tar_.gz | 13.08 KB | superbaloo |
| #31 | taxonomy_access_user.tar_.gz | 11.72 KB | superbaloo |
| #31 | taxonomy_access_user.zip | 17.99 KB | superbaloo |
| #29 | taxonomy_access_user.tgz | 11.71 KB | superbaloo |
| #25 | taxonomy_access_user.tar_.gz | 11.7 KB | superbaloo |
Comments
Comment #1
superbaloo commentedBTW, the module is sponsored by Open Web Solutions (http://ows.fr - http://drupal.org/profile/companies/Open%20web%20solutions)
Comment #2
ultimateboy commentedLink to tarball is in original post. This is ready for review.
Comment #3
avpadernohttp://github.com/baloo/taxonomy-access-user/tarball/master downloads a tarball archive that contains just two empty folders, and a README.
Comment #4
superbaloo commented?! It's working with me, but anyway here is the tgz as attachement ;)
If you prefer git ...
would do the work too ;)
Comment #5
avpadernoIt is better to use
element_children(), which filters out all the keys starting with "#" (you are not looking for them). This would speed up the search for the keys.The code should call
watchdog(), and then return to the caller.hook_enable()should be in the installation file.I am not sure what the purpose of that menu callback is. May you explain it?
The global variable is normally initialized; even when the user is anonymous, the variable is not null.
There is a easier way to count the number of rows matching a criteria (and it would not require
db_query_range(). Also, I am not sure aboutgrant_admin(tid, %d). That is not a syntax accepted from all the database engines; you should use a generic SQL syntax, so the module works in every database engines Drupal support (already Drupal 7 supports a new database engine, and more could be added in future).Follow the coding standards.
$db_prefixis never used from a module.The syntax of the query is not standard, or not accepted by all the database engine versions.
The module you propose extend something done by an already existing module. In these cases, Drupal suggests to join forces with the maintainer of that module.
Comment #6
superbaloo commentedto access taxonomy term list and display a nice form to edit permissions of a
vocabulary. They may access the list only if they have at least one grant_admin
on a term of the list.
are _much_ simplier to write and _much_ faster to execute :).
About functions support in DBMS:
can be extended to sqlite (@see http://php.net/manual/en/function.sqlite-create-function.php),
oracle that is very similar to mysql syntax (or probably the opposite ! :p), mssql, ...
(the only one not supporting functions is mysql 4.x)
As i said in the readme file, there are two other modules providing taxonomy access control :
- tac_lite,
- taxonomy_access
both of them don't provide the inheritence notion.
The first one don't use the database (that's the purpose of the "lite"),
the second one is role-based. If you really want me to merge with one of the existing modules,
i will have to convince the tac_lite maintainer that using database is okay or convince the taxonomy_access
module maintainer to convert his role-based access control module to user-based access control ...
Comment #7
avpadernoThis is what the Drupal coding standards report:
It doesn't say the braces are optional; it say you are strongly encouraged to use them.
If the user doesn't have permission to administer taxonomy, why should s/he be allowed to change the taxonomy permissions?
Also, the code is showing the taxonomy page at another URL; It would be better to alter the taxonomy page, not to duplicate it.
Comment #8
avpadernoComment #9
superbaloo commentedOk,
this element is just a fork of the menu element that gives /?q=admin/content/taxonomy/1,
this page is defined like this in the taxonomy.module
The menu here check if the user have "administer taxonomy". User that may have grant_admin on a term, may not have the "administer taxonomy" permission, but i want them to still administer terms they may admin.
The only way i've found was to reimplement a another page with my own access callback that check whether the user may admin one term of the taxonomy or not ... Is it incorrect ? what is the "drupal way" to do this ?
implement it like this ?
Comment #10
avpadernoWhen you need to alter a menu callback defined by another module, it's enough to implement
hook_menu_alter().Comment #11
superbaloo commentedHere are the updates
Comment #12
avpadernoThe tarball archive you attach keep to show me only two directories, and a README.
Comment #13
superbaloo commentedanyway ... here is another tarball and a zip file
Comment #14
avpadernoThe user object is always initialized.
Together
t()you useurl(), notl(). That is also reported in the documentation fort().The code doesn't work when the record being written in the database doesn't exist. That is because the last parameter is used only when you are updating a table record; if you are writing a new record, you don't need to pass the last parameter.
The code should check if the form it is acting on is the one it is supposed to work on; that is the reason of passing
$form_id.The error message doesn't help much, and it gives details that are not useful for who reads the error message. "Wrong parameters passed to taxonomy_access_user_control()." should be more helpful; at least they would know there is something wrong in the code, and they could report that there is something wrong in a call to
taxonomy_access_user_control(). It is then a kind of message that is not normally given by any modules. I would rather remove the checking for the parameters, in such cases.The code doesn't check if the record is trying to write already exist. If the module is writing data it should not be writing, then something must be corrected in the module; if then the module must write a record if that doesn't already exist, then it should check if the record exists, before to write it.
taxonomy_access_user_sql_uninstall(), andtaxonomy_access_user_sql_install()should be placed in the installation file, as they are only used during the installation/uninstallation of the module.See the coding standards.
Or simply (if the code calling that function is not strictly expecting a boolean, which is true most of the times)
The first IF-statement should be used if the function could return also -1; the second IF-statement should be used if the function can just return 1 and 0 (or a positive number, and zero).
Use more than one line if the code line is longer than 80 characters.
There is no need to use a variable for the result of a function, when the result is just used in the IF-statement.
Comment #15
superbaloo commentedPros:
Cons:
For me the compatibility with mysql 4.x doesn't justify the complexity increase. (Personnal choice)
Comment #16
avpadernoIt doesn't save the user name because when you know the user ID (uid), you can find the user name with
user_load(). If you save the user name, and then you present that on the settings page for taxonomy access, you would shown a wrong user name, if the users are allowed to change their own user names (as it happens on Drupal.org, in example). The only value that doesn't change for a user is his uid.Installation tasks are only executed during the installation/updating of the module;
hook_init()should not do such tasks. Check any modules, and you will see that none of the modules you see do that.I am not sure that using a SQL function in the module simplified the code; as I can see it, you added more code, and you didn't simplified it. I am not sure about the performance either.
Comment #17
superbaloo commentedFirst, I agree that the module should not store the username but only the user ID but you said i was saving the user name in the module database table while there is no field where to do that. So my question was "where do you think the code is trying to store the username in the database?"
Second, if I don't do the check in the hook_init() what is the best place to do so ? At least do you agree i should do the check at drupal startup ?
Third, the question of the SQL function would probably be an eternal question around the module, ... My choice was to code it with the sql function and this module is now up and running for almost a month without problems. I'm not against removing this sql function and doing it only in php. If someone submit a patch, has an way to do it, ... that's okay but i'd just don't know how to do it in a simple way :) .
Comment #18
avpadernoYou load the user object twice.
There is no need to use the
===; then the code can be simply written asYou should use the function
db_placeholders().See the coding standards.
To verify if the form is a node edit form, the code is a little more complicated.
There is no need to use
db_query_range()as you are callingdb_query()only once, in this case.It's enough to use the return statement.
See the Drupal coding standards.
What is the difference between the two code blocks?
Comment #19
superbaloo commentedComment #20
superbaloo commentedforgot the need review
Comment #21
avpadernoActually,
===could not be faster; the strictly equal operator first verify that the two values are of the same type, and then verify that the two values are equal. In the code I reported,count()always return an integer, and there is no reason to strictly compare the returned value with 3.Try to execute the following code:
The result will be a
bool(false); it is also reported in the PHP documentation, which saysComment #22
avpadernoThe code still needs to be changed; some of the observations I made before are still valid.
In particular, the installation code must be executed only inside
hook_install(), andhook_update_NN(), or from functions called from those hooks. Check the Drupal core code, or any third-party modules code; you will never see the implementation ofhook_enable()what the implementation of that hook is doing in your module.Comment #23
superbaloo commentedFor the === vs == debate :
=== is a little bit faster ...
Ok for other modifications :) I will post the corrected code :)
Comment #24
superbaloo commentedCorrected code here :)
Comment #25
superbaloo commentedforgot a ), this version should be better :p
Comment #26
superbaloo commentedis the code ready ?
Comment #27
superbaloo commented@KiamLaLuno : ping ?
Please, at least, respond to say "ok, i don't have the time to review this module right now" if you don't have the time ...
Comment #28
avpaderno@superbaloo: There are other people who can review your code.
Comment #29
superbaloo commentedminor corrections
Comment #30
AjK commentedModules shouldn't die. drupal_access_denied() is enough.
t() ?
Regarding ===, count() always returns an int and your constant is an int so I fail to see why a type check is needed.
Normally when querying the term_data table one would wrap the SQL in db_rewrite_sql(). For your module, being an access control module, you may have done this deliberately. However, as a reviewer, I'd like to see a comment as to why it isn't wrapped in db_rewrite_sql() to save me the bother of tracing the code path and so I understand what your code is doing. This is a common mistake but, as I said, in your case you may be not doing it for a specific reason.
Comment #31
superbaloo commentedSorry for being late, i was off last week :)
Comment #32
avpadernotaxonomy/access/edit/term/<tid>).Comment #33
superbaloo commentedok so:
Comment #34
superbaloo commentedforgot the status
Comment #35
superbaloo commentedAny news ? :)
Comment #36
avpadernoComment #37
superbaloo commentedThanks :)
Comment #40
avpaderno