diff --git a/badges/backpack_form.php b/badges/backpack_form.php index ad9d427feb1..dda5ed4774e 100644 --- a/badges/backpack_form.php +++ b/badges/backpack_form.php @@ -46,23 +46,6 @@ class edit_backpack_form extends moodleform { $mform->addElement('header', 'backpackheader', get_string('backpackconnection', 'badges')); $mform->addHelpButton('backpackheader', 'backpackconnection', 'badges'); $mform->addElement('static', 'url', get_string('url'), BADGE_BACKPACKURL); - $status = html_writer::tag('span', get_string('notconnected', 'badges'), - array('class' => 'notconnected', 'id' => 'connection-status')); - $mform->addElement('static', 'status', get_string('status'), $status); - - $nojs = html_writer::tag('noscript', get_string('error:personaneedsjs', 'badges'), - array('class' => 'notconnected')); - $personadiv = $OUTPUT->container($nojs, null, 'persona-container'); - - $mform->addElement('static', 'persona', '', $personadiv); - $mform->addHelpButton('persona', 'personaconnection', 'badges'); - - $PAGE->requires->js(new moodle_url('https://login.persona.org/include.js')); - $PAGE->requires->js('/badges/backpack.js'); - $PAGE->requires->js_init_call('badges_init_persona_login_button', null, false); - $PAGE->requires->strings_for_js(array('error:backpackloginfailed', 'signinwithyouremail', - 'error:noassertion', 'error:connectionunknownreason', 'error:badjson', 'connecting', - 'notconnected'), 'badges'); $mform->addElement('hidden', 'userid', $USER->id); $mform->setType('userid', PARAM_INT); @@ -70,23 +53,55 @@ class edit_backpack_form extends moodleform { $mform->addElement('hidden', 'backpackurl', BADGE_BACKPACKURL); $mform->setType('backpackurl', PARAM_URL); + if (isset($this->_customdata['email'])) { + // Email will be passed in when we're in the process of verifying the user's email address, + // so set the connection status, lock the email field, and provide options to resend the verification + // email or cancel the verification process entirely and start over. + $status = html_writer::tag('span', get_string('backpackemailverificationpending', 'badges'), + array('class' => 'notconnected', 'id' => 'connection-status')); + $mform->addElement('static', 'status', get_string('status'), $status); + $mform->addElement('hidden', 'email', $this->_customdata['email']); + $mform->setType('email', PARAM_RAW_TRIMMED); + $mform->hardFreeze(['email']); + $status = html_writer::tag('span', $this->_customdata['email'], []); + $mform->addElement('static', 'emailverify', get_string('email'), $status); + $buttonarray = []; + $buttonarray[] = &$mform->createElement('submit', 'submitbutton', + get_string('backpackconnectionresendemail', 'badges')); + $buttonarray[] = &$mform->createElement('submit', 'revertbutton', + get_string('backpackconnectioncancelattempt', 'badges')); + $mform->addGroup($buttonarray, 'buttonar', '', [''], false); + $mform->closeHeaderBefore('buttonar'); + } else { + // Email isn't present, so provide an input element to get it and a button to start the verification process. + $status = html_writer::tag('span', get_string('notconnected', 'badges'), + array('class' => 'notconnected', 'id' => 'connection-status')); + $mform->addElement('static', 'status', get_string('status'), $status); + $mform->addElement('text', 'email', get_string('email'), 'maxlength="100" size="30"'); + $mform->addHelpButton('email', 'backpackemail', 'badges'); + $mform->addRule('email', get_string('required'), 'required', null, 'client'); + $mform->setType('email', PARAM_RAW_TRIMMED); + $this->add_action_buttons(false, get_string('backpackconnectionconnect', 'badges')); + } } /** * Validates form data */ public function validation($data, $files) { - global $DB; $errors = parent::validation($data, $files); - $check = new stdClass(); - $check->backpackurl = $data['backpackurl']; - $check->email = $data['email']; + // We don't need to verify the email address if we're clearing a pending email verification attempt. + if (!isset($data['revertbutton'])) { + $check = new stdClass(); + $check->backpackurl = $data['backpackurl']; + $check->email = $data['email']; - $bp = new OpenBadgesBackpackHandler($check); - $request = $bp->curl_request('user'); - if (isset($request->status) && $request->status == 'missing') { - $errors['email'] = get_string('error:nosuchuser', 'badges'); + $bp = new OpenBadgesBackpackHandler($check); + $request = $bp->curl_request('user'); + if (isset($request->status) && $request->status == 'missing') { + $errors['email'] = get_string('error:nosuchuser', 'badges'); + } } return $errors; } diff --git a/badges/backpackemailverify.php b/badges/backpackemailverify.php new file mode 100644 index 00000000000..2ffb35fe315 --- /dev/null +++ b/badges/backpackemailverify.php @@ -0,0 +1,69 @@ +. + +/** + * Endpoint for the verification email link. + * + * @package core + * @subpackage badges + * @copyright 2016 Jake Dallimore + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +require_once(__DIR__ . '/../config.php'); +require_once($CFG->libdir . '/badgeslib.php'); +require_once(__DIR__ . '/lib/backpacklib.php'); + +$data = optional_param('data', '', PARAM_RAW); +require_login(); +$PAGE->set_url('/badges/openbackpackemailverify.php'); +$PAGE->set_context(context_user::instance($USER->id)); +$redirect = '/badges/mybackpack.php'; + +// Confirm the secret and create the backpack connection. +$storedsecret = get_user_preferences('badges_email_verify_secret'); +if (!is_null($storedsecret)) { + if ($data === $storedsecret) { + $storedemail = get_user_preferences('badges_email_verify_address'); + + $data = new stdClass(); + $data->backpackurl = BADGE_BACKPACKURL; + $data->email = $storedemail; + $bp = new OpenBadgesBackpackHandler($data); + + $obj = new stdClass(); + $obj->userid = $USER->id; + $obj->email = $data->email; + $obj->backpackurl = $data->backpackurl; + $obj->backpackuid = $bp->curl_request('user')->userId; + $obj->autosync = 0; + $obj->password = ''; + $DB->insert_record('badge_backpack', $obj); + + // Remove the verification vars and redirect to the mypackpack page. + unset_user_preference('badges_email_verify_secret'); + unset_user_preference('badges_email_verify_address'); + redirect(new moodle_url($redirect), get_string('backpackemailverifysuccess', 'badges'), + null, \core\output\notification::NOTIFY_SUCCESS); + } else { + // Stored secret doesn't match the supplied secret. Take user back to the mybackpack page and present a warning message. + redirect(new moodle_url($redirect), get_string('backpackemailverifytokenmismatch', 'badges'), + null, \core\output\notification::NOTIFY_ERROR); + } +} else { + // Stored secret is null. Either the email address has already been verified, or there is no record of a verification attempt + // for the current user. Either way, just redirect to the mybackpack page. + redirect(new moodle_url($redirect)); +} diff --git a/badges/lib/backpacklib.php b/badges/lib/backpacklib.php index a32e67f1430..1fe156f6a7e 100644 --- a/badges/lib/backpacklib.php +++ b/badges/lib/backpacklib.php @@ -117,3 +117,45 @@ class OpenBadgesBackpackHandler { return $this->backpack; } } + +/** + * Create and send a verification email to the email address supplied. + * + * Since we're not sending this email to a user, email_to_user can't be used + * but this function borrows largely the code from that process. + * + * @param string $email the email address to send the verification email to. + * @return true if the email was sent successfully, false otherwise. + */ +function send_verification_email($email) { + global $DB, $USER; + + // Store a user secret (badges_email_verify_secret) and the address (badges_email_verify_address) as users prefs. + // The address will be used by edit_backpack_form for display during verification and to facilitate the resending + // of verification emails to said address. + $secret = random_string(15); + set_user_preference('badges_email_verify_secret', $secret); + set_user_preference('badges_email_verify_address', $email); + + // To, from. + $tempuser = $DB->get_record('user', array('id' => $USER->id), '*', MUST_EXIST); + $tempuser->email = $email; + $noreplyuser = core_user::get_noreply_user(); + + // Generate the verification email body. + $verificationurl = '/badges/backpackemailverify.php'; + $verificationurl = new moodle_url($verificationurl); + $verificationpath = $verificationurl->out(false); + + $site = get_site(); + $args = new stdClass(); + $args->link = $verificationpath . '?data='. $secret; + $args->sitename = $site->fullname; + $args->admin = generate_email_signoff(); + + $messagesubject = get_string('backpackemailverifyemailsubject', 'badges', $site->fullname); + $messagetext = get_string('backpackemailverifyemailbody', 'badges', $args); + $messagehtml = text_to_html($messagetext, false, false, true); + + return email_to_user($tempuser, $noreplyuser, $messagesubject, $messagetext, $messagehtml); +} diff --git a/badges/mybackpack.php b/badges/mybackpack.php index 7343d92aa90..5a403386e94 100644 --- a/badges/mybackpack.php +++ b/badges/mybackpack.php @@ -110,23 +110,39 @@ if ($backpack) { } } else { // If backpack is not connected, need to connect first. - $form = new edit_backpack_form(); + // To create a new connection to the backpack, first we need to verify the user's email address: + // 1. User enters email and clicks 'Connect to backpack'. + // 2. After cross-checking the email address against the backpack provider, an email is sent to the specified address, + // and the email and secret are stored in user preferences. These will be cleared upon successful verification. + // 3. User clicks verification link in the email to confirm the backpack connection. + // 4. User redirected to the mybackpack page. + // While the verification process is pending, the edit_backpack_form form will present the user with options to resend the + // verification email, and to cancel the current verification attempt and start over. + // To pass through the current state of the verification attempt to the form. + $params['email'] = get_user_preferences('badges_email_verify_address'); + + $form = new edit_backpack_form(new moodle_url('/badges/mybackpack.php'), $params); if ($form->is_cancelled()) { redirect(new moodle_url('/badges/mybadges.php')); } else if ($data = $form->get_data()) { - $bp = new OpenBadgesBackpackHandler($data); - - $obj = new stdClass(); - $obj->userid = $data->userid; - $obj->email = $data->email; - $obj->backpackurl = $data->backpackurl; - $obj->backpackuid = $bp->curl_request('user')->userId; - $obj->autosync = 0; - $obj->password = ''; - $DB->insert_record('badge_backpack', $obj); - - redirect(new moodle_url('/badges/mybackpack.php')); + // The form may have been submitted under one of the following circumstances: + // 1. After clicking 'Connect to backpack'. We'll have $data->email. + // 2. After clicking 'Resend verification email'. We'll have $data->email. + // 3. After clicking 'Connect using a different email' to cancel the verification process. We'll have $data->revertbutton. + if (isset($data->revertbutton)) { + unset_user_preference('badges_email_verify_secret'); + unset_user_preference('badges_email_verify_address'); + redirect(new moodle_url('/badges/mybackpack.php')); + } else if (isset($data->email)) { + if (send_verification_email($data->email)) { + redirect(new moodle_url('/badges/mybackpack.php'), + get_string('backpackemailverifypending', 'badges', $data->email), + null, \core\output\notification::NOTIFY_INFO); + } else { + print_error ('backpackcannotsendverification', 'badges'); + } + } } } diff --git a/lang/en/badges.php b/lang/en/badges.php index ac1445db89c..7e9cda52ac9 100644 --- a/lang/en/badges.php +++ b/lang/en/badges.php @@ -79,6 +79,7 @@ You should fix this issue on any production site where you are planning to issue The only URL required for verification is [your-site-url]/badges/assertion.php so if you are able to modify your firewall to allow external access to that file, badge verification will still work.'; $string['backpackbadges'] = 'You have {$a->totalbadges} badge(s) displayed from {$a->totalcollections} collection(s). Change backpack settings.'; +$string['backpackcannotsendverification'] = 'Cannot send verification email'; $string['backpackconnection'] = 'Backpack connection'; $string['backpackconnection_help'] = 'This page allows you to set up connection to an external backpack provider. Connecting to a backpack lets you display external badges within this site and push badges earned here to your backpack. @@ -90,6 +91,26 @@ $string['personaconnection'] = 'Sign in with your email'; $string['personaconnection_help'] = 'Persona is a system for identifying yourself across the web, using an email address that you own. The Open Badges backpack uses Persona as a login system, so to be able to connect to a backpack you will need a Persona account. For more information about Persona visit https://login.persona.org/about.'; +$string['backpackconnectioncancelattempt'] = 'Connect using a different email address'; +$string['backpackconnectionconnect'] = 'Connect to Backpack'; +$string['backpackconnectionresendemail'] = 'Resend verification email'; +$string['backpackemailverificationpending'] = 'Verification pending'; +$string['backpackemailverifyemailbody'] = 'Hi, + +A new connection to your OpenBadges backpack has been requested from \'{$a->sitename}\' using your email address. + +To confirm and activate the connection to your backpack, please click the link below. + +{$a->link} + +In most mail programs, this should appear as a blue link which you can just click on. If that doesn\'t work, then cut and paste the address into the address line at the top of your web browser. + +If you need help, please contact the site administrator, +{$a->admin}'; +$string['backpackemailverifyemailsubject'] = '{$a}: OpenBadges Backpack email verification'; +$string['backpackemailverifypending'] = 'A verification email has been sent to {$a}. Click on the verification link in the email to activate your Backpack connection.'; +$string['backpackemailverifysuccess'] = 'Thanks for verifying your email address. You are now connected to your Backpack.'; +$string['backpackemailverifytokenmismatch'] = 'The token in the link you clicked does not match the stored token. Make sure you clicked the link in most recent email you received.'; $string['backpackimport'] = 'Badge import settings'; $string['backpackimport_help'] = 'After the backpack connection is successfully established, badges from your backpack can be displayed on your badges page and your profile page.