Index: modules/simpletest/drupal_web_test_case.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/drupal_web_test_case.php,v
retrieving revision 1.147
diff -u -p -r1.147 drupal_web_test_case.php
--- modules/simpletest/drupal_web_test_case.php	5 Sep 2009 13:05:30 -0000	1.147
+++ modules/simpletest/drupal_web_test_case.php	11 Sep 2009 15:26:45 -0000
@@ -901,8 +901,8 @@ class DrupalWebTestCase extends DrupalTe
     $role = new stdClass();
     $role->name = $name;
     user_role_save($role);
-    user_role_set_permissions($role->name, $permissions);
-    
+    user_role_grant_permissions($role, $permissions);
+
     $this->assertTrue(isset($role->rid), t('Created role of name: @name, id: @rid', array('@name' => $name, '@rid' => (isset($role->rid) ? $role->rid : t('-n/a-')))), t('Role'));
     if ($role && !empty($role->rid)) {
       $count = db_query('SELECT COUNT(*) FROM {role_permission} WHERE rid = :rid', array(':rid' => $role->rid))->fetchField();
Index: modules/user/user.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.admin.inc,v
retrieving revision 1.77
diff -u -p -r1.77 user.admin.inc
--- modules/user/user.admin.inc	5 Sep 2009 15:05:05 -0000	1.77
+++ modules/user/user.admin.inc	11 Sep 2009 15:26:45 -0000
@@ -663,8 +663,7 @@ function user_admin_permissions($form_st
  */
 function user_admin_permissions_submit($form, &$form_state) {
   foreach ($form_state['values']['role_names'] as $rid => $name) {
-    $permissions = array_filter($form_state['values'][$rid]);
-    user_role_set_permissions($rid, $permissions);
+    user_role_change_permissions($rid, $form_state['values'][$rid]);
   }
 
   drupal_set_message(t('The changes have been saved.'));
Index: modules/user/user.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.module,v
retrieving revision 1.1043
diff -u -p -r1.1043 user.module
--- modules/user/user.module	11 Sep 2009 06:20:49 -0000	1.1043
+++ modules/user/user.module	11 Sep 2009 15:40:49 -0000
@@ -2304,11 +2304,11 @@ function user_role_delete($role) {
     ->condition('rid', $role->rid)
     ->execute();
 
+  module_invoke_all('user_role_delete', $role);
+
   // Clear the user access cache.
   drupal_static_reset('user_access');
   drupal_static_reset('user_role_permissions');
-
-  module_invoke_all('user_role_delete', $role);
 }
 
 /**
@@ -2317,26 +2317,64 @@ function user_role_delete($role) {
  * @param $role
  *   A string with the role name, or an integer with the role ID.
  * @param $permissions
- *   An array of permissions strings.
- * @param $merge
- *   A boolean indicating whether to add permissions or to merge
- *   with all existing permissions.
+ *   An array of permissions, where the key holds the permission name and the
+ *   value is an integer or boolean that determines whether to grant or revoke
+ *   the permission:
+ *   @code
+ *     array(
+ *       'administer nodes' => 0,
+ *       'access user profiles' => 1,
+ *     )
+ *   @endcode
+ *   Existing permissions are not changed, unless specified in $permissions.
  */
-function user_role_set_permissions($role, array $permissions = array(), $merge = FALSE) {
+function user_role_change_permissions($role, array $permissions = array()) {
   $role = user_role_load($role);
-  if (!$merge) {
-    // Delete existing permissions for the role.
-    db_delete('role_permission')
-      ->condition('rid', $role->rid)
-      ->execute();
+  if (!is_object($role) || !isset($role->rid)) {
+    return FALSE;
+  }
+
+  // Grant new permissions for the role.
+  $grant = array_filter($permissions);
+  if (!empty($grant)) {
+    user_role_grant_permissions($role, $grant);
+  }
+  // Revoke permissions for the role.
+  $revoke = array_keys(array_diff_assoc($permissions, $grant));
+  if (!empty($revoke)) {
+    user_role_revoke_permissions($role, $revoke);
+  }
+
+  // Clear the user access cache.
+  drupal_static_reset('user_access');
+  drupal_static_reset('user_role_permissions');
+
+  return TRUE;
+}
+
+/**
+ * Grant permissions to a user role.
+ *
+ * @param $role
+ *   A user role object, as returned from the database. Or alternatively, a
+ *   string with the role name, or an integer with the role ID.
+ * @param $permissions
+ *   A list of permission names to grant.
+ */
+function user_role_grant_permissions($role, array $permissions = array()) {
+  if (!is_object($role)) {
+    $role = user_role_load($role);
+  }
+  if (!is_object($role) || !isset($role->rid)) {
+    return FALSE;
   }
 
-  // Assign the new permissions for the role.
-  foreach ($permissions as $permission_string) {
+  // Grant new permissions for the role.
+  foreach ($permissions as $name) {
     db_merge('role_permission')
       ->key(array(
         'rid' => $role->rid,
-        'permission' => $permission_string,
+        'permission' => $name,
       ))
       ->execute();
   }
@@ -2349,6 +2387,36 @@ function user_role_set_permissions($role
 }
 
 /**
+ * Revoke permissions from a user role.
+ *
+ * @param $role
+ *   A user role object, as returned from the database. Or alternatively, a
+ *   string with the role name, or an integer with the role ID.
+ * @param $permissions
+ *   A list of permission names to revoke.
+ */
+function user_role_revoke_permissions($role, array $permissions = array()) {
+  if (!is_object($role)) {
+    $role = user_role_load($role);
+  }
+  if (!is_object($role) || !isset($role->rid)) {
+    return FALSE;
+  }
+
+  // Revoke permissions for the role.
+  db_delete('role_permission')
+    ->condition('rid', $role->rid)
+    ->condition('permission', $permissions, 'IN')
+    ->execute();
+
+  // Clear the user access cache.
+  drupal_static_reset('user_access');
+  drupal_static_reset('user_role_permissions');
+
+  return TRUE;
+}
+
+/**
  * Implement hook_user_operations().
  */
 function user_user_operations($form_state = array()) {
Index: modules/user/user.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/user/user.test,v
retrieving revision 1.61
diff -u -p -r1.61 user.test
--- modules/user/user.test	11 Sep 2009 06:20:49 -0000	1.61
+++ modules/user/user.test	11 Sep 2009 15:29:39 -0000
@@ -928,6 +928,31 @@ class UserPermissionsTestCase extends Dr
     $this->drupalPost('admin/config/modules', $edit, t('Save configuration'));
     $this->assertTrue(user_access('administer news feeds', $this->admin_user), t('The permission was automatically assigned to the administrator role'));
   }
+
+  /**
+   * Verify proper permission changes by user_role_change_permissions().
+   */
+  function testUserRoleChangePermissions() {
+    $rid = $this->rid;
+    $account = $this->admin_user;
+
+    // Verify current permissions.
+    $this->assertFalse(user_access('administer nodes', $account, TRUE), t('User does not have "administer nodes" permission.'));
+    $this->assertTrue(user_access('access user profiles', $account, TRUE), t('User has "access user profiles" permission.'));
+    $this->assertTrue(user_access('administer site configuration', $account, TRUE), t('User has "administer site configuration" permission.'));
+
+    // Change permissions.
+    $permissions = array(
+      'administer nodes' => 1,
+      'access user profiles' => 0,
+    );
+    user_role_change_permissions($rid, $permissions);
+
+    // Verify proper permission changes.
+    $this->assertTrue(user_access('administer nodes', $account, TRUE), t('User now has "administer nodes" permission.'));
+    $this->assertFalse(user_access('access user profiles', $account, TRUE), t('User no longer has "access user profiles" permission.'));
+    $this->assertTrue(user_access('administer site configuration', $account, TRUE), t('User still has "administer site configuration" permission.'));
+  }
 }
 
 class UserAdminTestCase extends DrupalWebTestCase {
Index: profiles/default/default.install
===================================================================
RCS file: /cvs/drupal/drupal/profiles/default/default.install,v
retrieving revision 1.2
diff -u -p -r1.2 default.install
--- profiles/default/default.install	27 Aug 2009 20:25:29 -0000	1.2
+++ profiles/default/default.install	11 Sep 2009 15:28:45 -0000
@@ -184,15 +184,15 @@ function default_install() {
   db_insert('taxonomy_vocabulary_node_type')->fields(array('vid' => $vid, 'type' => 'article'))->execute();
 
   // Enable default permissions for system roles.
-  user_role_set_permissions(DRUPAL_ANONYMOUS_RID, array('access content'));
-  user_role_set_permissions(DRUPAL_AUTHENTICATED_RID, array('access content', 'access comments', 'post comments', 'post comments without approval'));
+  user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array('access content'));
+  user_role_grant_permissions(DRUPAL_AUTHENTICATED_RID, array('access content', 'access comments', 'post comments', 'post comments without approval'));
 
 
   // Create a default role for site administrators, with all available permissions assigned.
   $admin_role = new stdClass();
   $admin_role->name = 'administrator';
   user_role_save($admin_role);
-  user_role_set_permissions($admin_role->name, array_keys(module_invoke_all('permission')));
+  user_role_grant_permissions($admin_role, array_keys(module_invoke_all('permission')));
   // Set this as the administrator role.
   variable_set('user_admin_role', $admin_role->rid);
 
Index: profiles/expert/expert.install
===================================================================
RCS file: /cvs/drupal/drupal/profiles/expert/expert.install,v
retrieving revision 1.2
diff -u -p -r1.2 expert.install
--- profiles/expert/expert.install	27 Aug 2009 20:25:29 -0000	1.2
+++ profiles/expert/expert.install	11 Sep 2009 15:26:45 -0000
@@ -68,8 +68,8 @@ function expert_install() {
   $query->execute();  
 
   // Enable default permissions for system roles.
-  user_role_set_permissions(DRUPAL_ANONYMOUS_RID, array('access content'));
-  user_role_set_permissions(DRUPAL_AUTHENTICATED_RID, array('access content', 'access comments', 'post comments', 'post comments without approval'));
+  user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array('access content'));
+  user_role_grant_permissions(DRUPAL_AUTHENTICATED_RID, array('access content', 'access comments', 'post comments', 'post comments without approval'));
 }
 
 
