Skip to content

Commit

Permalink
MDL-65959 core_badges: Allow ability to upload badges cross domain.
Browse files Browse the repository at this point in the history
  • Loading branch information
Peter committed Oct 26, 2020
1 parent 6f7fe5d commit 8dbc7c6
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 17 deletions.
42 changes: 37 additions & 5 deletions badges/backpack-add.php
Expand Up @@ -48,7 +48,8 @@
$badgeid = $issuedbadge->badgeid;
$badge = new badge($badgeid);
$backpack = $DB->get_record('badge_backpack', array('userid' => $USER->id));
$sitebackpack = badges_get_site_backpack($backpack->externalbackpackid);
$sitebackpack = badges_get_site_primary_backpack();
$userbackpack = badges_get_site_backpack($backpack->externalbackpackid);
$assertion = new core_badges_assertion($id, $sitebackpack->apiversion);
$api = new \core_badges\backpack_api($sitebackpack);
$api->authenticate();
Expand All @@ -61,7 +62,8 @@
throw new moodle_exception('invalidrequest', 'error');
}
$issuerentityid = $response->id;
badges_external_create_mapping($sitebackpack->id, OPEN_BADGES_V2_TYPE_ISSUER, $issuer['email'], $issuerentityid);
badges_external_create_mapping($sitebackpack->id, OPEN_BADGES_V2_TYPE_ISSUER, $issuer['email'],
$issuerentityid, $response->openBadgeId);
}
// Create badge.
$badge = $assertion->get_badge_class(false);
Expand All @@ -72,25 +74,55 @@
throw new moodle_exception('invalidrequest', 'error');
}
$badgeentityid = $response->id;
badges_external_create_mapping($sitebackpack->id, OPEN_BADGES_V2_TYPE_BADGE, $badgeid, $badgeentityid);
badges_external_create_mapping($sitebackpack->id, OPEN_BADGES_V2_TYPE_BADGE, $badgeid,
$badgeentityid, $response->hostedUrl);
}

// Create assertion (Award the badge!).
$assertiondata = $assertion->get_badge_assertion(false, false);

$assertionid = $assertion->get_assertion_hash();
$assertionentityid = badges_external_get_mapping(
$sitebackpack->id,
OPEN_BADGES_V2_TYPE_ASSERTION,
$assertionid
);

if (!($assertionentityid = badges_external_get_mapping($sitebackpack->id, OPEN_BADGES_V2_TYPE_ASSERTION, $assertionid))) {
if (!$assertionentityid && strpos($sitebackpack->backpackapiurl, 'badgr')) {
$assertionentityid = badges_generate_badgr_open_url(
$sitebackpack,
OPEN_BADGES_V2_TYPE_ASSERTION,
$assertionentityid
);
}

// Create an assertion for the recipient in the issuer's account.
if (!$assertionentityid) {
$response = $api->put_badgeclass_assertion($badgeentityid, $assertiondata);
if (!$response) {
throw new moodle_exception('invalidrequest', 'error');
}
$assertionentityid = badges_generate_badgr_open_url($sitebackpack, OPEN_BADGES_V2_TYPE_ASSERTION, $response->id);
badges_external_create_mapping($sitebackpack->id, OPEN_BADGES_V2_TYPE_ASSERTION, $assertionid,
$response->id);
}

// Now award/upload the badge to the user's account.
if ($assertionentityid && !badges_external_get_mapping($userbackpack->id, OPEN_BADGES_V2_TYPE_ASSERTION, $assertionid)) {
$userapi = new \core_badges\backpack_api($userbackpack, $backpack);
$userapi->authenticate();
$response = $userapi->import_badge_assertion($assertionentityid);
if (!$response) {
throw new moodle_exception('invalidrequest', 'error');
}
$assertionentityid = $response->id;
badges_external_create_mapping($sitebackpack->id, OPEN_BADGES_V2_TYPE_ASSERTION, $assertionid, $assertionentityid);
badges_external_create_mapping($userbackpack->id, OPEN_BADGES_V2_TYPE_ASSERTION, $assertionid,
$assertionentityid);
$response = ['success' => 'addedtobackpack'];
} else {
$response = ['warning' => 'existsinbackpack'];
}

redirect(new moodle_url('/badges/mybadges.php', $response));
} else {
redirect(new moodle_url('/badges/mybadges.php'));
Expand Down
33 changes: 31 additions & 2 deletions badges/classes/backpack_api.php
Expand Up @@ -87,7 +87,6 @@ public function __construct($sitebackpack, $userbackpack = false) {
global $CFG;
$admin = get_admin();

$this->backpackapiurl = $sitebackpack->backpackapiurl;
$this->backpackapiurl = $sitebackpack->backpackapiurl;
$this->backpackapiversion = $sitebackpack->apiversion;
$this->password = $sitebackpack->password;
Expand Down Expand Up @@ -152,6 +151,21 @@ private function define_mappings() {
true, // JSON Encoded.
true // Auth required.
];
$mapping[] = [
'importbadge', // Action.
// Badgr.io does not return the public information about a badge
// if the issuer is associated with another user. We need to pass
// the expand parameters which are not in any specification to get
// additional information about the assertion in a single request.
'[URL]/backpack/import',
['url' => '[PARAM]'], // Post params.
'', // Request exporter.
'core_badges\external\assertion_exporter', // Response exporter.
false, // Multiple.
'post', // Method.
true, // JSON Encoded.
true // Auth required.
];
$mapping[] = [
'badges', // Action.
'[URL]/backpack/collections/[PARAM1]', // URL
Expand Down Expand Up @@ -408,6 +422,22 @@ public function put_badgeclass_assertion($entityid, $data) {
return $this->curl_request('assertions', null, $entityid, $data);
}

/**
* Import a badge assertion into a backpack. This is used to handle cross domain backpacks.
*
* @param string $data The structure of the badge class assertion.
* @return mixed
* @throws coding_exception
*/
public function import_badge_assertion(string $data) {
// V2 Only.
if ($this->backpackapiversion == OPEN_BADGES_V1) {
throw new coding_exception('Not supported in this backpack API');
}

return $this->curl_request('importbadge', null, null, $data);
}

/**
* Select collections from a backpack.
*
Expand Down Expand Up @@ -570,7 +600,6 @@ public function get_collection_record($collectionid) {
*
* @param integer $userid The user in Moodle
* @param integer $backpackid The backpack to disconnect
* @param integer $externalbackupid The external backpack to disconnect
* @return boolean
*/
public function disconnect_backpack($userid, $backpackid, $externalbackupid) {
Expand Down
3 changes: 3 additions & 0 deletions badges/classes/backpack_api_mapping.php
Expand Up @@ -209,6 +209,8 @@ private function get_post_params($email, $password, $param) {
} else if ($value == '[PASSWORD]') {
$value = $password;
$request[$key] = $value;
} else if ($value == '[PARAM]') {
$request[$key] = is_array($param) ? $param[0] : $param;
}
}
}
Expand Down Expand Up @@ -312,6 +314,7 @@ private function get_curl_options() {
return array(
'FRESH_CONNECT' => true,
'RETURNTRANSFER' => true,
'FOLLOWLOCATION' => true,
'FORBID_REUSE' => true,
'HEADER' => 0,
'CONNECTTIMEOUT' => 3,
Expand Down
4 changes: 0 additions & 4 deletions badges/renderer.php
Expand Up @@ -650,10 +650,6 @@ protected function render_badge_user_collection(\core_badges\output\badge_user_c
$externalhtml .= html_writer::start_tag('div', array('class' => 'generalbox'));
$externalhtml .= $this->output->heading_with_help(get_string('externalbadges', 'badges'), 'externalbadges', 'badges');
if (!is_null($backpack)) {
if ($backpack->backpackid != $CFG->badges_site_backpack) {
$externalhtml .= $this->output->notification(get_string('backpackneedsupdate', 'badges'), 'warning');

}
if ($backpack->totalcollections == 0) {
$externalhtml .= get_string('nobackpackcollectionssummary', 'badges', $backpack);
} else {
Expand Down
16 changes: 16 additions & 0 deletions badges/upgrade.txt
@@ -1,6 +1,22 @@
This files describes API changes in /badges/*,
information provided here is intended especially for developers.

=== 3.10 ===
* Users can now specify a backpack that differs from the site backpack. In order to do this, connection details need to
be set in 'Manage backpacks' with OR without auth details.
* Introduced new functions in backpack_api
** 'import_badge_assertion' to facilitate cross domain badge imports.
** 'update_assertion' updates a previously defined/created assertion.
* New badge lib functions introduced
** badges_save_external_backpack() - This method handles inserts/updates to the site wide backpacks' configuration details.
** badges_save_backpack_credentials() - This method handles inserts/updates any authentication details to connect to the backpacks created. This can either be site OR user backpack authentication details
** badges_get_user_backpack() - Gets a specific user's backpack. Defaults to current user's backpack if none provided.
** badges_get_site_primary_backpack() - Get the primary backpack set for the site as defined in $CFG->badges_site_backpack
* badges_open_badges_backpack_api() - Now accepts a backpackid(badge_external_backpack id) to check whether the version of the provided backpack.
This was introduced because now there is a difference between a site and user backpack. If null, defaults to site_backpack.
* badges_get_site_backpack() - Accepts an additional $userid param if we want to get a specific user's backpack. Defaults to 0 if we are trying to get the site/admin level backpack
* badges_external_get_mapping() - Accepts an additional argument to indicate which value it wants returned. Defaults to 'externalid' which contains the OBv2 badge URL

=== 3.9 ===
* BADGE_BACKPACKAPIURL and BADGE_BACKPACKWEBURL are deprecated and should not be used.
* OBv2 has been set to the default value when the obversion is not defined.
Expand Down
41 changes: 38 additions & 3 deletions lib/badgeslib.php
Expand Up @@ -936,6 +936,17 @@ function badges_get_site_backpack($id) {
return $DB->get_record('badge_external_backpack', ['id' => $id]);
}

/**
* Get the primary backpack for the site
*
* @return array(stdClass)
*/
function badges_get_site_primary_backpack() {
global $CFG;

return badges_get_site_backpack($CFG->badges_site_backpack);
}

/**
* List the backpacks at site level.
*
Expand Down Expand Up @@ -1026,9 +1037,10 @@ function badges_disconnect_user_backpack($userid) {
* @param integer $sitebackpackid The site backpack to connect to.
* @param string $type The type of this remote object.
* @param string $internalid The id for this object on the Moodle site.
* @param string $param The param we need to return. Defaults to the externalid.
* @return mixed The id or false if it doesn't exist.
*/
function badges_external_get_mapping($sitebackpackid, $type, $internalid) {
function badges_external_get_mapping($sitebackpackid, $type, $internalid, $param = 'externalid') {
global $DB;
// Return externalid if it exists.
$params = [
Expand All @@ -1037,9 +1049,9 @@ function badges_external_get_mapping($sitebackpackid, $type, $internalid) {
'internalid' => $internalid
];

$record = $DB->get_record('badge_external_identifier', $params, 'externalid', IGNORE_MISSING);
$record = $DB->get_record('badge_external_identifier', $params, $param, IGNORE_MISSING);
if ($record) {
return $record->externalid;
return $record->$param;
}
return false;
}
Expand Down Expand Up @@ -1320,3 +1332,26 @@ function badges_get_oauth2_service_options() {

return $options;
}

/**
* Generate a public badgr URL that conforms to OBv2. This is done because badgr responses do not currently conform to
* the spec.
*
* WARNING: This is an extremely hacky way of implementing this and should be removed once the standards are conformed to.
*
* @param stdClass $backpack The Badgr backpack we are pushing to
* @param string $type The type of object we are dealing with either Issuer, Assertion OR Badge.
* @param string $externalid The externalid as provided by the backpack
* @return string The public URL to access Badgr objects
*/
function badges_generate_badgr_open_url($backpack, $type, $externalid) {
if (badges_open_badges_backpack_api($backpack->id) == OPEN_BADGES_V2) {
$entity = strtolower($type);
if ($type == OPEN_BADGES_V2_TYPE_BADGE) {
$entity = "badge";
}
$url = new moodle_url($backpack->backpackapiurl);
return "{$url->get_scheme()}://{$url->get_host()}/public/{$entity}s/$externalid";

}
}
3 changes: 0 additions & 3 deletions lib/db/upgrade.php
Expand Up @@ -2868,9 +2868,6 @@ function xmldb_main_upgrade($oldversion) {
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
$table->deleteKey('backpackapiurlkey');
$table->deleteKey('backpackweburlkey');
$table->add_key('backpackapiurlkey', XMLDB_KEY_UNIQUE, ['backpackapiurl', 'backpackemail']);

// Main savepoint reached.
upgrade_main_savepoint(true, 2020102300.01);
Expand Down

0 comments on commit 8dbc7c6

Please sign in to comment.