File/program/lib/tokenlib.php

Description

/program/lib/tokenlib.php - functions to manipulate unique tokens via the database

This file provides the functions to manipulate tokens stored in the database. These tokens are used to create unique instances of dialogs (forms) making it impossible to POST data to a form without first retrieving the individual (blank) form to start with.

Tokens are stored in a table called 'tokens' which is defined as follows:

+--------------+--------------+------+-----+---------+----------------+
| Field        | Type         | Null | Key | Default | Extra          |
+--------------+--------------+------+-----+---------+----------------+
| token_id     | int(11)      |      | PRI | NULL    | auto_increment |
| token_key    | varchar(60)  |      | MUL |         |                |
| token_ref    | varchar(60)  |      |     |         |                |
| token_start  | int(11)      |      |     | 0       |                |
| token_end    | int(11)      |      |     | 0       |                |
| token_expire | int(11)      |      |     | 0       |                |
| remote_addr  | varchar(150) |      |     |         |                |
| data         | longtext     | YES  |     | NULL    |                |
+--------------+--------------+------+-----+---------+----------------+

Note: this table was added in v0.90.5 (June 2013), initially for the mailpage module.

The following functions are defined.

$token_id = token_create($ref, &$token_key, $delay_start, $delay_end, $ip_addr);
$token_id = token_lookup($ref, $token_key, &$timer_start, &$timer_end, &$ip_addr, &$data);
$retval   = token_store($token_id, $data);
$retval   = token_fetch($token_id, &$data);
$retval   = token_destroy($token_id);
$retval   = token_garbage_collect();

Functions
token_create (line 102)

create a new record in the tokens table, return the unique token_id

this creates generates a new unique token_key and stores it in a new record in the tokens table. Additional information is recorded in the new record too:

  • the dialog/form reference
  • the earliest unix timestamp POST'ed data will be accepted (based on $delay_start)
  • the latest unix timestamp POST'ed data will be accepted (based on $delay_end)
  • the unix timestamp after which this record can be deleted (via garbage collection)
  • the IP-address of the current caller (from $_SERVER['REMOTE_ADDR'])
This routine creates a unique token_key. This key consists of the hexadecimal representation of the token_id (which is the unique serial of the record) followed by a quasi-random string of hexadecimal characters. There is no way there will be a repeat ever, unless the serial wraps round. I assume (dangerous, I know) that this is very unlikely to happen any time soon. In order to further obfuscate the generated key the (sequential) serial number is xor'ed with 878133331 before the random string is appended.

The parameter $reference is used to further limit the chance that POST'ing a rogue token_key could influence another, valid $token_key. This $reference could be a number identifying a particular dialog definition, e.g. by using the filename and a line number in a define().

Finally, the IP-address of the visitor is recorded. This can be used in the future to limit brute force attacks on a form. Currently it is not used other than to add to the mail that is sent via the mailpage module. Note that by default we use the IP-address from $_SERVER but the caller is free to substitute something else, perhaps a canonical IPv6-address.

We call the garbage collector from here. This keeps the table clean. Furthermore, we have got the time: the user has to wait at least $delay_start seconds before she can submit anything, so I think there is no rush. YMMV.

  • return: FALSE on error or primary key of the generated token in the tokens table.
  • todo: Should we enforce valid UTF8 in $reference and $ip_addr? We might have substr() trouble...
bool|int token_create (string $reference, string &$token_key, [int $delay_start = 10], [int $delay_end = 14400], [string $ip_addr = NULL])
  • string $reference: is an identifier of the dialog requesting a token
  • string &$token_key: a generated unique identifier based on the pkey of the token
  • int $delay_start: seconds to wait before POST'ed data will be considered valid
  • int $delay_end: seconds after which the dialog no longer accepts POST'ed data
  • string $ip_addr: the IP-address this visitor is calling from
token_destroy (line 231)

remove a token record from the tokens table (it should still exist)

remove the specified record from the table. it is an error if the record does not exist.

  • return: FALSE on failure, TRUE otherwise
bool token_destroy (string $token_id)
  • string $token_id: the unique token_id (pkey) that identifies the record
token_fetch (line 195)

retrieve the (unserialised) data from the database

  • return: TRUE on success, FALSE otherwise
bool token_fetch (int $token_id, mixed &$data)
  • int $token_id: the unique token_id (pkey) that identifies the token record
  • mixed &$data: receives the unserialised data from the database
token_garbage_collect (line 260)

remove all expired tokens

this removes all records that are expired. Since this routine is also called from token_create() we have a big chance to keep the tokens table clean. However: logging every delete() may be a little too much so it is commented out.

This way, the worst that can happen is that someone keeps GET'ting a form that uses tokens every second for 8 hours in a row and subsequently nobody ever visits that form again. In that case we eventually have 8 x 3600 x 1 = 28800 garbage records. Oh well.

OTOH: with all those crawlers and spiders on the WWW there is bound to be a 'bot' visiting that form the next day, effectively cleaning up for us. (Robots are a feature, not a bug...)

  • return: FALSE on failure, TRUE otherwise
  • todo: This routine should be called from cron.php every once in a while
bool token_garbage_collect ()
token_lookup (line 173)

lookup $reference + $token_key in the table and retrieve token information

This checks the existence of a token record in the tokens table. This $token_key can only generated via token_create(). Furthermore, we require that the $reference matches the one in the record. This prevents us accepting spurious token keys via manipulated requests. If the key does not exist, the call fails and FALSE is returned, otherwise the token_id is returned + data in the other parameters (timers, remote_addr, etc).

Typical use:

if (($token_id = token_lookup($ref, $key, $t0, $t1, $ip_addr, $data)) === FALSE) {
  logger('error: no such token');
} elseif (time() < $t0) {
  logger('error: whoa, not so fast there!');
} elseif ($t1 < time()) {
  logger('error: too late');
} else {
  logger('welcome visitor from '.$ip_addr);
}

This allows for accepting POST'ed information only within the time window defined by $t0 and $t1 and denying access otherwise with a precise cause (no token, too early, too late).

  • return: FALSE on failure/non-existing, token_id otherwise and values in parameters
bool token_lookup (string $reference, string $token_key, int &$token_start, int &$token_end, string &$remote_addr, mixed &$data)
  • string $reference: is an identifier of the dialog requesting a token
  • string $token_key: a unique identifier based on the pkey of the token
  • int &$token_start: unix timestamp indicating start of the valid interval
  • int &$token_end: unix timestamp indicating end of the valid interval
  • string &$remote_addr: the IP-address this visitor that created this token
  • mixed &$data: receives the unserialised data from the database
token_store (line 215)

write the (serialised) data to the database

serialise and store $data in the database

  • return: TRUE on success, FALSE otherwise
bool token_store (int $token_id, mixed $data)
  • int $token_id: the unique token_id (pkey) that identifies the token record
  • mixed $data: holds the unserialised data to store

Documentation generated on Tue, 28 Jun 2016 19:12:17 +0200 by phpDocumentor 1.4.0