mirror of
https://github.com/moodle/moodle.git
synced 2025-04-21 00:12:56 +02:00
MDL-61789 auth_oauth2: Update profile fields based on data mapping.
After the user creation, the system must call an update function to update profile_fields_*. We also provided two functions into user/profile/lib.php to get available from other areas. We added PHP unit testing for new public functions and the Behat tests for custom profile fields with locked and unlocked statuses. Co-authored-by: Matt Porritt <matt.porritt@moodle.com>
This commit is contained in:
parent
6793891887
commit
b79231361b
@ -7,17 +7,11 @@ Feature: OAuth2 user profile fields functionality
|
||||
to custom user profile fields defined by an administrator.
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| userwithinformation | userwithinformation | 1 | userwithinformation@example.com |
|
||||
Given the following "custom profile fields" exist:
|
||||
| datatype | shortname | name | locked |
|
||||
| text | unlocked_field | Unlocked field | 0 |
|
||||
| text | locked_field | Locked field | 1 |
|
||||
And I log in as "admin"
|
||||
And I navigate to "Users > Accounts > User profile fields" in site administration
|
||||
And I click on "Create a new profile field" "link"
|
||||
And I click on "Text input" "link"
|
||||
And I set the following fields to these values:
|
||||
| Short name | test_shortname |
|
||||
| Name | test field name |
|
||||
And I click on "Save changes" "button"
|
||||
And I navigate to "Server > OAuth 2 services" in site administration
|
||||
|
||||
Scenario: Verify custom user profile field mapping
|
||||
@ -31,9 +25,19 @@ Feature: OAuth2 user profile fields functionality
|
||||
Then I should see "Changes saved"
|
||||
And I should see "Testing service"
|
||||
And I click on "Configure user field mappings" "link" in the "Testing service" "table_row"
|
||||
|
||||
# Create unlocked field
|
||||
And I click on "Create new user field mapping for issuer \"Testing service\"" "button"
|
||||
And I set the following fields to these values:
|
||||
| External field name | sub |
|
||||
| Internal field name | test field name |
|
||||
| External field name | External unlocked |
|
||||
| Internal field name | Unlocked field |
|
||||
And I click on "Save changes" "button"
|
||||
And I should see "test_shortname"
|
||||
And I should see "unlocked_field"
|
||||
|
||||
# Create locked field
|
||||
And I click on "Create new user field mapping for issuer \"Testing service\"" "button"
|
||||
And I set the following fields to these values:
|
||||
| External field name | External locked |
|
||||
| Internal field name | Locked field |
|
||||
And I click on "Save changes" "button"
|
||||
And I should see "locked_field"
|
||||
|
@ -258,16 +258,7 @@ class api {
|
||||
$user->password = '';
|
||||
$user->confirmed = 1; // Set the user to confirmed.
|
||||
|
||||
// Map supplied issuer user info to Moodle user fields.
|
||||
$userfieldmapping = new \core\oauth2\user_field_mapping();
|
||||
$userfieldlist = $userfieldmapping->get_internalfield_list();
|
||||
foreach (reset($userfieldlist) as $field) {
|
||||
if (isset($userinfo[$field]) && $userinfo[$field]) {
|
||||
$user->$field = $userinfo[$field];
|
||||
}
|
||||
}
|
||||
|
||||
$user->id = user_create_user($user, false, true);
|
||||
$user = self::save_user($userinfo, $user);
|
||||
|
||||
// The linked account is pre-confirmed.
|
||||
$record = new stdClass();
|
||||
@ -308,16 +299,7 @@ class api {
|
||||
$user->password = '';
|
||||
$user->confirmed = 0; // The user is not yet confirmed.
|
||||
|
||||
// Map supplied issuer user info to Moodle user fields.
|
||||
$userfieldmapping = new \core\oauth2\user_field_mapping();
|
||||
$userfieldlist = $userfieldmapping->get_internalfield_list();
|
||||
foreach (reset($userfieldlist) as $field) {
|
||||
if (isset($userinfo[$field]) && $userinfo[$field]) {
|
||||
$user->$field = $userinfo[$field];
|
||||
}
|
||||
}
|
||||
|
||||
$user->id = user_create_user($user, false, true);
|
||||
$user = self::save_user($userinfo, $user);
|
||||
|
||||
// The linked account is pre-confirmed.
|
||||
$record = new stdClass();
|
||||
@ -406,4 +388,36 @@ class api {
|
||||
public static function is_enabled() {
|
||||
return is_enabled_auth('oauth2');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new user & update the profile fields
|
||||
*
|
||||
* @param array $userinfo
|
||||
* @param object $user
|
||||
* @return object
|
||||
*/
|
||||
private static function save_user(array $userinfo, object $user): object {
|
||||
// Map supplied issuer user info to Moodle user fields.
|
||||
$userfieldmapping = new \core\oauth2\user_field_mapping();
|
||||
$userfieldlist = $userfieldmapping->get_internalfields();
|
||||
$hasprofilefield = false;
|
||||
foreach ($userfieldlist as $field) {
|
||||
if (isset($userinfo[$field]) && $userinfo[$field]) {
|
||||
$user->$field = $userinfo[$field];
|
||||
|
||||
// Check whether the profile fields exist or not.
|
||||
$hasprofilefield = $hasprofilefield || strpos($field, \core_user\fields::PROFILE_FIELD_PREFIX) === 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new user.
|
||||
$user->id = user_create_user($user, false, true);
|
||||
|
||||
// If profile fields exist then save custom profile fields data.
|
||||
if ($hasprofilefield) {
|
||||
profile_save_data($user);
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ class user_field_mapping extends persistent {
|
||||
* @return array
|
||||
*/
|
||||
private static function get_user_fields() {
|
||||
return array_merge(\core_user::AUTHSYNCFIELDS, ['picture', 'username'], self::get_profile_field_names());
|
||||
return array_merge(\core_user::AUTHSYNCFIELDS, ['picture', 'username'], get_profile_field_names());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -74,7 +74,26 @@ class user_field_mapping extends persistent {
|
||||
public function get_internalfield_list() {
|
||||
$userfields = array_merge(\core_user::AUTHSYNCFIELDS, ['picture', 'username']);
|
||||
$internalfields = array_combine($userfields, $userfields);
|
||||
return array_merge(['' => $internalfields], self::get_profile_field_list());
|
||||
return array_merge(['' => $internalfields], get_profile_field_list());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of internal fields with flat array
|
||||
*
|
||||
* Profile fields element has its array based on profile category.
|
||||
* These elements need to be turned flat to make it easier to read.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_internalfields() {
|
||||
$userfieldlist = $this->get_internalfield_list();
|
||||
$userfields = [];
|
||||
array_walk_recursive($userfieldlist,
|
||||
function($value, $key) use (&$userfields) {
|
||||
$userfields[] = $key;
|
||||
}
|
||||
);
|
||||
return $userfields;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -90,38 +109,4 @@ class user_field_mapping extends persistent {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of valid custom profile user fields.
|
||||
*
|
||||
* @return array array of profile field names
|
||||
*/
|
||||
private static function get_profile_field_names(): array {
|
||||
$profilefields = profile_get_user_fields_with_data(0);
|
||||
$profilefieldnames = [];
|
||||
foreach ($profilefields as $field) {
|
||||
$profilefieldnames[] = $field->inputname;
|
||||
}
|
||||
return $profilefieldnames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of profile fields
|
||||
* in a format they can be used for choices in a group select menu.
|
||||
*
|
||||
* @return array array of category name with its profile fields
|
||||
*/
|
||||
private function get_profile_field_list(): array {
|
||||
$customfields = profile_get_user_fields_with_data_by_category(0);
|
||||
$data = [];
|
||||
foreach ($customfields as $category) {
|
||||
foreach ($category as $field) {
|
||||
$categoryname = $field->get_category_name();
|
||||
if (!isset($data[$categoryname])) {
|
||||
$data[$categoryname] = [];
|
||||
}
|
||||
$data[$categoryname][$field->inputname] = $field->field->name;
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ use core\oauth2\api;
|
||||
use core\oauth2\endpoint;
|
||||
use core\oauth2\issuer;
|
||||
use core\oauth2\system_account;
|
||||
use \core\oauth2\user_field_mapping;
|
||||
|
||||
/**
|
||||
* Tests for oauth2 apis (\core\oauth2\*).
|
||||
@ -442,4 +443,178 @@ class oauth2_test extends \advanced_testcase {
|
||||
|
||||
$this->assertFalse($googleissuer->is_available_for_login());
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for test_get_internalfield_list and test_get_internalfields.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function create_custom_profile_fields(): array {
|
||||
return [
|
||||
'data' =>
|
||||
[
|
||||
'given' => [
|
||||
'Hobbies' => [
|
||||
'shortname' => 'hobbies',
|
||||
'name' => 'Hobbies',
|
||||
]
|
||||
],
|
||||
'expected' => [
|
||||
'Hobbies' => [
|
||||
'shortname' => 'hobbies',
|
||||
'name' => 'Hobbies',
|
||||
]
|
||||
]
|
||||
],
|
||||
[
|
||||
'given' => [
|
||||
'Billing' => [
|
||||
'shortname' => 'billingaddress',
|
||||
'name' => 'Billing Address',
|
||||
],
|
||||
'Payment' => [
|
||||
'shortname' => 'creditcardnumber',
|
||||
'name' => 'Credit Card Number',
|
||||
]
|
||||
],
|
||||
'expected' => [
|
||||
'Billing' => [
|
||||
'shortname' => 'billingaddress',
|
||||
'name' => 'Billing Address',
|
||||
],
|
||||
'Payment' => [
|
||||
'shortname' => 'creditcardnumber',
|
||||
'name' => 'Credit Card Number',
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getting the list of internal fields.
|
||||
*
|
||||
* @dataProvider create_custom_profile_fields
|
||||
* @covers ::get_internalfield_list
|
||||
* @param array $given Categories and profile fields.
|
||||
* @param array $expected Expected value.
|
||||
*/
|
||||
public function test_get_internalfield_list(array $given, array $expected): void {
|
||||
$this->resetAfterTest();
|
||||
self::generate_custom_profile_fields($given);
|
||||
|
||||
$userfieldmapping = new user_field_mapping();
|
||||
$internalfieldlist = $userfieldmapping->get_internalfield_list();
|
||||
|
||||
foreach ($expected as $category => $value) {
|
||||
// Custom profile fields must exist.
|
||||
$this->assertNotEmpty($internalfieldlist[$category]);
|
||||
|
||||
// Category must have the custom profile fields with expected value.
|
||||
$this->assertEquals(
|
||||
$internalfieldlist[$category][\core_user\fields::PROFILE_FIELD_PREFIX . $value['shortname']],
|
||||
$value['name']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getting the list of internal fields with flat array.
|
||||
*
|
||||
* @dataProvider create_custom_profile_fields
|
||||
* @covers ::get_internalfields
|
||||
* @param array $given Categories and profile fields.
|
||||
* @param array $expected Expected value.
|
||||
*/
|
||||
public function test_get_internalfields(array $given, array $expected): void {
|
||||
$this->resetAfterTest();
|
||||
self::generate_custom_profile_fields($given);
|
||||
|
||||
$userfieldmapping = new user_field_mapping();
|
||||
$internalfields = $userfieldmapping->get_internalfields();
|
||||
|
||||
// Custom profile fields must exist.
|
||||
foreach ($expected as $category => $value) {
|
||||
$this->assertContains( \core_user\fields::PROFILE_FIELD_PREFIX . $value['shortname'], $internalfields);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getting the list of empty external/custom profile fields.
|
||||
*
|
||||
* @covers ::get_internalfields
|
||||
*/
|
||||
public function test_get_empty_internalfield_list(): void {
|
||||
|
||||
// Get internal (profile) fields.
|
||||
$userfieldmapping = new user_field_mapping();
|
||||
$internalfieldlist = $userfieldmapping->get_internalfields();
|
||||
|
||||
// Get user fields.
|
||||
$userfields = array_merge(\core_user::AUTHSYNCFIELDS, ['picture', 'username']);
|
||||
|
||||
// Internal fields and user fields must exact same.
|
||||
$this->assertEquals($userfields, $internalfieldlist);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getting Return the list of profile fields.
|
||||
*
|
||||
* @dataProvider create_custom_profile_fields
|
||||
* @covers ::get_profile_field_list
|
||||
* @param array $given Categories and profile fields.
|
||||
* @param array $expected Expected value.
|
||||
*/
|
||||
public function test_get_profile_field_list(array $given, array $expected): void {
|
||||
$this->resetAfterTest();
|
||||
self::generate_custom_profile_fields($given);
|
||||
|
||||
$profilefieldlist = get_profile_field_list();
|
||||
|
||||
foreach ($expected as $category => $value) {
|
||||
$this->assertEquals(
|
||||
$profilefieldlist[$category][\core_user\fields::PROFILE_FIELD_PREFIX . $value['shortname']],
|
||||
$value['name']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getting the list of valid custom profile user fields.
|
||||
*
|
||||
* @dataProvider create_custom_profile_fields
|
||||
* @covers ::get_profile_field_names
|
||||
* @param array $given Categories and profile fields.
|
||||
* @param array $expected Expected value.
|
||||
*/
|
||||
public function test_get_profile_field_names(array $given, array $expected): void {
|
||||
$this->resetAfterTest();
|
||||
self::generate_custom_profile_fields($given);
|
||||
|
||||
$profilefieldnames = get_profile_field_names();
|
||||
|
||||
// Custom profile fields must exist.
|
||||
foreach ($expected as $category => $value) {
|
||||
$this->assertContains( \core_user\fields::PROFILE_FIELD_PREFIX . $value['shortname'], $profilefieldnames);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate data into DB for Testing getting user fields mapping.
|
||||
*
|
||||
* @param array $given Categories and profile fields.
|
||||
*/
|
||||
private function generate_custom_profile_fields(array $given): void {
|
||||
// Create a profile category and the profile fields.
|
||||
foreach ($given as $category => $value) {
|
||||
$customprofilefieldcategory = ['name' => $category, 'sortorder' => 1];
|
||||
$category = $this->getDataGenerator()->create_custom_profile_field_category($customprofilefieldcategory);
|
||||
$this->getDataGenerator()->create_custom_profile_field(
|
||||
['shortname' => $value['shortname'],
|
||||
'name' => $value['name'],
|
||||
'categoryid' => $category->id,
|
||||
'required' => 1, 'visible' => 1, 'locked' => 0, 'datatype' => 'text', 'defaultdata' => null]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -960,3 +960,38 @@ function profile_has_required_custom_fields_set($userid) {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of valid custom profile user fields.
|
||||
*
|
||||
* @return array array of profile field names
|
||||
*/
|
||||
function get_profile_field_names(): array {
|
||||
$profilefields = profile_get_user_fields_with_data(0);
|
||||
$profilefieldnames = [];
|
||||
foreach ($profilefields as $field) {
|
||||
$profilefieldnames[] = $field->inputname;
|
||||
}
|
||||
return $profilefieldnames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of profile fields
|
||||
* in a format they can be used for choices in a group select menu.
|
||||
*
|
||||
* @return array array of category name with its profile fields
|
||||
*/
|
||||
function get_profile_field_list(): array {
|
||||
$customfields = profile_get_user_fields_with_data_by_category(0);
|
||||
$data = [];
|
||||
foreach ($customfields as $category) {
|
||||
foreach ($category as $field) {
|
||||
$categoryname = $field->get_category_name();
|
||||
if (!isset($data[$categoryname])) {
|
||||
$data[$categoryname] = [];
|
||||
}
|
||||
$data[$categoryname][$field->inputname] = $field->field->name;
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
@ -1,5 +1,18 @@
|
||||
This files describes API changes for code that uses the user API.
|
||||
|
||||
=== 4.2 ===
|
||||
|
||||
* Added get_internalfield_list() and get_internalfields() in the user_field_mapping class.
|
||||
The get_internalfield_list() returns data in an array by grouping profile fields based on field categories,
|
||||
used for internal field name dropdown in the user field mapping of Oauth2 services
|
||||
The get_internalfields() converts the result from get_internalfield_list() into flat array,
|
||||
used to save/update the profile data when a user uses OAuth2 services.
|
||||
|
||||
* Added get_profile_field_names() and get_profile_field_list() in the profile_field_base class.
|
||||
The get_profile_field_names() returns the list of valid custom profile user fields.
|
||||
The get_profile_field_list() returns the profile fields
|
||||
in a format that can be used for choices in a group select menu.
|
||||
|
||||
=== 4.1 ===
|
||||
|
||||
* Added a new method is_transform_supported() in the profile_field_base class.
|
||||
|
Loading…
x
Reference in New Issue
Block a user