diff --git a/core/modules/user/user.admin.inc b/core/modules/user/user.admin.inc
index f7d4552..3bb710b 100644
--- a/core/modules/user/user.admin.inc
+++ b/core/modules/user/user.admin.inc
@@ -289,7 +289,7 @@ function user_admin_settings() {
     '#title' => t('Administrator role'),
     '#default_value' => variable_get('user_admin_role', 0),
     '#options' => $roles,
-    '#description' => t('This role will be automatically assigned new permissions whenever a module is enabled. Changing this setting will not affect existing permissions.'),
+    '#description' => t('This role will be automatically assigned new permissions whenever a module is installed. Changing this setting will not affect existing permissions.'),
   );
 
   // User registration settings.
diff --git a/core/modules/user/user.api.php b/core/modules/user/user.api.php
index dc16906..cf3caac 100644
--- a/core/modules/user/user.api.php
+++ b/core/modules/user/user.api.php
@@ -463,5 +463,17 @@ function hook_user_role_delete($role) {
 }
 
 /**
+ * Inform other modules that a user role has been deleted.
+ *
+ * Allows you act when a user role has been deleted. If your module stores references to roles,
+ * it's recommended that you implement this hook and delete existing instances of the deleted role in your module database tables 
+ *
+ * @param $role
+ *   The name of the role you're trying to find.
+ */
+function hook_user_permissions_insert($permissions) {
+}
+
+
/**
+ * Allows you act when a permission is enabled for a user.
+ *
+ * @param $permissions
+ *   An array of permissions.
+ */
+function hook_user_permissions_delete($permissions) {
+}
+
+/**
  * @} End of "addtogroup hooks".
  */
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index 016fa36..00fe813 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -3067,6 +3067,51 @@ function user_role_revoke_permissions($rid, array $permissions = array()) {
 }
 
 /**
+ * Adds new user permissions to the site.
+ *
+ * Modules should call this function when the list of permissions that the
+ * module provides has changed (for example, a dynamic permission has
+ * appeared). Modules do not always have to call it. 
+ *
+ * @param $permissions
+ *   A list of permission names to add.
+ */
+function user_permissions_insert(array $permissions = array()) {
+  // Allow modules to react on new permissions.
+  module_invoke_all('user_permissions_insert', $permissions);
+
+  // Grant any new permissions to the administration role.
+  $rid = variable_get('user_admin_role', 0);
+  if ($rid && !empty($permissions)) {
+    user_role_grant_permissions($rid, $permissions);
+  }
+}
+
+/**
+ * Removes user permissions that are no longer available.
+ *
+ * Modules should call this function when the list of permissions that the
+ * module provides has changed (for example, a dynamic permission has
+ * disappeared).
+ *
+ * @todo: Change documentation, or make all core modules actually call it.
+ *
+ * @param $permissions
+ *   A list of permission names to delete.
+ */
+function user_permissions_delete(array $permissions = array()) {
+  // Allow modules to act on the deleted permissions before they are removed.
+  module_invoke_all('user_permissions_delete', $permissions);
+
+  // Delete all records that currently reference the permissions.
+  db_delete('role_permission')
+    ->condition('permission', $permissions, 'IN')
+    ->execute();
+}
+
+/**
  * Implements hook_user_operations().
  */
 function user_user_operations($form = array(), $form_state = array()) {
@@ -3801,21 +3846,31 @@ function user_register_submit($form, &$form_state) {
 }
 
 /**
+ * Implements hook_modules_to_be_installed().
+ */
+function user_modules_to_be_installed($modules) {
+  // Record all currently defined permissions, for later use in
+  // user_modules_installed().
+  $all_permissions = &drupal_static(__FUNCTION__);
+  $all_permissions = array_keys(module_invoke_all('permission'));
+}
+
+/**
  * Implements hook_modules_installed().
  */
 function user_modules_installed($modules) {
-  // Assign all available permissions to the administrator role.
-  $rid = variable_get('user_admin_role', 0);
-  if ($rid) {
-    $permissions = array();
-    foreach ($modules as $module) {
-      if ($module_permissions = module_invoke($module, 'permission')) {
-        $permissions = array_merge($permissions, array_keys($module_permissions));
-      }
-    }
-    if (!empty($permissions)) {
-      user_role_grant_permissions($rid, $permissions);
-    }
+  // Find all permissions which were newly-added as a result of this module
+  // being installed. This could include permissions defined by the module
+  // itself as well as permissions defined by other modules in response to it
+  // (for example, new node permissions will appear if the module defines a
+  // content type).
+  $previous_permissions = &drupal_static('user_modules_to_be_installed', array());
+  $current_permissions = array_keys(module_invoke_all('permission'));
+  $new_permissions = array_diff($current_permissions, $previous_permissions);
+
+  // Add the new permissions to the site.
+  if (!empty($new_permissions)) {
+    user_permissions_insert($new_permissions);
   }
 }
 
@@ -3823,9 +3878,15 @@ function user_modules_installed($modules) {
  * Implements hook_modules_uninstalled().
  */
 function user_modules_uninstalled($modules) {
-   db_delete('role_permission')
-     ->condition('module', $modules, 'IN')
-     ->execute();
+  // Remove any permissions defined by this module from the site.
+  // @todo: This will skip any permissions that are not in the database (i.e.,
+  //   that are not assigned to any roles). This doesn't matter for our
+  //   purposes, but in theory it's a problem since user_permissions_delete()
+  //   invokes a hook which should get the full list of "deleted" permissions.
+  $permissions = db_query('SELECT DISTINCT(permission) FROM {role_permission} WHERE module IN (:modules)', array(':modules' => $modules))->fetchCol();
+  if (!empty($permissions)) {
+    user_permissions_delete($permissions);
+  }
 }
 
 /**
diff --git a/core/modules/user/user.test b/core/modules/user/user.test
index 81d3e07..3912e10 100644
--- a/core/modules/user/user.test
+++ b/core/modules/user/user.test
@@ -1152,8 +1152,12 @@ class UserPermissionsTestCase extends DrupalWebTestCase {
     // Enable aggregator module and ensure the 'administer news feeds'
     // permission is assigned by default.
     $edit = array();
+    // Enable book.
+    $edit['modules[Core][book][enable]'] = TRUE;
     $edit['modules[Core][aggregator][enable]'] = TRUE;
     $this->drupalPost('admin/modules', $edit, t('Save configuration'));
+    // Test general node permission for enabled book module.
+    $this->assertTrue(user_access('edit own book content', $this->admin_user), 'The permission for node access was assigned to the administrator role.');
     $this->assertTrue(user_access('administer news feeds', $this->admin_user), t('The permission was automatically assigned to the administrator role'));
   }
 