MDL-65959 core_badges: Allow ability to upload badges cross domain.
authorPeter <peter@moodle.com>
Wed, 2 Oct 2019 04:28:45 +0000 (12:28 +0800)
committerPeter Dias <peter@moodle.com>
Mon, 26 Oct 2020 16:27:18 +0000 (00:27 +0800)
badges/backpack-add.php
badges/classes/backpack_api.php
badges/classes/backpack_api_mapping.php
badges/renderer.php
badges/upgrade.txt
lib/badgeslib.php
lib/db/upgrade.php

index 9c57520..52b5d7d 100644 (file)
@@ -48,7 +48,8 @@ if (!empty($issuedbadge->recipient->id)) {
     $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();
@@ -61,7 +62,8 @@ if (!empty($issuedbadge->recipient->id)) {
             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);
@@ -72,25 +74,55 @@ if (!empty($issuedbadge->recipient->id)) {
             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'));
index b25b39f..d155443 100644 (file)
@@ -87,7 +87,6 @@ class backpack_api {
         global $CFG;
         $admin = get_admin();
 
-        $this->backpackapiurl = $sitebackpack->backpackapiurl;
         $this->backpackapiurl = $sitebackpack->backpackapiurl;
         $this->backpackapiversion = $sitebackpack->apiversion;
         $this->password = $sitebackpack->password;
@@ -152,6 +151,21 @@ class backpack_api {
                     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
@@ -408,6 +422,22 @@ class backpack_api {
         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.
      *
@@ -570,7 +600,6 @@ class backpack_api {
      *
      * @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) {
index efd57c4..5e70fe5 100644 (file)
@@ -209,6 +209,8 @@ class backpack_api_mapping {
                 } else if ($value == '[PASSWORD]') {
                     $value = $password;
                     $request[$key] = $value;
+                } else if ($value == '[PARAM]') {
+                    $request[$key] = is_array($param) ? $param[0] : $param;
                 }
             }
         }
@@ -312,6 +314,7 @@ class backpack_api_mapping {
         return array(
             'FRESH_CONNECT'     => true,
             'RETURNTRANSFER'    => true,
+            'FOLLOWLOCATION'    => true,
             'FORBID_REUSE'      => true,
             'HEADER'            => 0,
             'CONNECTTIMEOUT'    => 3,
index 28a182b..2d0d3f9 100644 (file)
@@ -650,10 +650,6 @@ class core_badges_renderer extends plugin_renderer_base {
             $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 {
index faca603..2ca56f3 100644 (file)
@@ -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.
index 4b54cb6..1fc0b46 100644 (file)
@@ -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.
  *
@@ -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 = [
@@ -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;
 }
@@ -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";
+
+    }
+}
\ No newline at end of file
index febd2fe..ae5aa60 100644 (file)
@@ -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, 2021052500.32);