I am currently using the Services module for a project where an external application is responsible for creating, updating and deleting user accounts in a Drupal based intranet.
Most things are working just fine (thank you all), however there are a few points which I have doubts about:
- The
user.saveservice - Drupal's special users (uids 0 and 1) require some configurable protection against being changed. I don't want the external application to be able to change the values of these accounts.
- There are other users (such as site editors or administrators) that I'd prefer the external application not change (signs of creeping paranoia?). These can be designated - and distinguished - by special roles.
- The
user.deleteservice - For the same reasons as above, I don't want the external application deleting uids 0 or 1, or any specially designated users.
From this short discussion hayrocker, one of the commiters, suggested integrating the User services module with the User Protect module. The attached patch is a result of that integration.
Limitations
The patch only takes into account the deletion and all account edits protections from User Protect. Single field protection, such as username or e-mail has not been integrated.
Testing
Testing has been carried out with and without the User Protect module enabled. When enabled, the following settings were used:
- Protections defaults (
admin/user/userprotect/protection_defaults) - All checkboxes unchecked
- Administrator bypass (
admin/user/userprotect/administrator_bypass) - As per the following snapshot (
sygeris the super user) - Protected roles (
admin/user/userprotect/protected_roles) - As per the following snapshot (
editoreis a protected role,remoteis the external application role) - Protected users (
admin/user/userprotect) - As per the following snapshot (
sygeris the super user) @@ -32,6 +32,13 @@ function user_service_delete($uid) {- Checks for User Protect
deletionprotection @@ -66,7 +73,8 @@ function user_service_get($uid) {- Minor aesthetical modification, in line with other ored tests
@@ -170,7 +178,7 @@ function user_service_save($account) {- The
drupal_executefunction doesn't return anything, and$retis not used elsewhere. @@ -180,17 +188,36 @@ function user_service_save($account) {- This is the big one. I'll go through it piece by piece.
-
+ // Preload the state with some of the original user object values + $allowed = array('name', 'mail', 'mode', 'theme', 'signature', 'status', 'timezone', 'language', 'picture', 'contact', 'roles'); + foreach ($update as $key => $value) { + if (in_array($key, $allowed)) { + $form_state['values'][$key] = $value; + } + } - Non integration code. By adding the previously saved user fields to the
$form_statethe external application does not have to provide all the values, but can make a partial change, by specifying theuidandstatusfor example. -
+ // Any logged in user is by default authenticated, + // and leaving this role set in the user's roles array + // causes big problems because of a FAPI hack that controls + // this checkbox on the user create and edit form (and thus + // causes problems with drupal_execute()). Therefore we just + // force it to 0 here. + if (isset($form_state['values']['roles'][2])) { + $form_state['values']['roles'][2] = 0; + } - Disables the
authenticated userrole in a similar way to the code on lines 161-163. -
+ drupal_execute('user_profile_form', $form_state, $update, $category); - The
drupal_executefunction doesn't return anything, and$retis not used elsewhere. - Non integration code. Instead of using the data received (
$account) as the third parameter, I have changed this to use the previously saved user data ($update) which will then correctly fill the form's default values, as per the menu itemuser/%uid/edit @@ -210,6 +237,14 @@ function user_service_save($account) {- Checks for User Protect
all account editsprotection
Explanation
As this patch is somewhat complex (and makes changes unrelated to the User Protect integration) I will try to explain each block in further detail for the committers.
Best regards,
John
| Comment | File | Size | Author |
|---|---|---|---|
| #9 | menu_get_item.patch | 1.72 KB | jhl.verona |
| #2 | userprotect.patch | 3.83 KB | jhl.verona |
Comments
Comment #1
jhl.verona commentedWrong status, sorry. Can't seem to be able to edit the original post.
Comment #2
jhl.verona commentedAnd the patch got lost, along with the images... Hmm.
Comment #3
alfthecat commentedA shot in the dark but maybe worth trying.... Have you considered using the path access module to restrict pages for the role being used by the external application to login to the site?
See http://drupal.org/project/path_access
Like I said, shot in the dark but if it works it is a simple and swift resolution...
Comment #4
jhl.verona commentedThanks for the link.
Firstly, this patch is working just fine for me. Secondly, using the XML-RPC server, there is only one URL (
services/xmlrpc) for all methods of all services.I just need to make sure that certain users can't be deleted or modified. User Protect is perfect for this because using the Web UI, all requests would pass through
user/%uid/edit. It just needed a small integration patch to keep it working its magic via a user service method call.John
Comment #5
marcingy commentedHaven't reviewed the patch but I'm against this going in as it is not drupal core functionality and as such has no place in services core as a result. If we add this then my fear is where does it stop....
Comment #6
gddIf we are handling users improperly, like going around traditional user save procedures that User Protect hooks into, then I am happy to look at those changes. I am less inclined to look at changes that exist only to integrate with User Protect, for the reasons that marcingy outlined. There may be some places where a patch there will make more sense. Additionally, I don't want to do an API change mid-major-version. I'm unsure if anything here would modify existing functionality.
Anyways I'll try to dig into this soon, thanks.
Comment #7
marcingy commentedI have had a chance to review and the very existence of
makes this patch a no go for me. Yes we can support user_protect if change simple add data passed into drupal_excute that is currently missing for user save but actually referencing that module in code is out in my opinion.
Comment #9
jhl.verona commentedWell I have to agree with marcingy in #5. Via the Web UI you can delete uid == 0 (anonymous user) and uid == 1 (superuser) - which is fun to correct afterwards. Programmatically, you can delete them all, of course.
I can't say that you're handling users improperly (heyrocker #6) exactly, and the previous patch creates a connection with User protect (as marcingy points out in #7).
However, I think that the maintainer for User Protect (hunmonk) could use your own arguments as to why he shouldn't allow a patch with a connection to User Service (I have not asked, the D6 project seems to be frozen while D7 work progresses).
However #6 does raise an interesting issue, so I will actually argue that you're using access handling improperly. You're using delete and save which, using the Web UI, correspond to
user/%uid/deleteanduser/%uid/edit, however you're ignoring themenu_routeraccess functions and providing (only) your own. User Protect actually modifies themenu_routeraccess functions usinghook_menu_alter.So I have prepared another patch, which actually has better functionality, because individual fields are also protected. This simply uses
menu_get_itemfor the corresponding Web UI path.But there is one other modification, I have still changed the
drupal_executecall:drupal_execute('user_profile_form', $form_state, $update, $category);to use the loaded user (
$update), and not the received, possibly partial user data ($account). Following the$formcontents around during the WEB UI process, on a sandbox with only user and profile modules enabled, the$form['_account']['#value']always contains the complete, unmodified, user object. See also http://api.drupal.org/api/function/user_profile_form/6I rest my case, m'lud. (And I won't bother you again, whatever the final decision.)
John
Comment #10
gddUnfortunately, the way that Services is architected, we can't rely on the menu access necessarily. API Key services often run as anonymous, which is why they need their own access control separate from those that Drupal provides. This is all changing down the road but for now it is what it is.
Another solution I would consider looking into is that we dump the user_delete() API function to delete users, which is the one thing that User Protect has no way to hook into. I was thinking we could switch this to use drupal_execute(), which in theory User Protect should be able to work with. I'm not entirely sure that's going to fly though, because of its form_alter()s. It is something that would take some experimentation. On the user_save() side, this service already uses drupal_execute() with user_profile_form and looking at the User Protect code, it appears it is already protecting that form. John have you tried User Protect + User Save Service with no extra code? I would be curious if that works or not.
Comment #11
jhl.verona commentedOops, didn't know abut that. Hmm, what about testing
(global) $user != 0 ...as well?Example:
I much prefer the access control approach over my previous effort, because it's independent of any other modules used. Which is why I'll keep trying to push this.
However I am aware of your (reasonable) reluctance to add 'gratuitous' code. And I agree, writing code is like having children - 5 minutes of fun, then a lifetime of maintenance.
User Protect is actually quite a small piece of code.
It protects again deletion ('deletion' flag) using access control (line 392).
It also protects via access control if you've set the 'all account edits' flag (also line 392).
Technically no. I tested with the patch.
However, the answer is (partially) yes, because UP modifies the form for all permissions except 'all account edits' (line 186).
I tested with a user (and role) where UP was protecting a single field - the username (in the UP UI 'username' checked, 'all account edits' unchecked'). In both cases the changes sent via
user.save(I changed both the username and email) were made only to the email field.The code also disables the 'Delete' button if the 'deletion' flag is checked - but that won't stop the programmatic form submission if you set 'op' to 'Delete' without testing.
Unfortunately UP doesn't modify the form if you've set the 'all account edits' flag - it just assumes you couldn't get there because of the access control. This is correct in the Web UI - even specifying
user/123/editgives you an 'access denied' error and no form.Also, unfortunately, it loads the user account via arg(1). However, someone has sent a patch: http://drupal.org/node/752158#comment-3008882 and that patch has now been accepted.
Comment #12
gddUltimately I've decided not to include this functionality in Services.