diff --git a/core/modules/migrate/lib/Drupal/migrate/Plugin/migrate/destination/Entity.php b/core/modules/migrate/lib/Drupal/migrate/Plugin/migrate/destination/Entity.php
new file mode 100644
index 0000000..8ff400e
--- /dev/null
+++ b/core/modules/migrate/lib/Drupal/migrate/Plugin/migrate/destination/Entity.php
@@ -0,0 +1,88 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate\Plugin\migrate\destination\Entity.
+ */
+
+namespace Drupal\migrate\Plugin\migrate\destination;
+
+use Drupal\Core\Entity\EntityStorageControllerInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\field\FieldInfo;
+use Drupal\migrate\Entity\Migration;
+use Drupal\migrate\Plugin\MigratePluginManager;
+use Drupal\migrate\Row;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * @PluginId("entity")
+ */
+class Entity extends DestinationBase implements ContainerFactoryPluginInterface {
+
+  public function __construct(array $configuration, $plugin_id, array $plugin_definition, array $entity_info, EntityStorageControllerInterface $storage_controller, MigratePluginManager $plugin_manager, FieldInfo $field_info) {
+    $this->entityInfo = $entity_info;
+    $this->storageController = $storage_controller;
+    $this->pluginManager = $plugin_manager;
+    $this->fieldInfo = $field_info;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, array $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('entity.manager')->getDefinition($configuration['entity_type']),
+      $container->get('entity.manager')->getStorageController($configuration['entity_type']),
+      $container->get('plugin.manager.migrate.entity_field'),
+      $container->get('field.info')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function import(Row $row) {
+    /*
+    // Field handling. Untested, unused and unnecessary yet. But it shows how
+    // it'll look.
+    if ($all_instances = $this->fieldInfo->getInstances($this->configuration['entity_type'])) {
+      $instances = reset($all_instances);
+      if (isset($this->entityInfo['entity keys']['bundle'])) {
+        $bundle = $row->getDestinationProperty($this->entityInfo['entity keys']['bundle']);
+        if (isset($all_instances[$bundle])) {
+          $instances = $all_instances[$bundle];
+        }
+      }
+      foreach ($instances as $field_name => $instance) {
+        $field_type = $instance->getFieldType();
+        if ($this->pluginManager->getDefinition($field_type)) {
+          $destination_value = $this->pluginManager->createInstance($field_type)->import($instance, $row->getDestinationProperty($field_name));
+          // @TODO: check for NULL return? Add an unset to $row? Maybe needed in exception handling? Propagate exception?
+          $row->setDestinationProperty($field_name, $destination_value);
+        }
+      }
+    }
+    */
+    // @TODO: validate! this will fatal if create() fails.
+    $this->storageController->create($row->getDestination())->save();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getIdsSchema() {
+    // TODO: Implement getIdsSchema() method.
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fields(Migration $migration = NULL) {
+    // TODO: Implement fields() method.
+  }
+
+}
diff --git a/core/modules/migrate_drupal/config/migrate.migration.d6_user_role.yml b/core/modules/migrate_drupal/config/migrate.migration.d6_user_role.yml
new file mode 100644
index 0000000..3ee7334
--- /dev/null
+++ b/core/modules/migrate_drupal/config/migrate.migration.d6_user_role.yml
@@ -0,0 +1,55 @@
+id: d6_user_role
+sourceIds:
+  rid:
+    type: int
+    "not null": true
+    default: 0
+destinationIds:
+  id:
+    type: varchar
+    length: 255
+source:
+  plugin: drupal6_user_role
+process:
+  id:
+    -
+      plugin: machine_name
+      source: name
+    -
+      plugin: dedupe_entity
+      entity_type: user_role
+      field: id
+  label: name
+# permissions start as array(array('perm' => array('perm1', 'perm2'))), array('perm' => array('perm3', 'perm4')))
+  permissions:
+    # extract gets array('perm' => array('perm1', 'perm2')) first
+    -
+      plugin: extract
+      source: permissions
+      index:
+        - perm
+    # the pipeline is now array(array('perm1', 'perm2'))
+    - plugin: flatten
+    # the pipeline is now array('perm1', 'perm2')
+    -
+      plugin: static_map
+      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: system_update_7000
+    - plugin: node_update_7008
+    - plugin: flatten
+destination:
+  plugin: entity
+  entity_type: user_role
diff --git a/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/Plugin/migrate/process/d6/NodeUpdate7008.php b/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/Plugin/migrate/process/d6/NodeUpdate7008.php
new file mode 100644
index 0000000..06014c4
--- /dev/null
+++ b/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/Plugin/migrate/process/d6/NodeUpdate7008.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate_drupal\Plugin\migrate\Process\d6\NodeUpdate7008.
+ */
+
+namespace Drupal\migrate_drupal\Plugin\migrate\Process\d6;
+
+use Drupal\migrate\MigrateExecutable;
+use Drupal\migrate\ProcessPluginBase;
+use Drupal\migrate\Row;
+
+/**
+ * Split the 'administer nodes' permission from 'access content overview'.
+ *
+ * @MigrateProcessPlugin(
+ *   id = "node_update_7008"
+ * )
+ */
+class NodeUpdate7008 extends ProcessPluginBase {
+
+  /**
+   * {@inheritdoc}
+   *
+   * Split the 'administer nodes' permission from 'access content overview'.
+   */
+  public function transform($value, MigrateExecutable $migrate_executable, Row $row, $destination_property) {
+    if ($value === 'administer nodes') {
+      return array($value, 'access content overview');
+    }
+    return $value;
+  }
+}
diff --git a/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/Plugin/migrate/process/d6/SystemUpdate7000.php b/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/Plugin/migrate/process/d6/SystemUpdate7000.php
new file mode 100644
index 0000000..aaa2dd5
--- /dev/null
+++ b/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/Plugin/migrate/process/d6/SystemUpdate7000.php
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate_drupal\Plugin\migrate\Process\d6\system_update_7000.
+ */
+
+namespace Drupal\migrate_drupal\Plugin\migrate\process\d6;
+
+use Drupal\migrate\MigrateExecutable;
+use Drupal\migrate\ProcessPluginBase;
+use Drupal\migrate\Row;
+
+/**
+ * Rename blog and forum permissions to be consistent with other content types.
+ *
+ * @MigrateProcessPlugin(
+ *   id = "system_update_7000"
+ * )
+ */
+class SystemUpdate7000 extends ProcessPluginBase {
+
+  /**
+   * {@inheritdoc}
+   *
+   * Rename blog and forum permissions to be consistent with other content types.
+   */
+  public function transform($value, MigrateExecutable $migrate_executable, Row $row, $destination_property) {
+    $value = preg_replace('/(?<=^|,\ )create\ blog\ entries(?=,|$)/', 'create blog content', $value);
+    $value = preg_replace('/(?<=^|,\ )edit\ own\ blog\ entries(?=,|$)/', 'edit own blog content', $value);
+    $value = preg_replace('/(?<=^|,\ )edit\ any\ blog\ entry(?=,|$)/', 'edit any blog content', $value);
+    $value = preg_replace('/(?<=^|,\ )delete\ own\ blog\ entries(?=,|$)/', 'delete own blog content', $value);
+    $value = preg_replace('/(?<=^|,\ )delete\ any\ blog\ entry(?=,|$)/', 'delete any blog content', $value);
+
+    $value = preg_replace('/(?<=^|,\ )create\ forum\ topics(?=,|$)/', 'create forum content', $value);
+    $value = preg_replace('/(?<=^|,\ )delete\ any\ forum\ topic(?=,|$)/', 'delete any forum content', $value);
+    $value = preg_replace('/(?<=^|,\ )delete\ own\ forum\ topics(?=,|$)/', 'delete own forum content', $value);
+    $value = preg_replace('/(?<=^|,\ )edit\ any\ forum\ topic(?=,|$)/', 'edit any forum content', $value);
+    $value = preg_replace('/(?<=^|,\ )edit\ own\ forum\ topics(?=,|$)/', 'edit own forum content', $value);
+    return $value;
+  }
+
+}
diff --git a/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/Plugin/migrate/source/d6/Role.php b/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/Plugin/migrate/source/d6/Role.php
new file mode 100644
index 0000000..b22765b
--- /dev/null
+++ b/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/Plugin/migrate/source/d6/Role.php
@@ -0,0 +1,62 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate\Plugin\migrate\source\d6\Role.
+ */
+
+namespace Drupal\migrate_drupal\Plugin\migrate\source\d6;
+
+
+use Drupal\migrate\Row;
+
+/**
+ * Drupal 6 role source from database.
+ *
+ * @PluginId("drupal6_user_role")
+ */
+class Role extends Drupal6SqlBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function query() {
+    $query = $this->select('role', 'r')
+      ->fields('r', array('rid', 'name'))
+      ->orderBy('rid');
+    return $query;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fields() {
+    return array(
+      'rid' => t('Role ID.'),
+      'name' => t('The name of the user role.'),
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  function prepareRow(Row $row, $keep = TRUE) {
+    $permissions = array();
+    $results = $this->database
+      ->select('permission', 'p', array('fetch' => \PDO::FETCH_ASSOC))
+      ->fields('p', array('pid', 'rid', 'perm', 'tid'))
+      ->condition('rid', $row->getSourceProperty('rid'))
+      ->execute();
+    foreach ($results as $perm) {
+      $permissions[] = array(
+        'pid' => $perm['pid'],
+        'rid' => $perm['rid'],
+        'perm' => array_map('trim', explode(',', $perm['perm'])),
+        'tid' => $perm['tid'],
+      );
+    }
+    $row->setSourceProperty('permissions', $permissions);
+    return parent::prepareRow($row);
+  }
+
+}
diff --git a/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/Tests/Dump/Drupal6UserRole.php b/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/Tests/Dump/Drupal6UserRole.php
new file mode 100644
index 0000000..934141e
--- /dev/null
+++ b/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/Tests/Dump/Drupal6UserRole.php
@@ -0,0 +1,123 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate\Tests\Dump\Drupal6UserRole.
+ */
+
+namespace Drupal\migrate_drupal\Tests\Dump;
+use Drupal\Core\Database\Connection;
+
+/**
+ * Database dump for testing user role migration.
+ */
+class Drupal6UserRole {
+
+  /**
+   * @param \Drupal\Core\Database\Connection $database
+   */
+  public static function load(Connection $database) {
+    $database->schema()->createTable('permission', array(
+      'description' => 'Stores permissions for users.',
+      'fields' => array(
+        'pid' => array(
+          'type' => 'serial',
+          'not null' => TRUE,
+          'description' => 'Primary Key: Unique permission ID.',
+        ),
+        'rid' => array(
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+          'default' => 0,
+          'description' => 'The {role}.rid to which the permissions are assigned.',
+        ),
+        'perm' => array(
+          'type' => 'text',
+          'not null' => FALSE,
+          'size' => 'big',
+          'description' => 'List of permissions being assigned.',
+        ),
+        'tid' => array(
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+          'default' => 0,
+          'description' => 'Originally intended for taxonomy-based permissions, but never used.',
+        ),
+      ),
+      'primary key' => array('pid'),
+      'indexes' => array('rid' => array('rid')),
+    ));
+    $database->schema()->createTable('role', array(
+      'description' => 'Stores user roles.',
+      'fields' => array(
+        'rid' => array(
+          'type' => 'serial',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+          'description' => 'Primary Key: Unique role id.',
+        ),
+        'name' => array(
+          'type' => 'varchar',
+          'length' => 64,
+          'not null' => TRUE,
+          'default' => '',
+          'description' => 'Unique role name.',
+        ),
+      ),
+      'unique keys' => array('name' => array('name')),
+      'primary key' => array('rid'),
+    ));
+    $database->schema()->createTable('users_roles', array(
+      'description' => 'Maps users to roles.',
+      'fields' => array(
+        'uid' => array(
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+          'default' => 0,
+          'description' => 'Primary Key: {users}.uid for user.',
+        ),
+        'rid' => array(
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+          'default' => 0,
+          'description' => 'Primary Key: {role}.rid for role.',
+        ),
+      ),
+      'primary key' => array('uid', 'rid'),
+      'indexes' => array(
+        'rid' => array('rid'),
+      ),
+    ));
+    $database->insert('permission')->fields(array('pid', 'rid', 'perm'))
+      ->values(array('pid' => 3, 'rid' => 3, 'perm' => 'migrate test role 1 test permission'))
+      ->values(array('pid' => 4, 'rid' => 4, 'perm' => 'migrate test role 2 test permission'))
+      ->values(array('pid' => 5, 'rid' => 4, 'perm' => 'use PHP for settings'))
+      ->values(array('pid' => 6, 'rid' => 4, 'perm' => 'administer contact forms'))
+      ->values(array('pid' => 7, 'rid' => 4, 'perm' => 'skip comment approval'))
+      ->values(array('pid' => 8, 'rid' => 4, 'perm' => 'edit own blog content'))
+      ->values(array('pid' => 9, 'rid' => 4, 'perm' => 'edit any blog content'))
+      ->values(array('pid' => 10, 'rid' => 4, 'perm' => 'delete own blog content'))
+      ->values(array('pid' => 11, 'rid' => 4, 'perm' => 'delete any blog content'))
+      ->values(array('pid' => 12, 'rid' => 4, 'perm' => 'create forum content'))
+      ->values(array('pid' => 13, 'rid' => 4, 'perm' => 'delete any forum content'))
+      ->values(array('pid' => 14, 'rid' => 4, 'perm' => 'delete own forum content'))
+      ->values(array('pid' => 15, 'rid' => 4, 'perm' => 'edit any forum content'))
+      ->values(array('pid' => 16, 'rid' => 4, 'perm' => 'edit own forum content'))
+      ->values(array('pid' => 17, 'rid' => 4, 'perm' => 'administer nodes'))
+      ->execute();
+    $database->insert('role')->fields(array('rid', 'name'))
+      ->values(array('rid' => 3, 'name' => 'migrate test role 1'))
+      ->values(array('rid' => 4, 'name' => 'migrate test role 2'))
+      ->values(array('rid' => 5, 'name' => 'migrate test role 3'))
+      ->execute();
+    $database->insert('users_roles')->fields(array('uid', 'rid'))
+      ->values(array('uid' => 1, 'rid' => 3))
+      ->values(array('uid' => 1, 'rid' => 4))
+      ->execute();
+  }
+
+}
diff --git a/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/Tests/d6/MigrateUserRoleTest.php b/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/Tests/d6/MigrateUserRoleTest.php
new file mode 100644
index 0000000..e6a8727
--- /dev/null
+++ b/core/modules/migrate_drupal/lib/Drupal/migrate_drupal/Tests/d6/MigrateUserRoleTest.php
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate\Tests\MigrateD6UserRoleTest.
+ */
+
+namespace Drupal\migrate_drupal\Tests\d6;
+
+use Drupal\migrate\MigrateExecutable;
+use Drupal\migrate\MigrateMessage;
+use Drupal\migrate_drupal\Tests\MigrateDrupalTestBase;
+
+class MigrateUserRoleTest extends MigrateDrupalTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name'  => 'Migrate user roles to user.role.*.yml',
+      'description'  => 'Upgrade user roles to user.role.*.yml',
+      'group' => 'Migrate Drupal',
+    );
+  }
+
+  function testUserRole() {
+    $migration = entity_load('migration', 'd6_user_role');
+    $dumps = array(
+      drupal_get_path('module', 'migrate_drupal') . '/lib/Drupal/migrate_drupal/Tests/Dump/Drupal6UserRole.php',
+    );
+    $this->prepare($migration, $dumps);
+    $executable = new MigrateExecutable($migration, new MigrateMessage());
+    $executable->import();
+
+    $migrate_test_role_1 = entity_load('user_role', 'migrate_test_role_1');
+    $this->assertTrue(is_object($migrate_test_role_1), 'The migrated test role 1 was retrieved from the database.');
+    $this->assertEqual($migrate_test_role_1->permissions, array(0 => 'migrate test role 1 test permission'));
+    $migrate_test_role_2 = entity_load('user_role', 'migrate_test_role_2');
+    $this->assertTrue(is_object($migrate_test_role_2), 'The migrated test role 2 was retrieved from the database.');
+    $this->assertEqual($migrate_test_role_2->permissions, array(
+      'migrate test role 2 test permission',
+      'use PHP for settings',
+      'administer contact forms',
+      'skip comment approval',
+      'edit own blog content',
+      'edit any blog content',
+      'delete own blog content',
+      'delete any blog content',
+      'create forum content',
+      'delete any forum content',
+      'delete own forum content',
+      'edit any forum content',
+      'edit own forum content',
+      'administer nodes',
+      'access content overview',
+    ));
+  }
+
+}
diff --git a/core/modules/migrate_drupal/tests/Drupal/migrate_drupal/Tests/source/d6/RoleSourceTest.php b/core/modules/migrate_drupal/tests/Drupal/migrate_drupal/Tests/source/d6/RoleSourceTest.php
new file mode 100644
index 0000000..1dbb2c5
--- /dev/null
+++ b/core/modules/migrate_drupal/tests/Drupal/migrate_drupal/Tests/source/d6/RoleSourceTest.php
@@ -0,0 +1,145 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate\Tests\source\d6\RoleSourceTest.
+ */
+
+namespace Drupal\migrate_drupal\Tests;
+
+use Drupal\migrate\Tests\MigrateSqlSourceTestCase;
+
+/**
+ * Tests user role migration from D6 to D8.
+ *
+ * @group migrate_drupal
+ */
+class RoleSourceTest extends MigrateSqlSourceTestCase {
+
+  // The plugin system is not working during unit testing so the source plugin
+  // class needs to be manually specified.
+  const PLUGIN_CLASS = 'Drupal\migrate_drupal\Plugin\migrate\source\d6\Role';
+
+  // The fake Migration configuration entity.
+  protected $migrationConfiguration = array(
+    // The ID of the entity, can be any string.
+    'id' => 'test',
+    // Leave it empty for now.
+    'idlist' => array(),
+    // This needs to be the identifier of the actual key: rid for comment, nid
+    // for node and so on.
+    'source' => array(
+      'plugin' => 'drupal6_user_role',
+    ),
+    'sourceIds' => array(
+      'rid' => array(
+        // This is where the field schema would go but for now we need to
+        // specify the table alias for the key. Most likely this will be the
+        // same as BASE_ALIAS.
+        'alias' => 'r',
+      ),
+    ),
+    'destinationIds' => array(
+      'rid' => array(
+        // This is where the field schema would go.
+      ),
+    ),
+  );
+
+  protected $expectedResults = array(
+    array(
+      'rid' => 1,
+      'name' => 'anonymous user',
+      'permissions' => array(
+        array(
+          'pid' => 1,
+          'rid' => 1,
+          'perm' => array(
+            'access content',
+          ),
+          'tid' => 0,
+        ),
+      ),
+    ),
+    array(
+      'rid' => 2,
+      'name' => 'authenticated user',
+      'permissions' => array(
+        array(
+          'pid' => 2,
+          'rid' => 2,
+          'perm' => array(
+            'access comments',
+            'access content',
+            'post comments',
+            'post comments without approval',
+          ),
+          'tid' => 0,
+        ),
+      ),
+    ),
+    array(
+      'rid' => 3,
+      'name' => 'administrator',
+      'permissions' => array(
+        array(
+          'pid' => 3,
+          'rid' => 3,
+          'perm' => array(
+            'access comments',
+            'administer comments',
+            'post comments',
+            'post comments without approval',
+            'access content',
+            'administer content types',
+            'administer nodes',
+          ),
+          'tid' => 0,
+        ),
+      ),
+    ),
+  );
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'D6 role source functionality',
+      'description' => 'Tests D6 role source plugin.',
+      'group' => 'Migrate Drupal',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    foreach ($this->expectedResults as $k => $row) {
+      foreach ($row['permissions'] as $perm) {
+        $this->databaseContents['permission'][$perm['pid']] = $perm;
+        $this->databaseContents['permission'][$perm['pid']]['perm'] = implode(',', $perm['perm']);
+        $this->databaseContents['permission'][$perm['pid']]['rid'] = $row['rid'];
+      }
+      unset($row['permissions']);
+      $this->databaseContents['role'][$k] = $row;
+    }
+    parent::setUp();
+  }
+
+}
+
+namespace Drupal\migrate_drupal\Tests\source\d6;
+
+use Drupal\Core\Database\Connection;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\migrate_drupal\Plugin\migrate\source\d6\Role;
+
+class TestRole extends Role {
+  function setDatabase(Connection $database) {
+    $this->database = $database;
+  }
+  function setModuleHandler(ModuleHandlerInterface $module_handler) {
+    $this->moduleHandler = $module_handler;
+  }
+}
