diff --git a/plugins/FeedsNodeProcessor.inc b/plugins/FeedsNodeProcessor.inc index b80953b1698f302f565d46ff61f806a614dfbfde..d2080473e25670fd72c734048606cf6b15423dd4 100644 --- a/plugins/FeedsNodeProcessor.inc +++ b/plugins/FeedsNodeProcessor.inc @@ -77,6 +77,39 @@ class FeedsNodeProcessor extends FeedsProcessor { return $node; } + /** + * Check that the user has permission to save a node. + */ + protected function entitySaveAccess($entity) { + + // The check will be skipped for anonymous nodes. + if ($this->config['authorize'] && !empty($entity->uid)) { + + $author = user_load($entity->uid); + + // If the uid was mapped directly, rather than by email or username, it + // could be invalid. + if (!$author) { + $message = 'User %uid is not a valid user.'; + throw new FeedsAccessException(t($message, array('%uid' => $entity->uid))); + } + + if (empty($entity->nid) || !empty($entity->is_new)) { + $op = 'create'; + $access = node_access($op, $entity->type, $author); + } + else { + $op = 'update'; + $access = node_access($op, $entity, $author); + } + + if (!$access) { + $message = 'User %name is not authorized to %op content type %content_type.'; + throw new FeedsAccessException(t($message, array('%name' => $author->name, '%op' => $op, '%content_type' => $entity->type))); + } + } + } + /** * Save a node. */ @@ -137,6 +170,7 @@ class FeedsNodeProcessor extends FeedsProcessor { 'content_type' => $type, 'expire' => FEEDS_EXPIRE_NEVER, 'author' => 0, + 'authorize' => TRUE, ) + parent::configDefaults(); } @@ -162,6 +196,12 @@ class FeedsNodeProcessor extends FeedsProcessor { '#autocomplete_path' => 'user/autocomplete', '#default_value' => empty($author->name) ? 'anonymous' : check_plain($author->name), ); + $form['authorize'] = array( + '#type' => 'checkbox', + '#title' => t('Authorize'), + '#description' => t('Check that the author has permission to create the node.'), + '#default_value' => $this->config['authorize'], + ); $period = drupal_map_assoc(array(FEEDS_EXPIRE_NEVER, 3600, 10800, 21600, 43200, 86400, 259200, 604800, 2592000, 2592000 * 3, 2592000 * 6, 31536000), 'feeds_format_expire'); $form['expire'] = array( '#type' => 'select', diff --git a/plugins/FeedsProcessor.inc b/plugins/FeedsProcessor.inc old mode 100644 new mode 100755 index 31d30b146cc8ca270f14f72b37b0b0d989280ee9..80e54d5791811f918d1ed2ce447cbc0ec6c7c369 --- a/plugins/FeedsProcessor.inc +++ b/plugins/FeedsProcessor.inc @@ -19,6 +19,11 @@ define('FEEDS_PROCESS_LIMIT', 50); */ class FeedsValidationException extends Exception {} +/** + * Thrown if a an access check fails. + */ +class FeedsAccessException extends Exception {} + /** * Abstract class, defines interface for processors. */ @@ -64,11 +69,22 @@ abstract class FeedsProcessor extends FeedsPlugin { */ protected function entityValidate($entity) {} + /** + * Access check for saving an enity. + * + * @param $entity + * Entity to be saved. + * + * @throws FeedsAccessException $e + * If the access check fails. + */ + protected function entitySaveAccess($entity) {} + /** * Save an entity. * * @param $entity - * Entity to b saved. + * Entity to be saved. */ protected abstract function entitySave($entity); @@ -159,6 +175,8 @@ abstract class FeedsProcessor extends FeedsPlugin { continue; } + // This will throw an exception on failure. + $this->entitySaveAccess($entity); $this->entitySave($entity); // Track progress. diff --git a/tests/feeds_processor_node.test b/tests/feeds_processor_node.test index 7e1b8d697b6b34021e6115c62cf249d69e4f2eb8..677bd95a826c4099b1c4ea04e013881da651ed06 100644 --- a/tests/feeds_processor_node.test +++ b/tests/feeds_processor_node.test @@ -144,9 +144,9 @@ class FeedsRSStoNodesTest extends FeedsWebTestCase { $this->assertText('Deleted 10 nodes'); $this->assertFeedItemCount(0); - // Change author. + // Change author and turn off authorization. $this->auth_user = $this->drupalCreateUser(array('access content')); - $this->setSettings('syndication', 'FeedsNodeProcessor', array('author' => $this->auth_user->name)); + $this->setSettings('syndication', 'FeedsNodeProcessor', array('author' => $this->auth_user->name, 'authorize' => FALSE)); // Change input format. $this->setSettings('syndication', 'FeedsNodeProcessor', array('input_format' => 'plain_text')); @@ -406,4 +406,53 @@ class FeedsRSStoNodesTest extends FeedsWebTestCase { $this->drupalGet('node/add/article'); $this->assertNoFieldByName('feeds[FeedsHTTPFetcher][source]'); } + + /** + * Test that nodes will not be created if the user is unauthorized to create + * them. + */ + public function testAuthorize() { + + // Create a user with limited permissions. We can't use + // $this->drupalCreateUser here because we need to to set a specific user + // name. + $edit = array( + 'name' => 'Development Seed', + 'mail' => 'devseed@example.com', + 'pass' => user_password(), + 'status' => 1, + ); + + $account = user_save(drupal_anonymous_user(), $edit); + + // Adding a mapping to the user_name will invoke authorization. + $this->addMappings('syndication', + array( + 5 => array( + 'source' => 'author_name', + 'target' => 'user_name', + ), + ) + ); + + $nid = $this->createFeedNode(); + + $this->assertText('Failed importing 10 nodes.'); + $this->assertText('User ' . $account->name . ' is not authorized to create content type article.'); + $node_count = db_query("SELECT COUNT(*) FROM {node}")->fetchField(); + + // We should have 1 node, the feed node. + $this->assertEqual($node_count, 1, t('Correct number of nodes in the database.')); + + // Give the user our admin powers. + $edit = array( + 'roles' => $this->admin_user->roles, + ); + $account = user_save($account, $edit); + + $this->drupalPost("node/$nid/import", array(), 'Import'); + $this->assertText('Created 10 nodes.'); + $node_count = db_query("SELECT COUNT(*) FROM {node}")->fetchField(); + $this->assertEqual($node_count, 11, t('Correct number of nodes in the database.')); + } }