Add enhanced OIDC binding ID token claim options
This commit is contained in:
Родитель
e5b25b3a3a
Коммит
44ba8aeb66
|
@ -64,6 +64,28 @@ class application extends moodleform {
|
|||
$mform->addElement('static', 'clientid_help', '', get_string('clientid_help', 'auth_oidc'));
|
||||
$mform->addRule('clientid', null, 'required', null, 'client');
|
||||
|
||||
// Add the new selector for "Binding username claim"
|
||||
$bindingusernameoptions = [
|
||||
'auto' => get_string('binding_username_auto', 'auth_oidc'), // Use current logic
|
||||
'preferred_username' => get_string('binding_username_preferred_username', 'auth_oidc'),
|
||||
'email' => get_string('binding_username_email', 'auth_oidc'),
|
||||
'upn' => get_string('binding_username_upn', 'auth_oidc'),
|
||||
'unique_name' => get_string('binding_username_unique_name', 'auth_oidc'),
|
||||
'sub' => get_string('binding_username_sub', 'auth_oidc'),
|
||||
'custom' => get_string('binding_username_custom', 'auth_oidc'), // Custom value
|
||||
];
|
||||
$mform->addElement('select', 'bindingusernameclaim', auth_oidc_config_name_in_form('bindingusernameclaim'), $bindingusernameoptions);
|
||||
$mform->setDefault('bindingusernameclaim', 'auto');
|
||||
$mform->addElement('static', 'bindingusernameclaim_help', '', get_string('bindingusernameclaim_help', 'auth_oidc'));
|
||||
|
||||
// Add a text field for custom claim name
|
||||
$mform->addElement('text', 'customclaimname', auth_oidc_config_name_in_form('customclaimname'), ['size' => 40]);
|
||||
$mform->setType('customclaimname', PARAM_TEXT);
|
||||
$mform->disabledIf('customclaimname', 'bindingusernameclaim', 'neq', 'custom'); // Enable only if "Custom" is selected
|
||||
|
||||
$mform->addElement('static', 'customclaimname_description', '', get_string('customclaimname_description', 'auth_oidc'));
|
||||
$mform->disabledIf('customclaimname_description', 'bindingusernameclaim', 'neq', 'custom');
|
||||
|
||||
// Authentication header.
|
||||
$mform->addElement('header', 'authentication', get_string('settings_section_authentication', 'auth_oidc'));
|
||||
$mform->setExpanded('authentication');
|
||||
|
|
|
@ -362,22 +362,13 @@ class authcode extends base {
|
|||
if (isloggedin() && !isguestuser() && (empty($tokenrec) || (isset($USER->auth) && $USER->auth !== 'oidc'))) {
|
||||
// If user is already logged in and trying to link Microsoft 365 account or use it for OIDC.
|
||||
// Check if that Microsoft 365 account already exists in moodle.
|
||||
if (get_config('auth_oidc', 'idptype') == AUTH_OIDC_IDP_TYPE_MICROSOFT_IDENTITY_PLATFORM) {
|
||||
$upn = $idtoken->claim('preferred_username');
|
||||
if (empty($upn)) {
|
||||
$upn = $idtoken->claim('email');
|
||||
}
|
||||
} else {
|
||||
$upn = $idtoken->claim('upn');
|
||||
if (empty($upn)) {
|
||||
$upn = $idtoken->claim('unique_name');
|
||||
}
|
||||
}
|
||||
$oidcusername = $this->get_oidc_username_from_token_claim($idtoken);
|
||||
|
||||
$userrec = $DB->count_records_sql('SELECT COUNT(*)
|
||||
FROM {user}
|
||||
WHERE username = ?
|
||||
AND id != ?',
|
||||
[$upn, $USER->id]);
|
||||
[$oidcusername, $USER->id]);
|
||||
|
||||
if (!empty($userrec)) {
|
||||
if (empty($additionaldata['redirect'])) {
|
||||
|
@ -562,20 +553,7 @@ class authcode extends base {
|
|||
|
||||
// Find the latest real Microsoft username.
|
||||
// Determine remote username depending on IdP type, or fall back to standard 'sub'.
|
||||
if (get_config('auth_oidc', 'idptype') == AUTH_OIDC_IDP_TYPE_MICROSOFT_IDENTITY_PLATFORM) {
|
||||
$oidcusername = $idtoken->claim('preferred_username');
|
||||
if (empty($oidcusername)) {
|
||||
$oidcusername = $idtoken->claim('email');
|
||||
}
|
||||
} else {
|
||||
$oidcusername = $idtoken->claim('upn');
|
||||
if (empty($oidcusername)) {
|
||||
$oidcusername = $idtoken->claim('unique_name');
|
||||
}
|
||||
}
|
||||
if (empty($oidcusername)) {
|
||||
$oidcusername = $idtoken->claim('sub');
|
||||
}
|
||||
$oidcusername = $this->get_oidc_username_from_token_claim($idtoken);
|
||||
|
||||
$usernamechanged = false;
|
||||
if ($oidcusername && $tokenrec && strtolower($oidcusername) !== strtolower($tokenrec->oidcusername)) {
|
||||
|
@ -695,17 +673,8 @@ class authcode extends base {
|
|||
|
||||
$existinguser = core_user::get_user($existingmatching->moodleid);
|
||||
|
||||
if (get_config('auth_oidc', 'idptype') == AUTH_OIDC_IDP_TYPE_MICROSOFT_IDENTITY_PLATFORM) {
|
||||
$username = $idtoken->claim('preferred_username');
|
||||
if (empty($username)) {
|
||||
$username = $idtoken->claim('email');
|
||||
}
|
||||
} else {
|
||||
$username = $idtoken->claim('upn');
|
||||
if (empty($username)) {
|
||||
$username = $idtoken->claim('unique_name');
|
||||
}
|
||||
}
|
||||
$username = $this->get_oidc_username_from_token_claim($idtoken);
|
||||
|
||||
$originalupn = null;
|
||||
|
||||
if (empty($username)) {
|
||||
|
@ -764,18 +733,9 @@ class authcode extends base {
|
|||
*/
|
||||
|
||||
// Generate a Moodle username.
|
||||
// Use 'upn' if available for username (Microsoft-specific), or fall back to lower-case oidcuniqid.
|
||||
if (get_config('auth_oidc', 'idptype') == AUTH_OIDC_IDP_TYPE_MICROSOFT_IDENTITY_PLATFORM) {
|
||||
$username = $idtoken->claim('preferred_username');
|
||||
if (empty($username)) {
|
||||
$username = $idtoken->claim('email');
|
||||
}
|
||||
} else {
|
||||
$username = $idtoken->claim('upn');
|
||||
if (empty($username)) {
|
||||
$username = $idtoken->claim('unique_name');
|
||||
}
|
||||
}
|
||||
// Use 'upn' if available for username (Azure-specific), or fall back to lower-case oidcuniqid.
|
||||
$username = $this->get_oidc_username_from_token_claim($idtoken);
|
||||
|
||||
$originalupn = null;
|
||||
|
||||
if (empty($username)) {
|
||||
|
|
|
@ -173,6 +173,7 @@ class base {
|
|||
$upn = $token->claim('unique_name');
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($upn)) {
|
||||
$userdata['userPrincipalName'] = $upn;
|
||||
}
|
||||
|
@ -252,6 +253,7 @@ class base {
|
|||
$upn = $token->claim('unique_name');
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($upn)) {
|
||||
$userdata['userPrincipalName'] = $upn;
|
||||
}
|
||||
|
@ -572,21 +574,7 @@ class base {
|
|||
if ($restrictions !== '') {
|
||||
$restrictions = explode("\n", $restrictions);
|
||||
// Check main user identifier claim based on IdP type, and falls back to oidc-standard "sub" if still empty.
|
||||
if (get_config('auth_oidc', 'idptype') == AUTH_OIDC_IDP_TYPE_MICROSOFT_IDENTITY_PLATFORM) {
|
||||
$tomatch = $idtoken->claim('preferred_username');
|
||||
if (empty($tomatch)) {
|
||||
$tomatch = $idtoken->claim('email');
|
||||
}
|
||||
} else {
|
||||
$tomatch = $idtoken->claim('upn');
|
||||
if (empty($tomatch)) {
|
||||
$tomatch = $idtoken->claim('unique_name');
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($tomatch)) {
|
||||
$tomatch = $idtoken->claim('sub');
|
||||
}
|
||||
$oidcusername = $this->get_oidc_username_from_token_claim($idtoken);
|
||||
foreach ($restrictions as $restriction) {
|
||||
$restriction = trim($restriction);
|
||||
if ($restriction !== '') {
|
||||
|
@ -597,7 +585,7 @@ class base {
|
|||
if (isset($this->config->userrestrictionscasesensitive) && !$this->config->userrestrictionscasesensitive) {
|
||||
$pattern .= 'i';
|
||||
}
|
||||
$count = @preg_match($pattern, $tomatch, $matches);
|
||||
$count = @preg_match($pattern, $oidcusername, $matches);
|
||||
if (!empty($count)) {
|
||||
$userpassed = true;
|
||||
break;
|
||||
|
@ -606,7 +594,7 @@ class base {
|
|||
$debugdata = [
|
||||
'exception' => $e,
|
||||
'restriction' => $restriction,
|
||||
'tomatch' => $tomatch,
|
||||
'tomatch' => $oidcusername,
|
||||
];
|
||||
utils::debug('Error running user restrictions.', __METHOD__, $debugdata);
|
||||
}
|
||||
|
@ -616,7 +604,7 @@ class base {
|
|||
$debugdata = [
|
||||
'contents' => $contents,
|
||||
'restriction' => $restriction,
|
||||
'tomatch' => $tomatch,
|
||||
'tomatch' => $oidcusername,
|
||||
];
|
||||
utils::debug('Output while running user restrictions.', __METHOD__, $debugdata);
|
||||
}
|
||||
|
@ -646,21 +634,7 @@ class base {
|
|||
$oidcusername = $originalupn;
|
||||
} else {
|
||||
// Determine remote username depending on IdP type, or fall back to standard 'sub'.
|
||||
if (get_config('auth_oidc', 'idptype') == AUTH_OIDC_IDP_TYPE_MICROSOFT_IDENTITY_PLATFORM) {
|
||||
$oidcusername = $idtoken->claim('preferred_username');
|
||||
if (empty($oidcusername)) {
|
||||
$oidcusername = $idtoken->claim('email');
|
||||
}
|
||||
} else {
|
||||
$oidcusername = $idtoken->claim('upn');
|
||||
if (empty($oidcusername)) {
|
||||
$oidcusername = $idtoken->claim('unique_name');
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($oidcusername)) {
|
||||
$oidcusername = $idtoken->claim('sub');
|
||||
}
|
||||
$oidcusername = $this->get_oidc_username_from_token_claim($idtoken);
|
||||
}
|
||||
|
||||
// We should not fail here (idtoken was verified earlier to at least contain 'sub', but just in case...).
|
||||
|
@ -726,4 +700,43 @@ class base {
|
|||
$tokenrec->idtoken = $tokenparams['id_token'];
|
||||
$DB->update_record('auth_oidc_token', $tokenrec);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get OIDC username from token claims based on configured claim.
|
||||
*
|
||||
* @param jwt $idtoken The OIDC ID token.
|
||||
* @return string|null The OIDC username if found, null otherwise.
|
||||
*/
|
||||
protected function get_oidc_username_from_token_claim($idtoken) {
|
||||
if (empty($idtoken)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$bindingclaim = get_config('auth_oidc', 'bindingusernameclaim');
|
||||
if ($bindingclaim === 'custom') {
|
||||
$bindingclaim = get_config('auth_oidc', 'custombindingclaim');
|
||||
}
|
||||
$oidcusername = $idtoken->claim($bindingclaim);
|
||||
|
||||
if (empty($oidcusername) || $bindingclaim === 'auto') {
|
||||
if (get_config('auth_oidc', 'idptype') == AUTH_OIDC_IDP_TYPE_MICROSOFT) {
|
||||
$oidcusername = $idtoken->claim('preferred_username');
|
||||
if (empty($oidcusername)) {
|
||||
$oidcusername = $idtoken->claim('email');
|
||||
}
|
||||
} else {
|
||||
$oidcusername = $idtoken->claim('upn');
|
||||
if (empty($oidcusername)) {
|
||||
$oidcusername = $idtoken->claim('unique_name');
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($oidcusername)) {
|
||||
$oidcusername = $idtoken->claim('sub');
|
||||
}
|
||||
}
|
||||
|
||||
return $oidcusername;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -335,6 +335,7 @@ $string['update_onlogin_and_usersync'] = 'On every login and every user sync tas
|
|||
|
||||
// Remote fields.
|
||||
$string['settings_fieldmap_feild_not_mapped'] = '(not mapped)';
|
||||
$string['settings_fieldmap_field_bindingusernameclaim'] = 'Binding Username Claim';
|
||||
$string['settings_fieldmap_field_city'] = 'City';
|
||||
$string['settings_fieldmap_field_companyName'] = 'Company Name';
|
||||
$string['settings_fieldmap_field_objectId'] = 'Object ID';
|
||||
|
@ -352,7 +353,7 @@ $string['settings_fieldmap_field_postalCode'] = 'Postal Code';
|
|||
$string['settings_fieldmap_field_preferredLanguage'] = 'Language';
|
||||
$string['settings_fieldmap_field_state'] = 'State';
|
||||
$string['settings_fieldmap_field_streetAddress'] = 'Street Address';
|
||||
$string['settings_fieldmap_field_userPrincipalName'] = 'Username (UPN)';
|
||||
$string['settings_fieldmap_field_userPrincipalName'] = 'User Principal Name';
|
||||
$string['settings_fieldmap_field_employeeId'] = 'Employee ID';
|
||||
$string['settings_fieldmap_field_businessPhones'] = 'Office phone';
|
||||
$string['settings_fieldmap_field_mobilePhone'] = 'Mobile phone';
|
||||
|
@ -375,3 +376,25 @@ $string['settings_fieldmap_field_sds_student_graduationYear'] = 'SDS student gra
|
|||
$string['settings_fieldmap_field_sds_student_studentNumber'] = 'SDS student number';
|
||||
$string['settings_fieldmap_field_sds_teacher_externalId'] = 'SDS teacher external ID';
|
||||
$string['settings_fieldmap_field_sds_teacher_teacherNumber'] = 'SDS teacher number';
|
||||
|
||||
// Binding username claim options.
|
||||
$string['binding_username_auto'] = 'Choose automatically';
|
||||
$string['binding_username_preferred_username'] = 'preferred_username';
|
||||
$string['binding_username_email'] = 'email';
|
||||
$string['binding_username_upn'] = 'upn';
|
||||
$string['binding_username_unique_name'] = 'unique_name';
|
||||
$string['binding_username_sub'] = 'sub';
|
||||
$string['binding_username_custom'] = 'Custom';
|
||||
|
||||
$string['bindingusernameclaim'] = 'Binding Username Claim';
|
||||
$string['customclaimname'] = 'Custom claim name';
|
||||
$string['customclaimname_description'] = 'This field is used only when <b>Binding Username Claim</b> is set to <b>Custom</b>.';
|
||||
|
||||
$string['bindingusernameclaim_help'] = 'This is an advanced feature. Select the ID token claim to be used for binding the username. The options include:<br/>
|
||||
- <b>Choose automatically</b>: Uses current logic, determining the token by IdP type and falling back to <b>sub</b> if no claim is found.<br/>
|
||||
- <b>preferred_username</b>: Default for Microsoft identity platform (v2.0) IdP type.<br/>
|
||||
- <b>email</b>: Fallback for Microsoft identity platform (v2.0).<br/>
|
||||
- <b>upn</b>: Default for Microsoft Entra ID (v1.0) and other IdP types.<br/>
|
||||
- <b>unique_name</b>: Fallback for Microsoft Entra ID (v1.0) and other IdP types.<br/>
|
||||
- <b>sub</b>: Fallback if no other claims are present.<br/>
|
||||
- <b>Custom</b>: Allows the site admin to enter a custom value.';
|
12
lib.php
12
lib.php
|
@ -239,9 +239,20 @@ function auth_oidc_delete_token(int $tokenid): void {
|
|||
* @return array
|
||||
*/
|
||||
function auth_oidc_get_remote_fields() {
|
||||
$bindingusernameclaim = get_config('auth_oidc', 'bindingusernameclaim');
|
||||
|
||||
if (empty($bindingusernameclaim) || $bindingusernameclaim === 'auto') {
|
||||
if (get_config('auth_oidc', 'idptype') == AUTH_OIDC_IDP_TYPE_MICROSOFT) {
|
||||
$bindingusernameclaim = 'preferred_username';
|
||||
} else {
|
||||
$bindingusernameclaim = 'upn';
|
||||
}
|
||||
}
|
||||
|
||||
if (auth_oidc_is_local_365_installed()) {
|
||||
$remotefields = [
|
||||
'' => get_string('settings_fieldmap_feild_not_mapped', 'auth_oidc'),
|
||||
$bindingusernameclaim => get_string('settings_fieldmap_field_bindingusernameclaim', 'auth_oidc'),
|
||||
'objectId' => get_string('settings_fieldmap_field_objectId', 'auth_oidc'),
|
||||
'userPrincipalName' => get_string('settings_fieldmap_field_userPrincipalName', 'auth_oidc'),
|
||||
'displayName' => get_string('settings_fieldmap_field_displayName', 'auth_oidc'),
|
||||
|
@ -299,6 +310,7 @@ function auth_oidc_get_remote_fields() {
|
|||
} else {
|
||||
$remotefields = [
|
||||
'' => get_string('settings_fieldmap_feild_not_mapped', 'auth_oidc'),
|
||||
$bindingusernameclaim => get_string('settings_fieldmap_field_bindingusernameclaim', 'auth_oidc'),
|
||||
'objectId' => get_string('settings_fieldmap_field_objectId', 'auth_oidc'),
|
||||
'userPrincipalName' => get_string('settings_fieldmap_field_userPrincipalName', 'auth_oidc'),
|
||||
'givenName' => get_string('settings_fieldmap_field_givenName', 'auth_oidc'),
|
||||
|
|
|
@ -57,12 +57,24 @@ $form = new application(null, ['oidcconfig' => $oidcconfig]);
|
|||
$formdata = [];
|
||||
foreach (['idptype', 'clientid', 'clientauthmethod', 'clientsecret', 'clientprivatekey', 'clientcert',
|
||||
'clientcertsource', 'clientprivatekeyfile', 'clientcertfile', 'clientcertpassphrase',
|
||||
'authendpoint', 'tokenendpoint', 'oidcresource', 'oidcscope', 'secretexpiryrecipients'] as $field) {
|
||||
'authendpoint', 'tokenendpoint', 'oidcresource', 'oidcscope', 'secretexpiryrecipients',
|
||||
'bindingusernameclaim', 'customclaimname'] as $field) {
|
||||
if (isset($oidcconfig->$field)) {
|
||||
$formdata[$field] = $oidcconfig->$field;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$bindingusernameclaim = get_config('auth_oidc', 'bindingusernameclaim');
|
||||
|
||||
$predefinedoptions = ['auto', 'preferred_username', 'email', 'upn', 'unique_name', 'sub'];
|
||||
|
||||
if (!in_array($bindingusernameclaim, $predefinedoptions)) {
|
||||
$formdata['bindingusernameclaim'] = 'custom';
|
||||
$formdata['customclaimname'] = $bindingusernameclaim;
|
||||
} else {
|
||||
$formdata['bindingusernameclaim'] = $bindingusernameclaim;
|
||||
}
|
||||
$form->set_data($formdata);
|
||||
|
||||
if ($form->is_cancelled()) {
|
||||
|
@ -73,9 +85,13 @@ if ($form->is_cancelled()) {
|
|||
$fromform->clientauthmethod = optional_param('clientauthmethod', AUTH_OIDC_AUTH_METHOD_SECRET, PARAM_INT);
|
||||
}
|
||||
|
||||
if ($fromform->bindingusernameclaim === 'custom') {
|
||||
$fromform->bindingusernameclaim = $fromform->customclaimname;
|
||||
}
|
||||
|
||||
// Prepare config settings to save.
|
||||
$configstosave = ['idptype', 'clientid', 'clientauthmethod', 'authendpoint', 'tokenendpoint',
|
||||
'oidcresource', 'oidcscope'];
|
||||
'oidcresource', 'oidcscope', 'bindingusernameclaim', 'customclaimname'];
|
||||
|
||||
// Depending on the value of clientauthmethod, save clientsecret or (clientprivatekey and clientcert).
|
||||
switch ($fromform->clientauthmethod) {
|
||||
|
|
Загрузка…
Ссылка в новой задаче