Passed
Push — 1.11.x ( bce6cd...c146d9 )
by Angel Fernando Quiroz
12:25
created

plugin/lti_provider/LtiProviderPlugin.php (1 issue)

1
<?php
2
/* For license terms, see /license.txt */
3
4
use Chamilo\PluginBundle\Entity\LtiProvider\Platform;
5
use Chamilo\PluginBundle\Entity\LtiProvider\PlatformKey;
6
use Doctrine\DBAL\DBALException;
7
use Doctrine\ORM\OptimisticLockException;
8
use Symfony\Component\Filesystem\Filesystem;
9
10
/**
11
 * Description of LtiProvider.
12
 *
13
 * @author Christian Beeznest <[email protected]>
14
 */
15
class LtiProviderPlugin extends Plugin
16
{
17
    const TABLE_PLATFORM = 'plugin_lti_provider_platform';
18
19
    public $isAdminPlugin = true;
20
21
    protected function __construct()
22
    {
23
        $version = '1.0';
24
        $author = 'Christian Beeznest';
25
26
        $message = Display::return_message($this->get_lang('Description'));
27
28
        if ($this->areTablesCreated()) {
29
            $publicKey = $this->getPublicKey();
30
31
            $pkHtml = '<div class="form-group">
32
                    <label for="lti_provider_public_key" class="col-sm-2 control-label">'
33
                .$this->get_lang('PublicKey').'</label>
34
                    <div class="col-sm-8">
35
                        <pre>'.$publicKey.'</pre>
36
                    </div>
37
                    <div class="col-sm-2"></div>
38
                </div>';
39
        } else {
40
            $pkHtml = $this->get_lang('GenerateKeyPairInfo');
41
        }
42
43
        $settings = [
44
            $message => 'html',
45
            'name' => 'text',
46
            'launch_url' => 'text',
47
            'login_url' => 'text',
48
            'redirect_url' => 'text',
49
            $pkHtml => 'html',
50
            'enabled' => 'boolean',
51
        ];
52
        parent::__construct($version, $author, $settings);
53
    }
54
55
    /**
56
     * Get a selectbox with quizzes in courses , used for a tool provider.
57
     *
58
     * @param null $issuer
59
     * @return string
60
     */
61
    public function getQuizzesSelect($issuer = null)
62
    {
63
        $courses = CourseManager::get_courses_list();
64
        $toolProvider = $this->getToolProvider($issuer);
65
        $htmlcontent = '<div class="form-group">
66
            <label for="lti_provider_create_platform_kid" class="col-sm-2 control-label">'.$this->get_lang('ToolProvider').'</label>
67
            <div class="col-sm-8">
68
                <select name="tool_provider">';
69
        $htmlcontent .= '<option value="">-- '.$this->get_lang('SelectOneActivity').' --</option>';
70
        foreach ($courses as $course) {
71
            $courseInfo = api_get_course_info($course['code']);
72
            $optgroupLabel = "{$course['title']} : ".get_lang('Quizzes');
73
            $htmlcontent .= '<optgroup label="'.$optgroupLabel.'">';
74
            $exerciseList = ExerciseLib::get_all_exercises_for_course_id(
75
                $courseInfo,
76
                0,
77
                $course['id'],
78
                false
79
            );
80
            foreach ($exerciseList as $key => $exercise) {
81
                $selectValue = "{$course['code']}@@quiz-{$exercise['iid']}";
82
                $htmlcontent .= '<option value="'.$selectValue.'" '.($toolProvider == $selectValue?' selected="selected"':'').'>'.Security::remove_XSS($exercise['title']).'</option>';
83
            }
84
            $htmlcontent .= '</optgroup>';
85
        }
86
        $htmlcontent .= "</select>";
87
        $htmlcontent .= '   </div>
88
                    <div class="col-sm-2"></div>
89
                    </div>';
90
        return $htmlcontent;
91
    }
92
93
    /**
94
     * Get the public key.
95
     */
96
    public function getPublicKey(): string
97
    {
98
        $publicKey = '';
99
        $platformKey = Database::getManager()
100
           ->getRepository('ChamiloPluginBundle:LtiProvider\PlatformKey')
101
           ->findOneBy([]);
102
103
        if ($platformKey) {
104
            $publicKey = $platformKey->getPublicKey();
105
        }
106
107
        return $publicKey;
108
    }
109
110
    /**
111
     * Get the tool provider.
112
     */
113
    public function getToolProvider($issuer): string
114
    {
115
        $toolProvider = '';
116
        $platform = Database::getManager()
117
            ->getRepository('ChamiloPluginBundle:LtiProvider\Platform')
118
            ->findOneBy(['issuer' => $issuer]);
119
120
        if ($platform) {
121
            $toolProvider = $platform->getToolProvider();
122
        }
123
124
        return $toolProvider;
125
    }
126
127
    public function getToolProviderVars($issuer): array
128
    {
129
        $toolProvider = $this->getToolProvider($issuer);
130
        list($courseCode, $tool) = explode('@@', $toolProvider);
131
        list($toolName, $toolId) = explode('-', $tool);
132
        $vars = ['courseCode' => $courseCode, 'toolName' => $toolName, 'toolId' => $toolId];
133
        return $vars;
134
    }
135
136
    /**
137
     * Get the class instance.
138
     *
139
     * @staticvar LtiProviderPlugin $result
140
     */
141
    public static function create(): LtiProviderPlugin
142
    {
143
        static $result = null;
144
145
        return $result ?: $result = new self();
146
    }
147
148
    /**
149
     * Check whether the current user is a teacher in this context.
150
     */
151
    public static function isInstructor()
152
    {
153
        api_is_allowed_to_edit(false, true);
154
    }
155
156
    /**
157
     * Get the plugin directory name.
158
     */
159
    public function get_name(): string
160
    {
161
        return 'lti_provider';
162
    }
163
164
    /**
165
     * Install the plugin. Set the database up.
166
     */
167
    public function install()
168
    {
169
        $pluginEntityPath = $this->getEntityPath();
170
171
        if (!is_dir($pluginEntityPath)) {
172
            if (!is_writable(dirname($pluginEntityPath))) {
173
                $message = get_lang('ErrorCreatingDir').': '.$pluginEntityPath;
174
                Display::addFlash(Display::return_message($message, 'error'));
175
176
                return;
177
            }
178
179
            mkdir($pluginEntityPath, api_get_permissions_for_new_directories());
180
        }
181
182
        $fs = new Filesystem();
183
        $fs->mirror(__DIR__.'/Entity/', $pluginEntityPath, null, ['override']);
184
185
        $this->createPluginTables();
186
    }
187
188
    /**
189
     * Get current entity sys path.
190
     */
191
    public function getEntityPath(): string
192
    {
193
        return api_get_path(SYS_PATH).'src/Chamilo/PluginBundle/Entity/'.$this->getCamelCaseName();
194
    }
195
196
    /**
197
     * Save configuration for plugin.
198
     *
199
     * Generate a new key pair for platform when enabling plugin.
200
     *
201
     * @throws OptimisticLockException
202
     * @throws \Doctrine\ORM\ORMException
203
     *
204
     * @return $this|Plugin
205
     */
206
    public function performActionsAfterConfigure()
207
    {
208
        $em = Database::getManager();
209
210
        /** @var PlatformKey $platformKey */
211
        $platformKey = $em
212
            ->getRepository('ChamiloPluginBundle:LtiProvider\PlatformKey')
213
            ->findOneBy([]);
214
215
        if ($this->get('enabled') === 'true') {
216
            if (!$platformKey) {
217
                $platformKey = new PlatformKey();
218
            }
219
220
            $keyPair = self::generatePlatformKeys();
221
222
            $platformKey->setKid($keyPair['kid']);
223
            $platformKey->publicKey = $keyPair['public'];
224
            $platformKey->setPrivateKey($keyPair['private']);
225
226
            $em->persist($platformKey);
227
        } else {
228
            if ($platformKey) {
229
                $em->remove($platformKey);
230
            }
231
        }
232
233
        $em->flush();
234
235
        return $this;
236
    }
237
238
    /**
239
     * Unistall plugin. Clear the database.
240
     */
241
    public function uninstall()
242
    {
243
        $pluginEntityPath = $this->getEntityPath();
244
        $fs = new Filesystem();
245
246
        if ($fs->exists($pluginEntityPath)) {
247
            $fs->remove($pluginEntityPath);
248
        }
249
250
        try {
251
            $this->dropPluginTables();
252
        } catch (DBALException $e) {
253
            error_log('Error while uninstalling IMS/LTI plugin: '.$e->getMessage());
254
        }
255
    }
256
257
    public function trimParams(array &$params)
258
    {
259
        foreach ($params as $key => $value) {
260
            $newValue = preg_replace('/\s+/', ' ', $value);
261
            $params[$key] = trim($newValue);
262
        }
263
    }
264
265
    /**
266
     * Creates the plugin tables on database.
267
     */
268
    private function createPluginTables(): void
269
    {
270
        if ($this->areTablesCreated()) {
271
            return;
272
        }
273
274
        $queries = [
275
            "CREATE TABLE plugin_lti_provider_platform (
276
                id int NOT NULL AUTO_INCREMENT,
277
                issuer varchar(255) NOT NULL,
278
                client_id varchar(255) NOT NULL,
279
                kid varchar(255) NOT NULL,
280
                auth_login_url varchar(255) NOT NULL,
281
                auth_token_url varchar(255) NOT NULL,
282
                key_set_url varchar(255) NOT NULL,
283
                deployment_id varchar(255) NOT NULL,
284
                tool_provider varchar(255) NULL,
285
                PRIMARY KEY(id)
286
            ) DEFAULT CHARACTER SET utf8 COLLATE `utf8_unicode_ci` ENGINE = InnoDB",
287
            "CREATE TABLE plugin_lti_provider_platform_key (
288
                    id INT AUTO_INCREMENT NOT NULL,
289
                    kid VARCHAR(255) NOT NULL,
290
                    public_key LONGTEXT NOT NULL,
291
                    private_key LONGTEXT NOT NULL,
292
                    PRIMARY KEY(id)
293
                ) DEFAULT CHARACTER SET utf8 COLLATE `utf8_unicode_ci` ENGINE = InnoDB",
294
        ];
295
296
        foreach ($queries as $query) {
297
            Database::query($query);
298
        }
299
    }
300
301
    private function areTablesCreated(): bool
302
    {
303
        $entityManager = Database::getManager();
304
        $connection = $entityManager->getConnection();
305
306
        return $connection->getSchemaManager()->tablesExist(self::TABLE_PLATFORM);
307
    }
308
309
    /**
310
     * Generate a key pair and key id for the platform.
311
     *
312
     * Return a associative array like ['kid' => '...', 'private' => '...', 'public' => '...'].
313
     */
314
    private static function generatePlatformKeys(): array
315
    {
316
        // Create the private and public key
317
        $res = openssl_pkey_new(
318
            [
319
                'digest_alg' => 'sha256',
320
                'private_key_bits' => 2048,
321
                'private_key_type' => OPENSSL_KEYTYPE_RSA,
322
            ]
323
        );
324
325
        // Extract the private key from $res to $privateKey
326
        $privateKey = '';
327
        openssl_pkey_export($res, $privateKey);
328
329
        // Extract the public key from $res to $publicKey
330
        $publicKey = openssl_pkey_get_details($res);
331
332
        return [
333
            'kid' => bin2hex(openssl_random_pseudo_bytes(10)),
334
            'private' => $privateKey,
335
            'public' => $publicKey["key"],
336
        ];
337
    }
338
339
    /**
340
     * Drops the plugin tables on database.
341
     */
342
    private function dropPluginTables(): bool
343
    {
344
        Database::query("DROP TABLE IF EXISTS plugin_lti_provider_platform");
345
        Database::query("DROP TABLE IF EXISTS plugin_lti_provider_platform_key");
346
347
        return true;
348
    }
349
350
    /**
351
     * Get a SimpleXMLElement object with the request received on php://input.
352
     *
353
     * @throws Exception
354
     */
355
    private function getRequestXmlElement(): ?SimpleXMLElement
0 ignored issues
show
The method getRequestXmlElement() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
356
    {
357
        $request = file_get_contents("php://input");
358
359
        if (empty($request)) {
360
            return null;
361
        }
362
363
        return new SimpleXMLElement($request);
364
    }
365
}
366