diff --git a/core/modules/field/src/Plugin/migrate/source/d7/FieldInstance.php b/core/modules/field/src/Plugin/migrate/source/d7/FieldInstance.php
index 2b5238c..42c6289 100755
--- a/core/modules/field/src/Plugin/migrate/source/d7/FieldInstance.php
+++ b/core/modules/field/src/Plugin/migrate/source/d7/FieldInstance.php
@@ -31,6 +31,15 @@ public function query() {
       ->condition('fc.storage_active', 1);
     $query->innerJoin('field_config', 'fc', 'fci.field_id = fc.id');
 
+    // Optionally filter by entity type and bundle.
+    if (isset($this->configuration['entity_type'])) {
+      $query->condition('entity_type', $this->configuration['entity_type']);
+
+      if (isset($this->configuration['bundle'])) {
+        $query->condition('bundle', $this->configuration['bundle']);
+      }
+    }
+
     return $query;
   }
 
diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/source/d7/FieldableEntity.php b/core/modules/migrate_drupal/src/Plugin/migrate/source/d7/FieldableEntity.php
new file mode 100644
index 0000000..aa69325
--- /dev/null
+++ b/core/modules/migrate_drupal/src/Plugin/migrate/source/d7/FieldableEntity.php
@@ -0,0 +1,79 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate_drupal\Plugin\migrate\source\d7\FieldableEntity.
+ */
+
+namespace Drupal\migrate_drupal\Plugin\migrate\source\d7;
+
+use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
+
+/**
+ * Base class for D7 source plugins which need to collect field values from
+ * the Field API.
+ */
+abstract class FieldableEntity extends DrupalSqlBase {
+
+  /**
+   * Returns all non-deleted field instances attached to a specific entity type.
+   *
+   * @param string $entity_type
+   *   The entity type ID.
+   * @param string|NULL $bundle
+   *   (optional) The bundle.
+   *
+   * @return array[]
+   *   The field instances, keyed by field name.
+   */
+  protected function getFields($entity_type, $bundle = NULL) {
+    return $this->select('field_config_instance', 'fci')
+      ->fields('fci')
+      ->condition('entity_type', $entity_type)
+      ->condition('bundle', isset($bundle) ? $bundle : $entity_type)
+      ->condition('deleted', 0)
+      ->execute()
+      ->fetchAllAssoc('field_name');
+  }
+
+  /**
+   * Retrieves field values for a single field of a single entity.
+   *
+   * @param string $entity_type
+   *   The entity type.
+   * @param string $field
+   *   The field name.
+   * @param int $entity_id
+   *   The entity ID.
+   * @param int|null $revision_id
+   *   (optional) The entity revision ID.
+   *
+   * @return array
+   *   The raw field values, keyed by delta.
+   *
+   * @todo Support multilingual field values.
+   */
+  protected function getFieldValues($entity_type, $field, $entity_id, $revision_id = NULL) {
+    $table = (isset($revision_id) ? 'field_revision_' : 'field_data_') . $field;
+    $query = $this->select($table, 't')
+      ->fields('t')
+      ->condition('entity_type', $entity_type)
+      ->condition('entity_id', $entity_id)
+      ->condition('deleted', 0);
+    if (isset($revision_id)) {
+      $query->condition('revision_id', $revision_id);
+    }
+    $values = [];
+    foreach ($query->execute() as $row) {
+      foreach ($row as $key => $value) {
+        $delta = $row['delta'];
+        if (strpos($key, $field) === 0) {
+          $column = substr($key, strlen($field) + 1);
+          $values[$delta][$column] = $value;
+        }
+      }
+    }
+    return $values;
+  }
+
+}
diff --git a/core/modules/migrate_drupal/src/Tests/Table/d7/Users.php b/core/modules/migrate_drupal/src/Tests/Table/d7/Users.php
index e14c767..47f80c3 100644
--- a/core/modules/migrate_drupal/src/Tests/Table/d7/Users.php
+++ b/core/modules/migrate_drupal/src/Tests/Table/d7/Users.php
@@ -140,8 +140,25 @@ public function load() {
       'init',
       'data',
     ))
-    ->execute();
+    ->values(array(
+      'uid' => '2',
+      'name' => 'Odo',
+      'pass' => '$S$DZ4P7zZOh92vgrgZDBbv8Pu6lQB337OJ1wsOy21602G4A5F7.M9K',
+      'mail' => 'odo@local.host',
+      'theme' => '',
+      'signature' => '',
+      'signature_format' => 'filtered_html',
+      'created' => '1440532218',
+      'access' => '0',
+      'login' => '0',
+      'status' => '1',
+      'timezone' => 'America/Chicago',
+      'language' => '',
+      'picture' => '0',
+      'init' => 'odo@local.host',
+      'data' => 'a:1:{s:7:"contact";i:1;}',
+    ))->execute();
   }
 
 }
-#c4f52bf31f5fe27b4742639dea21039a
+#b33078324746c718e26c067131e97dcd
diff --git a/core/modules/migrate_drupal/src/Tests/Table/d7/Variable.php b/core/modules/migrate_drupal/src/Tests/Table/d7/Variable.php
index 0591deb..64eed83 100644
--- a/core/modules/migrate_drupal/src/Tests/Table/d7/Variable.php
+++ b/core/modules/migrate_drupal/src/Tests/Table/d7/Variable.php
@@ -123,7 +123,7 @@ public function load() {
       'value' => 's:43:"_vWFj-dRR2rNoHDwl7N__J9uZNutDcLz3w4tlPJzRAM";',
     ))->values(array(
       'name' => 'cron_last',
-      'value' => 'i:1432653550;',
+      'value' => 'i:1440523817;',
     ))->values(array(
       'name' => 'css_js_query_string',
       'value' => 's:6:"nihmmw";',
@@ -150,7 +150,7 @@ public function load() {
       'value' => 's:43:"9eRJWxrMwQ5CufYJjXBZbPGz_t8vPIYRQr18PamdKmM";',
     ))->values(array(
       'name' => 'email__active_tab',
-      'value' => 's:25:"edit-email-password-reset";',
+      'value' => 's:27:"edit-email-pending-approval";',
     ))->values(array(
       'name' => 'field_bundle_settings_comment__comment_node_test_content_type',
       'value' => 'a:2:{s:10:"view_modes";a:0:{}s:12:"extra_fields";a:2:{s:4:"form";a:2:{s:6:"author";a:1:{s:6:"weight";s:2:"-2";}s:7:"subject";a:1:{s:6:"weight";s:2:"-1";}}s:7:"display";a:0:{}}}',
@@ -393,61 +393,61 @@ public function load() {
       'value' => 'i:86400;',
     ))->values(array(
       'name' => 'user_mail_cancel_confirm_body',
-      'value' => "s:381:\"[user:name],\r\n\r\nA request to cancel your account has been made at [site:name].\r\n\r\nYou may now cancel your account on [site:url-brief] by clicking this link or copying and pasting it into your browser:\r\n\r\n[user:cancel-url]\r\n\r\nNOTE: The cancellation of your account is not reversible.\r\n\r\nThis link expires in one day and nothing will happen if it is not used.\r\n\r\n--  [site:name] team\";",
+      'value' => 's:55:"A little birdie said you wanted to cancel your account.";',
     ))->values(array(
       'name' => 'user_mail_cancel_confirm_subject',
-      'value' => 's:59:"Account cancellation request for [user:name] at [site:name]";',
+      'value' => 's:13:"Are you sure?";',
     ))->values(array(
       'name' => 'user_mail_password_reset_body',
-      'value' => "s:407:\"[user:name],\r\n\r\nA request to reset the password for your account has been made at [site:name].\r\n\r\nYou may now log in by clicking this link or copying and pasting it to your browser:\r\n\r\n[user:one-time-login-url]\r\n\r\nThis link can only be used once to log in and will lead you to a page where you can set your password. It expires after one day and nothing will happen if it's not used.\r\n\r\n--  [site:name] team\";",
+      'value' => "s:32:\"Nope! You're locked out forever.\";",
     ))->values(array(
       'name' => 'user_mail_password_reset_subject',
-      'value' => 's:60:"Replacement login information for [user:name] at [site:name]";',
+      'value' => 's:17:"Fix your password";',
     ))->values(array(
       'name' => 'user_mail_register_admin_created_body',
-      'value' => "s:476:\"[user:name],\r\n\r\nA site administrator at [site:name] has created an account for you. You may now log in by clicking this link or copying and pasting it to your browser:\r\n\r\n[user:one-time-login-url]\r\n\r\nThis link can only be used once to log in and will lead you to a page where you can set your password.\r\n\r\nAfter setting your password, you will be able to log in at [site:login-url] in the future using:\r\n\r\nusername: [user:name]\r\npassword: Your password\r\n\r\n--  [site:name] team\";",
+      'value' => 's:30:"...and she could take it away.";',
     ))->values(array(
       'name' => 'user_mail_register_admin_created_subject',
-      'value' => 's:58:"An administrator created an account for you at [site:name]";',
+      'value' => 's:24:"Gawd made you an account";',
     ))->values(array(
       'name' => 'user_mail_register_no_approval_required_body',
-      'value' => "s:450:\"[user:name],\r\n\r\nThank you for registering at [site:name]. You may now log in by clicking this link or copying and pasting it to your browser:\r\n\r\n[user:one-time-login-url]\r\n\r\nThis link can only be used once to log in and will lead you to a page where you can set your password.\r\n\r\nAfter setting your password, you will be able to log in at [site:login-url] in the future using:\r\n\r\nusername: [user:name]\r\npassword: Your password\r\n\r\n--  [site:name] team\";",
+      'value' => 's:59:"You can now log in if you can figure out how to use Drupal!";',
     ))->values(array(
       'name' => 'user_mail_register_no_approval_required_subject',
-      'value' => 's:46:"Account details for [user:name] at [site:name]";',
+      'value' => 's:8:"Welcome!";',
     ))->values(array(
       'name' => 'user_mail_register_pending_approval_body',
-      'value' => "s:287:\"[user:name],\r\n\r\nThank you for registering at [site:name]. Your application for an account is currently pending approval. Once it has been approved, you will receive another e-mail containing information about how to log in, set your password, and other details.\r\n\r\n\r\n--  [site:name] team\";",
+      'value' => 's:61:"...you will join our Circle. Let the Drupal flow through you.";',
     ))->values(array(
       'name' => 'user_mail_register_pending_approval_subject',
-      'value' => 's:71:"Account details for [user:name] at [site:name] (pending admin approval)";',
+      'value' => 's:7:"Soon...";',
     ))->values(array(
       'name' => 'user_mail_status_activated_body',
-      'value' => "s:461:\"[user:name],\r\n\r\nYour account at [site:name] has been activated.\r\n\r\nYou may now log in by clicking this link or copying and pasting it into your browser:\r\n\r\n[user:one-time-login-url]\r\n\r\nThis link can only be used once to log in and will lead you to a page where you can set your password.\r\n\r\nAfter setting your password, you will be able to log in at [site:login-url] in the future using:\r\n\r\nusername: [user:name]\r\npassword: Your password\r\n\r\n--  [site:name] team\";",
+      'value' => 's:57:"Your account was activated, and there was much rejoicing.";',
     ))->values(array(
       'name' => 'user_mail_status_activated_notify',
       'value' => 'i:1;',
     ))->values(array(
       'name' => 'user_mail_status_activated_subject',
-      'value' => 's:57:"Account details for [user:name] at [site:name] (approved)";',
+      'value' => 's:25:"Your account is approved!";',
     ))->values(array(
       'name' => 'user_mail_status_blocked_body',
-      'value' => "s:85:\"[user:name],\r\n\r\nYour account on [site:name] has been blocked.\r\n\r\n--  [site:name] team\";",
+      'value' => 's:72:"You no longer please the robot overlords. Go to your room and chill out.";',
     ))->values(array(
       'name' => 'user_mail_status_blocked_notify',
-      'value' => 'i:0;',
+      'value' => 'i:1;',
     ))->values(array(
       'name' => 'user_mail_status_blocked_subject',
-      'value' => 's:56:"Account details for [user:name] at [site:name] (blocked)";',
+      'value' => 's:7:"BEGONE!";',
     ))->values(array(
       'name' => 'user_mail_status_canceled_body',
-      'value' => "s:86:\"[user:name],\r\n\r\nYour account on [site:name] has been canceled.\r\n\r\n--  [site:name] team\";",
+      'value' => 's:75:"The gates of Drupal are closed to you. Now you will work in the salt mines.";',
     ))->values(array(
       'name' => 'user_mail_status_canceled_notify',
-      'value' => 'i:0;',
+      'value' => 'i:1;',
     ))->values(array(
       'name' => 'user_mail_status_canceled_subject',
-      'value' => 's:57:"Account details for [user:name] at [site:name] (canceled)";',
+      'value' => 's:12:"So long, bub";',
     ))->values(array(
       'name' => 'user_pictures',
       'value' => 'i:1;',
@@ -479,4 +479,4 @@ public function load() {
   }
 
 }
-#dbc0f593050ff48cc18dfa4fed47daaf
+#502534c5e998ee5c14f5a2d510b9dc85
diff --git a/core/modules/migrate_drupal/src/Tests/d6/MigrateDrupal6Test.php b/core/modules/migrate_drupal/src/Tests/d6/MigrateDrupal6Test.php
index b7dfb9a..aaf281a 100644
--- a/core/modules/migrate_drupal/src/Tests/d6/MigrateDrupal6Test.php
+++ b/core/modules/migrate_drupal/src/Tests/d6/MigrateDrupal6Test.php
@@ -139,16 +139,15 @@ class MigrateDrupal6Test extends MigrateFullDrupalTestBase {
     'd6_upload_field_instance',
     'd6_upload',
     'd6_url_alias',
-    'd6_user_mail',
     'd6_user_contact_settings',
     'd6_user_profile_field_instance',
     'd6_user_profile_entity_display',
     'd6_user_profile_entity_form_display',
     'd6_user_profile_field',
-    'd6_user_picture_entity_display',
-    'd6_user_picture_entity_form_display',
-    'd6_user_picture_field_instance',
-    'd6_user_picture_field',
+    'user_picture_entity_display',
+    'user_picture_entity_form_display',
+    'user_picture_field_instance',
+    'user_picture_field',
     'd6_user_picture_file',
     'd6_user_role',
     'd6_user_settings',
diff --git a/core/modules/user/migration_templates/d7_user.yml b/core/modules/user/migration_templates/d7_user.yml
new file mode 100755
index 0000000..07df192
--- /dev/null
+++ b/core/modules/user/migration_templates/d7_user.yml
@@ -0,0 +1,31 @@
+id: d7_user
+label: Drupal 7 user accounts
+migration_tags:
+  - Drupal 7
+builder:
+  plugin: d7_user
+source:
+  plugin: d7_user
+process:
+  uid: uid
+  name: name
+  pass: pass
+  mail: mail
+  created: created
+  access: access
+  login: login
+  status: status
+  timezone: timezone
+  langcode: language
+  preferred_langcode: language
+  preferred_admin_langcode: language
+  init: init
+  roles:
+    plugin: migration
+    migration: d7_user_role
+    source: roles
+destination:
+  plugin: entity:user
+migration_dependencies:
+  required:
+    - d7_user_role
diff --git a/core/modules/user/migration_templates/d7_user_flood.yml b/core/modules/user/migration_templates/d7_user_flood.yml
new file mode 100755
index 0000000..016acec
--- /dev/null
+++ b/core/modules/user/migration_templates/d7_user_flood.yml
@@ -0,0 +1,20 @@
+id: d7_user_flood
+migration_tags:
+  - Drupal 7
+source:
+  plugin: variable
+  variables:
+    - user_failed_login_identifier_uid_only
+    - user_failed_login_ip_limit
+    - user_failed_login_ip_window
+    - user_failed_login_user_window
+    - user_failed_login_user_limit
+process:
+  uid_only: user_failed_login_identifier_uid_only
+  ip_limit: user_failed_login_ip_limit
+  ip_window: user_failed_login_ip_window
+  user_limit: user_failed_login_user_limit
+  user_window: user_failed_login_user_window
+destination:
+  plugin: config
+  config_name: user.flood
diff --git a/core/modules/user/migration_templates/d7_user_role.yml b/core/modules/user/migration_templates/d7_user_role.yml
new file mode 100755
index 0000000..6688083
--- /dev/null
+++ b/core/modules/user/migration_templates/d7_user_role.yml
@@ -0,0 +1,44 @@
+id: d7_user_role
+label: Drupal 7 user roles
+migration_tags:
+  - Drupal 7
+source:
+  plugin: d7_user_role
+process:
+  id:
+    -
+      plugin: machine_name
+      source: name
+    -
+      plugin: dedupe_entity
+      entity_type: user_role
+      field: id
+      length: 32
+    -
+      plugin: user_update_8002
+  label: name
+  permissions:
+    -
+      plugin: static_map
+      source: permissions
+      bypass: true
+      map:
+        'use PHP for block visibility': 'use PHP for settings'
+        'administer site-wide contact form': 'administer contact forms'
+        'post comments without approval': 'skip comment approval'
+        'edit own blog entries' : 'edit own blog content'
+        'edit any blog entry' : 'edit any blog content'
+        'delete own blog entries' : 'delete own blog content'
+        'delete any blog entry' : 'delete any blog content'
+        'create forum topics' : 'create forum content'
+        'delete any forum topic' : 'delete any forum content'
+        'delete own forum topics' : 'delete own forum content'
+        'edit any forum topic' : 'edit any forum content'
+        'edit own forum topics' : 'edit own forum content'
+    - plugin: flatten
+  weight: weight
+destination:
+  plugin: entity:user_role
+migration_dependencies:
+  optional:
+    - d7_filter_format
diff --git a/core/modules/user/migration_templates/d6_user_mail.yml b/core/modules/user/migration_templates/user_mail.yml
similarity index 69%
rename from core/modules/user/migration_templates/d6_user_mail.yml
rename to core/modules/user/migration_templates/user_mail.yml
index bc3badf..9eb1715 100644
--- a/core/modules/user/migration_templates/d6_user_mail.yml
+++ b/core/modules/user/migration_templates/user_mail.yml
@@ -1,7 +1,8 @@
-id: d6_user_mail
-label: Drupal 6 user mail configuration
+id: user_mail
+label: User mail configuration
 migration_tags:
   - Drupal 6
+  - Drupal 7
 source:
   plugin: variable
   variables:
@@ -9,14 +10,14 @@ source:
     - user_mail_status_activated_body
     - user_mail_password_reset_subject
     - user_mail_password_reset_body
-    - user_mail_status_deleted_subject
-    - user_mail_status_deleted_body
+    - user_mail_status_canceled_subject
+    - user_mail_status_canceled_body
     - user_mail_register_admin_created_subject
     - user_mail_register_admin_created_body
     - user_mail_register_no_approval_required_subject
     - user_mail_register_no_approval_required_body
-    - user_mail_user_mail_register_pending_approval_subject
-    - user_mail_user_mail_register_pending_approval_body
+    - user_mail_register_pending_approval_subject
+    - user_mail_register_pending_approval_body
     - user_mail_status_blocked_subject
     - user_mail_status_blocked_body
 process:
@@ -24,14 +25,14 @@ process:
   'status_activated/body': user_mail_status_activated_body
   'password_reset/subject': user_mail_password_reset_subject
   'password_reset/body': user_mail_password_reset_body
-  'cancel_confirm/subject': user_mail_status_deleted_subject
-  'cancel_confirm/body': user_mail_status_deleted_body
+  'cancel_confirm/subject': user_mail_status_canceled_subject
+  'cancel_confirm/body': user_mail_status_canceled_body
   'register_admin_created/subject': user_mail_register_admin_created_subject
   'register_admin_created/body': user_mail_register_admin_created_body
   'register_no_approval_required/subject': user_mail_register_no_approval_required_subject
   'register_no_approval_required/body': user_mail_register_no_approval_required_body
-  'register_pending_approval/subject': user_mail_user_mail_register_pending_approval_subject
-  'register_pending_approval/body': user_mail_user_mail_register_pending_approval_body
+  'register_pending_approval/subject': user_mail_register_pending_approval_subject
+  'register_pending_approval/body': user_mail_register_pending_approval_body
   'status_blocked/subject': user_mail_status_blocked_subject
   'status_blocked/body': user_mail_status_blocked_body
 destination:
diff --git a/core/modules/user/migration_templates/d6_user_picture_entity_display.yml b/core/modules/user/migration_templates/user_picture_entity_display.yml
similarity index 77%
rename from core/modules/user/migration_templates/d6_user_picture_entity_display.yml
rename to core/modules/user/migration_templates/user_picture_entity_display.yml
index f38bc36..78c0ae0 100644
--- a/core/modules/user/migration_templates/d6_user_picture_entity_display.yml
+++ b/core/modules/user/migration_templates/user_picture_entity_display.yml
@@ -1,9 +1,10 @@
-id: d6_user_picture_entity_display
-label: Drupal 6 user picture display configuration
+id: user_picture_entity_display
+label: User picture display configuration
 migration_tags:
   - Drupal 6
+  - Drupal 7
 source:
-  plugin: d6_user_picture_instance
+  plugin: user_picture_instance
   constants:
     entity_type: user
     bundle: user
@@ -27,4 +28,4 @@ destination:
   plugin: component_entity_display
 migration_dependencies:
   required:
-    - d6_user_picture_field_instance
+    - user_picture_field_instance
diff --git a/core/modules/user/migration_templates/d6_user_picture_entity_form_display.yml b/core/modules/user/migration_templates/user_picture_entity_form_display.yml
similarity index 77%
rename from core/modules/user/migration_templates/d6_user_picture_entity_form_display.yml
rename to core/modules/user/migration_templates/user_picture_entity_form_display.yml
index 4e42228..60d782f 100644
--- a/core/modules/user/migration_templates/d6_user_picture_entity_form_display.yml
+++ b/core/modules/user/migration_templates/user_picture_entity_form_display.yml
@@ -1,9 +1,10 @@
-id: d6_user_picture_entity_form_display
-label: Drupal 6 user picture form display configuration
+id: user_picture_entity_form_display
+label: User picture form display configuration
 migration_tags:
   - Drupal 6
+  - Drupal 7
 source:
-  plugin: d6_user_picture_instance
+  plugin: user_picture_instance
   constants:
     entity_type: user
     bundle: user
@@ -26,4 +27,4 @@ destination:
   plugin: component_entity_form_display
 migration_dependencies:
   required:
-    - d6_user_picture_field_instance
+    - user_picture_field_instance
diff --git a/core/modules/user/migration_templates/d6_user_picture_field.yml b/core/modules/user/migration_templates/user_picture_field.yml
similarity index 85%
rename from core/modules/user/migration_templates/d6_user_picture_field.yml
rename to core/modules/user/migration_templates/user_picture_field.yml
index 27a39a2..a484ab8 100644
--- a/core/modules/user/migration_templates/d6_user_picture_field.yml
+++ b/core/modules/user/migration_templates/user_picture_field.yml
@@ -1,7 +1,8 @@
-id: d6_user_picture_field
-label: Drupal 6 user picture field configuration
+id: user_picture_field
+label: User picture field configuration
 migration_tags:
   - Drupal 6
+  - Drupal 7
 source:
   # We do an empty source and a proper destination to have an idmap for
   # dependencies.
diff --git a/core/modules/user/migration_templates/d6_user_picture_field_instance.yml b/core/modules/user/migration_templates/user_picture_field_instance.yml
similarity index 80%
rename from core/modules/user/migration_templates/d6_user_picture_field_instance.yml
rename to core/modules/user/migration_templates/user_picture_field_instance.yml
index 7dc83b1..4332e09 100644
--- a/core/modules/user/migration_templates/d6_user_picture_field_instance.yml
+++ b/core/modules/user/migration_templates/user_picture_field_instance.yml
@@ -1,9 +1,10 @@
-id: d6_user_picture_field_instance
-label: Drupal 6 user picture field instance configuration
+id: user_picture_field_instance
+label: User picture field instance configuration
 migration_tags:
   - Drupal 6
+  - Drupal 7
 source:
-  plugin: d6_user_picture_instance
+  plugin: user_picture_instance
   constants:
     entity_type: user
     bundle: user
@@ -27,4 +28,4 @@ destination:
   plugin: entity:field_config
 migration_dependencies:
   required:
-    - d6_user_picture_field
+    - user_picture_field
diff --git a/core/modules/user/src/Plugin/migrate/builder/d7/User.php b/core/modules/user/src/Plugin/migrate/builder/d7/User.php
new file mode 100644
index 0000000..5c9868d
--- /dev/null
+++ b/core/modules/user/src/Plugin/migrate/builder/d7/User.php
@@ -0,0 +1,76 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\user\Plugin\migrate\builder\d7\User.
+ */
+
+namespace Drupal\user\Plugin\migrate\builder\d7;
+
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\migrate\Entity\Migration;
+use Drupal\migrate\Plugin\migrate\builder\BuilderBase;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * @PluginID("d7_user")
+ */
+class User extends BuilderBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * The module handler service.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * Constructs a d7_user builder plugin instance.
+   *
+   * @param array $configuration
+   *   The plugin configuration.
+   * @param string $plugin_id
+   *   The plugin ID.
+   * @param mixed $plugin_definition
+   *   The plugin definition.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler service.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, ModuleHandlerInterface $module_handler) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+    $this->moduleHandler = $module_handler;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('module_handler')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildMigrations(array $template) {
+    $migration = Migration::create($template);
+
+    if ($this->moduleHandler->moduleExists('field')) {
+      $template['source']['entity_type'] = 'user';
+
+      $source_plugin = $this->getSourcePlugin('d7_field_instance', $template['source']);
+      foreach ($source_plugin as $field) {
+        $field_name = $field->getSourceProperty('field_name');
+        $migration->setProcessOfProperty($field_name, $field_name);
+      }
+    }
+
+    return [$migration];
+  }
+
+}
diff --git a/core/modules/user/src/Plugin/migrate/process/d6/UserUpdate8002.php b/core/modules/user/src/Plugin/migrate/process/UserUpdate8002.php
similarity index 85%
rename from core/modules/user/src/Plugin/migrate/process/d6/UserUpdate8002.php
rename to core/modules/user/src/Plugin/migrate/process/UserUpdate8002.php
index ff8a816..41a9b03 100644
--- a/core/modules/user/src/Plugin/migrate/process/d6/UserUpdate8002.php
+++ b/core/modules/user/src/Plugin/migrate/process/UserUpdate8002.php
@@ -2,10 +2,11 @@
 
 /**
  * @file
- * Contains \Drupal\user\Plugin\migrate\process\d6\UserUpdate8002.
+ * Contains \Drupal\user\Plugin\migrate\process\UserUpdate8002.
  */
 
-namespace Drupal\user\Plugin\migrate\process\d6;
+namespace Drupal\user\Plugin\migrate\process;
+
 use Drupal\migrate\MigrateExecutableInterface;
 use Drupal\migrate\ProcessPluginBase;
 use Drupal\migrate\Row;
diff --git a/core/modules/user/src/Plugin/migrate/source/d6/UserPictureInstance.php b/core/modules/user/src/Plugin/migrate/source/UserPictureInstance.php
similarity index 86%
rename from core/modules/user/src/Plugin/migrate/source/d6/UserPictureInstance.php
rename to core/modules/user/src/Plugin/migrate/source/UserPictureInstance.php
index 48e409b..99099e3 100644
--- a/core/modules/user/src/Plugin/migrate/source/d6/UserPictureInstance.php
+++ b/core/modules/user/src/Plugin/migrate/source/UserPictureInstance.php
@@ -2,21 +2,21 @@
 
 /**
  * @file
- * Contains \Drupal\user\Plugin\migrate\source\d6\UserPictureInstance.
+ * Contains \Drupal\user\Plugin\migrate\source\UserPictureInstance.
  */
 
-namespace Drupal\user\Plugin\migrate\source\d6;
+namespace Drupal\user\Plugin\migrate\source;
 
 use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
 use Drupal\migrate\Plugin\migrate\source\DummyQueryTrait;
 
 /**
- * Drupal 6 user picture field instance source.
+ * User picture field instance source.
  *
  * @todo Support default picture?
  *
  * @MigrateSource(
- *   id = "d6_user_picture_instance"
+ *   id = "user_picture_instance"
  * )
  */
 class UserPictureInstance extends DrupalSqlBase {
diff --git a/core/modules/user/src/Plugin/migrate/source/d7/Role.php b/core/modules/user/src/Plugin/migrate/source/d7/Role.php
new file mode 100755
index 0000000..4f84290
--- /dev/null
+++ b/core/modules/user/src/Plugin/migrate/source/d7/Role.php
@@ -0,0 +1,62 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\user\Plugin\migrate\source\d7\Role.
+ */
+
+namespace Drupal\user\Plugin\migrate\source\d7;
+
+use Drupal\migrate\Row;
+use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
+
+/**
+ * Drupal 7 role source from database.
+ *
+ * @MigrateSource(
+ *   id = "d7_user_role"
+ * )
+ */
+class Role extends DrupalSqlBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function query() {
+    return $this->select('role', 'r')->fields('r');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fields() {
+    return array(
+      'rid' => $this->t('Role ID.'),
+      'name' => $this->t('The name of the user role.'),
+      'weight' => $this->t('The weight of the role.'),
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function prepareRow(Row $row) {
+    $permissions = $this->select('role_permission', 'rp')
+      ->fields('rp', ['permission'])
+      ->condition('rid', $row->getSourceProperty('rid'))
+      ->execute()
+      ->fetchCol();
+    $row->setSourceProperty('permissions', $permissions);
+
+    return parent::prepareRow($row);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getIds() {
+    $ids['rid']['type'] = 'integer';
+    return $ids;
+  }
+
+}
diff --git a/core/modules/user/src/Plugin/migrate/source/d7/User.php b/core/modules/user/src/Plugin/migrate/source/d7/User.php
new file mode 100755
index 0000000..e3b2c3a
--- /dev/null
+++ b/core/modules/user/src/Plugin/migrate/source/d7/User.php
@@ -0,0 +1,97 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\user\Plugin\migrate\source\d7\User.
+ */
+
+namespace Drupal\user\Plugin\migrate\source\d7;
+
+use Drupal\migrate\Row;
+use Drupal\migrate_drupal\Plugin\migrate\source\d7\FieldableEntity;
+
+/**
+ * Drupal 7 user source from database.
+ *
+ * @MigrateSource(
+ *   id = "d7_user"
+ * )
+ */
+class User extends FieldableEntity {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function query() {
+    return $this->select('users', 'u')
+      ->fields('u')
+      ->condition('uid', 1, '>');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fields() {
+    $fields = array(
+      'uid' => $this->t('User ID'),
+      'name' => $this->t('Username'),
+      'pass' => $this->t('Password'),
+      'mail' => $this->t('Email address'),
+      'signature' => $this->t('Signature'),
+      'signature_format' => $this->t('Signature format'),
+      'created' => $this->t('Registered timestamp'),
+      'access' => $this->t('Last access timestamp'),
+      'login' => $this->t('Last login timestamp'),
+      'status' => $this->t('Status'),
+      'timezone' => $this->t('Timezone'),
+      'language' => $this->t('Language'),
+      'picture' => $this->t('Picture'),
+      'init' => $this->t('Init'),
+      'data' => $this->t('User data'),
+      'roles' => $this->t('Roles'),
+    );
+
+    // Profile fields.
+    if ($this->moduleExists('profile')) {
+      $fields += $this->select('profile_fields', 'pf')
+        ->fields('pf', array('name', 'title'))
+        ->execute()
+        ->fetchAllKeyed();
+    }
+
+    return $fields;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function prepareRow(Row $row) {
+    $roles = $this->select('role', 'r')
+      ->fields('r', ['rid', 'name']);
+    $roles->join('users_roles', 'ur', 'ur.rid = r.rid');
+    $roles->condition('ur.uid', $row->getSourceProperty('uid'));
+    $row->setSourceProperty('roles', $roles->execute()->fetchAllKeyed());
+
+    $row->setSourceProperty('data', unserialize($row->getSourceProperty('data')));
+
+    // Get Field API field values.
+    foreach (array_keys($this->getFields('user')) as $field) {
+      $row->setSourceProperty($field, $this->getFieldValues('user', $field, $row->getSourceProperty('uid')));
+    }
+
+    return parent::prepareRow($row);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getIds() {
+    return array(
+      'uid' => array(
+        'type' => 'integer',
+        'alias' => 'u',
+      ),
+    );
+  }
+
+}
diff --git a/core/modules/user/src/Tests/Migrate/MigrateUserMailTest.php b/core/modules/user/src/Tests/Migrate/MigrateUserMailTest.php
new file mode 100644
index 0000000..f4a2b33
--- /dev/null
+++ b/core/modules/user/src/Tests/Migrate/MigrateUserMailTest.php
@@ -0,0 +1,56 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\user\Tests\Migrate\MigrateUserMailTest.
+ */
+
+namespace Drupal\user\Tests\Migrate;
+
+use Drupal\migrate_drupal\Tests\d7\MigrateDrupal7TestBase;
+
+/**
+ * Migrates user mail configuration.
+ *
+ * @group user
+ */
+class MigrateUserMailTest extends MigrateDrupal7TestBase {
+
+  /**
+   * The modules to be enabled during the test.
+   *
+   * @var array
+   */
+  static $modules = ['system', 'user'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->installConfig(static::$modules);
+    $this->executeMigration('user_mail');
+  }
+
+  /**
+   * Tests the migration.
+   */
+  public function testMigration() {
+    $config = $this->config('user.mail');
+    $this->assertIdentical('Your account is approved!', $config->get('status_activated.subject'));
+    $this->assertIdentical('Your account was activated, and there was much rejoicing.', $config->get('status_activated.body'));
+    $this->assertIdentical('Fix your password', $config->get('password_reset.subject'));
+    $this->assertIdentical("Nope! You're locked out forever.", $config->get('password_reset.body'));
+    $this->assertIdentical('So long, bub', $config->get('cancel_confirm.subject'));
+    $this->assertIdentical('The gates of Drupal are closed to you. Now you will work in the salt mines.', $config->get('cancel_confirm.body'));
+    $this->assertIdentical('Gawd made you an account', $config->get('register_admin_created.subject'));
+    $this->assertIdentical('...and she could take it away.', $config->get('register_admin_created.body'));
+    $this->assertIdentical('Welcome!', $config->get('register_no_approval_required.subject'));
+    $this->assertIdentical('You can now log in if you can figure out how to use Drupal!', $config->get('register_no_approval_required.body'));
+    $this->assertIdentical('Soon...', $config->get('register_pending_approval.subject'));
+    $this->assertIdentical('...you will join our Circle. Let the Drupal flow through you.', $config->get('register_pending_approval.body'));
+    $this->assertIdentical('BEGONE!', $config->get('status_blocked.subject'));
+    $this->assertIdentical('You no longer please the robot overlords. Go to your room and chill out.', $config->get('status_blocked.body'));
+  }
+
+}
diff --git a/core/modules/user/src/Tests/Migrate/MigrateUserPictureEntityDisplayTest.php b/core/modules/user/src/Tests/Migrate/MigrateUserPictureEntityDisplayTest.php
new file mode 100644
index 0000000..0613261
--- /dev/null
+++ b/core/modules/user/src/Tests/Migrate/MigrateUserPictureEntityDisplayTest.php
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\user\Tests\Migrate\MigrateUserPictureEntityDisplayTest.
+ */
+
+namespace Drupal\user\Tests\Migrate;
+
+use Drupal\migrate_drupal\Tests\d7\MigrateDrupal7TestBase;
+
+/**
+ * User picture entity display.
+ *
+ * @group user
+ */
+class MigrateUserPictureEntityDisplayTest extends MigrateDrupal7TestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  static $modules = array('file', 'image');
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->installEntitySchema('file');
+    $this->executeMigration('user_picture_field');
+    $this->executeMigration('user_picture_field_instance');
+    $this->executeMigration('user_picture_entity_display');
+  }
+
+  /**
+   * Tests the Drupal 7 user picture to Drupal 8 entity display migration.
+   */
+  public function testUserPictureEntityDisplay() {
+    $component = entity_get_display('user', 'user', 'default')->getComponent('user_picture');
+    $this->assertIdentical('image', $component['type']);
+    $this->assertIdentical('', $component['settings']['image_style']);
+    $this->assertIdentical('content', $component['settings']['image_link']);
+  }
+
+}
diff --git a/core/modules/user/src/Tests/Migrate/MigrateUserPictureEntityFormDisplayTest.php b/core/modules/user/src/Tests/Migrate/MigrateUserPictureEntityFormDisplayTest.php
new file mode 100644
index 0000000..0902194
--- /dev/null
+++ b/core/modules/user/src/Tests/Migrate/MigrateUserPictureEntityFormDisplayTest.php
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\user\Tests\Migrate\MigrateUserPictureEntityFormDisplayTest.
+ */
+
+namespace Drupal\user\Tests\Migrate;
+
+use Drupal\migrate_drupal\Tests\d7\MigrateDrupal7TestBase;
+
+/**
+ * Tests migration of the user_picture field's entity form display settings.
+ *
+ * @group user
+ */
+class MigrateUserPictureEntityFormDisplayTest extends MigrateDrupal7TestBase {
+
+  static $modules = array('image', 'file');
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->executeMigration('user_picture_field');
+    $this->executeMigration('user_picture_field_instance');
+    $this->executeMigration('user_picture_entity_form_display');
+  }
+
+  /**
+   * Tests the field's entity form display settings.
+   */
+  public function testEntityFormDisplaySettings() {
+    $component = entity_get_form_display('user', 'user', 'default')->getComponent('user_picture');
+    $this->assertIdentical('image_image', $component['type']);
+    $this->assertIdentical('throbber', $component['settings']['progress_indicator']);
+    $this->assertIdentical('thumbnail', $component['settings']['preview_image_style']);
+  }
+
+}
diff --git a/core/modules/user/src/Tests/Migrate/MigrateUserPictureFieldInstanceTest.php b/core/modules/user/src/Tests/Migrate/MigrateUserPictureFieldInstanceTest.php
new file mode 100644
index 0000000..45367f9
--- /dev/null
+++ b/core/modules/user/src/Tests/Migrate/MigrateUserPictureFieldInstanceTest.php
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\user\Tests\Migrate\MigrateUserPictureFieldInstanceTest.
+ */
+
+namespace Drupal\user\Tests\Migrate;
+
+use Drupal\Core\Field\FieldConfigInterface;
+use Drupal\field\Entity\FieldConfig;
+use Drupal\migrate_drupal\Tests\d7\MigrateDrupal7TestBase;
+
+/**
+ * User picture field instance migration.
+ *
+ * @group user
+ */
+class MigrateUserPictureFieldInstanceTest extends MigrateDrupal7TestBase {
+
+  static $modules = array('image', 'file');
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->executeMigration('user_picture_field');
+    $this->executeMigration('user_picture_field_instance');
+  }
+
+  /**
+   * Test the user picture field migration.
+   */
+  public function testUserPictureField() {
+    /** @var \Drupal\field\FieldConfigInterface $field */
+    $field = FieldConfig::load('user.user.user_picture');
+    $this->assertTrue($field instanceof FieldConfigInterface);
+    $this->assertIdentical('user', $field->getTargetEntityTypeId());
+    $this->assertIdentical('user', $field->getTargetBundle());
+    $this->assertIdentical('user_picture', $field->getName());
+  }
+
+}
diff --git a/core/modules/user/src/Tests/Migrate/MigrateUserPictureFieldTest.php b/core/modules/user/src/Tests/Migrate/MigrateUserPictureFieldTest.php
new file mode 100644
index 0000000..cf4120f
--- /dev/null
+++ b/core/modules/user/src/Tests/Migrate/MigrateUserPictureFieldTest.php
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\user\Tests\Migrate\MigrateUserPictureFieldTest.
+ */
+
+namespace Drupal\user\Tests\Migrate;
+
+use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\field\FieldStorageConfigInterface;
+use Drupal\migrate_drupal\Tests\d7\MigrateDrupal7TestBase;
+
+/**
+ * User picture field migration.
+ *
+ * @group user
+ */
+class MigrateUserPictureFieldTest extends MigrateDrupal7TestBase {
+
+  static $modules = array('image', 'file');
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->executeMigration('user_picture_field');
+  }
+
+  /**
+   * Test the user picture field migration.
+   */
+  public function testUserPictureField() {
+    /** @var \Drupal\field\FieldStorageConfigInterface $field_storage */
+    $field_storage = FieldStorageConfig::load('user.user_picture');
+    $this->assertTrue($field_storage instanceof FieldStorageConfigInterface);
+    $this->assertIdentical('user.user_picture', $field_storage->id());
+    $this->assertIdentical('image', $field_storage->getType());
+    $this->assertIdentical('user', $field_storage->getTargetEntityTypeId());
+  }
+
+}
diff --git a/core/modules/user/src/Tests/Migrate/d6/MigrateUserConfigsTest.php b/core/modules/user/src/Tests/Migrate/d6/MigrateUserConfigsTest.php
index b958943..f9c36cf 100644
--- a/core/modules/user/src/Tests/Migrate/d6/MigrateUserConfigsTest.php
+++ b/core/modules/user/src/Tests/Migrate/d6/MigrateUserConfigsTest.php
@@ -24,33 +24,10 @@ class MigrateUserConfigsTest extends MigrateDrupal6TestBase {
    */
   protected function setUp() {
     parent::setUp();
-    $this->executeMigration('d6_user_mail');
     $this->executeMigration('d6_user_settings');
   }
 
   /**
-   * Tests migration of user variables to user.mail.yml.
-   */
-  public function testUserMail() {
-    $config = $this->config('user.mail');
-    $this->assertIdentical('Account details for !username at !site (approved)', $config->get('status_activated.subject'));
-    $this->assertIdentical("!username,\n\nYour account at !site has been activated.\n\nYou may now log in by clicking on this link or copying and pasting it in your browser:\n\n!login_url\n\nThis is a one-time login, so it can be used only once.\n\nAfter logging in, you will be redirected to !edit_uri so you can change your password.\n\nOnce you have set your own password, you will be able to log in to !login_uri in the future using:\n\nusername: !username\n", $config->get('status_activated.body'));
-    $this->assertIdentical('Replacement login information for !username at !site', $config->get('password_reset.subject'));
-    $this->assertIdentical("!username,\n\nA request to reset the password for your account has been made at !site.\n\nYou may now log in to !uri_brief by clicking on this link or copying and pasting it in your browser:\n\n!login_url\n\nThis is a one-time login, so it can be used only once. It expires after one day and nothing will happen if it's not used.\n\nAfter logging in, you will be redirected to !edit_uri so you can change your password.", $config->get('password_reset.body'));
-    $this->assertIdentical('Account details for !username at !site (deleted)', $config->get('cancel_confirm.subject'));
-    $this->assertIdentical("!username,\n\nYour account on !site has been deleted.", $config->get('cancel_confirm.body'));
-    $this->assertIdentical('An administrator created an account for you at !site', $config->get('register_admin_created.subject'));
-    $this->assertIdentical("!username,\n\nA site administrator at !site has created an account for you. You may now log in to !login_uri using the following username and password:\n\nusername: !username\npassword: !password\n\nYou may also log in by clicking on this link or copying and pasting it in your browser:\n\n!login_url\n\nThis is a one-time login, so it can be used only once.\n\nAfter logging in, you will be redirected to !edit_uri so you can change your password.\n\n\n--  !site team", $config->get('register_admin_created.body'));
-    $this->assertIdentical('Account details for !username at !site', $config->get('register_no_approval_required.subject'));
-    $this->assertIdentical("!username,\n\nThank you for registering at !site. You may now log in to !login_uri using the following username and password:\n\nusername: !username\npassword: !password\n\nYou may also log in by clicking on this link or copying and pasting it in your browser:\n\n!login_url\n\nThis is a one-time login, so it can be used only once.\n\nAfter logging in, you will be redirected to !edit_uri so you can change your password.\n\n\n--  !site team", $config->get('register_no_approval_required.body'));
-    $this->assertIdentical('Account details for !username at !site (pending admin approval)', $config->get('register_pending_approval.subject'));
-    $this->assertIdentical("!username,\n\nThank you for registering at !site. Your application for an account is currently pending approval. Once it has been approved, you will receive another email containing information about how to log in, set your password, and other details.\n\n\n--  !site team", $config->get('register_pending_approval.body'));
-    $this->assertIdentical('Account details for !username at !site (blocked)', $config->get('status_blocked.subject'));
-    $this->assertIdentical("!username,\n\nYour account on !site has been blocked.", $config->get('status_blocked.body'));
-    $this->assertConfigSchema(\Drupal::service('config.typed'), 'user.mail', $config->get());
-  }
-
-  /**
    * Tests migration of user variables to user.settings.yml.
    */
   public function testUserSettings() {
diff --git a/core/modules/user/src/Tests/Migrate/d6/MigrateUserPictureEntityDisplayTest.php b/core/modules/user/src/Tests/Migrate/d6/MigrateUserPictureEntityDisplayTest.php
deleted file mode 100644
index ca18375..0000000
--- a/core/modules/user/src/Tests/Migrate/d6/MigrateUserPictureEntityDisplayTest.php
+++ /dev/null
@@ -1,53 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\user\Tests\Migrate\d6\MigrateUserPictureEntityDisplayTest.
- */
-
-namespace Drupal\user\Tests\Migrate\d6;
-
-use Drupal\migrate_drupal\Tests\d6\MigrateDrupal6TestBase;
-
-/**
- * User picture entity display.
- *
- * @group migrate_drupal_6
- */
-class MigrateUserPictureEntityDisplayTest extends MigrateDrupal6TestBase {
-
-  /**
-   * Modules to enable.
-   *
-   * @var array
-   */
-  static $modules = array('image');
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function setUp() {
-    parent::setUp();
-
-    $id_mappings = array(
-      'd6_user_picture_field_instance' => array(
-        array(array(1), array('user', 'user', 'user_picture')),
-      ),
-    );
-    $this->prepareMigrations($id_mappings);
-    $this->executeMigration('d6_user_picture_entity_display');
-  }
-
-  /**
-   * Tests the Drupal 6 user picture to Drupal 8 entity display migration.
-   */
-  public function testUserPictureEntityDisplay() {
-    $display = entity_get_display('user', 'user', 'default');
-    $component = $display->getComponent('user_picture');
-    $this->assertIdentical('image', $component['type']);
-    $this->assertIdentical('content', $component['settings']['image_link']);
-
-    $this->assertIdentical(array('user', 'user', 'default', 'user_picture'), entity_load('migration', 'd6_user_picture_entity_display')->getIdMap()->lookupDestinationID(array('')));
-  }
-
-}
diff --git a/core/modules/user/src/Tests/Migrate/d6/MigrateUserPictureEntityFormDisplayTest.php b/core/modules/user/src/Tests/Migrate/d6/MigrateUserPictureEntityFormDisplayTest.php
deleted file mode 100644
index 05c3575..0000000
--- a/core/modules/user/src/Tests/Migrate/d6/MigrateUserPictureEntityFormDisplayTest.php
+++ /dev/null
@@ -1,52 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\user\Tests\Migrate\d6\MigrateUserPictureEntityFormDisplayTest.
- */
-
-namespace Drupal\user\Tests\Migrate\d6;
-
-use Drupal\migrate_drupal\Tests\d6\MigrateDrupal6TestBase;
-
-/**
- * User picture entity form display.
- *
- * @group migrate_drupal_6
- */
-class MigrateUserPictureEntityFormDisplayTest extends MigrateDrupal6TestBase {
-
-  /**
-   * Modules to enable.
-   *
-   * @var array
-   */
-  static $modules = array('image');
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function setUp() {
-    parent::setUp();
-    $id_mappings = array(
-      'd6_user_picture_field_instance' => array(
-        array(array(1), array('user', 'user', 'user_picture')),
-      ),
-    );
-    $this->prepareMigrations($id_mappings);
-    $this->executeMigration('d6_user_picture_entity_form_display');
-  }
-
-  /**
-   * Tests the Drupal 6 user picture to Drupal 8 entity form display migration.
-   */
-  public function testUserPictureEntityFormDisplay() {
-    $display = entity_get_form_display('user', 'user', 'default');
-    $component = $display->getComponent('user_picture');
-    $this->assertIdentical('image_image', $component['type']);
-    $this->assertIdentical('throbber', $component['settings']['progress_indicator']);
-
-    $this->assertIdentical(array('user', 'user', 'default', 'user_picture'), entity_load('migration', 'd6_user_picture_entity_form_display')->getIdMap()->lookupDestinationID(array('')));
-  }
-
-}
diff --git a/core/modules/user/src/Tests/Migrate/d6/MigrateUserPictureFieldTest.php b/core/modules/user/src/Tests/Migrate/d6/MigrateUserPictureFieldTest.php
deleted file mode 100644
index ace1a35..0000000
--- a/core/modules/user/src/Tests/Migrate/d6/MigrateUserPictureFieldTest.php
+++ /dev/null
@@ -1,39 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\user\Tests\Migrate\d6\MigrateUserPictureFieldTest.
- */
-
-namespace Drupal\user\Tests\Migrate\d6;
-
-use Drupal\field\Entity\FieldStorageConfig;
-use Drupal\migrate_drupal\Tests\d6\MigrateDrupal6TestBase;
-
-/**
- * User picture field migration.
- *
- * @group migrate_drupal_6
- */
-class MigrateUserPictureFieldTest extends MigrateDrupal6TestBase {
-
-  static $modules = array('image', 'file');
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function setUp() {
-    parent::setUp();
-    $this->executeMigration('d6_user_picture_field');
-  }
-
-  /**
-   * Test the user picture field migration.
-   */
-  public function testUserPictureField() {
-    $field_storage = FieldStorageConfig::load('user.user_picture');
-    $this->assertIdentical('user.user_picture', $field_storage->id());
-    $this->assertIdentical(array('user', 'user_picture'), entity_load('migration', 'd6_user_picture_field')->getIdMap()->lookupDestinationID(array('')));
-  }
-
-}
diff --git a/core/modules/user/src/Tests/Migrate/d6/MigrateUserPictureInstanceTest.php b/core/modules/user/src/Tests/Migrate/d6/MigrateUserPictureInstanceTest.php
deleted file mode 100644
index a4219fd..0000000
--- a/core/modules/user/src/Tests/Migrate/d6/MigrateUserPictureInstanceTest.php
+++ /dev/null
@@ -1,63 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\user\Tests\Migrate\d6\MigrateUserPictureInstanceTest.
- */
-
-namespace Drupal\user\Tests\Migrate\d6;
-
-use Drupal\field\Entity\FieldConfig;
-use Drupal\migrate_drupal\Tests\d6\MigrateDrupal6TestBase;
-
-/**
- * User picture field instance migration.
- *
- * @group migrate_drupal_6
- */
-class MigrateUserPictureInstanceTest extends MigrateDrupal6TestBase {
-
-  /**
-   * Modules to enable.
-   *
-   * @var array
-   */
-  static $modules = array('image', 'file');
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function setUp() {
-    parent::setUp();
-    // Add some node mappings to get past checkRequirements().
-    $id_mappings = array(
-      'd6_user_picture_field' => array(
-        array(array('user_upload'), array('name', 'bundle')),
-      ),
-    );
-    $this->prepareMigrations($id_mappings);
-    entity_create('field_storage_config', array(
-      'entity_type' => 'user',
-      'field_name' => 'user_picture',
-      'type' => 'image',
-      'translatable' => '0',
-    ))->save();
-
-    $this->executeMigration('d6_user_picture_field_instance');
-  }
-
-  /**
-   * Tests the Drupal 6 user picture to Drupal 8 picture field instance migration.
-   */
-  public function testUserPictureFieldInstance() {
-    $field = FieldConfig::load('user.user.user_picture');
-    $settings = $field->getSettings();
-    $this->assertIdentical('png gif jpg jpeg', $settings['file_extensions']);
-    $this->assertIdentical('pictures', $settings['file_directory']);
-    $this->assertIdentical('30KB', $settings['max_filesize']);
-    $this->assertIdentical('85x85', $settings['max_resolution']);
-
-    $this->assertIdentical(array('user', 'user', 'user_picture'), entity_load('migration', 'd6_user_picture_field_instance')->getIdMap()->lookupDestinationID(array('')));
-  }
-
-}
diff --git a/core/modules/user/src/Tests/Migrate/d6/MigrateUserTest.php b/core/modules/user/src/Tests/Migrate/d6/MigrateUserTest.php
index b38bdee..9980ba0 100644
--- a/core/modules/user/src/Tests/Migrate/d6/MigrateUserTest.php
+++ b/core/modules/user/src/Tests/Migrate/d6/MigrateUserTest.php
@@ -95,10 +95,10 @@ protected function setUp() {
         array(array(4), array('migrate test role 2')),
         array(array(5), array('migrate test role 3')),
       ),
-      'd6_user_picture_entity_display' => array(
+      'user_picture_entity_display' => array(
         array(array(1), array('user', 'user', 'default', 'user_picture')),
       ),
-      'd6_user_picture_entity_form_display' => array(
+      'user_picture_entity_form_display' => array(
         array(array(1), array('user', 'user', 'default', 'user_picture')),
       ),
       'd6_user_picture_file' => array(
diff --git a/core/modules/user/src/Tests/Migrate/ProfileValuesBuilderTest.php b/core/modules/user/src/Tests/Migrate/d6/ProfileValuesBuilderTest.php
similarity index 89%
copy from core/modules/user/src/Tests/Migrate/ProfileValuesBuilderTest.php
copy to core/modules/user/src/Tests/Migrate/d6/ProfileValuesBuilderTest.php
index 747edc9..c159d9e 100644
--- a/core/modules/user/src/Tests/Migrate/ProfileValuesBuilderTest.php
+++ b/core/modules/user/src/Tests/Migrate/d6/ProfileValuesBuilderTest.php
@@ -2,10 +2,10 @@
 
 /**
  * @file
- * Contains \Drupal\user\Tests\Migrate\ProfileValuesBuilderTest.
+ * Contains \Drupal\user\Tests\Migrate\d6\ProfileValuesBuilderTest.
  */
 
-namespace Drupal\user\Tests\Migrate;
+namespace Drupal\user\Tests\Migrate\d6;
 
 use Drupal\migrate_drupal\Tests\d6\MigrateDrupal6TestBase;
 
diff --git a/core/modules/user/src/Tests/Migrate/d7/MigrateUserFloodTest.php b/core/modules/user/src/Tests/Migrate/d7/MigrateUserFloodTest.php
new file mode 100644
index 0000000..92c2c23
--- /dev/null
+++ b/core/modules/user/src/Tests/Migrate/d7/MigrateUserFloodTest.php
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\user\Tests\Migrate\d7\MigrateUserFloodTest.
+ */
+
+namespace Drupal\user\Tests\Migrate\d7;
+
+use Drupal\migrate_drupal\Tests\d7\MigrateDrupal7TestBase;
+
+/**
+ * Migrates user flood control configuration.
+ *
+ * @group user
+ */
+class MigrateUserFloodTest extends MigrateDrupal7TestBase {
+
+  /**
+   * The modules to be enabled during the test.
+   *
+   * @var array
+   */
+  static $modules = array('system');
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->installConfig(['user']);
+    $this->executeMigration('d7_user_flood');
+  }
+
+  /**
+   * Tests the migration.
+   */
+  public function testMigration() {
+    $expected = [
+      'uid_only' => TRUE,
+      'ip_limit' => 30,
+      'ip_window' => 7200,
+      'user_limit' => 22,
+      'user_window' => 86400,
+    ];
+    $this->assertIdentical($expected, $this->config('user.flood')->get());
+  }
+
+}
diff --git a/core/modules/user/src/Tests/Migrate/d7/MigrateUserRoleTest.php b/core/modules/user/src/Tests/Migrate/d7/MigrateUserRoleTest.php
new file mode 100644
index 0000000..51f8f97
--- /dev/null
+++ b/core/modules/user/src/Tests/Migrate/d7/MigrateUserRoleTest.php
@@ -0,0 +1,73 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\user\Tests\Migrate\d7\MigrateUserRoleTest.
+ */
+
+namespace Drupal\user\Tests\Migrate\d7;
+
+use Drupal\Core\Database\Database;
+use Drupal\migrate_drupal\Tests\d7\MigrateDrupal7TestBase;
+use Drupal\user\Entity\Role;
+use Drupal\user\RoleInterface;
+
+/**
+ * Upgrade user roles to user.role.*.yml.
+ *
+ * @group user
+ */
+class MigrateUserRoleTest extends MigrateDrupal7TestBase {
+
+  /**
+   * The modules to be enabled during the test.
+   *
+   * @var array
+   */
+  static $modules = array('user');
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->executeMigration('d7_user_role');
+  }
+
+  /**
+   * Asserts aspects of a user role config entity.
+   *
+   * @param string $id
+   *   The role ID.
+   * @param string $label
+   *   The role's expected label.
+   * @param int|NULL $original_rid
+   *   The original (integer) ID of the role, to check permissions.
+   */
+  protected function assertEntity($id, $label, $original_rid) {
+    /** @var \Drupal\user\RoleInterface $entity */
+    $entity = Role::load($id);
+    $this->assertTrue($entity instanceof RoleInterface);
+    $this->assertIdentical($label, $entity->label());
+
+    if (isset($original_rid)) {
+      $permissions = Database::getConnection('default', 'migrate')
+        ->select('role_permission', 'rp')
+        ->fields('rp', ['permission'])
+        ->condition('rid', $original_rid)
+        ->execute()
+        ->fetchCol();
+      $this->assertIdentical($permissions, $entity->getPermissions());
+    }
+  }
+
+  /**
+   * Tests user role migration.
+   */
+  public function testUserRole() {
+    $this->assertEntity('anonymous', 'anonymous user', 1);
+    $this->assertEntity('authenticated', 'authenticated user', 2);
+    $this->assertEntity('administrator', 'administrator', 3);
+  }
+
+}
diff --git a/core/modules/user/src/Tests/Migrate/d7/MigrateUserTest.php b/core/modules/user/src/Tests/Migrate/d7/MigrateUserTest.php
new file mode 100644
index 0000000..3b70a08
--- /dev/null
+++ b/core/modules/user/src/Tests/Migrate/d7/MigrateUserTest.php
@@ -0,0 +1,86 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\user\Tests\Migrate\d7\MigrateUserTest.
+ */
+
+namespace Drupal\user\Tests\Migrate\d7;
+
+use Drupal\migrate_drupal\Tests\d7\MigrateDrupal7TestBase;
+use Drupal\user\Entity\User;
+use Drupal\user\RoleInterface;
+use Drupal\user\UserInterface;
+
+/**
+ * Users migration.
+ *
+ * @group user
+ */
+class MigrateUserTest extends MigrateDrupal7TestBase {
+
+  /**
+   * The modules to be enabled during the test.
+   *
+   * @var array
+   */
+  static $modules = array('user');
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->executeMigration('d7_user_role');
+    $this->executeMigration('d7_user');
+  }
+
+  /**
+   * Asserts various aspects of a user account.
+   *
+   * @param string $id
+   *   The user ID.
+   * @param string $label
+   *   The username.
+   * @param string $mail
+   *   The user's e-mail address.
+   * @param int $access
+   *   The last access time.
+   * @param int $login
+   *   The last login time.
+   * @param bool $blocked
+   *   Whether or not the account is blocked.
+   * @param string $langcode
+   *   The user account's language code.
+   * @param string $init
+   *   The user's initial e-mail address.
+   * @param string[] $roles
+   *   Role IDs the user account is expected to have.
+   */
+  protected function assertEntity($id, $label, $mail, $access, $login, $blocked, $langcode, $init, array $roles = [RoleInterface::AUTHENTICATED_ID]) {
+    /** @var \Drupal\user\UserInterface $user */
+    $user = User::load($id);
+    $this->assertTrue($user instanceof UserInterface);
+    $this->assertIdentical($label, $user->label());
+    $this->assertIdentical($mail, $user->getEmail());
+    $this->assertIdentical($access, $user->getLastAccessedTime());
+    $this->assertIdentical($login, $user->getLastLoginTime());
+    $this->assertIdentical($blocked, $user->isBlocked());
+    // $user->getPreferredLangcode() might fallback to default language if the
+    // user preferred language is not configured on the site. We just want to
+    // test if the value was imported correctly.
+    $this->assertIdentical($langcode, $user->langcode->value);
+    $this->assertIdentical($langcode, $user->preferred_langcode->value);
+    $this->assertIdentical($langcode, $user->preferred_admin_langcode->value);
+    $this->assertIdentical($init, $user->getInitialEmail());
+    $this->assertIdentical($roles, $user->getRoles());
+  }
+
+  /**
+   * Tests the Drupal 7 user to Drupal 8 migration.
+   */
+  public function testUser() {
+    $this->assertEntity(2, 'Odo', 'odo@local.host', '0', '0', FALSE, '', 'odo@local.host');
+  }
+
+}
diff --git a/core/modules/user/src/Tests/Migrate/ProfileValuesBuilderTest.php b/core/modules/user/src/Tests/Migrate/d7/UserMigrationBuilderTest.php
similarity index 55%
rename from core/modules/user/src/Tests/Migrate/ProfileValuesBuilderTest.php
rename to core/modules/user/src/Tests/Migrate/d7/UserMigrationBuilderTest.php
index 747edc9..77b7abb 100644
--- a/core/modules/user/src/Tests/Migrate/ProfileValuesBuilderTest.php
+++ b/core/modules/user/src/Tests/Migrate/d7/UserMigrationBuilderTest.php
@@ -2,17 +2,17 @@
 
 /**
  * @file
- * Contains \Drupal\user\Tests\Migrate\ProfileValuesBuilderTest.
+ * Contains \Drupal\user\Tests\Migrate\d7\UserMigrationBuilderTest.
  */
 
-namespace Drupal\user\Tests\Migrate;
+namespace Drupal\user\Tests\Migrate\d7;
 
-use Drupal\migrate_drupal\Tests\d6\MigrateDrupal6TestBase;
+use Drupal\migrate_drupal\Tests\d7\MigrateDrupal7TestBase;
 
 /**
  * @group user
  */
-class ProfileValuesBuilderTest extends MigrateDrupal6TestBase {
+class UserMigrationBuilderTest extends MigrateDrupal7TestBase {
 
   public static $modules = ['migrate', 'migrate_drupal', 'user'];
 
@@ -22,15 +22,15 @@ class ProfileValuesBuilderTest extends MigrateDrupal6TestBase {
    */
   public function testBuilder() {
     $template = \Drupal::service('migrate.template_storage')
-      ->getTemplateByName('d6_profile_values');
+      ->getTemplateByName('d7_user');
     /** @var \Drupal\migrate\Entity\MigrationInterface[] $migrations */
     $migrations = \Drupal::service('plugin.manager.migrate.builder')
-      ->createInstance('d6_profile_values')
+      ->createInstance('d7_user')
       ->buildMigrations($template);
 
-    $this->assertIdentical('d6_profile_values', $migrations[0]->id());
+    $this->assertIdentical('d7_user', $migrations[0]->id());
     $process = $migrations[0]->getProcess();
-    $this->assertIdentical('profile_color', $process['profile_color'][0]['source']);
+    $this->assertIdentical('field_file', $process['field_file'][0]['source']);
   }
 
 }
diff --git a/core/modules/user/tests/src/Unit/Plugin/migrate/source/d7/RoleTest.php b/core/modules/user/tests/src/Unit/Plugin/migrate/source/d7/RoleTest.php
new file mode 100644
index 0000000..3fcde05
--- /dev/null
+++ b/core/modules/user/tests/src/Unit/Plugin/migrate/source/d7/RoleTest.php
@@ -0,0 +1,78 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\user\Unit\Plugin\migrate\source\d7\RoleTest.
+ */
+
+namespace Drupal\Tests\user\Unit\Plugin\migrate\source\d7;
+
+use Drupal\Tests\migrate\Unit\MigrateSqlSourceTestCase;
+
+/**
+ * Tests D7 role source plugin.
+ *
+ * @group user
+ */
+class RoleTest extends MigrateSqlSourceTestCase {
+
+  const PLUGIN_CLASS = 'Drupal\user\Plugin\migrate\source\d7\Role';
+
+  protected $migrationConfiguration = array(
+    'id' => 'test',
+    'source' => array(
+      'plugin' => 'd7_user_role',
+    ),
+  );
+
+  protected $expectedResults = array(
+    array(
+      'rid' => 1,
+      'name' => 'anonymous user',
+      'permissions' => array(
+        'access content',
+      ),
+    ),
+    array(
+      'rid' => 2,
+      'name' => 'authenticated user',
+      'permissions' => array(
+        'access comments',
+        'access content',
+        'post comments',
+        'post comments without approval',
+      ),
+    ),
+    array(
+      'rid' => 3,
+      'name' => 'administrator',
+      'permissions' => array(
+        'access comments',
+        'administer comments',
+        'post comments',
+        'post comments without approval',
+        'access content',
+        'administer content types',
+        'administer nodes',
+      ),
+    ),
+  );
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    foreach ($this->expectedResults as $row) {
+      foreach ($row['permissions'] as $permission) {
+        $this->databaseContents['role_permission'][] = array(
+          'permission' => $permission,
+          'rid' => $row['rid'],
+        );
+      }
+      unset($row['permissions']);
+      $this->databaseContents['role'][] = $row;
+    }
+    parent::setUp();
+  }
+
+}
diff --git a/core/modules/user/tests/src/Unit/Plugin/migrate/source/d7/UserTest.php b/core/modules/user/tests/src/Unit/Plugin/migrate/source/d7/UserTest.php
new file mode 100644
index 0000000..c7e35e1
--- /dev/null
+++ b/core/modules/user/tests/src/Unit/Plugin/migrate/source/d7/UserTest.php
@@ -0,0 +1,119 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\user\Unit\Plugin\migrate\source\d7\UserTest.
+ */
+
+namespace Drupal\Tests\user\Unit\Plugin\migrate\source\d7;
+
+use Drupal\Tests\migrate\Unit\MigrateSqlSourceTestCase;
+
+/**
+ * Tests D7 user source plugin.
+ *
+ * @group user
+ */
+class UserTest extends MigrateSqlSourceTestCase {
+
+  const PLUGIN_CLASS = 'Drupal\user\Plugin\migrate\source\d7\User';
+
+  protected $migrationConfiguration = [
+    'id' => 'test',
+    'source' => [
+      'plugin' => 'd7_user',
+    ],
+  ];
+
+  protected $expectedResults = [
+    [
+      'uid' => '2',
+      'name' => 'Odo',
+      'pass' => '$S$DVpvPItXvnsmF3giVEe7Jy2lG.SCoEs8uKwpHsyPvdeNAaNZYxZ8',
+      'mail' => 'odo@local.host',
+      'signature' => '',
+      'signature_format' => 'filtered_html',
+      'created' => '1432750741',
+      'access' => '0',
+      'login' => '0',
+      'status' => '1',
+      'timezone' => 'America/Chicago',
+      'language' => '',
+      'picture' => '0',
+      'init' => 'odo@local.host',
+      'roles' => [
+        2 => 'authenticated user',
+      ],
+      'data' => [
+        'contact' => 1,
+      ],
+      'field_file' => [
+        [
+          'fid' => 99,
+          'display' => 1,
+          'description' => 'None',
+        ],
+      ],
+    ],
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    $this->databaseContents['users'][] = [
+      'uid' => '2',
+      'name' => 'Odo',
+      'pass' => '$S$DVpvPItXvnsmF3giVEe7Jy2lG.SCoEs8uKwpHsyPvdeNAaNZYxZ8',
+      'mail' => 'odo@local.host',
+      'theme' => '',
+      'signature' => '',
+      'signature_format' => 'filtered_html',
+      'created' => '1432750741',
+      'access' => '0',
+      'login' => '0',
+      'status' => '1',
+      'timezone' => 'America/Chicago',
+      'language' => '',
+      'picture' => '0',
+      'init' => 'odo@local.host',
+      'data' => 'a:1:{s:7:"contact";i:1;}',
+    ];
+    $this->databaseContents['users_roles'][] = [
+      'uid' => 2,
+      'rid' => 2,
+    ];
+    $this->databaseContents['role'][] = [
+      'rid' => 2,
+      'name' => 'authenticated user',
+      'weight' => 0,
+    ];
+    $this->databaseContents['field_config_instance'] = [
+      [
+        'id' => '33',
+        'field_id' => '11',
+        'field_name' => 'field_file',
+        'entity_type' => 'user',
+        'bundle' => 'user',
+        'data' => 'a:0:{}',
+        'deleted' => '0',
+      ],
+    ];
+    $this->databaseContents['field_data_field_file'] = [
+      [
+        'entity_type' => 'user',
+        'bundle' => 'user',
+        'deleted' => 0,
+        'entity_id' => 2,
+        'revision_id' => NULL,
+        'language' => 'und',
+        'delta' => 0,
+        'field_file_fid' => 99,
+        'field_file_display' => 1,
+        'field_file_description' => 'None',
+      ],
+    ];
+    parent::setUp();
+  }
+
+}
