Skip to content

Commit

Permalink
Merge branch 'MDL-61789-master' of https://github.com/meirzamoodle/mo…
Browse files Browse the repository at this point in the history
  • Loading branch information
sarjona committed Jan 16, 2023
2 parents 574f68f + b792313 commit d62f6ae
Show file tree
Hide file tree
Showing 9 changed files with 348 additions and 27 deletions.
2 changes: 1 addition & 1 deletion admin/tool/oauth2/classes/form/user_field_mapping.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public function definition() {

// Internal.
$choices = $userfieldmapping->get_internalfield_list();
$mform->addElement('select', 'internalfield', get_string('userfieldinternalfield', 'tool_oauth2'), $choices);
$mform->addElement('selectgroups', 'internalfield', get_string('userfieldinternalfield', 'tool_oauth2'), $choices);
$mform->addHelpButton('internalfield', 'userfieldinternalfield', 'tool_oauth2');

$mform->addElement('hidden', 'action', 'edit');
Expand Down
43 changes: 43 additions & 0 deletions admin/tool/oauth2/tests/behat/user_profile_fields.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
@tool @tool_oauth2 @javascript

Feature: OAuth2 user profile fields functionality
In order to use them later for authentication or repository plugins
As an administrator
I need to be able to map data fields provided by an Oauth2 provider
to custom user profile fields defined by an administrator.

Background:
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 "Server > OAuth 2 services" in site administration

Scenario: Verify custom user profile field mapping
Given I press "Microsoft"
And I should see "Create new service: Microsoft"
And I set the following fields to these values:
| Name | Testing service |
| Client ID | thisistheclientid |
| Client secret | supersecret |
When I press "Save changes"
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 | External unlocked |
| Internal field name | Unlocked field |
And I click on "Save changes" "button"
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"
52 changes: 34 additions & 18 deletions auth/oauth2/classes/api.php
Original file line number Diff line number Diff line change
Expand Up @@ -258,15 +258,7 @@ public static function create_new_confirmed_account($userinfo, $issuer) {
$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();
foreach ($userfieldmapping->get_internalfield_list() 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();
Expand Down Expand Up @@ -307,15 +299,7 @@ public static function send_confirm_account_email($userinfo, $issuer) {
$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();
foreach ($userfieldmapping->get_internalfield_list() 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();
Expand Down Expand Up @@ -404,4 +388,36 @@ public static function user_deleted(\core\event\user_deleted $event) {
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;
}
}
26 changes: 21 additions & 5 deletions auth/oauth2/classes/auth.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ class auth extends \auth_plugin_base {
public function __construct() {
$this->authtype = 'oauth2';
$this->config = get_config('auth_oauth2');
$this->customfields = $this->get_custom_user_profile_fields();
}

/**
Expand Down Expand Up @@ -309,23 +310,35 @@ private function update_user(array $externaldata, $userdata) {
return $userdata;
}

$allfields = array_merge($this->userfields, $this->customfields);

// Go through each field from the external data.
foreach ($externaldata as $fieldname => $value) {
if (!in_array($fieldname, $this->userfields)) {
if (!in_array($fieldname, $allfields)) {
// Skip if this field doesn't belong to the list of fields that can be synced with the OAuth2 issuer.
continue;
}

if (!property_exists($userdata, $fieldname)) {
// Just in case this field is on the list, but not part of the user data. This shouldn't happen though.
$userhasfield = property_exists($userdata, $fieldname);
// Find out if it is a profile field.
$isprofilefield = strpos($fieldname, 'profile_field_') === 0;
$profilefieldname = str_replace('profile_field_', '', $fieldname);
$userhasprofilefield = $isprofilefield && array_key_exists($profilefieldname, $userdata->profile);

// Just in case this field is on the list, but not part of the user data. This shouldn't happen though.
if (!($userhasfield || $userhasprofilefield)) {
continue;
}

// Get the old value.
$oldvalue = (string)$userdata->$fieldname;
$oldvalue = $isprofilefield ? (string) $userdata->profile[$profilefieldname] : (string) $userdata->$fieldname;

// Get the lock configuration of the field.
$lockvalue = $this->config->{'field_lock_' . $fieldname};
if (!empty($this->config->{'field_lock_' . $fieldname})) {
$lockvalue = $this->config->{'field_lock_' . $fieldname};
} else {
$lockvalue = 'unlocked';
}

// We should update fields that meet the following criteria:
// - Lock value set to 'unlocked'; or 'unlockedifempty', given the current value is empty.
Expand Down Expand Up @@ -525,6 +538,9 @@ public function complete_login(client $client, $redirecturl) {
exit();
} else {
\auth_oauth2\api::link_login($userinfo, $issuer, $moodleuser->id, true);
// We dont have profile loaded on $moodleuser, so load it.
require_once($CFG->dirroot.'/user/profile/lib.php');
profile_load_custom_fields($moodleuser);
$userinfo = $this->update_user($userinfo, $moodleuser);
// No redirect, we will complete this login.
}
Expand Down
3 changes: 2 additions & 1 deletion auth/oauth2/settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,6 @@

$authplugin = get_auth_plugin('oauth2');
display_auth_lock_options($settings, $authplugin->authtype, $authplugin->userfields,
get_string('auth_fieldlocks_help', 'auth'), false, false);
get_string('auth_fieldlocks_help', 'auth'), false, false,
$authplugin->customfields);
}
26 changes: 24 additions & 2 deletions lib/classes/oauth2/user_field_mapping.php
Original file line number Diff line number Diff line change
Expand Up @@ -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']);
return array_merge(\core_user::AUTHSYNCFIELDS, ['picture', 'username'], get_profile_field_names());
}

/**
Expand Down Expand Up @@ -72,7 +72,28 @@ protected static function define_properties() {
* @return array
*/
public function get_internalfield_list() {
return array_combine(self::get_user_fields(), self::get_user_fields());
$userfields = array_merge(\core_user::AUTHSYNCFIELDS, ['picture', 'username']);
$internalfields = array_combine($userfields, $userfields);
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;
}

/**
Expand All @@ -87,4 +108,5 @@ protected function validate_externalfield($value){
}
return true;
}

}
Loading

0 comments on commit d62f6ae

Please sign in to comment.