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

plugin/lti_provider/LtiProviderPlugin.php (3 issues)

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
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $issuer is correct as it would always require null to be passed?
Loading history...
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) {
0 ignored issues
show
$platformKey is of type Chamilo\PluginBundle\Ent...LtiProvider\PlatformKey, thus it always evaluated to true.
Loading history...
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) {
0 ignored issues
show
$platformKey is of type Chamilo\PluginBundle\Ent...LtiProvider\PlatformKey, thus it always evaluated to true.
Loading history...
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
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