Allow passwordless shares and keep share links copyable

This commit is contained in:
Terranom674
2026-08-17 00:04:07 +02:00
parent ee6852b100
commit 26f5aefa3f

View File

@@ -34,6 +34,41 @@ function bratonien_tools_drop_album_shares_table()
pwg_query('DROP TABLE IF EXISTS `'.bratonien_tools_shares_table().'`'); pwg_query('DROP TABLE IF EXISTS `'.bratonien_tools_shares_table().'`');
} }
/**
* Secret used to derive stable, non-guessable share tokens without storing the
* raw token in the database. This makes links reproducible for the admin UI.
*/
function bratonien_tools_share_secret()
{
$key = 'bratonien_album_share_secret';
$secret = function_exists('conf_get_param') ? (string)conf_get_param($key, '') : '';
if ($secret === '')
{
$secret = bin2hex(random_bytes(32));
if (function_exists('conf_update_param'))
{
conf_update_param($key, $secret);
}
}
return $secret;
}
function bratonien_tools_share_token($user_id, $category_id)
{
return substr(
hash_hmac('sha256', 'share:'.(int)$user_id.':'.(int)$category_id, bratonien_tools_share_secret()),
0,
48
);
}
function bratonien_tools_share_url($token)
{
return get_absolute_root_url().'?brshare='.$token;
}
function bratonien_tools_album_shares_init() function bratonien_tools_album_shares_init()
{ {
if (!isset($_GET['brshare'])) if (!isset($_GET['brshare']))
@@ -58,11 +93,12 @@ function bratonien_tools_album_shares_init()
bratonien_tools_share_access_page('Diese Freigabe ist abgelaufen.'); bratonien_tools_share_access_page('Diese Freigabe ist abgelaufen.');
} }
$password_required = !empty($share['password_hash']);
$session_key = 'bratonien_share_'.(int)$share['id']; $session_key = 'bratonien_share_'.(int)$share['id'];
$authorized = !empty($_SESSION[$session_key]); $authorized = !$password_required || !empty($_SESSION[$session_key]);
$error = ''; $error = '';
if (!$authorized && $_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['bratonien_share_password'])) if ($password_required && !$authorized && $_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['bratonien_share_password']))
{ {
if (password_verify((string)$_POST['bratonien_share_password'], $share['password_hash'])) if (password_verify((string)$_POST['bratonien_share_password'], $share['password_hash']))
{ {
@@ -105,7 +141,19 @@ function bratonien_tools_get_album_shares()
.'LEFT JOIN '.CATEGORIES_TABLE.' c ON c.id = s.category_id ' .'LEFT JOIN '.CATEGORIES_TABLE.' c ON c.id = s.category_id '
.'LEFT JOIN '.USERS_TABLE.' u ON u.id = s.created_by ' .'LEFT JOIN '.USERS_TABLE.' u ON u.id = s.created_by '
.'ORDER BY s.created_at DESC'; .'ORDER BY s.created_at DESC';
return query2array($query); $shares = query2array($query);
foreach ($shares as &$share)
{
$token = bratonien_tools_share_token((int)$share['user_id'], (int)$share['category_id']);
$expected_hash = hash('sha256', $token);
$share['password_protected'] = !empty($share['password_hash']);
$share['link_copyable'] = hash_equals((string)$share['token_hash'], $expected_hash);
$share['share_url'] = $share['link_copyable'] ? bratonien_tools_share_url($token) : '';
}
unset($share);
return $shares;
} }
function bratonien_tools_get_private_albums() function bratonien_tools_get_private_albums()
@@ -126,10 +174,6 @@ function bratonien_tools_create_album_share()
{ {
throw new Exception('Bitte ein privates Album auswählen.'); throw new Exception('Bitte ein privates Album auswählen.');
} }
if ($password === '')
{
throw new Exception('Für eine geschützte Freigabe ist ein Passwort erforderlich.');
}
$query = 'SELECT id FROM '.CATEGORIES_TABLE." WHERE id = $category_id AND status = 'private' LIMIT 1"; $query = 'SELECT id FROM '.CATEGORIES_TABLE." WHERE id = $category_id AND status = 'private' LIMIT 1";
if (pwg_db_num_rows(pwg_query($query)) === 0) if (pwg_db_num_rows(pwg_query($query)) === 0)
@@ -175,9 +219,9 @@ function bratonien_tools_create_album_share()
pwg_query('UPDATE '.USER_INFOS_TABLE." SET status = 'generic', level = ".$level.' WHERE user_id = '.(int)$new_user_id); pwg_query('UPDATE '.USER_INFOS_TABLE." SET status = 'generic', level = ".$level.' WHERE user_id = '.(int)$new_user_id);
bratonien_tools_grant_album_access((int)$new_user_id, $category_id); bratonien_tools_grant_album_access((int)$new_user_id, $category_id);
$token = bin2hex(random_bytes(24)); $token = bratonien_tools_share_token((int)$new_user_id, $category_id);
$token_hash = hash('sha256', $token); $token_hash = hash('sha256', $token);
$password_hash = password_hash($password, PASSWORD_DEFAULT); $password_hash = $password === '' ? '' : password_hash($password, PASSWORD_DEFAULT);
$expires_sql = $expires_at === null ? 'NULL' : "'".pwg_db_real_escape_string($expires_at)."'"; $expires_sql = $expires_at === null ? 'NULL' : "'".pwg_db_real_escape_string($expires_at)."'";
$query = 'INSERT INTO '.bratonien_tools_shares_table() $query = 'INSERT INTO '.bratonien_tools_shares_table()
@@ -189,7 +233,40 @@ function bratonien_tools_create_album_share()
invalidate_user_cache(); invalidate_user_cache();
return array( return array(
'message' => 'Geschützte Albumfreigabe erstellt: '.get_absolute_root_url().'?brshare='.$token, 'message' => ($password === '' ? 'Albumfreigabe' : 'Passwortgeschützte Albumfreigabe').' erstellt: '.bratonien_tools_share_url($token),
);
}
/**
* Legacy shares created before reproducible tokens cannot expose their old raw
* token because only its hash was stored. Regeneration intentionally replaces
* that old token so the link becomes copyable from the admin UI afterwards.
*/
function bratonien_tools_regenerate_album_share_link()
{
$share_id = isset($_POST['share_id']) ? (int)$_POST['share_id'] : 0;
if ($share_id < 1)
{
throw new Exception('Ungültige Freigabe.');
}
$query = 'SELECT id, category_id, user_id FROM '.bratonien_tools_shares_table().' WHERE id = '.$share_id.' AND active = 1 LIMIT 1';
$result = pwg_query($query);
if (!pwg_db_num_rows($result))
{
throw new Exception('Freigabe nicht gefunden.');
}
$share = pwg_db_fetch_assoc($result);
$token = bratonien_tools_share_token((int)$share['user_id'], (int)$share['category_id']);
$token_hash = hash('sha256', $token);
pwg_query(
'UPDATE '.bratonien_tools_shares_table()." SET token_hash = '".pwg_db_real_escape_string($token_hash)."' WHERE id = ".$share_id.' LIMIT 1'
);
return array(
'message' => 'Neuer Freigabelink erzeugt: '.bratonien_tools_share_url($token),
); );
} }
@@ -359,7 +436,7 @@ function bratonien_tools_album_shares_on_delete_categories($category_ids)
function bratonien_tools_share_access_page($error = '', $token = '', $show_form = false) function bratonien_tools_share_access_page($error = '', $token = '', $show_form = false)
{ {
header('Content-Type: text/html; charset=UTF-8'); header('Content-Type: text/html; charset=UTF-8');
$action = htmlspecialchars(get_absolute_root_url().'?brshare='.$token, ENT_QUOTES, 'UTF-8'); $action = htmlspecialchars(bratonien_tools_share_url($token), ENT_QUOTES, 'UTF-8');
$message = $error !== '' ? '<p class="brshare-error">'.htmlspecialchars($error, ENT_QUOTES, 'UTF-8').'</p>' : ''; $message = $error !== '' ? '<p class="brshare-error">'.htmlspecialchars($error, ENT_QUOTES, 'UTF-8').'</p>' : '';
$form = ''; $form = '';
if ($show_form) if ($show_form)