Automatically creating albums with cron job

spags

Joined: 2010-03-26
Posts: 120
Posted: Wed, 2013-11-06 08:29

I've been trying to work on a module to automatically create albums. Unfortunately I have run into a problem that I'm not sure how to work around. I can sort of see why there is a problem, but I don't know how to resolve it.

I have been basing some rough code on the existing scheduler and folder_sync modules (unfortunately neither do exactly quite what I want to do, so I'm using them as a basis). The idea is to use a cron job that would run the module. The module would determine the date and create an album for it in the Gallery. I'm finding that the creation of the new album isn't working (due to "failure" of access_Core::verify_csrf() so I'm guessing it has something to do with my authentication).

I have a module controller called calendar_auto_structure.php. The stripped down code looks something like this:


class calendar_auto_structure_Controller extends Controller {
  static function crontest()
  {
    // When crontest is called as CLI, then set a value for
    // PHP REMOTE_ADDR so we don't have any auth::login problems. This could be part of my problem.
    if(isset($_SERVER['REMOTE_ADDR'])==false)
      $_SERVER['REMOTE_ADDR']='127.0.0.1';
    
    // Login as Admin
    $session = Session::instance();
    $session->delete("user");
    auth::login(IdentityProvider::instance()->admin_user());
    
    // Get current date information to use as the name for a new folder in the root album
    $name = date('Ymd');
    
    // Create the folder called $name - largely replicated from Gallery Albums_Controller
    access::verify_csrf(); // This line gives an error
    $album = ORM::factory("item", 1); // 1 is the root folder
    access::required("view", $album);
    access::required("add", $album);
    $album = ORM::factory("item");
    $album->type = "album";
    $album->parent_id = 0; // 0 is the parent for the root folder
    $album->name = strval($name);
    $album->title = strval($name);
    $album->slug = strval($name);
    $album->validate();
    $album->save();
  }
}

I created a cron job as the www-data user (Debian install, using "sudo su www-data" then just "crontab -e"). The cron job that calls the above looks something like this: "*/1 * * * * php /var/www/gallery3/modules/calendar_auto_structure/cron.php" (yes I know it calls every minute in this example).

The cron.php file (basically copied from scheduler and folder_sync) looks something like this:


<?php

// Acquire lock
@$fp = fopen(sys_get_temp_dir().DIRECTORY_SEPARATOR."calendargallery.lock", "w+");
if (!flock($fp, LOCK_EX | LOCK_NB)) {
  echo "Couldn't get the lock!";
  exit;
}

set_time_limit(3600);
error_reporting(E_ALL);
ini_set("display_errors", "on");

define("IN_PRODUCTION", true);

version_compare(PHP_VERSION, "5.2.3", "<") and
  exit("Gallery requires PHP 5.2.3 or newer (you're using " . PHP_VERSION  . ")");

chdir(dirname(dirname(dirname(__FILE__))));

define("EXT", ".php");
define("DOCROOT", getcwd() . "/");
define("KOHANA",  "index.php");

// If the front controller is a symlink, change to the real docroot
is_link(basename(__FILE__)) and chdir(dirname(realpath(__FILE__)));

// Define application and system paths
define("APPPATH", realpath("application") . "/");
define("MODPATH", realpath("modules") . "/");
define("THEMEPATH", realpath("themes") . "/");
define("SYSPATH", realpath("system") . "/");

define("TEST_MODE", 0);
define("VARPATH", realpath("var") . "/");
define("TMPPATH", VARPATH . "/tmp/");

if (file_exists("local.php")) {
  include("local.php");
}

define('SYSTEM_BENCHMARK', 'system_benchmark');
require SYSPATH.'core/Benchmark'.EXT;

require SYSPATH.'core/Event'.EXT;
final class Event extends Event_Core {}

require SYSPATH.'core/Kohana'.EXT;
final class Kohana extends Kohana_Core {}

require SYSPATH.'core/Kohana_Exception'.EXT;
require MODPATH.'gallery/libraries/MY_Kohana_Exception'.EXT;

require SYSPATH.'core/Kohana_Config'.EXT;
require SYSPATH.'libraries/drivers/Config'.EXT;
require SYSPATH.'libraries/drivers/Config/Array'.EXT;
final class Kohana_Config extends Kohana_Config_Core {}

Kohana::setup();

Event::run('system.ready');

//Call crontest
calendar_auto_structure_Controller::crontest();

// Release lock
@flock($fp, LOCK_UN);
@fclose($fp);

The Gallery log shows the following:

2013-11-06 18:02:03 +11:00 --- error: Kohana_Exception [ 403 ]: @todo FORBIDDEN
/var/www/gallery3/modules/gallery/helpers/access.php [ 202 ]
#0 /var/www/gallery3/modules/gallery/helpers/access.php(425): access_Core::forbidden()
#1 /var/www/gallery3/modules/calendar_auto_structure/controllers/calendar_auto_structure.php(18): access_Core::verify_csrf()
#2 /var/www/gallery3/modules/calendar_auto_structure/cron.php(64): calendar_auto_structure_Controller::crontest()
#3 {main}
2013-11-06 18:02:03 +11:00 --- error: Missing messages entry kohana/core.errors.403 for message kohana/core

The problem is that access::verify_csrf() fails and I assume this is because I'm not doing or setting something correctly. I'm guessing that Gallery knows the Admin is logged in, but I haven't captured/set/passed back a csrf token (I'm not really sure about this). The folder_sync module doesn't have this problem and as far as I can see I have closely followed how it operates in this stripped down code. I suspect folder_sync doesn't have this problem because it doesn't actually create the folders in /var/www/gallery3/var.

In access_Core::verify_csrf, the code looks like this:


$input = Input::instance();
if ($input->post("csrf", $input->get("csrf", null)) !== Session::instance()->get("csrf")) {
  access::forbidden();
}

While I don't completely understand it, I can see the concept is that when then cron job is called, the csrf of Input::instance() doesn't match Session::instance(). I believe that Input::instance has a null or empty value for csrf, while Session::instance has the random alphanumerics. I'm note sure how I go about resolving this.

I assume that in this section of my code:


if(isset($_SERVER['REMOTE_ADDR'])==false)
  $_SERVER['REMOTE_ADDR']='127.0.0.1';

// Login as Admin
$session = Session::instance();
$session->delete("user");
auth::login(IdentityProvider::instance()->admin_user());

I should be doing some other stuff, but I'm not sure what. Can someone give me any guidance?

 
floridave
floridave's picture

Joined: 2003-12-22
Posts: 27300
Posted: Wed, 2013-11-06 18:24

access::csrf_token() should give you the session token.
perhaps it can go before access::verify_csrf(); // This line gives an error

If not then I'm not the guy to answer much more. I have not played with sessions or tokens much.

Dave
_____________________________________________
Blog & G2 || floridave - Gallery Team

 
spags

Joined: 2010-03-26
Posts: 120
Posted: Sat, 2013-11-09 06:10

It looks like that was along the right track, but it is a little more involved. Since the cron job is starting the module from the CLI, it seems that POST is not generated. Since the Input class is using POST to check for a valid token, we need to create it and then inject the token into it. A little more work like the below code is required.


// For this module, we need to stuff the csrf token into a POST variable.
// Normally Apache/PHP would just do this, but we are doing a dodgy workaround.

$input = Input::instance(); // Make sure Input has started an instance
if (Kohana::$server_api === 'cli') {
  //If we get here, we know this was involked from the command line
  $curtoken = access::csrf_token();
  if (!isset($_POST)) // Just check in case POST has already been set elsewhere
    $_POST = array();
  $_POST['csrf'] = $curtoken; // Shovel the token into POST
}

if ( ! isset($_POST["csrf"]))
  // if we get here, the CSRF was not set so code onward from here may fail to operate correctly
else
  access::verify_csrf(); // Should now work

I don't know what else may be missing from POST and whether it is as easy as this, but for now it seems to work. Thanks floridave.