Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | 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
namespace auth_lti;
18
 
19
/**
20
 * Tests for the auth_plugin_lti class.
21
 *
22
 * @package    auth_lti
23
 * @copyright  2021 Jake Dallimore <jrhdallimore@gmail.com>
24
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25
 * @coversDefaultClass \auth_plugin_lti
26
 */
27
class auth_test extends \advanced_testcase {
28
 
29
    /** @var string issuer URL used for test cases. */
30
    protected $issuer = 'https://lms.example.org';
31
 
32
    /** @var int const representing cases where no PII is present. */
33
    protected const PII_NONE = 0;
34
 
35
    /** @var int const representing cases where only names are included in PII. */
36
    protected const PII_NAMES_ONLY = 1;
37
 
38
    /** @var int const representing cases where only email is included in PII. */
39
    protected const PII_EMAILS_ONLY = 2;
40
 
41
    /** @var int const representing cases where both names and email are included in PII. */
42
    protected const PII_ALL = 3;
43
 
44
    /**
45
     * Verify the user's profile picture has been set, which is useful to verify picture syncs.
46
     *
47
     * @param int $userid the id of the Moodle user.
48
     */
49
    protected function verify_user_profile_image_updated(int $userid): void {
50
        global $CFG;
51
        $user = \core_user::get_user($userid);
52
        $usercontext = \context_user::instance($user->id);
53
        $expected = $CFG->wwwroot . '/pluginfile.php/' . $usercontext->id . '/user/icon/boost/f2?rev='. $user->picture;
54
 
55
        $page = new \moodle_page();
56
        $page->set_url('/user/profile.php');
57
        $page->set_context(\context_system::instance());
58
        $renderer = $page->get_renderer('core');
59
        $userpicture = new \user_picture($user);
60
        $this->assertEquals($expected, $userpicture->get_url($page, $renderer)->out(false));
61
    }
62
 
63
    /**
64
     * Get a list of users ready for use with mock authentication requests by providing an array of user ids.
65
     *
66
     * @param array $ids the platform user_ids for the users.
67
     * @param string $role the LTI role to include in the user data.
68
     * @param bool $includenames whether to include the firstname and lastname of the user
69
     * @param bool $includeemail whether to include the email of the user
70
     * @param bool $includepicture whether to include a profile picture or not (slows tests, so defaults to false).
71
     * @return array the users list.
72
     */
73
    protected function get_mock_users_with_ids(array $ids,
74
            string $role = 'http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor', bool $includenames = true,
75
            bool $includeemail = true, bool $includepicture = false): array {
76
 
77
        $users = [];
78
        foreach ($ids as $id) {
79
            $user = [
80
                'user_id' => $id,
81
                'given_name' => 'Firstname' . $id,
82
                'family_name' => 'Surname' . $id,
83
                'email' => "firstname.surname{$id}@lms.example.org",
84
                'roles' => [$role]
85
            ];
86
            if (!$includenames) {
87
                unset($user['given_name']);
88
                unset($user['family_name']);
89
            }
90
            if (!$includeemail) {
91
                unset($user['email']);
92
            }
93
            if ($includepicture) {
94
                $user['picture'] = $this->getExternalTestFileUrl('/test.jpg');
95
            }
96
            $users[] = $user;
97
        }
98
        return $users;
99
    }
100
 
101
    /**
102
     * Get a mock member structure based on a mock user and, optionally, a legacy user id.
103
     *
104
     * @param array $mockuser the user data
105
     * @param string $legacyuserid the id of the user in the platform in 1.1, if different from the id used in 1.3.
106
     * @return array
107
     */
108
    protected function get_mock_member_data_for_user(array $mockuser, string $legacyuserid = ''): array {
109
        $data = [
110
            'user_id' => $mockuser['user_id'],
111
            'roles' => $mockuser['roles']
112
        ];
113
        if (isset($mockuser['given_name'])) {
114
            $data['given_name'] = $mockuser['given_name'];
115
        }
116
        if (isset($mockuser['family_name'])) {
117
            $data['family_name'] = $mockuser['family_name'];
118
        }
119
        if (isset($mockuser['email'])) {
120
            $data['email'] = $mockuser['email'];
121
        }
122
        if (!empty($mockuser['picture'])) {
123
            $data['picture'] = $mockuser['picture'];
124
        }
125
        if (!empty($legacyuserid)) {
126
            $data['lti11_legacy_user_id'] = $legacyuserid;
127
        }
128
        return $data;
129
    }
130
 
131
    /**
132
     * Get mocked JWT data for the given user, including optionally the migration claim information if provided.
133
     *
134
     * @param array $mockuser the user data
135
     * @param array $mockmigration information needed to mock the migration claim
136
     * @return array the mock JWT data
137
     */
138
    protected function get_mock_launchdata_for_user(array $mockuser, array $mockmigration = []): array {
139
        $data = [
140
            'iss' => $this->issuer, // Must match registration in create_test_environment.
141
            'aud' => '123', // Must match registration in create_test_environment.
142
            'sub' => $mockuser['user_id'], // User id on the platform site.
143
            'exp' => time() + 60,
144
            'nonce' => 'some-nonce-value-123',
145
            'https://purl.imsglobal.org/spec/lti/claim/deployment_id' => '1', // Must match registration.
146
            'https://purl.imsglobal.org/spec/lti/claim/roles' => $mockuser['roles'],
147
            'https://purl.imsglobal.org/spec/lti/claim/resource_link' => [
148
                'title' => "Res link title",
149
                'id' => 'res-link-id-123',
150
            ],
151
            "https://purl.imsglobal.org/spec/lti/claim/context" => [
152
                "id" => "context-id-12345",
153
                "label" => "ITS 123",
154
                "title" => "ITS 123 Machine Learning",
155
                "type" => ["http://purl.imsglobal.org/vocab/lis/v2/course#CourseOffering"]
156
            ],
157
            'https://purl.imsglobal.org/spec/lti/claim/target_link_uri' =>
158
                'https://this-moodle-tool.example.org/context/24/resource/14',
159
            'https://purl.imsglobal.org/spec/lti/claim/custom' => [
160
                'id' => '1'
161
            ]
162
        ];
163
 
164
        if (isset($mockuser['given_name'])) {
165
            $data['given_name'] = $mockuser['given_name'];
166
        }
167
        if (isset($mockuser['family_name'])) {
168
            $data['family_name'] = $mockuser['family_name'];
169
        }
170
        if (isset($mockuser['email'])) {
171
            $data['email'] = $mockuser['email'];
172
        }
173
 
174
        if (!empty($mockuser['picture'])) {
175
            $data['picture'] = $mockuser['picture'];
176
        }
177
 
178
        if ($mockmigration) {
179
            if (isset($mockmigration['consumer_key'])) {
180
                $base = [
181
                    $mockmigration['consumer_key'],
182
                    $data['https://purl.imsglobal.org/spec/lti/claim/deployment_id'],
183
                    $data['iss'],
184
                    $data['aud'],
185
                    $data['exp'],
186
                    $data['nonce']
187
                ];
188
                $basestring = implode('&', $base);
189
 
190
                $data['https://purl.imsglobal.org/spec/lti/claim/lti1p1'] = [
191
                    'oauth_consumer_key' => $mockmigration['consumer_key'],
192
                ];
193
 
194
                if (isset($mockmigration['signing_secret'])) {
195
                    $sig = base64_encode(hash_hmac('sha256', $basestring, $mockmigration['signing_secret']));
196
                    $data['https://purl.imsglobal.org/spec/lti/claim/lti1p1']['oauth_consumer_key_sign'] = $sig;
197
                }
198
            }
199
 
200
            if (isset($mockmigration['user_id'])) {
201
                $data['https://purl.imsglobal.org/spec/lti/claim/lti1p1']['user_id'] =
202
                    $mockmigration['user_id'];
203
            }
204
        }
205
        return $data;
206
    }
207
 
208
    /**
209
     * Test which verifies a user account can be created/found using the find_or_create_user_from_launch() method.
210
     *
211
     * @dataProvider launch_data_provider
212
     * @param array|null $legacydata legacy user and tool data, if testing migration cases.
213
     * @param array $launchdata data describing the launch, including user data and migration claim data.
214
     * @param array $expected the test case expectations.
215
     * @covers ::find_or_create_user_from_launch
216
     */
11 efrain 217
    public function test_find_or_create_user_from_launch(?array $legacydata, array $launchdata, array $expected = []): void {
1 efrain 218
        $this->resetAfterTest();
219
        global $DB;
220
        $auth = get_auth_plugin('lti');
221
 
222
        // When testing platform users who have authenticated before, make that first auth call.
223
        if (!empty($launchdata['has_authenticated_before'])) {
224
            $mockjwtdata = $this->get_mock_launchdata_for_user($launchdata['user']);
225
            $firstauthuser = $auth->find_or_create_user_from_launch($mockjwtdata);
226
        }
227
 
228
        // Create legacy users and mocked tool secrets if desired.
229
        $legacysecrets = [];
230
        if ($legacydata) {
231
            $legacyusers = [];
232
            $generator = $this->getDataGenerator();
233
            foreach ($legacydata['users'] as $legacyuser) {
234
                $username = 'enrol_lti' . sha1($legacydata['consumer_key'] . '::' . $legacydata['consumer_key'] .
235
                        ':' . $legacyuser['user_id']);
236
 
237
                $legacyusers[] = $generator->create_user([
238
                    'username' => $username,
239
                    'auth' => 'lti'
240
                ]);
241
            }
242
            // In a real usage, legacy tool secrets are only passed for a consumer, as indicated in the migration claim.
243
            if (!empty($launchdata['migration_claim'])) {
244
                $legacysecrets = array_column($legacydata['tools'], 'secret');
245
            }
246
        }
247
 
248
        // Mock the launchdata.
249
        $mockjwtdata = $this->get_mock_launchdata_for_user($launchdata['user'], $launchdata['migration_claim'] ?? []);
250
 
251
        // Authenticate the platform user.
252
        $sink = $this->redirectEvents();
253
        $countusersbefore = $DB->count_records('user');
254
        $user = $auth->find_or_create_user_from_launch($mockjwtdata, $legacysecrets);
255
        if (!empty($expected['migration_debugging'])) {
256
            $this->assertDebuggingCalled();
257
        }
258
        $countusersafter = $DB->count_records('user');
259
        $events = $sink->get_events();
260
        $sink->close();
261
 
262
        // Verify user count is correct. i.e. no user is created when migration claim is correctly processed or when
263
        // the user has authenticated with the tool before.
264
        $numnewusers = (!empty($expected['migrated'])) ? 0 : 1;
265
        $numnewusers = (!empty($launchdata['has_authenticated_before'])) ?
266
 
267
        $this->assertEquals($numnewusers, $countusersafter - $countusersbefore);
268
 
269
        if (!empty($expected['migrated'])) {
270
            // If migrated, verify the user account is reusing the legacy user account.
271
            $legacyuserids = array_column($legacyusers, 'id');
272
            $this->assertContains($user->id, $legacyuserids);
273
            $this->assertEmpty($events); // No updates as part of this method.
274
        } else if (isset($firstauthuser)) {
275
            // If the user is authenticating a second time, confirm the same account is being returned.
276
            $this->assertEquals($firstauthuser->id, $user->id);
277
            $this->assertEmpty($events); // No updates as part of this method.
278
        } else {
279
            // The user wasn't migrated and hasn't launched before, so we expect a user_created event.
280
            $this->assertInstanceOf(\core\event\user_created::class, $events[0]);
281
        }
282
    }
283
 
284
    /**
285
     * Data provider for testing launch-based authentication.
286
     *
287
     * @return array the test case data.
288
     */
289
    public function launch_data_provider(): array {
290
        return [
291
            'New (unlinked) platform learner including PII, no legacy user, no migration claim' => [
292
                'legacy_data' => null,
293
                'launch_data' => [
294
                    'user' => $this->get_mock_users_with_ids(
295
                        ['1'],
296
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner'
297
                    )[0],
298
                    'migration_claim' => null
299
                ],
300
            ],
301
            'New (unlinked) platform learner excluding names, no legacy user, no migration claim' => [
302
                'legacy_data' => null,
303
                'launch_data' => [
304
                    'user' => $this->get_mock_users_with_ids(
305
                        ['1'],
306
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner',
307
                        false
308
                    )[0],
309
                    'migration_claim' => null
310
                ],
311
            ],
312
            'New (unlinked) platform learner excluding emails, no legacy user, no migration claim' => [
313
                'legacy_data' => null,
314
                'launch_data' => [
315
                    'user' => $this->get_mock_users_with_ids(
316
                        ['1'],
317
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner',
318
                        true,
319
                        false
320
                    )[0],
321
                    'migration_claim' => null
322
                ],
323
            ],
324
            'New (unlinked) platform learner excluding all PII, no legacy user, no migration claim' => [
325
                'legacy_data' => null,
326
                'launch_data' => [
327
                    'user' => $this->get_mock_users_with_ids(
328
                        ['1'],
329
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner',
330
                        false,
331
                        false
332
                    )[0],
333
                    'migration_claim' => null
334
                ],
335
            ],
336
            'New (unlinked) platform learner including PII, existing legacy user, valid migration claim' => [
337
                'legacy_data' => [
338
                    'users' => [
339
                        ['user_id' => '123-abc'],
340
                    ],
341
                    'consumer_key' => 'CONSUMER_1',
342
                    'tools' => [
343
                        ['secret' => 'toolsecret1'],
344
                        ['secret' => 'toolsecret2'],
345
                    ]
346
                ],
347
                'launch_data' => [
348
                    'user' => $this->get_mock_users_with_ids(
349
                        ['1'],
350
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner'
351
                    )[0],
352
                    'migration_claim' => [
353
                        'consumer_key' => 'CONSUMER_1',
354
                        'signing_secret' => 'toolsecret1',
355
                        'user_id' => '123-abc',
356
                        'context_id' => 'd345b',
357
                        'tool_consumer_instance_guid' => '12345-123',
358
                        'resource_link_id' => '4b6fa'
359
                    ]
360
                ],
361
                'expected' => [
362
                    'migrated' => true
363
                ]
364
            ],
365
            'New (unlinked) platform learner including PII, existing legacy user, no migration claim' => [
366
                'legacy_data' => [
367
                    'users' => [
368
                        ['user_id' => '123-abc'],
369
                    ],
370
                    'consumer_key' => 'CONSUMER_1',
371
                    'tools' => [
372
                        ['secret' => 'toolsecret1'],
373
                        ['secret' => 'toolsecret2'],
374
                    ]
375
                ],
376
                'launch_data' => [
377
                    'user' => $this->get_mock_users_with_ids(
378
                        ['1'],
379
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner'
380
                    )[0],
381
                    'migration_claim' => null,
382
                ],
383
                'expected' => [
384
                    'migrated' => false,
385
                ]
386
            ],
387
            'New (unlinked) platform learner including PII, existing legacy user, migration missing consumer_key' => [
388
                'legacy_data' => [
389
                    'users' => [
390
                        ['user_id' => '123-abc'],
391
                    ],
392
                    'consumer_key' => 'CONSUMER_1',
393
                    'tools' => [
394
                        ['secret' => 'toolsecret1'],
395
                        ['secret' => 'toolsecret2'],
396
                    ]
397
                ],
398
                'launch_data' => [
399
                    'user' => $this->get_mock_users_with_ids(
400
                        ['1'],
401
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner'
402
                    )[0],
403
                    'migration_claim' => [
404
                        'signing_secret' => 'toolsecret1',
405
                        'user_id' => '123-abc',
406
                        'context_id' => 'd345b',
407
                        'tool_consumer_instance_guid' => '12345-123',
408
                        'resource_link_id' => '4b6fa'
409
                    ]
410
                ],
411
                'expected' => [
412
                    'migrated' => false,
413
                    'migration_debugging' => true,
414
                ]
415
            ],
416
            'New (unlinked) platform learner including PII, existing legacy user, migration bad consumer_key' => [
417
                'legacy_data' => [
418
                    'users' => [
419
                        ['user_id' => '123-abc'],
420
                    ],
421
                    'consumer_key' => 'CONSUMER_1',
422
                    'tools' => [
423
                        ['secret' => 'toolsecret1'],
424
                        ['secret' => 'toolsecret2'],
425
                    ]
426
                ],
427
                'launch_data' => [
428
                    'user' => $this->get_mock_users_with_ids(
429
                        ['1'],
430
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner'
431
                    )[0],
432
                    'migration_claim' => [
433
                        'consumer_key' => 'CONSUMER_BAD',
434
                        'signing_secret' => 'toolsecret1',
435
                        'user_id' => '123-abc',
436
                        'context_id' => 'd345b',
437
                        'tool_consumer_instance_guid' => '12345-123',
438
                        'resource_link_id' => '4b6fa'
439
                    ]
440
                ],
441
                'expected' => [
442
                    'migrated' => false,
443
                ]
444
            ],
445
            'New (unlinked) platform learner including PII, existing legacy user, migration user not matched' => [
446
                'legacy_data' => [
447
                    'users' => [
448
                        ['user_id' => '123-abc'],
449
                    ],
450
                    'consumer_key' => 'CONSUMER_1',
451
                    'tools' => [
452
                        ['secret' => 'toolsecret1'],
453
                        ['secret' => 'toolsecret2'],
454
                    ]
455
                ],
456
                'launch_data' => [
457
                    'user' => $this->get_mock_users_with_ids(
458
                        ['1'],
459
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner'
460
                    )[0],
461
                    'migration_claim' => [
462
                        'consumer_key' => 'CONSUMER_1',
463
                        'signing_secret' => 'toolsecret1',
464
                        'user_id' => '234-bcd',
465
                        'context_id' => 'd345b',
466
                        'tool_consumer_instance_guid' => '12345-123',
467
                        'resource_link_id' => '4b6fa'
468
                    ]
469
                ],
470
                'expected' => [
471
                    'migrated' => false
472
                ]
473
            ],
474
            'New (unlinked) platform learner including PII, existing legacy user, valid migration claim secret2' => [
475
                'legacy_data' => [
476
                    'users' => [
477
                        ['user_id' => '123-abc'],
478
                    ],
479
                    'consumer_key' => 'CONSUMER_1',
480
                    'tools' => [
481
                        ['secret' => 'toolsecret1'],
482
                        ['secret' => 'toolsecret2'],
483
                    ]
484
                ],
485
                'launch_data' => [
486
                    'user' => $this->get_mock_users_with_ids(
487
                        ['1'],
488
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner'
489
                    )[0],
490
                    'migration_claim' => [
491
                        'consumer_key' => 'CONSUMER_1',
492
                        'signing_secret' => 'toolsecret2',
493
                        'user_id' => '123-abc',
494
                        'context_id' => 'd345b',
495
                        'tool_consumer_instance_guid' => '12345-123',
496
                        'resource_link_id' => '4b6fa'
497
                    ]
498
                ],
499
                'expected' => [
500
                    'migrated' => true
501
                ]
502
            ],
503
            'New (unlinked) platform learner including PII, existing legacy user, migration claim bad secret' => [
504
                'legacy_data' => [
505
                    'users' => [
506
                        ['user_id' => '123-abc'],
507
                    ],
508
                    'consumer_key' => 'CONSUMER_1',
509
                    'tools' => [
510
                        ['secret' => 'toolsecret1'],
511
                        ['secret' => 'toolsecret2'],
512
                    ]
513
                ],
514
                'launch_data' => [
515
                    'user' => $this->get_mock_users_with_ids(
516
                        ['1'],
517
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner'
518
                    )[0],
519
                    'migration_claim' => [
520
                        'consumer_key' => 'CONSUMER_1',
521
                        'signing_secret' => 'bad_secret',
522
                        'user_id' => '123-abc',
523
                        'context_id' => 'd345b',
524
                        'tool_consumer_instance_guid' => '12345-123',
525
                        'resource_link_id' => '4b6fa'
526
                    ]
527
                ],
528
                'expected' => [
529
                    'migrated' => false,
530
                    'migration_debugging' => true,
531
                ]
532
            ],
533
            'New (unlinked) platform learner including PII, no legacy user, valid migration claim' => [
534
                'legacy_data' => [
535
                    'users' => [],
536
                    'consumer_key' => 'CONSUMER_1',
537
                    'tools' => [
538
                        ['secret' => 'toolsecret1'],
539
                        ['secret' => 'toolsecret2'],
540
                    ]
541
                ],
542
                'launch_data' => [
543
                    'user' => $this->get_mock_users_with_ids(
544
                        ['1'],
545
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner'
546
                    )[0],
547
                    'migration_claim' => [
548
                        'consumer_key' => 'CONSUMER_1',
549
                        'signing_secret' => 'toolsecret2',
550
                        'user_id' => '123-abc',
551
                        'context_id' => 'd345b',
552
                        'tool_consumer_instance_guid' => '12345-123',
553
                        'resource_link_id' => '4b6fa'
554
                    ]
555
                ],
556
                'expected' => [
557
                    'migrated' => false
558
                ]
559
            ],
560
            'New (unlinked) platform learner excluding PII, existing legacy user, valid migration claim' => [
561
                'legacy_data' => [
562
                    'users' => [
563
                        ['user_id' => '123-abc'],
564
                    ],
565
                    'consumer_key' => 'CONSUMER_1',
566
                    'tools' => [
567
                        ['secret' => 'toolsecret1'],
568
                        ['secret' => 'toolsecret2'],
569
                    ]
570
                ],
571
                'launch_data' => [
572
                    'user' => $this->get_mock_users_with_ids(
573
                        ['1'],
574
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner',
575
                        false,
576
                        false
577
                    )[0],
578
                    'migration_claim' => [
579
                        'consumer_key' => 'CONSUMER_1',
580
                        'signing_secret' => 'toolsecret1',
581
                        'user_id' => '123-abc',
582
                        'context_id' => 'd345b',
583
                        'tool_consumer_instance_guid' => '12345-123',
584
                        'resource_link_id' => '4b6fa'
585
                    ]
586
                ],
587
                'expected' => [
588
                    'migrated' => true
589
                ]
590
            ],
591
            'New (unlinked) platform instructor including PII, no legacy user, no migration claim' => [
592
                'legacy_data' => null,
593
                'launch_data' => [
594
                    'user' => $this->get_mock_users_with_ids(
595
                        ['1'],
596
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor'
597
                    )[0],
598
                    'migration_claim' => null
599
                ],
600
            ],
601
            'New (unlinked) platform instructor excluding PII, no legacy user, no migration claim' => [
602
                'legacy_data' => null,
603
                'launch_data' => [
604
                    'user' => $this->get_mock_users_with_ids(
605
                        ['1'],
606
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor',
607
                        false,
608
                        false
609
                    )[0],
610
                    'migration_claim' => null
611
                ],
612
            ],
613
            'New (unlinked) platform instructor including PII, existing legacy user, valid migration claim' => [
614
                'legacy_data' => [
615
                    'users' => [
616
                        ['user_id' => '123-abc'],
617
                    ],
618
                    'consumer_key' => 'CONSUMER_1',
619
                    'tools' => [
620
                        ['secret' => 'toolsecret1'],
621
                        ['secret' => 'toolsecret2'],
622
                    ]
623
                ],
624
                'launch_data' => [
625
                    'user' => $this->get_mock_users_with_ids(
626
                        ['1'],
627
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor'
628
                    )[0],
629
                    'migration_claim' => [
630
                        'consumer_key' => 'CONSUMER_1',
631
                        'signing_secret' => 'toolsecret1',
632
                        'user_id' => '123-abc',
633
                        'context_id' => 'd345b',
634
                        'tool_consumer_instance_guid' => '12345-123',
635
                        'resource_link_id' => '4b6fa'
636
                    ]
637
                ],
638
                'expected' => [
639
                    'migrated' => true
640
                ]
641
            ],
642
            'Existing (linked) platform learner including PII, no legacy user, no migration claim' => [
643
                'legacy_data' => null,
644
                'launch_data' => [
645
                    'has_authenticated_before' => true,
646
                    'user' => $this->get_mock_users_with_ids(
647
                        ['1'],
648
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner'
649
                    )[0],
650
                    'migration_claim' => null
651
                ],
652
            ],
653
            'Existing (linked) platform learner excluding PII, no legacy user, no migration claim' => [
654
                'legacy_data' => null,
655
                'launch_data' => [
656
                    'has_authenticated_before' => true,
657
                    'user' => $this->get_mock_users_with_ids(
658
                        ['1'],
659
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner',
660
                        false,
661
                        false
662
                    )[0],
663
                    'migration_claim' => null
664
                ],
665
            ],
666
            'Existing (linked) platform instructor including PII, no legacy user, no migration claim' => [
667
                'legacy_data' => null,
668
                'launch_data' => [
669
                    'has_authenticated_before' => true,
670
                    'user' => $this->get_mock_users_with_ids(
671
                        ['1'],
672
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor'
673
                    )[0],
674
                    'migration_claim' => null
675
                ],
676
            ],
677
            'Existing (linked) platform instructor excluding PII, no legacy user, no migration claim' => [
678
                'legacy_data' => null,
679
                'launch_data' => [
680
                    'has_authenticated_before' => true,
681
                    'user' => $this->get_mock_users_with_ids(
682
                        ['1'],
683
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor',
684
                        false,
685
                        false
686
                    )[0],
687
                    'migration_claim' => null
688
                ],
689
            ],
690
            'New (unlinked) platform instructor excluding PII, picture included' => [
691
                'legacy_data' => null,
692
                'launch_data' => [
693
                    'has_authenticated_before' => false,
694
                    'user' => $this->get_mock_users_with_ids(
695
                        ['1'],
696
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor',
697
                        false,
698
                        false,
699
                        true
700
                    )[0],
701
                    'migration_claim' => null
702
                ],
703
            ]
704
        ];
705
    }
706
 
707
    /**
708
     * Test which verifies a user account can be created/found using the find_or_create_user_from_membership() method.
709
     *
710
     * @dataProvider membership_data_provider
711
     * @param array|null $legacydata legacy user and tool data, if testing migration cases.
712
     * @param array $memberdata data describing the membership data, including user data and legacy user id info.
713
     * @param string $iss the issuer URL string
714
     * @param string|null $legacyconsumerkey optional legacy consumer_key value for testing user migration
715
     * @param array $expected the test case expectations.
716
     * @covers ::find_or_create_user_from_membership
717
     */
718
    public function test_find_or_create_user_from_membership(?array $legacydata, array $memberdata, string $iss,
11 efrain 719
            ?string $legacyconsumerkey, array $expected): void {
1 efrain 720
 
721
        $this->resetAfterTest();
722
        global $DB;
723
        $auth = get_auth_plugin('lti');
724
 
725
        // When testing platform users who have authenticated before, make that first auth call.
726
        if (!empty($memberdata['has_authenticated_before'])) {
727
            $mockmemberdata = $this->get_mock_member_data_for_user($memberdata['user'],
728
                $memberdata['legacy_user_id'] ?? '');
729
            $firstauthuser = $auth->find_or_create_user_from_membership($mockmemberdata, $iss,
730
                $legacyconsumerkey ?? '');
731
        }
732
 
733
        // Create legacy users and mocked tool secrets if desired.
734
        if ($legacydata) {
735
            $legacyusers = [];
736
            $generator = $this->getDataGenerator();
737
            foreach ($legacydata['users'] as $legacyuser) {
738
                $username = 'enrol_lti' . sha1($legacydata['consumer_key'] . '::' . $legacydata['consumer_key'] .
739
                        ':' . $legacyuser['user_id']);
740
 
741
                $legacyusers[] = $generator->create_user([
742
                    'username' => $username,
743
                    'auth' => 'lti'
744
                ]);
745
            }
746
        }
747
 
748
        // Mock the membership data.
749
        $mockmemberdata = $this->get_mock_member_data_for_user($memberdata['user'], $memberdata['legacy_user_id'] ?? '');
750
 
751
        // Authenticate the platform user.
752
        $sink = $this->redirectEvents();
753
        $countusersbefore = $DB->count_records('user');
754
        $user = $auth->find_or_create_user_from_membership($mockmemberdata, $iss, $legacyconsumerkey ?? '');
755
        $countusersafter = $DB->count_records('user');
756
        $events = $sink->get_events();
757
        $sink->close();
758
 
759
        // Verify user count is correct. i.e. no user is created when migration claim is correctly processed or when
760
        // the user has authenticated with the tool before.
761
        $numnewusers = (!empty($expected['migrated'])) ? 0 : 1;
762
        $numnewusers = (!empty($memberdata['has_authenticated_before'])) ?
763
 
764
        $this->assertEquals($numnewusers, $countusersafter - $countusersbefore);
765
 
766
        // Verify PII is updated appropriately.
767
        switch ($expected['PII']) {
768
            case self::PII_ALL:
769
                $this->assertEquals($memberdata['user']['given_name'], $user->firstname);
770
                $this->assertEquals($memberdata['user']['family_name'], $user->lastname);
771
                $this->assertEquals($memberdata['user']['email'], $user->email);
772
                break;
773
            case self::PII_NAMES_ONLY:
774
                $this->assertEquals($memberdata['user']['given_name'], $user->firstname);
775
                $this->assertEquals($memberdata['user']['family_name'], $user->lastname);
776
                $email = 'enrol_lti_13_' . sha1($iss . '_' . $mockmemberdata['user_id']) . "@example.com";
777
                $this->assertEquals($email, $user->email);
778
                break;
779
            case self::PII_EMAILS_ONLY:
780
                $this->assertEquals($iss, $user->lastname);
781
                $this->assertEquals($mockmemberdata['user_id'], $user->firstname);
782
                $this->assertEquals($memberdata['user']['email'], $user->email);
783
                break;
784
            default:
785
            case self::PII_NONE:
786
                $this->assertEquals($iss, $user->lastname);
787
                $this->assertEquals($mockmemberdata['user_id'], $user->firstname);
788
                $email = 'enrol_lti_13_' . sha1($iss . '_' . $mockmemberdata['user_id']) . "@example.com";
789
                $this->assertEquals($email, $user->email);
790
                break;
791
        }
792
 
793
        if (!empty($expected['migrated'])) {
794
            // If migrated, verify the user account is reusing the legacy user account.
795
            $legacyuserids = array_column($legacyusers, 'id');
796
            $this->assertContains($user->id, $legacyuserids);
797
            $this->assertInstanceOf(\core\event\user_updated::class, $events[0]);
798
        } else if (isset($firstauthuser)) {
799
            // If the user is authenticating a second time, confirm the same account is being returned.
800
            $this->assertEquals($firstauthuser->id, $user->id);
801
            $this->assertEmpty($events); // The user authenticated with the same data once before, so we don't expect an update.
802
        } else {
803
            // The user wasn't migrated and hasn't launched before, so we expect a user_created event.
804
            $this->assertInstanceOf(\core\event\user_created::class, $events[0]);
805
        }
806
    }
807
 
808
    /**
809
     * Data provider for testing membership-service-based authentication.
810
     *
811
     * @return array the test case data.
812
     */
813
    public function membership_data_provider(): array {
814
        return [
815
            'New (unlinked) platform learner including PII, no legacy data, no consumer key bound, no legacy id' => [
816
                'legacy_data' => null,
817
                'membership_data' => [
818
                    'user' => $this->get_mock_users_with_ids(
819
                        ['1'],
820
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner'
821
                    )[0],
822
                ],
823
                'iss' => $this->issuer,
824
                'legacy_consumer_key' => null,
825
                'expected' => [
826
                    'PII' => self::PII_ALL,
827
                    'migrated' => false
828
                ]
829
            ],
830
            'New (unlinked) platform learner excluding PII, no legacy data, no consumer key bound, no legacy id' => [
831
                'legacy_data' => null,
832
                'membership_data' => [
833
                    'user' => $this->get_mock_users_with_ids(
834
                        ['1'],
835
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner',
836
                        false,
837
                        false
838
                    )[0],
839
                ],
840
                'iss' => $this->issuer,
841
                'legacy_consumer_key' => null,
842
                'expected' => [
843
                    'PII' => self::PII_NONE,
844
                    'migrated' => false
845
                ]
846
            ],
847
            'New (unlinked) platform learner excluding names, no legacy data, no consumer key bound, no legacy id' => [
848
                'legacy_data' => null,
849
                'membership_data' => [
850
                    'user' => $this->get_mock_users_with_ids(
851
                        ['1'],
852
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner',
853
                        false,
854
                    )[0],
855
                ],
856
                'iss' => $this->issuer,
857
                'legacy_consumer_key' => null,
858
                'expected' => [
859
                    'PII' => self::PII_EMAILS_ONLY,
860
                    'migrated' => false
861
                ]
862
            ],
863
            'New (unlinked) platform learner excluding email, no legacy data, no consumer key bound, no legacy id' => [
864
                'legacy_data' => null,
865
                'membership_data' => [
866
                    'user' => $this->get_mock_users_with_ids(
867
                        ['1'],
868
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner',
869
                        true,
870
                        false
871
                    )[0],
872
                ],
873
                'iss' => $this->issuer,
874
                'legacy_consumer_key' => null,
875
                'expected' => [
876
                    'PII' => self::PII_NAMES_ONLY,
877
                    'migrated' => false
878
                ]
879
            ],
880
            'New (unlinked) platform learner including PII, legacy user, consumer key bound, legacy user id sent' => [
881
                'legacy_data' => [
882
                    'users' => [
883
                        ['user_id' => '123-abc'],
884
                    ],
885
                    'consumer_key' => 'CONSUMER_1',
886
                ],
887
                'membership_data' => [
888
                    'user' => $this->get_mock_users_with_ids(
889
                        ['1'],
890
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner'
891
                    )[0],
892
                    'legacy_user_id' => '123-abc'
893
                ],
894
                'iss' => $this->issuer,
895
                'legacy_consumer_key' => 'CONSUMER_1',
896
                'expected' => [
897
                    'PII' => self::PII_ALL,
898
                    'migrated' => true
899
                ]
900
            ],
901
            'New (unlinked) platform learner including PII, legacy user, consumer key bound, legacy user id omitted' => [
902
                'legacy_data' => [
903
                    'users' => [
904
                        ['user_id' => '123-abc'],
905
                    ],
906
                    'consumer_key' => 'CONSUMER_1',
907
                ],
908
                'membership_data' => [
909
                    'user' => $this->get_mock_users_with_ids(
910
                        ['1'],
911
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner'
912
                    )[0],
913
                ],
914
                'iss' => $this->issuer,
915
                'legacy_consumer_key' => 'CONSUMER_1',
916
                'expected' => [
917
                    'PII' => self::PII_ALL,
918
                    'migrated' => false,
919
                ]
920
            ],
921
            'New (unlinked) platform learner including PII, legacy user, consumer key bound, no change in user id' => [
922
                'legacy_data' => [
923
                    'users' => [
924
                        ['user_id' => '123-abc'],
925
                    ],
926
                    'consumer_key' => 'CONSUMER_1',
927
                ],
928
                'membership_data' => [
929
                    'user' => $this->get_mock_users_with_ids(
930
                        ['123-abc'],
931
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner'
932
                    )[0],
933
                ],
934
                'iss' => $this->issuer,
935
                'legacy_consumer_key' => 'CONSUMER_1',
936
                'expected' => [
937
                    'PII' => self::PII_ALL,
938
                    'migrated' => true
939
                ]
940
            ],
941
            'New (unlinked) platform learner including PII, legacy user, unexpected consumer key bound, no change in user id' => [
942
                'legacy_data' => [
943
                    'users' => [
944
                        ['user_id' => '123-abc'],
945
                    ],
946
                    'consumer_key' => 'CONSUMER_1',
947
                ],
948
                'membership_data' => [
949
                    'user' => $this->get_mock_users_with_ids(
950
                        ['123-abc'],
951
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner'
952
                    )[0],
953
                ],
954
                'iss' => $this->issuer,
955
                'legacy_consumer_key' => 'CONSUMER_ABCDEF',
956
                'expected' => [
957
                    'PII' => self::PII_ALL,
958
                    'migrated' => false,
959
                ]
960
            ],
961
            'New (unlinked) platform learner including PII, legacy user, consumer key not bound, legacy user id sent' => [
962
                'legacy_data' => [
963
                    'users' => [
964
                        ['user_id' => '123-abc'],
965
                    ],
966
                    'consumer_key' => 'CONSUMER_1',
967
                ],
968
                'membership_data' => [
969
                    'user' => $this->get_mock_users_with_ids(
970
                        ['1'],
971
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner'
972
                    )[0],
973
                    'legacy_user_id' => '123-abc'
974
                ],
975
                'iss' => $this->issuer,
976
                'legacy_consumer_key' => null,
977
                'expected' => [
978
                    'PII' => self::PII_ALL,
979
                    'migrated' => false
980
                ]
981
            ],
982
            'New (unlinked) platform learner including PII, no legacy data, consumer key bound, legacy user id sent' => [
983
                'legacy_data' => null,
984
                'membership_data' => [
985
                    'user' => $this->get_mock_users_with_ids(
986
                        ['1'],
987
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner'
988
                    )[0],
989
                    'legacy_user_id' => '123-abc'
990
                ],
991
                'iss' => $this->issuer,
992
                'legacy_consumer_key' => 'CONSUMER_1',
993
                'expected' => [
994
                    'PII' => self::PII_ALL,
995
                    'migrated' => false
996
                ]
997
            ],
998
            'New (unlinked) platform instructor including PII, no legacy data, no consumer key bound, no legacy id' => [
999
                'legacy_data' => null,
1000
                'membership_data' => [
1001
                    'user' => $this->get_mock_users_with_ids(
1002
                        ['1'],
1003
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor'
1004
                    )[0],
1005
                ],
1006
                'iss' => $this->issuer,
1007
                'legacy_consumer_key' => null,
1008
                'expected' => [
1009
                    'PII' => self::PII_ALL,
1010
                    'migrated' => false
1011
                ]
1012
            ],
1013
            'New (unlinked) platform instructor excluding PII, no legacy data, no consumer key bound, no legacy id' => [
1014
                'legacy_data' => null,
1015
                'membership_data' => [
1016
                    'user' => $this->get_mock_users_with_ids(
1017
                        ['1'],
1018
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor',
1019
                        false,
1020
                        false
1021
                    )[0],
1022
                ],
1023
                'iss' => $this->issuer,
1024
                'legacy_consumer_key' => null,
1025
                'expected' => [
1026
                    'PII' => self::PII_NONE,
1027
                    'migrated' => false
1028
                ]
1029
            ],
1030
            'Existing (linked) platform learner including PII, no legacy data, no consumer key bound, no legacy id' => [
1031
                'legacy_data' => null,
1032
                'launch_data' => [
1033
                    'has_authenticated_before' => true,
1034
                    'user' => $this->get_mock_users_with_ids(
1035
                        ['1'],
1036
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner'
1037
                    )[0],
1038
                ],
1039
                'iss' => $this->issuer,
1040
                'legacy_consumer_key' => null,
1041
                'expected' => [
1042
                    'PII' => self::PII_ALL,
1043
                    'migrated' => false
1044
                ]
1045
            ],
1046
        ];
1047
    }
1048
 
1049
    /**
1050
     * Test the behaviour of create_user_binding().
1051
     *
1052
     * @covers ::create_user_binding
1053
     */
11 efrain 1054
    public function test_create_user_binding(): void {
1 efrain 1055
        $this->resetAfterTest();
1056
        global $DB;
1057
        $auth = get_auth_plugin('lti');
1058
        $user = $this->getDataGenerator()->create_user();
1059
        $mockiss = $this->issuer;
1060
        $mocksub = '1';
1061
 
1062
        // Create a binding and verify it exists.
1063
        $this->assertFalse($DB->record_exists('auth_lti_linked_login', ['userid' => $user->id]));
1064
        $auth->create_user_binding($mockiss, $mocksub, $user->id);
1065
        $this->assertTrue($DB->record_exists('auth_lti_linked_login', ['userid' => $user->id]));
1066
 
1067
        // Now, try to get an authenticated user USING that binding. Verify the bound user is returned.
1068
        $numusersbefore = $DB->count_records('user');
1069
        $matcheduser = $auth->find_or_create_user_from_launch(
1070
            $this->get_mock_launchdata_for_user(
1071
                $this->get_mock_users_with_ids([$mocksub])[0]
1072
            )
1073
        );
1074
        $numusersafter = $DB->count_records('user');
1075
        $this->assertEquals($numusersafter, $numusersbefore);
1076
        $this->assertEquals($user->id, $matcheduser->id);
1077
 
1078
        // Assert idempotency of the bind call.
1079
        $this->assertNull($auth->create_user_binding($mockiss, $mocksub, $user->id));
1080
    }
1081
 
1082
    /**
1083
     * Test updating a user account based on a given set of launchdata.
1084
     *
1085
     * @param array $firstlaunchdata the data from the first launch the user made.
1086
     * @param array $launchdata the current launch data, which will dictate what data is updated.
1087
     * @param array $expected array of test expectations
1088
     * @dataProvider update_user_account_provider
1089
     * @covers ::update_user_account
1090
     */
1091
    public function test_update_user_account(array $firstlaunchdata, array $launchdata, array $expected): void {
1092
        $this->resetAfterTest();
1093
        $auth = get_auth_plugin('lti');
1094
 
1095
        // Mock the first authentication of the user.
1096
        $firstmockjwtdata = $this->get_mock_launchdata_for_user($firstlaunchdata['user']);
1097
        $user = $auth->find_or_create_user_from_launch($firstmockjwtdata);
1098
 
1099
        // Now, mock the recent authentication, confirming updates.
1100
        $mockjwtdata = $this->get_mock_launchdata_for_user($launchdata['user']);
1101
        $sink = $this->redirectEvents();
1102
        $auth->update_user_account($user, $mockjwtdata, $mockjwtdata['iss']);
1103
        $user = \core_user::get_user($user->id);
1104
        $events = $sink->get_events();
1105
        $sink->close();
1106
 
1107
        if (!empty($expected['user_updated'])) {
1108
            $this->assertInstanceOf(\core\event\user_updated::class, $events[0]);
1109
        } else {
1110
            $this->assertEmpty($events);
1111
        }
1112
 
1113
        // Verify PII is updated appropriately.
1114
        switch ($expected['PII']) {
1115
            case self::PII_ALL:
1116
                $this->assertEquals($launchdata['user']['given_name'], $user->firstname);
1117
                $this->assertEquals($launchdata['user']['family_name'], $user->lastname);
1118
                $this->assertEquals($launchdata['user']['email'], $user->email);
1119
                break;
1120
            case self::PII_NAMES_ONLY:
1121
                $this->assertEquals($launchdata['user']['given_name'], $user->firstname);
1122
                $this->assertEquals($launchdata['user']['family_name'], $user->lastname);
1123
                $email = 'enrol_lti_13_' . sha1($mockjwtdata['iss'] . '_' . $mockjwtdata['sub']) . "@example.com";
1124
                $this->assertEquals($email, $user->email);
1125
                break;
1126
            case self::PII_EMAILS_ONLY:
1127
                $this->assertEquals($mockjwtdata['iss'], $user->lastname);
1128
                $this->assertEquals($mockjwtdata['sub'], $user->firstname);
1129
                $this->assertEquals($launchdata['user']['email'], $user->email);
1130
                break;
1131
            default:
1132
            case self::PII_NONE:
1133
                $this->assertEquals($mockjwtdata['iss'], $user->lastname);
1134
                $this->assertEquals($mockjwtdata['sub'], $user->firstname);
1135
                $email = 'enrol_lti_13_' . sha1($mockjwtdata['iss'] . '_' . $mockjwtdata['sub']) . "@example.com";
1136
                $this->assertEquals($email, $user->email);
1137
                break;
1138
        }
1139
 
1140
        // Verify picture sync occurs, if expected.
1141
        if (!empty($expected['picture_updated'])) {
1142
            $this->verify_user_profile_image_updated($user->id);
1143
        }
1144
    }
1145
 
1146
    /**
1147
     * Data provider for testing user user_update_account.
1148
     *
1149
     * @return array the test case data.
1150
     */
1151
    public function update_user_account_provider(): array {
1152
        return [
1153
            'Full PII included in both auths, no picture in either' => [
1154
                'first_launch_data' => [
1155
                     'user' => $this->get_mock_users_with_ids(
1156
                        ['1'],
1157
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner'
1158
                    )[0]
1159
                ],
1160
                'launch_data' => [
1161
                    'user' => $this->get_mock_users_with_ids(
1162
                        ['1'],
1163
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner'
1164
                    )[0],
1165
                ],
1166
                'expected' => [
1167
                    'PII' => self::PII_ALL,
1168
                    'user_updated' => false,
1169
                    'picture_updated' => false
1170
                ]
1171
            ],
1172
            'No PII included in both auths, no picture in either' => [
1173
                'first_launch_data' => [
1174
                    'user' => $this->get_mock_users_with_ids(
1175
                        ['1'],
1176
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner',
1177
                        false,
1178
                        false
1179
                    )[0]
1180
                ],
1181
                'launch_data' => [
1182
                    'user' => $this->get_mock_users_with_ids(
1183
                        ['1'],
1184
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner',
1185
                        false,
1186
                        false
1187
                    )[0],
1188
                ],
1189
                'expected' => [
1190
                    'PII' => self::PII_NONE,
1191
                    'user_updated' => false,
1192
                    'picture_updated' => false
1193
                ]
1194
            ],
1195
            'First auth no PII, second auth including PII, no picture in either' => [
1196
                'first_launch_data' => [
1197
                    'user' => $this->get_mock_users_with_ids(
1198
                        ['1'],
1199
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner',
1200
                        false,
1201
                        false
1202
                    )[0]
1203
                ],
1204
                'launch_data' => [
1205
                    'user' => $this->get_mock_users_with_ids(
1206
                        ['1'],
1207
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner'
1208
                    )[0],
1209
                ],
1210
                'expected' => [
1211
                    'PII' => self::PII_ALL,
1212
                    'user_updated' => true,
1213
                    'picture_updated' => false
1214
                ]
1215
            ],
1216
            'First auth full PII, second auth no PII, no picture in either' => [
1217
                'first_launch_data' => [
1218
                    'user' => $this->get_mock_users_with_ids(
1219
                        ['1'],
1220
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner',
1221
                    )[0]
1222
                ],
1223
                'launch_data' => [
1224
                    'user' => $this->get_mock_users_with_ids(
1225
                        ['1'],
1226
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner',
1227
                        false,
1228
                        false
1229
                    )[0],
1230
                ],
1231
                'expected' => [
1232
                    'PII' => self::PII_NONE,
1233
                    'user_updated' => true,
1234
                    'picture_updated' => false
1235
                ]
1236
            ],
1237
            'First auth full PII, second auth emails only, no picture in either' => [
1238
                'first_launch_data' => [
1239
                    'user' => $this->get_mock_users_with_ids(
1240
                        ['1'],
1241
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner',
1242
                    )[0]
1243
                ],
1244
                'launch_data' => [
1245
                    'user' => $this->get_mock_users_with_ids(
1246
                        ['1'],
1247
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner',
1248
                        false
1249
                    )[0],
1250
                ],
1251
                'expected' => [
1252
                    'PII' => self::PII_EMAILS_ONLY,
1253
                    'user_updated' => true,
1254
                    'picture_updated' => false
1255
                ]
1256
            ],
1257
            'First auth full PII, second auth names only, no picture in either' => [
1258
                'first_launch_data' => [
1259
                    'user' => $this->get_mock_users_with_ids(
1260
                        ['1'],
1261
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner',
1262
                    )[0]
1263
                ],
1264
                'launch_data' => [
1265
                    'user' => $this->get_mock_users_with_ids(
1266
                        ['1'],
1267
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner',
1268
                        true,
1269
                        false
1270
                    )[0],
1271
                ],
1272
                'expected' => [
1273
                    'PII' => self::PII_NAMES_ONLY,
1274
                    'user_updated' => true,
1275
                    'picture_updated' => false
1276
                ]
1277
            ],
1278
            'Full PII included in both auths, picture included in the second auth' => [
1279
                'first_launch_data' => [
1280
                    'user' => $this->get_mock_users_with_ids(
1281
                        ['1'],
1282
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner'
1283
                    )[0]
1284
                ],
1285
                'launch_data' => [
1286
                    'user' => $this->get_mock_users_with_ids(
1287
                        ['1'],
1288
                        'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner',
1289
                        true,
1290
                        true,
1291
                        true
1292
                    )[0],
1293
                ],
1294
                'expected' => [
1295
                    'PII' => self::PII_ALL,
1296
                    'user_updated' => false,
1297
                    'picture_updated' => false
1298
                ]
1299
            ],
1300
        ];
1301
    }
1302
 
1303
}