Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
/**
18
 * Communicate with backpacks.
19
 *
20
 * @copyright  2020 Tung Thai based on Totara Learning Solutions Ltd {@link http://www.totaralms.com/} dode
21
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22
 * @author     Tung Thai <Tung.ThaiDuc@nashtechglobal.com>
23
 */
24
 
25
namespace core_badges;
26
 
27
defined('MOODLE_INTERNAL') || die();
28
 
29
require_once($CFG->libdir . '/filelib.php');
30
 
31
use cache;
32
use coding_exception;
33
use context_system;
34
use moodle_url;
35
use core_badges\backpack_api2p1_mapping;
36
use core_badges\oauth2\client;
37
use curl;
38
use stdClass;
39
use core\oauth2\issuer;
40
use core\oauth2\endpoint;
41
use core\oauth2\discovery\imsbadgeconnect;
42
 
43
/**
44
 * To process badges with backpack and control api request and this class using for Open Badge API v2.1 methods.
45
 *
46
 * @package   core_badges
47
 * @copyright  2020 Tung Thai
48
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
49
 */
50
class backpack_api2p1 {
51
 
52
    /** @var object is the external backpack. */
53
    private $externalbackpack;
54
 
55
    /** @var array define api mapping. */
56
    private $mappings = [];
57
 
58
    /** @var false|null|stdClass|\core_badges\backpack_api2p1 to */
59
    private $tokendata;
60
 
61
    /** @var null clienid. */
62
    private $clientid = null;
63
 
64
    /** @var null version api of the backpack. */
65
    protected $backpackapiversion;
66
 
67
    /** @var issuer The OAuth2 Issuer for this backpack */
68
    protected issuer $issuer;
69
 
70
    /** @var endpoint The apiBase endpoint */
71
    protected endpoint $apibase;
72
 
73
    /**
74
     * backpack_api2p1 constructor.
75
     *
76
     * @param object $externalbackpack object
77
     * @throws coding_exception error message
78
     */
79
    public function __construct($externalbackpack) {
80
 
81
        if (!empty($externalbackpack)) {
82
            $this->externalbackpack = $externalbackpack;
83
            $this->backpackapiversion = $externalbackpack->apiversion;
84
            $this->get_clientid($externalbackpack->oauth2_issuerid);
85
 
86
            if (!($this->tokendata = $this->get_stored_token($externalbackpack->id))
87
                && $this->backpackapiversion != OPEN_BADGES_V2P1) {
88
                throw new coding_exception('Backpack incorrect');
89
            }
90
        }
91
 
92
        $this->define_mappings();
93
    }
94
 
95
    /**
96
     * Initialises or returns the OAuth2 issuer associated to this backpack.
97
     *
98
     * @return issuer
99
     */
100
    protected function get_issuer(): issuer {
101
        if (!isset($this->issuer)) {
102
            $this->issuer = new \core\oauth2\issuer($this->externalbackpack->oauth2_issuerid);
103
        }
104
        return $this->issuer;
105
    }
106
 
107
    /**
108
     * Gets the apiBase url associated to this backpack.
109
     *
110
     * @return string
111
     */
112
    protected function get_api_base_url(): string {
113
        if (!isset($this->apibase)) {
114
            $apibase = endpoint::get_record([
115
                'issuerid' => $this->externalbackpack->oauth2_issuerid,
116
                'name' => 'apiBase',
117
            ]);
118
 
119
            if (empty($apibase)) {
120
                imsbadgeconnect::create_endpoints($this->get_issuer());
121
                $apibase = endpoint::get_record([
122
                    'issuerid' => $this->externalbackpack->oauth2_issuerid,
123
                    'name' => 'apiBase',
124
                ]);
125
            }
126
 
127
            $this->apibase = $apibase;
128
        }
129
 
130
        return $this->apibase->get('url');
131
    }
132
 
133
 
134
    /**
135
     * Define the mappings supported by this usage and api version.
136
     */
137
    private function define_mappings() {
138
        if ($this->backpackapiversion == OPEN_BADGES_V2P1) {
139
 
140
            $mapping = [];
141
            $mapping[] = [
142
                'post.assertions',                               // Action.
143
                '[URL]/assertions',   // URL
144
                '[PARAM]',                                  // Post params.
145
                false,                                      // Multiple.
146
                'post',                                     // Method.
147
                true,                                       // JSON Encoded.
148
                true                                        // Auth required.
149
            ];
150
 
151
            $mapping[] = [
152
                'get.assertions',                               // Action.
153
                '[URL]/assertions',   // URL
154
                '[PARAM]',                                  // Post params.
155
                false,                                      // Multiple.
156
                'get',                                     // Method.
157
                true,                                       // JSON Encoded.
158
                true                                        // Auth required.
159
            ];
160
 
161
            foreach ($mapping as $map) {
162
                $map[] = false; // Site api function.
163
                $map[] = OPEN_BADGES_V2P1; // V2 function.
164
                $this->mappings[] = new backpack_api2p1_mapping(...$map);
165
            }
166
 
167
        }
168
    }
169
 
170
    /**
171
     * Disconnect the backpack from this user.
172
     *
173
     * @param object $backpack to disconnect.
174
     * @return bool
175
     * @throws \dml_exception
176
     */
177
    public function disconnect_backpack($backpack) {
178
        global $USER, $DB;
179
 
180
        if ($backpack) {
181
            $DB->delete_records_select('badge_external', 'backpackid = :backpack', ['backpack' => $backpack->id]);
182
            $DB->delete_records('badge_backpack', ['id' => $backpack->id]);
183
            $DB->delete_records('badge_backpack_oauth2', ['externalbackpackid' => $this->externalbackpack->id,
184
                'userid' => $USER->id]);
185
 
186
            return true;
187
        }
188
        return false;
189
    }
190
 
191
    /**
192
     * Make an api request.
193
     *
194
     * @param string $action The api function.
195
     * @param string $postdata The body of the api request.
196
     * @return mixed
197
     */
198
    public function curl_request($action, $postdata = null) {
199
        $tokenkey = $this->tokendata->token;
200
        foreach ($this->mappings as $mapping) {
201
            if ($mapping->is_match($action)) {
202
                return $mapping->request(
203
                    $this->get_api_base_url(),
204
                    $tokenkey,
205
                    $postdata
206
                );
207
            }
208
        }
209
 
210
        throw new coding_exception('Unknown request');
211
    }
212
 
213
    /**
214
     * Get token.
215
     *
216
     * @param int $externalbackpackid ID of external backpack.
217
     * @return oauth2\badge_backpack_oauth2|false|stdClass|null
218
     */
219
    protected function get_stored_token($externalbackpackid) {
220
        global $USER;
221
 
222
        $token = \core_badges\oauth2\badge_backpack_oauth2::get_record(
223
            ['externalbackpackid' => $externalbackpackid, 'userid' => $USER->id]);
224
        if ($token !== false) {
225
            $token = $token->to_record();
226
            return $token;
227
        }
228
        return null;
229
    }
230
 
231
    /**
232
     * Get client id.
233
     *
234
     * @param int $issuerid id of Oauth2 service.
235
     * @throws coding_exception
236
     */
237
    private function get_clientid($issuerid) {
238
        $issuer = \core\oauth2\api::get_issuer($issuerid);
239
        if (!empty($issuer)) {
240
            $this->issuer = $issuer;
241
            $this->clientid = $issuer->get('clientid');
242
        }
243
    }
244
 
245
    /**
246
     * Export a badge to the backpack site.
247
     *
248
     * @param string $hash of badge issued.
249
     * @return array
250
     * @throws \moodle_exception
251
     * @throws coding_exception
252
     */
253
    public function put_assertions($hash) {
254
        $data = [];
255
        if (!$hash) {
256
            return false;
257
        }
258
 
259
        $issuer = $this->get_issuer();
260
        $client = new client($issuer, new moodle_url('/badges/mybadges.php'), '', $this->externalbackpack);
261
        if (!$client->is_logged_in()) {
262
            $redirecturl = new moodle_url('/badges/mybadges.php', ['error' => 'backpackexporterror']);
263
            redirect($redirecturl);
264
        }
265
 
266
        $this->tokendata = $this->get_stored_token($this->externalbackpack->id);
267
 
268
        $assertion = new \core_badges_assertion($hash, OPEN_BADGES_V2);
269
        $data['assertion'] = $assertion->get_badge_assertion();
270
        $response = $this->curl_request('post.assertions', $data);
271
        if ($response && isset($response->status->statusCode) && $response->status->statusCode == 200) {
272
            $msg['status'] = \core\output\notification::NOTIFY_SUCCESS;
273
            $msg['message'] = get_string('addedtobackpack', 'badges');
274
        } else {
275
            if ($response) {
276
                // Although the specification defines that status error is a string, some providers, like Badgr, are wrongly
277
                // returning an array. It has been reported, but adding these extra checks doesn't hurt, just in case.
278
                if (
279
                    property_exists($response, 'status') &&
280
                    is_object($response->status) &&
281
                    property_exists($response->status, 'error')
282
                ) {
283
                    $statuserror = $response->status->error;
284
                    if (is_array($statuserror)) {
285
                        $statuserror = implode($statuserror);
286
                    }
287
                } else if (property_exists($response, 'error')) {
288
                    $statuserror = $response->error;
289
                    if (property_exists($response, 'message')) {
290
                        $statuserror .= '. Message: ' . $response->message;
291
                    }
292
                }
293
            } else {
294
                $statuserror = 'Empty response';
295
            }
296
            $data = [
297
                'badgename' => $data['assertion']['badge']['name'],
298
                'error' => $statuserror,
299
            ];
300
 
301
            $msg['status'] = \core\output\notification::NOTIFY_ERROR;
302
            $msg['message'] = get_string('backpackexporterrorwithinfo', 'badges', $data);
303
        }
304
        return $msg;
305
    }
306
}