From 5d6308d491e6dcba87afdb1f0438311c3e07a393 Mon Sep 17 00:00:00 2001 From: nicolasconnault Date: Mon, 10 Mar 2008 17:32:32 +0000 Subject: [PATCH] MDL-13796 Most of the recaptcha implementation is done. --- admin/settings/users.php | 4 +- auth/email/auth.php | 7 + auth/email/config.html | 20 +- lang/en_utf8/admin.php | 4 + lang/en_utf8/auth.php | 10 +- lang/en_utf8/help/grade/decimalpoints.html | 2 +- lang/en_utf8/help/grade/index.html | 77 +++++- lib/form/recaptcha.php | 84 +++++++ lib/formslib.php | 1 + lib/recaptchalib.php | 276 +++++++++++++++++++++ login/signup.php | 13 +- login/signup_form.php | 16 ++ 12 files changed, 501 insertions(+), 13 deletions(-) create mode 100644 lib/form/recaptcha.php create mode 100644 lib/recaptchalib.php diff --git a/admin/settings/users.php b/admin/settings/users.php index aafbfffd2f..9922d0b5cf 100644 --- a/admin/settings/users.php +++ b/admin/settings/users.php @@ -25,7 +25,9 @@ if ($hassiteconfig get_string('authinstructions', 'auth'), '')); $temp->add(new admin_setting_configtext('allowemailaddresses', get_string('allowemailaddresses', 'admin'), get_string('configallowemailaddresses', 'admin'), '', PARAM_NOTAGS)); $temp->add(new admin_setting_configtext('denyemailaddresses', get_string('denyemailaddresses', 'admin'), get_string('configdenyemailaddresses', 'admin'), '', PARAM_NOTAGS)); - + + $temp->add(new admin_setting_configtext('recaptchapublickey', get_string('recaptchapublickey', 'admin'), get_string('configrecaptchapublickey', 'admin'), '', PARAM_NOTAGS)); + $temp->add(new admin_setting_configtext('recaptchaprivatekey', get_string('recaptchaprivatekey', 'admin'), get_string('configrecaptchaprivatekey', 'admin'), '', PARAM_NOTAGS)); $ADMIN->add('authsettings', $temp); diff --git a/auth/email/auth.php b/auth/email/auth.php index 2480271ec5..82e0ba29a6 100644 --- a/auth/email/auth.php +++ b/auth/email/auth.php @@ -197,6 +197,13 @@ class auth_plugin_email extends auth_plugin_base { * Processes and stores configuration data for this authentication plugin. */ function process_config($config) { + // set to defaults if undefined + if (!isset($config->recaptcha)) { + $config->recaptcha = false; + } + + // save settings + set_config('recaptcha', $config->recaptcha, 'auth/email'); return true; } diff --git a/auth/email/config.html b/auth/email/config.html index a2c1bcd5fa..3b11a09a8d 100644 --- a/auth/email/config.html +++ b/auth/email/config.html @@ -1,7 +1,25 @@ -
+recaptcha)) { + $config->recaptcha = false; + } + + $yesno = array( get_string('no'), get_string('yes') ); + +?> + + + + + + + +If this is left empty, no password is required.'; $string['configrcache'] = 'Use the cache to store database records. Remember to set \'cachetype\' as well!'; $string['configrcachettl'] = 'Time-to-live for cached records, in seconds. Use a short (<15) value here.'; +$string['configrecaptchaprivatekey'] = 'String of characters used to communicate between your Moodle server and the recaptcha.net server. Obtain one for this site by visiting http://recaptcha.net'; +$string['configrecaptchapublickey'] = 'String of characters used to display the reCAPTCHA element in the signup form. Generated by http://recaptcha.net'; $string['configrequestedstudentname'] = 'Word for student used in requested courses'; $string['configrequestedstudentsname'] = 'Word for students used in requested courses'; $string['configrequestedteachername'] = 'Word for teacher used in requested courses'; @@ -586,6 +588,8 @@ $string['questioncwqpfscheck'] = 'One or more \'random\' questions in a quiz are $string['questioncwqpfsok'] = 'Good. There are no \'random\' questions in your quizzes that are set up to select questions from a mixture of shared and unshared question categories.'; $string['rcache'] = 'Record cache'; $string['rcachettl'] = 'Record cache TTL'; +$string['recaptchapublickey'] = 'ReCAPTCHA public key'; +$string['recaptchaprivatekey'] = 'ReCAPTCHA private key'; $string['releasenoteslink'] = 'For information about this version of Moodle, please see the online Release Notes'; $string['remotelangnotavailable'] = 'Because Moodle can not connect to download.moodle.org, we are unable to do language pack installation automatically. Please download the appropriate zip file(s) from the list below, copy them to your $a directory and unzip them manually.'; $string['renameerrors'] = 'Rename errors'; diff --git a/lang/en_utf8/auth.php b/lang/en_utf8/auth.php index dcb4d9bb0b..bf0c32399d 100644 --- a/lang/en_utf8/auth.php +++ b/lang/en_utf8/auth.php @@ -117,6 +117,9 @@ $string['auth_emaildescription'] = 'Email confirmation is the default authentica $string['auth_emailtitle'] = 'Email-based self-registration'; $string['auth_emailnoinsert'] = 'Could not add your record to the database!'; $string['auth_emailnoemail'] = 'Tried to send you an email but failed!'; +$string['auth_emailrecaptcha'] = 'Adds a visual/audio confirmation form element to the signup page for email self-registering users. This protects your site against spammers and contributes to a worthwhile cause. See http://recaptcha.net/learnmore.html for more details.'; +$string['auth_emailrecaptcha_key'] = 'Enable reCAPTCHA element'; +$string['auth_emailsettings'] = 'Settings'; // FirstClass plugin $string['auth_fccreators'] = 'List of groups whose members are allowed to create new courses. Separate multiple groups with \';\'. Names must be spelled exactly as on FirstClass server. System is case-sensitive.'; @@ -374,5 +377,10 @@ $string['update_never'] = 'Never'; $string['unlocked'] = 'Unlocked'; $string['unlockedifempty'] = 'Unlocked if empty'; $string['locked'] = 'Locked'; - +$string['incorrectpleasetryagain'] = 'Incorrect. Please try again.'; +$string['enterthewordsabove'] = 'Enter the words above:'; +$string['enterthenumbersyouhear'] = 'Enter the numbers you hear:'; +$string['getanothercaptcha'] = 'Get another CAPTCHA'; +$string['getanaudiocaptcha'] = 'Get an audio CAPTCHA'; +$string['getanimagecaptcha'] = 'Get an image CAPTCHA'; ?> diff --git a/lang/en_utf8/help/grade/decimalpoints.html b/lang/en_utf8/help/grade/decimalpoints.html index fbf61e2f93..a4e89f2b83 100644 --- a/lang/en_utf8/help/grade/decimalpoints.html +++ b/lang/en_utf8/help/grade/decimalpoints.html @@ -1,2 +1,2 @@

Overall decimal points

-

Specifies the number of decimal points to display for each grade. This setting may be overridden per grading item.

+

Specifies the number of decimal points to display for each grade. This setting has no effect on grade calculations, which are made with an accuracy of 5 decimal places.

diff --git a/lang/en_utf8/help/grade/index.html b/lang/en_utf8/help/grade/index.html index 091bde856d..450410edc4 100644 --- a/lang/en_utf8/help/grade/index.html +++ b/lang/en_utf8/help/grade/index.html @@ -1,10 +1,71 @@

Grade

\ No newline at end of file +
  • Aggregate only non-empty grades
  • +
  • Include outcomes in aggregation
  • +
  • Aggregate including subcategories
  • +
  • Category aggregation
  • +
  • Extra credit
  • +
  • Extra credit
  • +
  • Item weight
  • +
  • Aggregation position
  • +
  • Aggregation view
  • +
  • Decimals in column averages
  • +
  • Column averages display type
  • +
  • Calculations
  • +
  • Category
  • +
  • Overall decimal points
  • +
  • Drop the lowest
  • +
  • Exceptions
  • +
  • Excluded grades
  • +
  • Export decimal points
  • +
  • Feedback
  • +
  • Final grade
  • +
  • Grade boundary
  • +
  • Grade display type
  • +
  • Grade export display type
  • +
  • Grade letters
  • +
  • Maximum Grade
  • +
  • Minimum Grade
  • +
  • Grade to pass
  • +
  • Grade type
  • +
  • Hidden
  • +
  • Hidden until
  • +
  • Id numbers
  • +
  • Item info
  • +
  • Keep the highest
  • +
  • Letter scale
  • +
  • Linked activity
  • +
  • Locked
  • +
  • Locked after
  • +
  • Grades selected for column averages
  • +
  • Multiplicator
  • +
  • Outcome
  • +
  • Standard outcomes
  • +
  • Overridden
  • +
  • Override site defaults
  • +
  • Offset
  • +
  • Set Preferences
  • +
  • Quick grading
  • +
  • Decimals shown in ranges
  • +
  • Range display type
  • +
  • Scale
  • +
  • Standard scales
  • +
  • Show activity icons
  • +
  • Show averages
  • +
  • Show calculations
  • +
  • Show show/hide icons
  • +
  • Show feedback
  • +
  • Show groups
  • +
  • Show hidden items
  • +
  • Show locks
  • +
  • Show number of grades
  • +
  • Show quick feedback
  • +
  • Show ranges
  • +
  • Show rank
  • +
  • Show user id numbers
  • +
  • Student Grade Help
  • +
  • Students per page
  • +
  • Grades
  • +
  • User key
  • +
  • Weight
  • + diff --git a/lib/form/recaptcha.php b/lib/form/recaptcha.php new file mode 100644 index 0000000000..34ce626e77 --- /dev/null +++ b/lib/form/recaptcha.php @@ -0,0 +1,84 @@ + + * @version $Id$ + */ + + +/** + * @category Admin + * @package admin + */ +class MoodleQuickForm_recaptcha extends HTML_QuickForm_input { + + /** + * + * $form->addElement('textarea_counter', 'message', 'Message', + * array('cols'=>60, 'rows'=>10), 160); + * + */ + function HTML_QuickForm_recaptcha($elementName = null, $elementLabel = null, $attributes = null) { + parent::HTML_QuickForm_input($elementName, $elementLabel, $attributes); + $this->_type = 'recaptcha'; + } + + /** + * Returns the recaptcha element in HTML + * + * @since 1.0 + * @access public + * @return string + */ + function toHtml() { + global $CFG; + require_once $CFG->libdir . '/recaptchalib.php'; + + $html = '' . "\n"; + + + if (empty($_SESSION['recaptcha_error'])) { + $_SESSION['recaptcha_error'] = null; + } + $error = $_SESSION['recaptcha_error']; + unset($_SESSION['recaptcha_error']); + + $strincorrectpleasetryagain = get_string('incorrectpleasetryagain', 'auth'); + $strenterthewordsabove = get_string('enterthewordsabove', 'auth'); + $strenterthenumbersyouhear = get_string('enterthenumbersyouhear', 'auth'); + $strgetanothercaptcha = get_string('getanothercaptcha', 'auth'); + $strgetanaudiocaptcha = get_string('getanaudiocaptcha', 'auth'); + $strgetanimagecaptcha = get_string('getanimagecaptcha', 'auth'); + + $html .= ' +'; + + return $html . recaptcha_get_html($CFG->recaptchapublickey, $error); + } +} + +?> diff --git a/lib/formslib.php b/lib/formslib.php index 57ebb4173f..e2f3e7dea0 100644 --- a/lib/formslib.php +++ b/lib/formslib.php @@ -1876,4 +1876,5 @@ MoodleQuickForm::registerElementType('header', "$CFG->libdir/form/header.php", ' MoodleQuickForm::registerElementType('submit', "$CFG->libdir/form/submit.php", 'MoodleQuickForm_submit'); MoodleQuickForm::registerElementType('questioncategory', "$CFG->libdir/form/questioncategory.php", 'MoodleQuickForm_questioncategory'); MoodleQuickForm::registerElementType('advcheckbox', "$CFG->libdir/form/advcheckbox.php", 'MoodleQuickForm_advcheckbox'); +MoodleQuickForm::registerElementType('recaptcha', "$CFG->libdir/form/recaptcha.php", 'MoodleQuickForm_recaptcha'); ?> diff --git a/lib/recaptchalib.php b/lib/recaptchalib.php new file mode 100644 index 0000000000..aea6e0d75e --- /dev/null +++ b/lib/recaptchalib.php @@ -0,0 +1,276 @@ + $value ) + $req .= $key . '=' . urlencode( stripslashes($value) ) . '&'; + + // Cut the last '&' + $req=substr($req,0,strlen($req)-1); + return $req; +} + + + +/** + * Submits an HTTP POST to a reCAPTCHA server + * @param string $host + * @param string $path + * @param array $data + * @param int port + * @return array response + */ +function _recaptcha_http_post($host, $path, $data, $port = 80) { + + $req = _recaptcha_qsencode ($data); + + $http_request = "POST $path HTTP/1.0\r\n"; + $http_request .= "Host: $host\r\n"; + $http_request .= "Content-Type: application/x-www-form-urlencoded;\r\n"; + $http_request .= "Content-Length: " . strlen($req) . "\r\n"; + $http_request .= "User-Agent: reCAPTCHA/PHP\r\n"; + $http_request .= "\r\n"; + $http_request .= $req; + + $response = ''; + if( false == ( $fs = @fsockopen($host, $port, $errno, $errstr, 10) ) ) { + die ('Could not open socket'); + } + + fwrite($fs, $http_request); + + while ( !feof($fs) ) + $response .= fgets($fs, 1160); // One TCP-IP packet + fclose($fs); + $response = explode("\r\n\r\n", $response, 2); + + return $response; +} + + + +/** + * Gets the challenge HTML (javascript and non-javascript version). + * This is called from the browser, and the resulting reCAPTCHA HTML widget + * is embedded within the HTML form it was called from. + * @param string $pubkey A public key for reCAPTCHA + * @param string $error The error given by reCAPTCHA (optional, default is null) + * @param boolean $use_ssl Should the request be made over ssl? (optional, default is false) + + * @return string - The HTML to be embedded in the user's form. + */ +function recaptcha_get_html ($pubkey, $error = null, $use_ssl = false) +{ + if ($pubkey == null || $pubkey == '') { + die ("To use reCAPTCHA you must get an API key from http://recaptcha.net/api/getkey"); + } + + if ($use_ssl) { + $server = RECAPTCHA_API_SECURE_SERVER; + } else { + $server = RECAPTCHA_API_SERVER; + } + + $errorpart = ""; + if ($error) { + $errorpart = "&error=" . $error; + } + return ' + + '; +} + + + + +/** + * A ReCaptchaResponse is returned from recaptcha_check_answer() + */ +class ReCaptchaResponse { + var $is_valid; + var $error; +} + + +/** + * Calls an HTTP POST function to verify if the user's guess was correct + * @param string $privkey + * @param string $remoteip + * @param string $challenge + * @param string $response + * @return ReCaptchaResponse + */ +function recaptcha_check_answer ($privkey, $remoteip, $challenge, $response) +{ + if ($privkey == null || $privkey == '') { + die ("To use reCAPTCHA you must get an API key from http://recaptcha.net/api/getkey"); + } + + if ($remoteip == null || $remoteip == '') { + die ("For security reasons, you must pass the remote ip to reCAPTCHA"); + } + + + + //discard spam submissions + if ($challenge == null || strlen($challenge) == 0 || $response == null || strlen($response) == 0) { + $recaptcha_response = new ReCaptchaResponse(); + $recaptcha_response->is_valid = false; + $recaptcha_response->error = 'incorrect-captcha-sol'; + return $recaptcha_response; + } + + $response = _recaptcha_http_post (RECAPTCHA_VERIFY_SERVER, "/verify", + array ( + 'privatekey' => $privkey, + 'remoteip' => $remoteip, + 'challenge' => $challenge, + 'response' => $response + ) + ); + + $answers = explode ("\n", $response [1]); + $recaptcha_response = new ReCaptchaResponse(); + + if (trim ($answers [0]) == 'true') { + $recaptcha_response->is_valid = true; + } + else { + $recaptcha_response->is_valid = false; + $recaptcha_response->error = $answers [1]; + } + return $recaptcha_response; + +} + +/** + * gets a URL where the user can sign up for reCAPTCHA. If your application + * has a configuration page where you enter a key, you should provide a link + * using this function. + * @param string $domain The domain where the page is hosted + * @param string $appname The name of your application + */ +function recaptcha_get_signup_url ($domain = null, $appname = null) { + return "http://recaptcha.net/api/getkey?" . _recaptcha_qsencode (array ('domain' => $domain, 'app' => $appname)); +} + +function _recaptcha_aes_pad($val) { + $block_size = 16; + $numpad = $block_size - (strlen ($val) % $block_size); + return str_pad($val, strlen ($val) + $numpad, chr($numpad)); +} + +/* Mailhide related code */ + +function _recaptcha_aes_encrypt($val,$ky) { + if (! function_exists ("mcrypt_encrypt")) { + die ("To use reCAPTCHA Mailhide, you need to have the mcrypt php module installed."); + } + $mode=MCRYPT_MODE_CBC; + $enc=MCRYPT_RIJNDAEL_128; + $val=_recaptcha_aes_pad($val); + return mcrypt_encrypt($enc, $ky, $val, $mode, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"); +} + + +function _recaptcha_mailhide_urlbase64 ($x) { + return strtr(base64_encode ($x), '+/', '-_'); +} + +/* gets the reCAPTCHA Mailhide url for a given email, public key and private key */ +function recaptcha_mailhide_url($pubkey, $privkey, $email) { + if ($pubkey == '' || $pubkey == null || $privkey == "" || $privkey == null) { + die ("To use reCAPTCHA Mailhide, you have to sign up for a public and private key, " . + "you can do so at http://mailhide.recaptcha.net/apikey"); + } + + + $ky = pack('H*', $privkey); + $cryptmail = _recaptcha_aes_encrypt ($email, $ky); + + return "http://mailhide.recaptcha.net/d?k=" . $pubkey . "&c=" . _recaptcha_mailhide_urlbase64 ($cryptmail); +} + +/** + * gets the parts of the email to expose to the user. + * eg, given johndoe@example,com return ["john", "example.com"]. + * the email is then displayed as john...@example.com + */ +function _recaptcha_mailhide_email_parts ($email) { + $arr = preg_split("/@/", $email ); + + if (strlen ($arr[0]) <= 4) { + $arr[0] = substr ($arr[0], 0, 1); + } else if (strlen ($arr[0]) <= 6) { + $arr[0] = substr ($arr[0], 0, 3); + } else { + $arr[0] = substr ($arr[0], 0, 4); + } + return $arr; +} + +/** + * Gets html to display an email address given a public an private key. + * to get a key, go to: + * + * http://mailhide.recaptcha.net/apikey + */ +function recaptcha_mailhide_html($pubkey, $privkey, $email) { + $emailparts = _recaptcha_mailhide_email_parts ($email); + $url = recaptcha_mailhide_url ($pubkey, $privkey, $email); + + return htmlentities($emailparts[0]) . "...@" . htmlentities ($emailparts [1]); + +} + + +?> diff --git a/login/signup.php b/login/signup.php index b5b780ed7b..1cac1cb109 100644 --- a/login/signup.php +++ b/login/signup.php @@ -1,7 +1,18 @@ recaptchapublickey) && isset($CFG->recaptchaprivatekey) && get_config('auth/email', 'recaptcha'); + } + require_once('signup_form.php'); + if (empty($CFG->registerauth)) { error("Sorry, you may not use this page."); @@ -48,7 +59,7 @@ $navlinks[] = array('name' => $newaccount, 'link' => null, 'type' => 'misc'); $navigation = build_navigation($navlinks); print_header($newaccount, $newaccount, $navigation, $mform_signup->focus(), "", true, "
    $langmenu
    "); - + $mform_signup->display(); print_footer(); diff --git a/login/signup_form.php b/login/signup_form.php index fc6e9ea87e..5a5e3ac3e3 100644 --- a/login/signup_form.php +++ b/login/signup_form.php @@ -62,6 +62,10 @@ class login_signup_form extends moodleform { }else{ $mform->setDefault('country', ''); } + + if (signup_captcha_enabled()) { + $mform->addElement('recaptcha', 'recaptcha_element', get_string('visualconfirmation')); + } profile_signup_fields($mform); @@ -131,6 +135,18 @@ class login_signup_form extends moodleform { if (!check_password_policy($data['password'], $errmsg)) { $errors['password'] = $errmsg; } + + if (signup_captcha_enabled()) { + require_once $CFG->libdir . '/recaptchalib.php'; + $response = recaptcha_check_answer($CFG->recaptchaprivatekey, + $_SERVER['REMOTE_ADDR'], + $this->_form->_submitValues['recaptcha_challenge_field'], + $this->_form->_submitValues['recaptcha_response_field']); + if (!$response->is_valid) { + $_SESSION['recaptcha_error'] = $response->error; + $errors['recaptcha'] = $response->error; + } + } return $errors; -- 2.39.5
    +

    +
    recaptcha, ''); ?>