Is there some security feature built into Drupal to prevent submissions by scripts?
Yes, there are. First off, a script that is not bootstrapping Drupal cannot submit a form that has not requested to a Drupal site. Drupal explicitly check that condition is verified, and that task is done from drupal_validate_form(), which contains the following code.
// If the session token was set by drupal_prepare_form(), ensure that it
// matches the current user's session.
if (isset($form['#token'])) {
if (!drupal_valid_token($form_state['values']['form_token'], $form['#token'])) {
$path = current_path();
$query = drupal_get_query_parameters();
$url = url($path, array('query' => $query));
// Setting this error will cause the form to fail validation.
form_set_error('form_token', t('The form has become outdated. Copy any unsaved work in the form below and then <a href="@link">reload this page</a>.', array('@link' => $url)));
}
}
_form_validate() then verifies there is a correspondence between the values submitted and the form elements contained in the form requested to Drupal. If there isn't an exact correspondence, the submission fails.
if (isset($elements['#maxlength']) && drupal_strlen($elements['#value']) > $elements['#maxlength']) {
form_error($elements, $t('!name cannot be longer than %max characters but is currently %length characters long.', array('!name' => empty($elements['#title']) ? $elements['#parents'][0] : $elements['#title'], '%max' => $elements['#maxlength'], '%length' => drupal_strlen($elements['#value']))));
}
if (!isset($options[$v])) {
form_error($elements, $t('An illegal choice has been detected. Please contact the site administrator.'));
watchdog('form', 'Illegal choice %choice in !name element.', array('%choice' => $v, '!name' => empty($elements['#title']) ? $elements['#parents'][0] : $elements['#title']), WATCHDOG_ERROR);
}
elseif (!isset($options[$elements['#value']])) {
form_error($elements, $t('An illegal choice has been detected. Please contact the site administrator.'));
watchdog('form', 'Illegal choice %choice in %name element.', array('%choice' => $elements['#value'], '%name' => empty($elements['#title']) ? $elements['#parents'][0] : $elements['#title']), WATCHDOG_ERROR);
}
Is there some other reason scripts would fail to log into Drupal on a page where a user in a normal browser would have no problem?
As you are asking for a headless browser, the form submission could be successful if:
- The browser requests the login form
- It inject JavaScript code that fills out the necessary fields, and submit the form back to Drupal
The condition necessary for the operation to work is that the browser is able to keep the session open. In fact, the token used to validate the form is obtained from drupal_get_token(), which contains the following code.
return drupal_hmac_base64($value, session_id() . drupal_get_private_key() . drupal_get_hash_salt());
The function used to validate the token is drupal_valid_token().
function drupal_valid_token($token, $value = '', $skip_anonymous = FALSE) {
global $user;
return (($skip_anonymous && $user->uid == 0) || ($token == drupal_get_token($value)));
}
As side note, to create content is not necessary to log in as a specific user: Just bootstrap Drupal, build a node object, and call node_save().
For example, the following code put in a file in the same directory containing the index.php Drupal comes with would create a node. (The code is for Drupal 7, but a similar code can be written for Drupal 6.)
define('DRUPAL_ROOT', getcwd());
require_once DRUPAL_ROOT . '/includes/bootstrap.inc';
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
$node = new stdClass();
$node->title = $node_title;
$node->type = $node_type;
// Sets some defaults. Invokes hook_prepare() and hook_node_prepare().
node_object_prepare($node);
$node->language = LANGUAGE_NONE;
$node->uid = $user_id;
$node->status = 1;
$node->promote = 0;
// Comments are disabled; change it to COMMENT_NODE_OPEN to enable them.
$node->comment = COMMENT_NODE_CLOSED;
$node = node_submit($node);
node_save($node);
Using this code, Drupal would create a node.
If the code is used for a publicaly available site, and you want to avoid everybody runs the file (let's suppose the file is migration.php), you can use the following code.
define('DRUPAL_ROOT', getcwd());
require_once DRUPAL_ROOT . '/includes/bootstrap.inc';
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
if (!isset($_GET['cron_key']) || variable_get('migration_key', 'drupal') != $_GET['cron_key']) {
watchdog('migration', 'Migration could not run because an invalid key was used.', array(), WATCHDOG_NOTICE);
drupal_access_denied();
}
elseif (variable_get('maintenance_mode', 0)) {
watchdog('Migration', 'Migration could not run because the site is in maintenance mode.', array(), WATCHDOG_NOTICE);
drupal_access_denied();
}
else {
$node = new stdClass();
$node->title = $node_title;
$node->type = $node_type;
// Sets some defaults. Invokes hook_prepare() and hook_node_prepare().
node_object_prepare($node);
$node->language = LANGUAGE_NONE;
$node->uid = $user_id;
$node->status = 1;
$node->promote = 0;
// Comments are disabled; change it to COMMENT_NODE_OPEN to enable them.
$node->comment = COMMENT_NODE_CLOSED;
$node = node_submit($node);
node_save($node);
}
The Drupal variable migration_cron should be initialized with variable_set('migration_key', drupal_get_token('update'));
. The script would be then called as http://example.com/migration.php?migration_key=XYX, where XYZ is the value saved in the Drupal variable migration_key. As drupal_get_token()
returns a different value each time the function is called, external attachers could not get the value to use to run the script.
The code I shown creates just a node, but using similar code would be possible to migrate content. The code would execute a loop to create a node for each entry loaded from a file, an external database, or returned from a drupal_http_request()
call to an external site (among other methods). In any case, it is not necessary to first log in to create a node, if the PHP code first bootstrap code.
If you can use Drush, that would probably be a better way to programmatically create content.
As alternative, there are many modules to migrate content. As far as I recall, also the Feeds module is used to migrate content to Drupal.