Problem/Motivation

Flag module allows an admin to determine which roles can use a particular flag. This access control is stored as an array of role IDs in the flag object, and in turn saved in the serialized options in the {flag} table.

This has a number of drawbacks:

- The user permissions page doesn't give a clear view of what a user can achieve with respect to Flag module
- Role IDs are not portable
- A flag and its permissions can't be separated. In times of features and exported configuration, it is quite likely that one feature will set up a flag and another feature set the permissions for it. Since the flag permissions are stored in the flag object itself – not as a standard permission – you can't really export flags and permissions separately. The problem can be worked around using Features Override, but it would be nice if this was not necessary.
- More philosophically, keeping what are effectively permissions inside a flag means that flag is managing its own permissions system rather than letting Drupal core do it.

Proposed resolution

Change flags to use standard user permissions, with permissions per flag:

- flag entities with flag 'foo'
- unflag entities with flag 'foo'

The UI in the flag admin form thus reads and saves standard permissions.

Remaining tasks

  • Discuss whether this is an approach more people would find useful.
  • Implement it.
  • Test the implementation.

User interface changes

Access to flags can now be set in the main user permissions admin page as well as on the flag.

API changes

None that affect how other modules use Flag.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Itangalo’s picture

I'm overriding the Flag permissions with standard permissions in a custom module I have – for allowing granular export. The code I'm using would be the base for a Flag patch, if more people find this useful.

/**
 * Helper function for overriding permissions for selected flags.
 * @return array
 *   An array with the machine names of the flags whose permissions should be
 *   overridden. (This should be replaced with a function reading flag settings.)
 */
function mymodule_flags() {
  return array(
    'mymodule_completed',
    'mymodule_proficiency',
  );
}

/**
 * Implements hook_permission().
 *
 * Provides permissions to override the stadard flag permissions.
 */
function mymodule_permission() {
  $all_flags = flag_get_flags();
  $permissions = array();

  // Loop through the flags to override, and provide permissions.
  foreach (mymodule_flags() as $flag_name) {
    $permissions[$flag_name] = array(
      'title' => t('Set/unset the flag @flag_name', array('@flag_name' => $all_flags[$flag_name]->title)),
      'description' => t('This permission overrides all the permissions set in the Flag configuration.'),
    );
  }
  return $permissions;
}

/**
 * Implements hook_flag_access().
 */
function mymodule_flag_access($flag, $content_id, $uid = NULL, $action = NULL) {
  // Override the standard Flag settings any overridden permissions, but leave
  // flags we don't override untouched. (Note that the permissions are named to
  // match the flags names.)
  if (in_array($flag->name, mymodule_flags())) {
    return user_access('mymodule ' . $flag->name);
  }
}

/**
 * Implements hook_flag_access_multiple().
 */
function mymodule_flag_access_multiple($flag_info, $flags, $account) {
  // Do the same magic as in the hook for single flags, but for a whole array.
  if (in_array($flag_info->name, mymodule_flags())) {
    $permission = user_access('mymodule ' . $flag_info->name);
    $access = array();
    foreach ($flags as $id => $flag) {
      $access[$id] = $permission;
    }
    return $access;
  }
}
hefox’s picture

Input formats switched completely over to using permission table, so it seems reasonable for flag to do the same.

Not sure, but flag currently has the issue that it stores it as role id's so doesn't transfer well to other sites with different roles (and can cause a security issue on the other site due to mismatch?). This would address that issue as well (since features role, perm support doesn't use rids).

Means about 4 permissions per flag: flag [flag name] content, unflag [flag name] content, flag [flag name] content, flag [flag name] own content, unflag [flag name] content.

could likely do something where keep the same ui, but save to permissions table for role settings, else just do like formats did (which I'm not totally sure XD).

Would probably mean some sort of handling for depracted flag definations, etc..

Itangalo’s picture

#2: I was thinking adding a new, simple, option on top of the existing permissions settings, but yeah – it makes sense to move all the permissions to the standard permissions table.

Currently Flag has hooks for altering these permissions. I'm not sure how these would translate if the permissions are moved.

Scyther’s picture

This looks good!

joachim’s picture

Version: 7.x-2.x-dev » 7.x-3.x-dev
Priority: Normal » Major

> Means about 4 permissions per flag: flag [flag name] content, unflag [flag name] content, flag [flag name] content, flag [flag name] own content, unflag [flag name] content.

Probably only two for now:

- 'flag FLAGNAME content'
- 'unflag FLAGNAME content'

To have 'own' permissions, we need to have a notion of which flag types support it: see #879988: Flag permission for 'own entities', which I've marked as a follow-on to this.

joachim’s picture

Assigned: Unassigned » joachim

Assigning this to myself.

joachim’s picture

Issue tags: +Needs change record

Adding the Needs change notification tag. This should be created as a stub before this patch is committed, so that the hook_update_n() that sets up permissions based on flags can give users a link to it.

The reason for this is that this change will introduct a significant change in access to flag properties: users who can admin permissions but can't admin flags will be able to change access to a flag, whereas before they could not. This is something site admins need to be made aware of, as it could have implications for their site roles and access.

joachim’s picture

Status: Active » Needs review
FileSize
8.95 KB

This is a work in progress:

- the update path needs testing and possibly tweaking, as I'm not sure why on my copy flags are still showing they have a roles array.
- some things need changing in flag export.
- I suspect we may need to change the flag API version to 3 to reflect that we no longer have a roles array stored in the flag object.
- possibly need a form validation on the main permissions form to warn of incompatible permissions, such as:
-- can't have unflag permission without flag permission
-- can't have only flag permission without an 'unflag denied' message

(These special rules are a good example of why flag handling its access in-house has advantages!)

Testing or review of what there is so far would be very welcome!

Status: Needs review » Needs work

The last submitted patch, 1525242.flag_.flag-permissions.patch, failed testing.

joachim’s picture

Status: Needs work » Needs review
FileSize
10.17 KB

Fixed the problem the testbot was dying on.

joachim’s picture

I've changed the issue summary, as it was out of sync with what I'm doing in the patch.

Previously: suggested adding an option to each flag 'use core permissions'
Changed to: all flags provide core permissions.

This is considerably simpler to implement, as it's replacing the flag access system, rather than implementing a new system to live in parallel with the current one!

Scyther’s picture

+  // Saving the flag now will remove the roles array from the serialized options.
+  $flag->save();

Shouldn't that be inside the foreach loop?

joachim’s picture

Status: Needs review » Needs work

Oh heck yes! Good catch! :D

joachim’s picture

Status: Needs work » Needs review
FileSize
10.18 KB

Updated patch with that fix. See #8 for list of things still to do.

joachim’s picture

Update function works fine now :)

joachim’s picture

Issue tags: +7.x-3.0-alpha1 blocker

Tagging.

joachim’s picture

Status: Needs review » Needs work

I've figured out what's going wrong in the update; will work on it soon.

In the meantime do please test this as a fresh install and report any findings.

joachim’s picture

Status: Needs work » Needs review

Here's the updated patch.

It would be great if this could see some testing -- it's a pretty big change!

joachim’s picture

Status: Needs review » Needs work

Needs work.

I realized that loading roles into the flag object with user_roles() is adding a ton of queries to flag loading, and flag loading happens on most page requests.
Currently reworking this so we use user_access() when needed instead.

joachim’s picture

Status: Needs work » Needs review
FileSize
13.84 KB

Status: Needs review » Needs work

The last submitted patch, 1525242.20.flag_.flag-permissions.patch, failed testing.

joachim’s picture

Status: Needs work » Needs review
FileSize
16.2 KB

Updated tests.

I think we're going to have to give validation of user permissions a miss, because of #222380: No error highlighting on form checkbox or radio input types. Showing validation errors on such a large form, and with no easy way of finding the offending element is just going to be a UX horror.

Status: Needs review » Needs work

The last submitted patch, 1525242.22.flag_.flag-permissions.patch, failed testing.

joachim’s picture

Status: Needs work » Needs review
FileSize
16.79 KB

Test was failing because permissions cache needed clearing in the test.

But this also let me discover that changing the flag name caused permissions pollution. Fixed that too.

Status: Needs review » Needs work

The last submitted patch, 1525242.24.flag_.flag-permissions.patch, failed testing.

joachim’s picture

Status: Needs work » Needs review
FileSize
16.79 KB

Fixed the test again.

joachim’s picture

Finally! :D

joachim’s picture

Updated patch which restores the default values for the role access checkboxes when creating a new flag.

joachim’s picture

Any more reviews before I commit this?

joachim’s picture

Status: Needs review » Needs work

Another thing to do: somehow handle the change from $flag->roles to permissions in the flag update system.

What I'm thinking is to change the key to $flag->import_roles and then take those as incoming data only when saving a new flag (ie with no fid).

joachim’s picture

Status: Needs work » Needs review
FileSize
18.24 KB

Added updating of roles on API 2 flags in code.

joachim’s picture

And another thing. Disabling $flag->roles no longer makes sense. If you want to lock that, use something that locks permissions generally.

socketwench’s picture

Yikes. Flag_bookmark tosses a nasty error when enabling:

[tess@nitori drupal7]$ drush en -y flag_bookmark
PHP Warning:  Module 'xdebug' already loaded in Unknown on line 0
The following extensions will be enabled: flag_bookmark
Do you really want to continue? (y/n): y
WD php: PDOException: SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'module' cannot be null: INSERT INTO {role_permission} (rid,    [error]
permission, module) VALUES (:db_insert_placeholder_0, :db_insert_placeholder_1, :db_insert_placeholder_2); Array
(
    [:db_insert_placeholder_0] => 2
    [:db_insert_placeholder_1] => flag bookmarks
    [:db_insert_placeholder_2] => 
)
 in user_role_grant_permissions() (line 3034 of /home/tess/sites/drupal7/modules/user/user.module).
Cannot modify header information - headers already sent by (output started at /usr/lib/drush/includes/drush.inc:596) bootstrap.inc:1239            [warning]
PDOException: SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'module' cannot be null: INSERT INTO {role_permission} (rid, permission, module) VALUES (:db_insert_placeholder_0, :db_insert_placeholder_1, :db_insert_placeholder_2); Array
(
    [:db_insert_placeholder_0] => 2
    [:db_insert_placeholder_1] => flag bookmarks
    [:db_insert_placeholder_2] => 
)
 in user_role_grant_permissions() (line 3034 of /home/tess/sites/drupal7/modules/user/user.module).
Drush command terminated abnormally due to an unrecoverable error.                                                                                 [error]
Error: Module 'xdebug' already loaded in Unknown, line 0

Interestingly, the module does enable.

Beyond that, everything tests cleans and functions correctly.

joachim’s picture

Status: Needs review » Needs work

Thanks for trying it out! I didn't think of enabling the bookmark module... for that matter, not 100% I've tested creating a new flag, which may have the same sort of problem...

> Column 'module' cannot be null: INSERT INTO {role_permission} (rid, [error]
permission, module)

Quite a showstopper! This is because the flag doesn't exist at this point, so hook_permission() doesn't define permissions for it, which means that the attempt to give users those permissions is trying to grant permissions that system doesn't know about.

Urgh. No idea how to resolve that.

socketwench’s picture

Forgot to mark as 'needs work'.

joachim’s picture

Ah, just a cache clear thing. Phew. Will fix later.

joachim’s picture

Status: Needs work » Needs review
FileSize
19.08 KB

Yet another patch :)

socketwench’s picture

Status: Needs review » Reviewed & tested by the community

There we go! Enables clean, tests clean, confirmed with manual testing. Marking RTBC!

joachim’s picture

Status: Reviewed & tested by the community » Fixed

Woohoo!!! let's get this in!

Thanks for the reviews.

joachim’s picture

Title: Allow using standard permissions table for flags » Change notification for: Allow using standard permissions table for flags
Status: Fixed » Active

Oh yes I need to write the change notification too.

joachim’s picture

Status: Active » Fixed

Done.

joachim’s picture

Title: Change notification for: Allow using standard permissions table for flags » Allow using standard permissions table for flags

Automatically closed -- issue fixed for 2 weeks with no activity.

Anonymous’s picture

Issue summary: View changes

expanded problems; changed proposed resolution to reflect actual patch!