<?php
/**
* Display a form to login using knowledge based authentication questions about wiki activities.
*
* @package actions
* @version $RCSfile: qidlogin.php,v $ $Revision: 1.30 $ $Date: 2008/09/22 07:18:45 $
* @license http://www.opensource.org/licenses/bsd-license.php
* @author Chi Nguyen
* @author {@link http://qid.cnfolio.com Chi Nguyen}
*/
//-------------------------------------------------------
//
// QID configuration settings
//
if ( !
defined( 'QID_HASH_SALT' ) ) define( 'QID_HASH_SALT',
"saltvalue" );
/* MUST BE CHANGED FOR EACH QID INSTALLATION */
if ( !
defined( 'QID_METADATA_REFRESH_RATE' ) ) define( 'QID_METADATA_REFRESH_RATE',
"7" );
/* Refresh rate specified in days */
if ( !
defined( 'QID_SMALL_DATA_LIMIT' ) ) define( 'QID_SMALL_DATA_LIMIT',
"10" );
/* Limit iterations when randomly selecting authentication attributes for small data sets */
if ( !
defined( 'USERSETTINGS_PAGE' ) ) define( 'USERSETTINGS_PAGE',
"UserSettings" );
/* Name of wiki page to manage user settings after login */
//-------------------------------------------------------
//
// Attributes used to generate challenge questions
//
if ( !
defined( 'COMMENTS_ATTRIBUTE' ) ) define( 'COMMENTS_ATTRIBUTE',
"1" );
/* Number of comments written */
if ( !
defined( 'PAGES_OWNED_ATTRIBUTE' ) ) define( 'PAGES_OWNED_ATTRIBUTE',
"2" );
/* Number of pages owned */
if ( !
defined( 'PAGES_EDITED_ATTRIBUTE' ) ) define( 'PAGES_EDITED_ATTRIBUTE',
"3" );
/* Number of pages edited */
if ( !
defined( 'LOGIN_ATTRIBUTE' ) ) define( 'LOGIN_ATTRIBUTE',
"4" );
/* Number of successful logins */
if ( !
defined( 'PAGES_READ_ATTRIBUTE' ) ) define( 'PAGES_READ_ATTRIBUTE',
"5" );
/* Number of pages read */
//-------------------------------------------------------
//
// Display same error message text to resist brute force attacks
//
if ( !
defined( 'ERROR_NON_EXISTENT_USERNAME' ) ) define( 'ERROR_NON_EXISTENT_USERNAME',
"Login attempt was not successful, please try again.");
if ( !
defined( 'ERROR_USER_SUSPENDED' ) ) define( 'ERROR_USER_SUSPENDED',
"Login attempt was not successful, please try again.." );
if ( !
defined( 'ERROR_WRONG_ANSWERS' ) ) define( 'ERROR_WRONG_ANSWERS',
"Login attempt was not successful, please try again.");
if ( !
defined( 'ERROR_INVALID_FORM' ) ) define( 'ERROR_INVALID_FORM',
"Login attempt was not successful, please try again.");
if ( !
defined( 'ERROR_UNDEF_ATTR_VALUE' ) ) define( 'ERROR_UNDEF_ATTR_VALUE',
"-99999" );
//-------------------------------------------------------
//
// Login form parameters and settings
//
if ( !
defined( 'LOGIN_PARAM' ) ) define( 'LOGIN_PARAM',
"name" );
if ( !
defined( 'ACTION_PARAM' ) ) define( 'ACTION_PARAM',
"action" );
if ( !
defined( 'ACTION_STEP1' ) ) define( 'ACTION_STEP1',
"qid_step_1" );
if ( !
defined( 'ACTION_STEP2' ) ) define( 'ACTION_STEP2',
"qid_step_2" );
if ( !
defined( 'ANSWER_1' ) ) define( 'ANSWER_1',
"a1" );
if ( !
defined( 'ANSWER_2' ) ) define( 'ANSWER_2',
"a2" );
if ( !
defined( 'ANSWER_HASH_1' ) ) define( 'ANSWER_HASH_1',
"a1x" );
if ( !
defined( 'ANSWER_HASH_2' ) ) define( 'ANSWER_HASH_2',
"a2x" );
//-------------------------------------------------------
//
// SQL statements
//
if ( !
defined( 'SQL_SAVE_NEXT_ATTR' ) ) define( 'SQL_SAVE_NEXT_ATTR',
"UPDATE " .
$this->
config[ 'table_prefix' ] .
"qid_meta SET b0 = %d WHERE a0 = '%s' " );
if ( !
defined( 'SQL_GET_ATTR_PMF' ) ) define( 'SQL_GET_ATTR_PMF',
"SELECT %s AS val FROM " .
$this->
config[ 'table_prefix' ] .
"qid_meta WHERE a0 = '%s' " );
if ( !
defined( 'SQL_ADD_METADATA' ) ) define( 'SQL_ADD_METADATA',
"INSERT INTO " .
$this->
config[ 'table_prefix' ] .
"qid_meta VALUES ( %d, '%s', %d, %f, %f, %f ) " );
if ( !
defined( 'SQL_GET_UID' ) ) define( 'SQL_GET_UID',
"SELECT uid FROM " .
$this->
config[ 'table_prefix' ] .
"qid_attr WHERE a0 = '%s' " );
if ( !
defined( 'SQL_COUNT_META' ) ) define( 'SQL_COUNT_META',
"SELECT COUNT( uid ) AS val FROM " .
$this->
config[ 'table_prefix' ] .
"qid_attr WHERE %s = %d AND %s = %d " .
"AND a0 != 'REFRESHINPROGRESS' " );
if ( !
defined( 'SQL_CLEAR_METADATA' ) ) define( 'SQL_CLEAR_METADATA',
"DELETE FROM " .
$this->
config[ 'table_prefix' ] .
"qid_meta " );
if ( !
defined( 'SQL_ADD_USERDATA' ) ) define( 'SQL_ADD_USERDATA',
"INSERT INTO " .
$this->
config[ 'table_prefix' ] .
"qid_attr VALUES ( NULL, '%s', %d, %d, %d, NOW() ) " );
if ( !
defined( 'SQL_GET_PAGES_EDITED_ATTR' ) ) define( 'SQL_GET_PAGES_EDITED_ATTR',
"SELECT COUNT( DISTINCT( tag ) ) AS val FROM " .
$this->
config[ 'table_prefix' ] .
"pages WHERE user = '%s' " .
"AND TIMESTAMPDIFF( DAY, time, NOW() ) <= " . QID_METADATA_REFRESH_RATE .
' ' );
if ( !
defined( 'SQL_GET_PAGES_OWNED_ATTR' ) ) define( 'SQL_GET_PAGES_OWNED_ATTR',
"SELECT COUNT(id) AS val FROM " .
$this->
config[ 'table_prefix' ] .
"pages WHERE owner = '%s' AND latest = 'Y' " );
if ( !
defined( 'SQL_GET_COMMENTS_ATTR' ) ) define( 'SQL_GET_COMMENTS_ATTR',
"SELECT COUNT(id) AS val FROM " .
$this->
config[ 'table_prefix' ] .
"comments WHERE user = '%s' " .
"AND TIMESTAMPDIFF( DAY, time, NOW() ) <= " . QID_METADATA_REFRESH_RATE .
' ' );
if ( !
defined( 'SQL_STOP_REFRESH' ) ) define( 'SQL_STOP_REFRESH',
"DELETE FROM " .
$this->
config[ 'table_prefix' ] .
"qid_attr WHERE a0 = 'REFRESHINPROGRESS' " );
if ( !
defined( 'SQL_START_REFRESH' ) ) define( 'SQL_START_REFRESH',
"UPDATE " .
$this->
config[ 'table_prefix' ] .
"qid_attr SET a0 = 'REFRESHINPROGRESS' " );
if ( !
defined( 'SQL_IS_REFRESH' ) ) define( 'SQL_IS_REFRESH',
"SELECT uid FROM " .
$this->
config[ 'table_prefix' ] .
"qid_attr WHERE a0 = 'REFRESHINPROGRESS' " );
if ( !
defined( 'SQL_GET_ATTR_VALUE' ) ) define( 'SQL_GET_ATTR_VALUE',
"SELECT %s AS val FROM " .
$this->
config[ 'table_prefix' ] .
"qid_attr WHERE a0 = '%s' " );
if ( !
defined( 'SQL_GET_ATTR_PAIR' ) ) define( 'SQL_GET_ATTR_PAIR',
"SELECT b0 FROM " .
$this->
config[ 'table_prefix' ] .
"qid_meta WHERE a0 = '%s' " );
if ( !
defined( 'SQL_CHECK_REFRESH_TS' ) ) define( 'SQL_CHECK_REFRESH_TS',
"SELECT TIMESTAMPDIFF(DAY,ts,NOW()) AS days FROM " .
$this->
config[ 'table_prefix' ] .
"qid_attr " );
if ( !
defined( 'SQL_CHECK_USER_EXISTS' ) ) define( 'SQL_CHECK_USER_EXISTS',
"SELECT uid FROM " .
$this->
config[ 'table_prefix' ] .
"qid_attr WHERE a0 = '%s' " );
if ( !
defined( 'SQL_CREATE_META_TABLE' ) ) define( 'SQL_CREATE_META_TABLE',
"CREATE TABLE " .
$this->
config[ 'table_prefix' ] .
"qid_meta ( " .
"uid bigint(20) NOT NULL default 0, " .
"a0 varchar(50) NOT NULL, " .
"b0 bigint(20) NOT NULL, " .
"a12 float NOT NULL, " .
"a13 float NOT NULL, " .
"a23 float NOT NULL, " .
"PRIMARY KEY (uid), " .
"KEY a0 (a0) ) TYPE=MyISAM; " );
if ( !
defined( 'SQL_CREATE_ATTR_TABLE' ) ) define( 'SQL_CREATE_ATTR_TABLE',
"CREATE TABLE " .
$this->
config[ 'table_prefix' ] .
"qid_attr ( " .
"uid bigint(20) NOT NULL auto_increment, " .
"a0 varchar(50) NOT NULL default '', " .
"a1 bigint(20) NOT NULL default 0, " .
"a2 bigint(20) NOT NULL default 0, " .
"a3 bigint(20) NOT NULL default 0, " .
"ts datetime NOT NULL default '0000-00-00 00:00:00', " .
"PRIMARY KEY (uid), " .
"KEY a0 (a0) ) TYPE=MyISAM; " );
if ( !
defined( 'SQL_CHECK_FOR_TABLES' ) ) define( 'SQL_CHECK_FOR_TABLES',
"SHOW TABLES LIKE '%qid_attr' " );
//-------------------------------------------------------
//
// User interface customizations which do not affect QID functionality
//
if ( !
defined( 'LOGIN_HEADING1' ) ) define( 'LOGIN_HEADING1',
"<h3>QID login step 1 of 2</h3>" );
if ( !
defined( 'LOGIN_HEADING2' ) ) define( 'LOGIN_HEADING2',
"<h3>QID login step 2 of 2</h3>" );
if ( !
defined( 'STEP1_INSTRUCTIONS' ) ) define( 'STEP1_INSTRUCTIONS',
"Please type your registered login name." );
if ( !
defined( 'STEP2_INSTRUCTIONS' ) ) define( 'STEP2_INSTRUCTIONS',
"Please answer both questions to verify your login." );
if ( !
defined( 'LOGIN_BUTTON_LABEL' ) ) define( 'LOGIN_BUTTON_LABEL',
"Login" );
if ( !
defined( 'LOGOUT_BUTTON_LABEL') ) define( 'LOGOUT_BUTTON_LABEL',
"Logout" );
//-------------------------------------------------------
//
// Initialize global variables
//
$error_msg =
'';
$display_login_form =
1;
/* Default is to display login form 1 */
$login_settings =
$this->
config[ 'base_url' ] . USERSETTINGS_PAGE;
/* Display default user settings page after successful login */
//-------------------------------------------------------
//
// Log the user out
//
if ( isset( $_REQUEST[ ACTION_PARAM
] ) &&
( $_REQUEST[ ACTION_PARAM
] == LOGOUT_BUTTON_LABEL
) )
{
$this->
LogoutUser();
$this->
Redirect( $login_form =
$this->
config[ 'base_url' ] .
$this->
tag,
'' );
}
//-------------------------------------------------------
//
// Redirect in the event that login is already done
//
if ( $user =
$this->
GetUser() )
{
$this->
Redirect( $login_settings,
'' );
}
//-------------------------------------------------------
//
// Process authentication request
//
if ( isset( $_POST[ 'submit' ] ) &&
( $_POST[ 'submit' ] == LOGIN_BUTTON_LABEL
) )
{
// Assume user name does not exist
$error_msg = ERROR_USER_SUSPENDED;
// Load user information
if ( isset( $_POST[ LOGIN_PARAM
] ) &&
( $existingUser =
$this->
LoadUser( $_POST[ LOGIN_PARAM
] ) ) )
{
// User name does exist, assume that account is suspended
$error_msg = ERROR_USER_SUSPENDED;
// Check status of user account
$status =
$existingUser[ 'status' ];
if ( !
( ( $status ==
'deleted' ) ||
( $status ==
'suspended' ) ||
( $status ==
'banned' ) ) )
{
if ( isset( $_POST[ ACTION_PARAM
] ) &&
( $_POST[ ACTION_PARAM
] == ACTION_STEP1
) )
{
// User name exist and account status is OK, so display login form 2
$error_msg =
'';
$display_login_form =
2;
// Generate challenge questions
$question1 =
'';
$question1_key =
'';
$question2 =
'';
$question2_key =
'';
generate_challenge_questions
( $this,
$this->
GetSafeVar( LOGIN_PARAM,
'post' ),
$question1,
$question1_key,
$question2,
$question2_key );
}
elseif ( isset( $_POST[ ACTION_PARAM
] ) &&
( $_POST[ ACTION_PARAM
] == ACTION_STEP2
) )
{
// User name exist and account status is OK, assume authentication failed and reset to display login form 1
$error_msg = ERROR_WRONG_ANSWERS;
// Verify user answers
if ( verify_answers
( $this->
GetSafeVar( LOGIN_PARAM,
'post' ),
$this->
GetSafeVar( ANSWER_1,
'post' ),
$this->
GetSafeVar( ANSWER_HASH_1,
'post' ),
$this->
GetSafeVar( ANSWER_2,
'post' ),
$this->
GetSafeVar( ANSWER_HASH_2,
'post' ) ) )
{
// Successful authentication attempt
$this->
SetUser( $existingUser );
$this->
Redirect( $login_settings,
'' );
}
}
else
{
// Reset to display login form 1
$error_msg = ERROR_INVALID_FORM;
}
}
}
}
//-------------------------------------------------------
//
// Display QID login form 1
//
if ( $display_login_form ==
1 )
{
echo $this->
FormOpen();
echo '<input type="hidden" name="' . ACTION_PARAM .
'" value="' . ACTION_STEP1 .
'" />';
echo '<table class="usersettings">';
echo '<tr><td>' . LOGIN_HEADING1 .
'</td><td> </td></tr>';
echo '<tr><td>' . STEP1_INSTRUCTIONS .
'</td></tr>' .
"\n";
if ( $error_msg )
echo '<tr><td><em class="error">' .
$error_msg .
'</em></td></tr>';
echo '<tr><td><input name="' . LOGIN_PARAM .
'" size="40" value="' .
$this->
GetSafeVar( LOGIN_PARAM,
'post' ) .
'" /></td></tr>';
echo '<tr><td><input name="submit" type="submit" value="' . LOGIN_BUTTON_LABEL .
'" size="40" /></td></tr>';
echo '</table>' .
$this->
FormClose() .
"\n";
}
//-------------------------------------------------------
//
// Display QID login form 2
//
if ( $display_login_form ==
2 )
{
echo $this->
FormOpen();
echo '<input type="hidden" name="' . ACTION_PARAM .
'" value="' . ACTION_STEP2 .
'" />';
echo '<input type="hidden" name="' . LOGIN_PARAM .
'" value="' .
$this->
GetSafeVar( LOGIN_PARAM,
'post' ) .
'" />';
echo '<input type="hidden" name="' . ANSWER_HASH_1 .
'" value="' .
$question1_key .
'" />';
echo '<input type="hidden" name="' . ANSWER_HASH_2 .
'" value="' .
$question2_key .
'" />' .
"\n";
echo '<table class="usersettings">';
echo '<tr><td>' . LOGIN_HEADING2 .
'</td><td> </td></tr>';
echo '<tr><td>' . STEP2_INSTRUCTIONS .
'</td></tr>' .
"\n";
echo '<tr><td> </td></tr>';
echo '<tr><td>' .
$question1 .
'</td></tr>';
echo '<tr><td><input name="' . ANSWER_1 .
'" size="20" value="" /></td></tr>' .
"\n";
echo '<tr><td> </td></tr>';
echo '<tr><td>' .
$question2 .
'</td></tr>';
echo '<tr><td><input name="' . ANSWER_2 .
'" size="20" value="" /></td></tr>' .
"\n";
echo '<tr><td> </td></tr>';
echo '<tr><td><input name="submit" type="submit" value="' . LOGIN_BUTTON_LABEL .
'" size="40" /></td></tr>';
echo '</table>' .
$this->
FormClose() .
"\n";
}
//-------------------------------------------------------
//
// Generate challenge questions
//
function generate_challenge_questions
( &
$wiki,
$login_user, &
$login_q1, &
$login_q1key, &
$login_q2, &
$login_q2key )
{
init_db_once
( $wiki );
generate_metadata
( $wiki,
$login_user );
$attribute_1 =
0;
$attribute_2 =
0;
get_current_attributes
( $wiki,
$login_user,
$attribute_1,
$attribute_2 );
set_next_attributes
( $wiki,
$login_user,
$attribute_1,
$attribute_2 );
$login_q1 = generate_question_text
( $attribute_1 );
$login_q2 = generate_question_text
( $attribute_2 );
$login_q1_answer = get_attribute_value
( $wiki,
$login_user,
$attribute_1 );
$login_q2_answer = get_attribute_value
( $wiki,
$login_user,
$attribute_2 );
// round the answers to the nearest 10 and encode as hash keys
$login_q1key =
sha1( QID_HASH_SALT . round_answer
( $login_q1_answer ) );
$login_q2key =
sha1( QID_HASH_SALT . round_answer
( $login_q2_answer ) );
}
//-------------------------------------------------------
//
// Verify answers to challenge questions
//
function verify_answers
( $login_user,
$login_a1,
$login_a1key,
$login_a2,
$login_a2key )
{
if ( isset( $login_a1key ) &&
( strlen( trim( $login_a1key ) ) ==
40 ) &&
( trim( $login_a1key ) ==
sha1( QID_HASH_SALT .
$login_a1 ) ) &&
isset( $login_a2key ) &&
( strlen( trim( $login_a2key ) ) ==
40 ) &&
( trim( $login_a2key ) ==
sha1( QID_HASH_SALT .
$login_a2 ) ) )
{
// Successful authentication attempt
return 1;
}
else
{
// Failed authentication attempt
return 0;
}
}
//-------------------------------------------------------
//
// Initialize the database if this is the first time the action plugin has been used
//
function init_db_once
( &
$wiki )
{
// End function if tables exist
if ( $wiki->
LoadSingle( SQL_CHECK_FOR_TABLES
) )
return;
// Create attributes table
$wiki->
Query( SQL_CREATE_ATTR_TABLE
);
// Create meta data table
$wiki->
Query( SQL_CREATE_META_TABLE
);
}
//-------------------------------------------------------
//
// Generate attributes and meta data for use with the Metropolis Hastings algorithm
//
function generate_metadata
( &
$wiki,
$unique_id )
{
// Wait if meta data generation is in progress
while ( $wiki->
LoadSingle( SQL_IS_REFRESH
) )
{
sleep( 3 );
// wait 3 seconds before checking again
}
// Assume meta data is up to date and does not need to be generated
$refresh_metadata =
0;
// Refresh meta data if the unique ID is not found
if ( !
$wiki->
LoadSingle( sprintf( SQL_CHECK_USER_EXISTS,
mysql_real_escape_string( $unique_id ) ) ) )
$refresh_metadata =
1;
else
{
// Refresh meta data if it is older than the specified refresh rate
$age_of_attributes =
$wiki->
LoadSingle( SQL_CHECK_REFRESH_TS
);
if ( $age_of_attributes &&
( $age_of_attributes[ 'days' ] >= QID_METADATA_REFRESH_RATE
) )
$refresh_metadata =
1;
}
if ( $refresh_metadata )
{
// Turn flag on to indicate meta data generation in progress
$wiki->
Query( SQL_START_REFRESH
);
// Refresh attribute values
$all_users =
$wiki->
LoadUsers();
foreach ( $all_users as $current_user )
{
$a0_value =
$current_user[ 'name' ];
$number_of_comments =
$wiki->
LoadSingle( sprintf( SQL_GET_COMMENTS_ATTR,
mysql_real_escape_string( $a0_value ) ) );
if ( $number_of_comments ) $a1_value =
$number_of_comments[ 'val' ];
else $a1_value = ERROR_UNDEF_ATTR_VALUE;
$number_of_pages_owned =
$wiki->
LoadSingle( sprintf( SQL_GET_PAGES_OWNED_ATTR,
mysql_real_escape_string( $a0_value ) ) );
if ( $number_of_pages_owned ) $a2_value =
$number_of_pages_owned[ 'val' ];
else $a2_value = ERROR_UNDEF_ATTR_VALUE;
$number_of_pages_edited =
$wiki->
LoadSingle( sprintf( SQL_GET_PAGES_EDITED_ATTR,
mysql_real_escape_string( $a0_value ) ) );
if ( $number_of_pages_edited ) $a3_value =
$number_of_pages_edited[ 'val' ];
else $a3_value = ERROR_UNDEF_ATTR_VALUE;
$wiki->
Query( sprintf( SQL_ADD_USERDATA,
$a0_value,
$a1_value,
$a2_value,
$a3_value ) );
}
// Clear meta data
$wiki->
Query( SQL_CLEAR_METADATA
);
// Use the total number of users to calculate the pmf value
$max_count =
count( $all_users );
// Refresh meta data
foreach ( $all_users as $current_user )
{
// Start with attribute values of current user
$a0_value =
$current_user[ 'name' ];
$a1_value = get_attribute_value
( $wiki,
$a0_value, COMMENTS_ATTRIBUTE
);
$a2_value = get_attribute_value
( $wiki,
$a0_value, PAGES_OWNED_ATTRIBUTE
);
$a3_value = get_attribute_value
( $wiki,
$a0_value, PAGES_EDITED_ATTRIBUTE
);
// Count similar value combinations and calculate the inverted pmf value
$meta_count =
$wiki->
LoadSingle( sprintf( SQL_COUNT_META,
'a1',
$a1_value,
'a2',
$a2_value ) );
if ( $meta_count ) $a12_value =
round( 1 -
( $meta_count[ 'val' ] /
$max_count ),
5 );
else $a12_value = ERROR_UNDEF_ATTR_VALUE;
$meta_count =
$wiki->
LoadSingle( sprintf( SQL_COUNT_META,
'a1',
$a1_value,
'a3',
$a3_value ) );
if ( $meta_count ) $a13_value =
round( 1 -
( $meta_count[ 'val' ] /
$max_count ),
5 );
else $a13_value = ERROR_UNDEF_ATTR_VALUE;
$meta_count =
$wiki->
LoadSingle( sprintf( SQL_COUNT_META,
'a2',
$a2_value,
'a3',
$a3_value ) );
if ( $meta_count ) $a23_value =
round( 1 -
( $meta_count[ 'val' ] /
$max_count ),
5 );
else $a23_value = ERROR_UNDEF_ATTR_VALUE;
// Randomly select the first value combination to use for the next authentication request
switch ( rand( 1,
3 ) )
{
case 1 :
$b0_value =
( COMMENTS_ATTRIBUTE *
10 ) + PAGES_OWNED_ATTRIBUTE;
break;
case 2 :
$b0_value =
( COMMENTS_ATTRIBUTE *
10 ) + PAGES_EDITED_ATTRIBUTE;
break;
case 3 :
default :
$b0_value =
( PAGES_OWNED_ATTRIBUTE *
10 ) + PAGES_EDITED_ATTRIBUTE;
break;
}
// Find matching uid number for current user
$user_id_number =
$wiki->
LoadSingle( sprintf( SQL_GET_UID,
$a0_value ) );
if ( $user_id_number ) $uid_value =
$user_id_number[ 'uid' ];
else $uid_value = ERROR_UNDEF_ATTR_VALUE;
// Add completed meta data record
$wiki->
Query( sprintf( SQL_ADD_METADATA,
$uid_value,
$a0_value,
$b0_value,
$a12_value,
$a13_value,
$a23_value ) );
}
// Meta data generation is done, so turn flag off
$wiki->
Query( SQL_STOP_REFRESH
);
}
}
//-------------------------------------------------------
//
// Use meta table to identify attributes to use for the current authentication request
//
function get_current_attributes
( &
$wiki,
$uid, &
$attribute_number_1, &
$attribute_number_2 )
{
// Read attribute pair from meta table
$attribute_pair =
$wiki->
LoadSingle( sprintf( SQL_GET_ATTR_PAIR,
mysql_real_escape_string( $uid ) ) );
// Convert to actual attributes
$attribute_combo =
$attribute_pair[ 'b0' ];
// Index of attribute 1 is the quotient after division by 10
$attribute_number_1 =
floor( $attribute_combo /
10 );
// Index of attribute 2 is the remainder after division by 10
$attribute_number_2 =
$attribute_combo %
10;
}
//-------------------------------------------------------
//
// Calculate and save the attributes to use in the next authentication request
//
function set_next_attributes
( &
$wiki,
$uid,
$attribute_number_1,
$attribute_number_2 )
{
// Accomodate small amounts of data by limiting the number of iterations
$max_iterations = QID_SMALL_DATA_LIMIT;
// Modified Metropolis Hastings algorithm
$current_attributes =
( $attribute_number_1 *
10 ) +
$attribute_number_2;
do
{
$next_attributes = get_random_attribute_pair
( $current_attributes );
$mh_ratio = get_attributes_pmf
( $wiki,
$uid,
$next_attributes ) / get_attributes_pmf
( $wiki,
$uid,
$current_attributes );
$std_random_variable =
rand( 1,
100 ) /
100;
} while ( ( $mh_ratio <=
1 ) &&
( $mh_ratio <=
$std_random_variable ) &&
( $max_iterations-- >
0 ) );
// Save attributes for next authentication request
$wiki->
Query( sprintf( SQL_SAVE_NEXT_ATTR,
$next_attributes,
mysql_real_escape_string( $uid ) ) );
}
//-------------------------------------------------------
//
// Select random attributes pair which is not the same as the current attributes paire
//
function get_random_attribute_pair
( $current_attributes )
{
// Accomodate small amounts of data by limiting the number of iterations
$max_iterations = QID_SMALL_DATA_LIMIT;
// Number of available attribute pairs
$max_pairs_available =
3;
// Randomly generate number until random number is not the same as the current attributes pair
do
{
switch( rand( 1,
$max_pairs_available ) )
{
case 1 :
$next_attributes =
( COMMENTS_ATTRIBUTE *
10 ) + PAGES_OWNED_ATTRIBUTE;
break;
case 2 :
$next_attributes =
( COMMENTS_ATTRIBUTE *
10 ) + PAGES_EDITED_ATTRIBUTE;
break;
case 3 :
default :
$next_attributes =
( PAGES_OWNED_ATTRIBUTE *
10 ) + PAGES_EDITED_ATTRIBUTE;
break;
}
} while ( ( $current_attributes ==
$next_attributes ) &&
( $max_iterations-- >
0 ) );
// Return next attributes
return $next_attributes;
}
//-------------------------------------------------------
//
// Retrieve the inverted pmf value for particular attributes
//
function get_attributes_pmf
( &
$wiki,
$uid,
$attributes )
{
$a12_equivalent =
( COMMENTS_ATTRIBUTE *
10 ) + PAGES_OWNED_ATTRIBUTE;
$a13_equivalent =
( COMMENTS_ATTRIBUTE *
10 ) + PAGES_EDITED_ATTRIBUTE;
$a23_equivalent =
( PAGES_OWNED_ATTRIBUTE *
10 ) + PAGES_EDITED_ATTRIBUTE;
switch( $attributes )
{
case $a12_equivalent :
$field_name =
'a12';
break;
case $a13_equivalent :
$field_name =
'a13';
break;
case $a23_equivalent :
default :
$field_name =
'a23';
break;
}
// Read from table
$attribute_pmf =
$wiki->
LoadSingle( sprintf( SQL_GET_ATTR_PMF,
$field_name,
mysql_real_escape_string( $uid ) ) );
if ( $attribute_pmf )
return $attribute_pmf[ 'val' ];
else
return ERROR_UNDEF_ATTR_VALUE;
}
//-------------------------------------------------------
//
// Generate challenge question
//
function generate_question_text
( $attribute_id )
{
$question_text =
"Estimate the number of ";
$append_refresh_range =
1;
switch ( $attribute_id )
{
case PAGES_READ_ATTRIBUTE :
$question_text .=
"pages you've <strong>read</strong> on this website";
break;
case LOGIN_ATTRIBUTE :
$question_text .=
"successful logins you've had";
break;
case PAGES_EDITED_ATTRIBUTE :
$question_text .=
"pages you've <strong>edited</strong> on this website";
break;
case PAGES_OWNED_ATTRIBUTE :
$question_text .=
"pages you <strong>own</strong> on this website. ";
$append_refresh_range =
0;
break;
case COMMENTS_ATTRIBUTE :
default :
$question_text .=
"comments you've written";
break;
}
if ( $append_refresh_range )
$question_text .=
' during the past ' . QID_METADATA_REFRESH_RATE .
' days. ';
$question_text .=
'<strong>Round to the nearest 10</strong>. For example, 0, 10 or 20.';
return $question_text;
}
//-------------------------------------------------------
//
// Find the value (answer) for a selected attribute
//
function get_attribute_value
( &
$wiki,
$uid,
$attribute_number )
{
// Select relevant table column
switch ( $attribute_number )
{
case COMMENTS_ATTRIBUTE :
$attribute_field =
'a1';
break;
case PAGES_OWNED_ATTRIBUTE :
$attribute_field =
'a2';
break;
case PAGES_EDITED_ATTRIBUTE :
default :
$attribute_field =
'a3';
break;
}
// Read from table
$attribute =
$wiki->
LoadSingle( sprintf( SQL_GET_ATTR_VALUE,
$attribute_field,
mysql_real_escape_string( $uid ) ) );
if ( $attribute )
return $attribute[ 'val' ];
else
return ERROR_UNDEF_ATTR_VALUE;
}
//-------------------------------------------------------
//
// Round answer to nearest multiple of 10
//
function round_answer
( $answer_value )
{
if ( ( $answer_value <
0 ) &&
( $answer_value >=
-5 ) )
{
// Manually round to zero because PHP rounding function returns -0 instead of plain zero
return 0;
}
else
return round( $answer_value,
-1 );
}
?>