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
 * This file contains a class definition for the Memberships service
19
 *
20
 * @package    ltiservice_memberships
21
 * @copyright  2015 Vital Source Technologies http://vitalsource.com
22
 * @author     Stephen Vickers
23
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24
 */
25
 
26
namespace ltiservice_memberships\local\service;
27
 
28
defined('MOODLE_INTERNAL') || die();
29
 
30
/**
31
 * A service implementing Memberships.
32
 *
33
 * @package    ltiservice_memberships
34
 * @since      Moodle 3.0
35
 * @copyright  2015 Vital Source Technologies http://vitalsource.com
36
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
37
 */
38
class memberships extends \mod_lti\local\ltiservice\service_base {
39
 
40
    /** Default prefix for context-level roles */
41
    const CONTEXT_ROLE_PREFIX = 'http://purl.imsglobal.org/vocab/lis/v2/membership#';
42
    /** Context-level role for Instructor */
43
    const CONTEXT_ROLE_INSTRUCTOR = 'http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor';
44
    /** Context-level role for Learner */
45
    const CONTEXT_ROLE_LEARNER = 'http://purl.imsglobal.org/vocab/lis/v2/membership#Learner';
46
    /** Capability used to identify Instructors */
47
    const INSTRUCTOR_CAPABILITY = 'moodle/course:manageactivities';
48
    /** Always include field */
49
    const ALWAYS_INCLUDE_FIELD = 1;
50
    /** Allow the instructor to decide if included */
51
    const DELEGATE_TO_INSTRUCTOR = 2;
52
    /** Instructor chose to include field */
53
    const INSTRUCTOR_INCLUDED = 1;
54
    /** Instructor delegated and approved for include */
55
    const INSTRUCTOR_DELEGATE_INCLUDED = array(self::DELEGATE_TO_INSTRUCTOR && self::INSTRUCTOR_INCLUDED);
56
    /** Scope for reading membership data */
57
    const SCOPE_MEMBERSHIPS_READ = 'https://purl.imsglobal.org/spec/lti-nrps/scope/contextmembership.readonly';
58
 
59
    /**
60
     * Class constructor.
61
     */
62
    public function __construct() {
63
 
64
        parent::__construct();
65
        $this->id = 'memberships';
66
        $this->name = get_string($this->get_component_id(), $this->get_component_id());
67
 
68
    }
69
 
70
    /**
71
     * Get the resources for this service.
72
     *
73
     * @return array
74
     */
75
    public function get_resources() {
76
 
77
        if (empty($this->resources)) {
78
            $this->resources = array();
79
            $this->resources[] = new \ltiservice_memberships\local\resources\contextmemberships($this);
80
            $this->resources[] = new \ltiservice_memberships\local\resources\linkmemberships($this);
81
        }
82
 
83
        return $this->resources;
84
 
85
    }
86
 
87
    /**
88
     * Get the scope(s) permitted for the tool relevant to this service.
89
     *
90
     * @return array
91
     */
92
    public function get_permitted_scopes() {
93
 
94
        $scopes = array();
95
        $ok = !empty($this->get_type());
96
        if ($ok && isset($this->get_typeconfig()[$this->get_component_id()]) &&
97
            ($this->get_typeconfig()[$this->get_component_id()] == parent::SERVICE_ENABLED)) {
98
            $scopes[] = self::SCOPE_MEMBERSHIPS_READ;
99
        }
100
 
101
        return $scopes;
102
 
103
    }
104
 
105
    /**
106
     * Get the scope(s) defined by this service.
107
     *
108
     * @return array
109
     */
110
    public function get_scopes() {
111
        return [self::SCOPE_MEMBERSHIPS_READ];
112
    }
113
 
114
    /**
115
     * Get the JSON for members.
116
     *
117
     * @param \mod_lti\local\ltiservice\resource_base $resource       Resource handling the request
118
     * @param \context_course   $context    Course context
119
     * @param string            $contextid  Course ID
120
     * @param object            $tool       Tool instance object
121
     * @param string            $role       User role requested (empty if none)
122
     * @param int               $limitfrom  Position of first record to be returned
123
     * @param int               $limitnum   Maximum number of records to be returned
124
     * @param object            $lti        LTI instance record
125
     * @param \core_availability\info_module $info Conditional availability information
126
     * for LTI instance (null if context-level request)
127
     *
128
     * @return string
129
     * @deprecated since Moodle 3.7 MDL-62599 - please do not use this function any more.
130
     * @see memberships::get_members_json($resource, $context, $course, $role, $limitfrom, $limitnum, $lti, $info, $response)
131
     */
132
    public static function get_users_json($resource, $context, $contextid, $tool, $role, $limitfrom, $limitnum, $lti, $info) {
133
        global $DB;
134
 
135
        debugging('get_users_json() has been deprecated, ' .
136
                  'please use memberships::get_members_json() instead.', DEBUG_DEVELOPER);
137
 
138
        $course = $DB->get_record('course', array('id' => $contextid), 'id,shortname,fullname', IGNORE_MISSING);
139
 
140
        $memberships = new memberships();
141
        $memberships->check_tool($tool->id, null, array(self::SCOPE_MEMBERSHIPS_READ));
142
 
143
        $response = new \mod_lti\local\ltiservice\response();
144
 
145
        $json = $memberships->get_members_json($resource, $context, $course, $role, $limitfrom, $limitnum, $lti, $info, $response);
146
 
147
        return $json;
148
    }
149
 
150
    /**
151
     * Get the JSON for members.
152
     *
153
     * @param \mod_lti\local\ltiservice\resource_base $resource       Resource handling the request
154
     * @param \context_course   $context    Course context
155
     * @param \course           $course     Course
156
     * @param string            $role       User role requested (empty if none)
157
     * @param int               $limitfrom  Position of first record to be returned
158
     * @param int               $limitnum   Maximum number of records to be returned
159
     * @param object            $lti        LTI instance record
160
     * @param \core_availability\info_module $info Conditional availability information
161
     *      for LTI instance (null if context-level request)
162
     * @param \mod_lti\local\ltiservice\response $response       Response object for the request
163
     *
164
     * @return string
165
     */
166
    public function get_members_json($resource, $context, $course, $role, $limitfrom, $limitnum, $lti, $info, $response) {
167
 
168
        $withcapability = '';
169
        $exclude = array();
170
        if (!empty($role)) {
171
            if ((strpos($role, 'http://') !== 0) && (strpos($role, 'https://') !== 0)) {
172
                $role = self::CONTEXT_ROLE_PREFIX . $role;
173
            }
174
            if ($role === self::CONTEXT_ROLE_INSTRUCTOR) {
175
                $withcapability = self::INSTRUCTOR_CAPABILITY;
176
            } else if ($role === self::CONTEXT_ROLE_LEARNER) {
177
                $exclude = array_keys(get_enrolled_users($context, self::INSTRUCTOR_CAPABILITY, 0, 'u.id',
178
                                                         null, null, null, true));
179
            }
180
        }
181
        $users = get_enrolled_users($context, $withcapability, 0, 'u.*', null, 0, 0, true);
182
        if (($response->get_accept() === 'application/vnd.ims.lti-nrps.v2.membershipcontainer+json') ||
183
            (($response->get_accept() !== 'application/vnd.ims.lis.v2.membershipcontainer+json') &&
184
            ($this->get_type()->ltiversion === LTI_VERSION_1P3))) {
185
            $json = $this->users_to_json($resource, $users, $course, $exclude, $limitfrom, $limitnum, $lti, $info, $response);
186
        } else {
187
            $json = $this->users_to_jsonld($resource, $users, $course->id, $exclude, $limitfrom, $limitnum, $lti, $info, $response);
188
        }
189
 
190
        return $json;
191
    }
192
 
193
    /**
194
     * Get the JSON-LD representation of the users.
195
     *
196
     * Note that when a limit is set and the exclude array is not empty, then the number of memberships
197
     * returned may be less than the limit.
198
     *
199
     * @param \mod_lti\local\ltiservice\resource_base $resource       Resource handling the request
200
     * @param array  $users               Array of user records
201
     * @param string $contextid           Course ID
202
     * @param array  $exclude             Array of user records to be excluded from the response
203
     * @param int    $limitfrom           Position of first record to be returned
204
     * @param int    $limitnum            Maximum number of records to be returned
205
     * @param object $lti                 LTI instance record
206
     * @param \core_availability\info_module $info Conditional availability information
207
     *      for LTI instance (null if context-level request)
208
     * @param \mod_lti\local\ltiservice\response $response       Response object for the request
209
     *
210
     * @return string
211
     */
212
    private function users_to_jsonld($resource, $users, $contextid, $exclude, $limitfrom, $limitnum,
213
            $lti, $info, $response) {
214
        global $DB;
215
 
216
        $tool = $this->get_type();
217
        $toolconfig = $this->get_typeconfig();
218
        $arrusers = [
219
            '@context' => 'http://purl.imsglobal.org/ctx/lis/v2/MembershipContainer',
220
            '@type' => 'Page',
221
            '@id' => $resource->get_endpoint(),
222
        ];
223
 
224
        $arrusers['pageOf'] = [
225
            '@type' => 'LISMembershipContainer',
226
            'membershipSubject' => [
227
                '@type' => 'Context',
228
                'contextId' => $contextid,
229
                'membership' => []
230
            ]
231
        ];
232
 
233
        $enabledcapabilities = lti_get_enabled_capabilities($tool);
234
        $islti2 = $tool->toolproxyid > 0;
235
        $n = 0;
236
        $more = false;
237
        foreach ($users as $user) {
238
            if (in_array($user->id, $exclude)) {
239
                continue;
240
            }
241
            if (!empty($info) && !$info->is_user_visible($info->get_course_module(), $user->id)) {
242
                continue;
243
            }
244
            $n++;
245
            if ($limitnum > 0) {
246
                if ($n <= $limitfrom) {
247
                    continue;
248
                }
249
                if (count($arrusers['pageOf']['membershipSubject']['membership']) >= $limitnum) {
250
                    $more = true;
251
                    break;
252
                }
253
            }
254
 
255
            $member = new \stdClass();
256
            $member->{"@type" } = 'LISPerson';
257
            $membership = new \stdClass();
258
            $membership->status = 'Active';
259
            $membership->role = explode(',', lti_get_ims_role($user->id, null, $contextid, true));
260
 
261
            $instanceconfig = null;
262
            if (!is_null($lti)) {
263
                $instanceconfig = lti_get_type_config_from_instance($lti->id);
264
            }
265
            $isallowedlticonfig = self::is_allowed_field_set($toolconfig, $instanceconfig,
266
                                    ['name' => 'sendname', 'email' => 'sendemailaddr']);
267
 
268
            $includedcapabilities = [
269
                'User.id'              => ['type' => 'id',
270
                                            'member.field' => 'userId',
271
                                            'source.value' => $user->id],
272
                'Person.sourcedId'     => ['type' => 'id',
273
                                            'member.field' => 'sourcedId',
274
                                            'source.value' => format_string($user->idnumber)],
275
                'Person.name.full'     => ['type' => 'name',
276
                                            'member.field' => 'name',
277
                                            'source.value' => format_string("{$user->firstname} {$user->lastname}")],
278
                'Person.name.given'    => ['type' => 'name',
279
                                            'member.field' => 'givenName',
280
                                            'source.value' => format_string($user->firstname)],
281
                'Person.name.family'   => ['type' => 'name',
282
                                            'member.field' => 'familyName',
283
                                            'source.value' => format_string($user->lastname)],
284
                'Person.email.primary' => ['type' => 'email',
285
                                            'member.field' => 'email',
286
                                            'source.value' => format_string($user->email)],
287
                'User.username'        => ['type' => 'name',
288
                                           'member.field' => 'ext_user_username',
289
                                           'source.value' => format_string($user->username)]
290
            ];
291
 
292
            if (!is_null($lti)) {
293
                $message = new \stdClass();
294
                $message->message_type = 'basic-lti-launch-request';
295
                $conditions = array('courseid' => $contextid, 'itemtype' => 'mod',
296
                        'itemmodule' => 'lti', 'iteminstance' => $lti->id);
297
 
298
                if (!empty($lti->servicesalt) && $DB->record_exists('grade_items', $conditions)) {
299
                    $message->lis_result_sourcedid = json_encode(lti_build_sourcedid($lti->id,
300
                                                                                     $user->id,
301
                                                                                     $lti->servicesalt,
302
                                                                                     $lti->typeid));
303
                    // Not per specification but added to comply with earlier version of the service.
304
                    $member->resultSourcedId = $message->lis_result_sourcedid;
305
                }
306
                $membership->message = [$message];
307
            }
308
 
309
            foreach ($includedcapabilities as $capabilityname => $capability) {
310
                if ($islti2) {
311
                    if (in_array($capabilityname, $enabledcapabilities)) {
312
                        $member->{$capability['member.field']} = $capability['source.value'];
313
                    }
314
                } else {
315
                    if (($capability['type'] === 'id')
316
                     || ($capability['type'] === 'name' && $isallowedlticonfig['name'])
317
                     || ($capability['type'] === 'email' && $isallowedlticonfig['email'])) {
318
                        $member->{$capability['member.field']} = $capability['source.value'];
319
                    }
320
                }
321
            }
322
 
323
            $membership->member = $member;
324
 
325
            $arrusers['pageOf']['membershipSubject']['membership'][] = $membership;
326
        }
327
        if ($more) {
328
            $nextlimitfrom = $limitfrom + $limitnum;
329
            $nextpage = "{$resource->get_endpoint()}?limit={$limitnum}&from={$nextlimitfrom}";
330
            if (!is_null($lti)) {
331
                $nextpage .= "&rlid={$lti->id}";
332
            }
333
            $arrusers['nextPage'] = $nextpage;
334
        }
335
 
336
        $response->set_content_type('application/vnd.ims.lis.v2.membershipcontainer+json');
337
 
338
        return json_encode($arrusers);
339
    }
340
 
341
    /**
342
     * Get the NRP service JSON representation of the users.
343
     *
344
     * Note that when a limit is set and the exclude array is not empty, then the number of memberships
345
     * returned may be less than the limit.
346
     *
347
     * @param \mod_lti\local\ltiservice\resource_base $resource       Resource handling the request
348
     * @param array   $users               Array of user records
349
     * @param \course $course              Course
350
     * @param array   $exclude             Array of user records to be excluded from the response
351
     * @param int     $limitfrom           Position of first record to be returned
352
     * @param int     $limitnum            Maximum number of records to be returned
353
     * @param object  $lti                 LTI instance record
354
     * @param \core_availability\info_module  $info     Conditional availability information for LTI instance
355
     * @param \mod_lti\local\ltiservice\response $response       Response object for the request
356
     *
357
     * @return string
358
     */
359
    private function users_to_json($resource, $users, $course, $exclude, $limitfrom, $limitnum,
360
            $lti, $info, $response) {
361
        global $DB, $CFG;
362
 
363
        $tool = $this->get_type();
364
        $toolconfig = $this->get_typeconfig();
365
 
366
        $context = new \stdClass();
367
        $context->id = $course->id;
368
        $context->label = trim(html_to_text($course->shortname, 0));
369
        $context->title = trim(html_to_text($course->fullname, 0));
370
 
371
        $arrusers = [
372
            'id' => $resource->get_endpoint(),
373
            'context' => $context,
374
            'members' => []
375
        ];
376
 
377
        $islti2 = $tool->toolproxyid > 0;
378
        $n = 0;
379
        $more = false;
380
        foreach ($users as $user) {
381
            if (in_array($user->id, $exclude)) {
382
                continue;
383
            }
384
            if (!empty($info) && !$info->is_user_visible($info->get_course_module(), $user->id)) {
385
                continue;
386
            }
387
            $n++;
388
            if ($limitnum > 0) {
389
                if ($n <= $limitfrom) {
390
                    continue;
391
                }
392
                if (count($arrusers['members']) >= $limitnum) {
393
                    $more = true;
394
                    break;
395
                }
396
            }
397
 
398
            $member = new \stdClass();
399
            $member->status = 'Active';
400
            $member->roles = explode(',', lti_get_ims_role($user->id, null, $course->id, true));
401
 
402
            $instanceconfig = null;
403
            if (!is_null($lti)) {
404
                $instanceconfig = lti_get_type_config_from_instance($lti->id);
405
            }
406
            if (!$islti2) {
407
                $isallowedlticonfig = self::is_allowed_field_set($toolconfig, $instanceconfig,
408
                                        ['name' => 'sendname', 'givenname' => 'sendname', 'familyname' => 'sendname',
409
                                         'email' => 'sendemailaddr']);
410
            } else {
411
                $isallowedlticonfig = self::is_allowed_capability_set($tool,
412
                                        ['name' => 'Person.name.full', 'givenname' => 'Person.name.given',
413
                                         'familyname' => 'Person.name.family', 'email' => 'Person.email.primary']);
414
            }
415
            $includedcapabilities = [
416
                'User.id'              => ['type' => 'id',
417
                                            'member.field' => 'user_id',
418
                                            'source.value' => $user->id],
419
                'Person.sourcedId'     => ['type' => 'id',
420
                                            'member.field' => 'lis_person_sourcedid',
421
                                            'source.value' => format_string($user->idnumber)],
422
                'Person.name.full'     => ['type' => 'name',
423
                                            'member.field' => 'name',
424
                                            'source.value' => format_string("{$user->firstname} {$user->lastname}")],
425
                'Person.name.given'    => ['type' => 'givenname',
426
                                            'member.field' => 'given_name',
427
                                            'source.value' => format_string($user->firstname)],
428
                'Person.name.family'   => ['type' => 'familyname',
429
                                            'member.field' => 'family_name',
430
                                            'source.value' => format_string($user->lastname)],
431
                'Person.email.primary' => ['type' => 'email',
432
                                            'member.field' => 'email',
433
                                            'source.value' => format_string($user->email)],
434
                'User.username'        => ['type' => 'name',
435
                                           'member.field' => 'ext_user_username',
436
                                           'source.value' => format_string($user->username)],
437
            ];
438
 
439
            if (!is_null($lti)) {
440
                $message = new \stdClass();
441
                $message->{'https://purl.imsglobal.org/spec/lti/claim/message_type'} = 'LtiResourceLinkRequest';
442
                $conditions = array('courseid' => $course->id, 'itemtype' => 'mod',
443
                        'itemmodule' => 'lti', 'iteminstance' => $lti->id);
444
 
445
                if (!empty($lti->servicesalt) && $DB->record_exists('grade_items', $conditions)) {
446
                    $basicoutcome = new \stdClass();
447
                    $basicoutcome->lis_result_sourcedid = json_encode(lti_build_sourcedid($lti->id,
448
                                                                                     $user->id,
449
                                                                                     $lti->servicesalt,
450
                                                                                     $lti->typeid));
451
                    // Add outcome service URL.
452
                    $serviceurl = new \moodle_url('/mod/lti/service.php');
453
                    $serviceurl = $serviceurl->out();
454
                    $forcessl = false;
455
                    if (!empty($CFG->mod_lti_forcessl)) {
456
                        $forcessl = true;
457
                    }
458
                    if ((isset($toolconfig['forcessl']) && ($toolconfig['forcessl'] == '1')) or $forcessl) {
459
                        $serviceurl = lti_ensure_url_is_https($serviceurl);
460
                    }
461
                    $basicoutcome->lis_outcome_service_url = $serviceurl;
462
                    $message->{'https://purl.imsglobal.org/spec/lti-bo/claim/basicoutcome'} = $basicoutcome;
463
                }
464
                $member->message = [$message];
465
            }
466
 
467
            foreach ($includedcapabilities as $capabilityname => $capability) {
468
                if (($capability['type'] === 'id') || $isallowedlticonfig[$capability['type']]) {
469
                    $member->{$capability['member.field']} = $capability['source.value'];
470
                }
471
            }
472
 
473
            $arrusers['members'][] = $member;
474
        }
475
        if ($more) {
476
            $nextlimitfrom = $limitfrom + $limitnum;
477
            $nextpage = "{$resource->get_endpoint()}?limit={$limitnum}&from={$nextlimitfrom}";
478
            if (!is_null($lti)) {
479
                $nextpage .= "&rlid={$lti->id}";
480
            }
481
            $response->add_additional_header("Link: <{$nextpage}>; rel=\"next\"");
482
        }
483
 
484
        $response->set_content_type('application/vnd.ims.lti-nrps.v2.membershipcontainer+json');
485
 
486
        return json_encode($arrusers);
487
    }
488
 
489
    /**
490
     * Determines whether a user attribute may be used as part of LTI membership
491
     * @param array             $toolconfig      Tool config
492
     * @param object            $instanceconfig  Tool instance config
493
     * @param array             $fields          Set of fields to return if allowed or not
494
     * @return array Verification which associates an attribute with a boolean (allowed or not)
495
     */
496
    private static function is_allowed_field_set($toolconfig, $instanceconfig, $fields) {
497
        $isallowedstate = [];
498
        foreach ($fields as $key => $field) {
499
            $allowed = isset($toolconfig[$field]) && (self::ALWAYS_INCLUDE_FIELD == $toolconfig[$field]);
500
            if (!$allowed && isset($toolconfig[$field]) && (self::DELEGATE_TO_INSTRUCTOR == $toolconfig[$field]) &&
501
                !is_null($instanceconfig)) {
502
                $allowed = isset($instanceconfig->{"lti_{$field}"}) &&
503
                          ($instanceconfig->{"lti_{$field}"} == self::INSTRUCTOR_INCLUDED);
504
            }
505
            $isallowedstate[$key] = $allowed;
506
        }
507
        return $isallowedstate;
508
    }
509
 
510
    /**
511
     * Adds form elements for membership add/edit page.
512
     *
513
     * @param \MoodleQuickForm $mform
514
     */
515
    public function get_configuration_options(&$mform) {
516
        $elementname = $this->get_component_id();
517
        $options = [
518
            get_string('notallow', $this->get_component_id()),
519
            get_string('allow', $this->get_component_id())
520
        ];
521
 
522
        $mform->addElement('select', $elementname, get_string($elementname, $this->get_component_id()), $options);
523
        $mform->setType($elementname, 'int');
524
        $mform->setDefault($elementname, 0);
525
        $mform->addHelpButton($elementname, $elementname, $this->get_component_id());
526
    }
527
 
528
    /**
529
     * Return an array of key/values to add to the launch parameters.
530
     *
531
     * @param string $messagetype 'basic-lti-launch-request' or 'ContentItemSelectionRequest'.
532
     * @param string $courseid The course id.
533
     * @param string $user The user id.
534
     * @param string $typeid The tool lti type id.
535
     * @param string $modlti The id of the lti activity.
536
     *
537
     * The type is passed to check the configuration
538
     * and not return parameters for services not used.
539
     *
540
     * @return array of key/value pairs to add as launch parameters.
541
     */
542
    public function get_launch_parameters($messagetype, $courseid, $user, $typeid, $modlti = null) {
543
        global $COURSE;
544
 
545
        $launchparameters = array();
546
        $tool = lti_get_type_type_config($typeid);
547
        if (isset($tool->{$this->get_component_id()})) {
548
            if ($tool->{$this->get_component_id()} == parent::SERVICE_ENABLED && $this->is_used_in_context($typeid, $courseid)) {
549
                $launchparameters['context_memberships_url'] = '$ToolProxyBinding.memberships.url';
550
                $launchparameters['context_memberships_v2_url'] = '$ToolProxyBinding.memberships.url';
551
                $launchparameters['context_memberships_versions'] = '1.0,2.0';
552
            }
553
        }
554
        return $launchparameters;
555
    }
556
 
557
    /**
558
     * Return an array of key/claim mapping allowing LTI 1.1 custom parameters
559
     * to be transformed to LTI 1.3 claims.
560
     *
561
     * @return array Key/value pairs of params to claim mapping.
562
     */
563
    public function get_jwt_claim_mappings(): array {
564
        return [
565
            'custom_context_memberships_v2_url' => [
566
                'suffix' => 'nrps',
567
                'group' => 'namesroleservice',
568
                'claim' => 'context_memberships_url',
569
                'isarray' => false
570
            ],
571
            'custom_context_memberships_versions' => [
572
                'suffix' => 'nrps',
573
                'group' => 'namesroleservice',
574
                'claim' => 'service_versions',
575
                'isarray' => true
576
            ]
577
        ];
578
    }
579
}