Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions auth/oidc/classes/adminsetting/iconselect.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ label.iconselect img {
width: 25px;
height: 25px;
padding: 10px;
margin-right: 0;
}
input.iconselect {
display: none;
Expand Down
10 changes: 7 additions & 3 deletions auth/oidc/classes/loginflow/authcode.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,25 +59,29 @@ public function loginpage_idp_list($wantsurl) {
return [];
}
$showicon = isset($this->config->set_pix) ? $this->config->set_pix : true;
$name = strip_tags(format_text($this->config->opname));
$idpentry = [
'url' => new url('/auth/oidc/', ['source' => 'loginpage']),
'name' => strip_tags(format_text($this->config->opname)),
'name' => $name,
];
if ($showicon) {
if (!empty($this->config->customicon)) {
$iconvalue = new pix_icon('0/customicon', get_string('pluginname', 'auth_oidc'), 'auth_oidc');
} else {
$icon = (!empty($this->config->icon)) ? $this->config->icon : 'auth_oidc:o365';
$icon = (!empty($this->config->icon)) ? $this->config->icon : 'auth_oidc:microsoft_365';
$icon = explode(':', $icon);
if (isset($icon[1])) {
[$iconcomponent, $iconname] = $icon;
} else {
$iconcomponent = 'auth_oidc';
$iconname = 'o365';
$iconname = 'microsoft_365';
}
$iconvalue = new pix_icon($iconname, get_string('pluginname', 'auth_oidc'), $iconcomponent);
}
$idpentry['icon'] = $iconvalue;
// Two non-breaking spaces so the button text keeps a visible gap after the icon.
// Regular spaces would be collapsed to one by normal HTML whitespace handling.
$idpentry['name'] = "\u{00A0}\u{00A0}" . $name;
}
return [$idpentry];
}
Expand Down
96 changes: 96 additions & 0 deletions auth/oidc/classes/utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
use Exception;
use moodle_exception;
use auth_oidc\event\action_failed;
use core\context\system;
use core\url;

/**
Expand Down Expand Up @@ -252,4 +253,99 @@ public static function get_openssl_internal_path() {

return $CFG->dataroot . '/microsoft_certs';
}

/**
* Migrate a site's selected stock icon to the custom icon setting if it used one of the
* icon choices that have been removed from the icon selector.
*
* The 'auth_oidc/icon' setting stores a "component:pix" identifier. The set of stock
* choices has been reduced to a handful of icons relevant to this plugin; any site that had
* selected one of the removed choices (all generic core Moodle icons) needs that icon copied
* into the custom icon file area so the login page keeps showing the same image.
*
* Safe to call more than once: once a site has been migrated (or its 'icon' setting was
* never one of the removed choices), every subsequent call is a no-op, since the checks
* above always return early once either 'auth_oidc/icon' is empty/unset or
* 'auth_oidc/customicon' is populated. The file and config writes are wrapped in a
* delegated transaction so a failure partway through can't leave those two settings out of
* sync with each other, which is what the early-return checks rely on.
*/
public static function migrate_removed_icon_choices(): void {
global $CFG, $DB;

$currenticon = get_config('auth_oidc', 'icon');
if (empty($currenticon)) {
return;
}

if (!empty(get_config('auth_oidc', 'customicon'))) {
// A custom icon is already in use and takes priority, so the stock icon setting is
// not currently affecting what is displayed. Nothing to migrate.
return;
}

// The old default icon has simply been replaced with a new image under a new pix name.
if ($currenticon === 'auth_oidc:o365') {
set_config('icon', 'auth_oidc:office_365', 'auth_oidc');
return;
}

$keepicons = [
'auth_oidc:microsoft_365',
'auth_oidc:microsoft',
'auth_oidc:microsoft_365_copilot',
'auth_oidc:office_365',
'auth_oidc:openid',
'auth_oidc:keycloak',
];
if (in_array($currenticon, $keepicons, true)) {
return;
}

$parts = explode(':', $currenticon, 2);
if (count($parts) !== 2) {
return;
}
[, $pix] = $parts;

$sourcefile = null;
$extension = null;
foreach (['svg', 'png', 'gif', 'jpg', 'jpeg'] as $candidateextension) {
$candidatefile = "{$CFG->dirroot}/pix/{$pix}.{$candidateextension}";
if (file_exists($candidatefile)) {
$sourcefile = $candidatefile;
$extension = $candidateextension;
break;
}
}
if ($sourcefile === null) {
// Can't locate the source image for the removed choice, so there is nothing to copy.
return;
}

$systemcontext = system::instance();
$fs = get_file_storage();
$filename = 'migrated_' . clean_param(str_replace('/', '_', $pix), PARAM_FILE) . '.' . $extension;
$filerecord = [
'contextid' => $systemcontext->id,
'component' => 'auth_oidc',
'filearea' => 'customicon',
'itemid' => 0,
'filepath' => '/',
'filename' => $filename,
];

// Wrapped in a transaction so a failure partway through (e.g. the file write succeeding
// but a config write failing) can't leave 'icon' and 'customicon' out of sync, which
// would break the early-return guards above on any later call.
$transaction = $DB->start_delegated_transaction();
$fs->delete_area_files($systemcontext->id, 'auth_oidc', 'customicon', 0);
$fs->create_file_from_pathname($filerecord, $sourcefile);
set_config('customicon', '/' . $filename, 'auth_oidc');
unset_config('icon', 'auth_oidc');
$transaction->allow_commit();

require_once($CFG->dirroot . '/auth/oidc/lib.php');
auth_oidc_initialize_customicon('/' . $filename);
}
}
5 changes: 5 additions & 0 deletions auth/oidc/db/upgrade.php
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,11 @@ function xmldb_auth_oidc_upgrade($oldversion) {
upgrade_plugin_savepoint(true, 2024100730.01, 'auth', 'oidc');
}

if ($oldversion < 2024100735.05) {
\auth_oidc\utils::migrate_removed_icon_choices();
upgrade_plugin_savepoint(true, 2024100735.05, 'auth', 'oidc');
}
Comment thread
weilai-irl marked this conversation as resolved.

return true;
}

Expand Down
23 changes: 7 additions & 16 deletions auth/oidc/lang/en/auth_oidc.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,23 +119,14 @@
$string['cfg_set_pix_desc'] = 'If enabled, displays an icon next to the provider name on the login page.';
$string['cfg_icon_key'] = 'Icon';
$string['cfg_icon_desc'] = 'An icon to display next to the provider name on the login page.';
$string['cfg_iconalt_o365'] = 'Microsoft 365 icon';
$string['cfg_iconalt_locked'] = 'Locked icon';
$string['cfg_iconalt_lock'] = 'Lock icon';
$string['cfg_iconalt_go'] = 'Green circle';
$string['cfg_iconalt_stop'] = 'Red circle';
$string['cfg_iconalt_user'] = 'User icon';
$string['cfg_iconalt_user2'] = 'User icon alternate';
$string['cfg_iconalt_key'] = 'Key icon';
$string['cfg_iconalt_group'] = 'Group icon';
$string['cfg_iconalt_group2'] = 'Group icon alternate';
$string['cfg_iconalt_mnet'] = 'MNET icon';
$string['cfg_iconalt_userlock'] = 'User with lock icon';
$string['cfg_iconalt_plus'] = 'Plus icon';
$string['cfg_iconalt_check'] = 'Checkmark icon';
$string['cfg_iconalt_rightarrow'] = 'Right-facing arrow icon';
$string['cfg_iconalt_o365'] = 'Office 365 icon';
$string['cfg_iconalt_microsoft365'] = 'Microsoft 365 icon';
$string['cfg_iconalt_microsoft'] = 'Microsoft icon';
$string['cfg_iconalt_microsoft365copilot'] = 'Microsoft 365 Copilot icon';
$string['cfg_iconalt_openid'] = 'OpenID icon';
$string['cfg_iconalt_keycloak'] = 'Keycloak icon';
$string['cfg_customicon_key'] = 'Custom Icon';
$string['cfg_customicon_desc'] = 'If you\'d like to use your own icon, upload it here. This overrides any icon chosen above. <br /><br /><b>Notes on using custom icons:</b><ul><li>This image will <b>not</b> be resized on the login page, so we recommend uploading an image no bigger than 35x35 pixels.</li><li>If you have uploaded a custom icon and want to go back to one of the stock icons, click the custom icon in the box above, then click "Delete", then click "OK", then click "Save Changes" at the bottom of this form. The selected stock icon will now appear on the Moodle login page.</li></ul>';
$string['cfg_customicon_desc'] = 'If you\'d like to use your own icon, upload it here. This overrides any icon chosen above. <br /><br /><b>Notes on using custom icons:</b><ul><li>The uploaded file is not resized. It will be displayed at a fixed size of 24x24 pixels on the login page, with your browser scaling it to fit, so we recommend uploading a square image to avoid distortion.</li><li>If you have uploaded a custom icon and want to go back to one of the stock icons, click the custom icon in the box above, then click "Delete", then click "OK", then click "Save Changes" at the bottom of this form. The selected stock icon will now appear on the Moodle login page.</li></ul>';
$string['cfg_debugmode_key'] = 'Record debug messages';
$string['cfg_debugmode_desc'] = 'If enabled, information will be logged to the Moodle log that can help in identifying problems.';
$string['cfg_loginflow_key'] = 'Login Flow';
Expand Down
31 changes: 30 additions & 1 deletion auth/oidc/lib.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,17 @@
*/
const AUTH_OIDC_AUTH_CERT_SOURCE_FILE = 2;

/**
* File extensions accepted for the 'auth_oidc/customicon' upload setting.
*
* SVG is deliberately excluded: unlike the plugin's own bundled stock icons, this file is
* admin-uploaded and served as-is from dataroot, so allowing SVG here would let an admin
* upload active content (script/event handlers). Shared by the setting's file picker
* (settings.php) and the extension allow-list checked before copying the file into
* pix_plugins (auth_oidc_initialize_customicon()) so the two can't drift apart.
*/
const AUTH_OIDC_CUSTOMICON_ALLOWED_EXTENSIONS = ['png', 'jpg', 'gif'];

/**
* Callback invoked when application credentials or endpoint settings are updated.
*
Expand Down Expand Up @@ -227,7 +238,25 @@ function auth_oidc_initialize_customicon($filefullname) {
}

if (file_exists($CFG->dataroot . '/pix_plugins/auth/oidc/0')) {
$file->copy_content_to($CFG->dataroot . '/pix_plugins/auth/oidc/0/customicon.jpg');
// Remove any previously stored custom icon so a stale file with a different
// extension can't take priority when the theme resolves the icon image.
$oldiconfiles = glob($CFG->dataroot . '/pix_plugins/auth/oidc/0/customicon.*');
foreach ($oldiconfiles ?: [] as $oldiconfile) {
// A failed unlink (e.g. permissions, or the file already being gone) isn't fatal
// here: copy_content_to() below will still overwrite/create the current extension's
// file, so at worst a stale file of a different extension is left behind.
@unlink($oldiconfile);
}

$extension = strtolower(pathinfo($file->get_filename(), PATHINFO_EXTENSION));
if (!in_array($extension, AUTH_OIDC_CUSTOMICON_ALLOWED_EXTENSIONS, true)) {
// Unexpected/empty extension: don't create a weird or unvalidated file under
// pix_plugins. The stale files for previously-valid extensions were already
// removed above, so this leaves no custom icon in place.
return false;
}

$file->copy_content_to($CFG->dataroot . "/pix_plugins/auth/oidc/0/customicon.{$extension}");
theme_reset_all_caches();
}
}
Expand Down
Binary file added auth/oidc/pix/keycloak.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions auth/oidc/pix/microsoft.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 34 additions & 0 deletions auth/oidc/pix/microsoft_365.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading