1
0
forked from Public/pics

Match OIDC users by sub claim, auto-enroll, sync admin from groups

Switch from email-based OIDC matching to the stable `sub` claim.
Existing users are migrated by email on first login, new users are
auto-enrolled from OIDC claims, and admin status is synced from the
IdP's groups claim. Also expose oidc_sub on the admin edit-user page.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-15 19:37:14 +01:00
parent a361df2668
commit 555a3dbb95
7 changed files with 130 additions and 16 deletions

View File

@@ -27,6 +27,15 @@ class Member extends User
['email_address' => $email_address]);
}
public static function fromOidcSub($sub)
{
return Registry::get('db')->queryObject(static::class, '
SELECT *
FROM users
WHERE oidc_sub = :oidc_sub',
['oidc_sub' => $sub]);
}
public static function fromId($id_user)
{
$row = Registry::get('db')->queryAssoc('
@@ -73,18 +82,27 @@ class Member extends User
'surname' => !empty($data['surname']) ? $data['surname'] : $error |= true,
'slug' => !empty($data['slug']) ? $data['slug'] : $error |= true,
'emailaddress' => !empty($data['emailaddress']) ? $data['emailaddress'] : $error |= true,
'password_hash' => !empty($data['password']) ? Authentication::computeHash($data['password']) : $error |= true,
'creation_time' => time(),
'ip_address' => isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '',
'is_admin' => empty($data['is_admin']) ? 0 : 1,
'reset_key' => '',
];
// Password is required unless oidc_sub is provided.
if (!empty($data['password']))
$new_user['password_hash'] = Authentication::computeHash($data['password']);
elseif (!empty($data['oidc_sub']))
$new_user['password_hash'] = '';
else
$error |= true;
if (!empty($data['oidc_sub']))
$new_user['oidc_sub'] = $data['oidc_sub'];
if ($error)
return false;
$db = Registry::get('db');
$bool = $db->insert('insert', 'users', [
$columns = [
'first_name' => 'string-30',
'surname' => 'string-60',
'slug' => 'string-90',
@@ -93,8 +111,14 @@ class Member extends User
'creation_time' => 'int',
'ip_address' => 'string-45',
'is_admin' => 'int',
'reset_key' => 'string-16'
], $new_user, ['id_user']);
'reset_key' => 'string-16',
];
if (isset($new_user['oidc_sub']))
$columns['oidc_sub'] = 'string-255';
$db = Registry::get('db');
$bool = $db->insert('insert', 'users', $columns, $new_user, ['id_user']);
if (!$bool)
return false;
@@ -113,7 +137,7 @@ class Member extends User
{
foreach ($new_data as $key => $value)
{
if (in_array($key, ['first_name', 'surname', 'slug', 'emailaddress']))
if (in_array($key, ['first_name', 'surname', 'slug', 'emailaddress', 'oidc_sub']))
$this->$key = $value;
elseif ($key === 'password')
$this->password_hash = Authentication::computeHash($value);
@@ -132,7 +156,8 @@ class Member extends User
slug = :slug,
emailaddress = :emailaddress,
password_hash = :password_hash,
is_admin = :is_admin
is_admin = :is_admin,
oidc_sub = :oidc_sub
WHERE id_user = :id_user',
get_object_vars($this));
}

View File

@@ -24,6 +24,7 @@ abstract class User
protected $is_admin;
protected $reset_key;
protected $reset_blocked_until;
protected $oidc_sub;
protected bool $is_logged;
protected bool $is_guest;