| Server IP : 188.114.96.3 / Your IP : 216.73.216.143 Web Server : Apache System : Linux sv058.home.net.pl 6.8.2 husak_dirty_pages_fix #1 SMP PREEMPT_DYNAMIC Tue Apr 2 22:06:51 CEST 2024 x86_64 User : ( 3194769) PHP Version : 8.5.12 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : OFF Directory : /polana/admin/ |
Upload File : |
<?php
declare(strict_types=1);
/** POLANA V18.10: One-time credential bridge (Google Calendar -> Drive backup).
* Never copies Calendar access/refresh tokens or touches its encrypted store.
* Requires existing POLANA v17.3 calendar and v18.3 backup modules.
*/
require_once dirname(__DIR__) . '/includes/polana_backup_v182.php';
require_admin_role();
header('Cache-Control: private, no-store, max-age=0');
header('X-Robots-Tag: noindex,nofollow');
header('X-Content-Type-Options: nosniff');
header('Referrer-Policy: no-referrer');
function bridge_h(mixed $s): string { return htmlspecialchars((string)$s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); }
function bridge_rollback_file(): string { return pbk_private() . '/v1810-google-bridge-rollback.json'; }
function bridge_calendar_secrets(): array {
$lib = dirname(__DIR__) . '/includes/polana_gcal.php';
if (!is_file($lib)) throw new RuntimeException('Brak biblioteki Kalendarza Google (includes/polana_gcal.php).');
require_once $lib;
if (!function_exists('pg173_read_secrets')) throw new RuntimeException('Biblioteka kalendarza nie udostępnia odczytu OAuth.');
$s = pg173_read_secrets();
if (!is_array($s) || empty($s['refresh_token'])) throw new RuntimeException('Kalendarz nie ma aktywnego połączenia Google. Najpierw sprawdź moduł Kalendarz.');
$id = trim((string)($s['client_id'] ?? ''));
$secret = trim((string)($s['client_secret'] ?? ''));
if (strlen($id) < 20 || strlen($id) > 260 || !str_ends_with($id, '.apps.googleusercontent.com') || strlen($secret) < 8 || strlen($secret) > 500) {
throw new RuntimeException('W kalendarzu nie znaleziono poprawnego Client ID i Secret.');
}
return ['client_id' => $id, 'client_secret' => $secret]; // never return a token
}
function bridge_write_rollback(array $before, array $after): void {
$path = bridge_rollback_file();
if (file_exists($path)) throw new RuntimeException('Istnieje już punkt cofania V18.10. Przywróć go lub zakończ konfigurację.');
$keys = ['drive_client_id','drive_client_secret','drive_refresh_token','drive_folder_id','drive_enabled'];
$original = [];
foreach ($keys as $key) {
$original[$key] = ['exists' => array_key_exists($key, $before), 'value' => $before[$key] ?? null];
}
$data = [
'version' => '18.10', 'created' => gmdate('c'), 'before' => $original,
'after_client_id' => (string)($after['drive_client_id'] ?? ''),
'after_encrypted_secret' => (string)($after['drive_client_secret'] ?? ''),
];
$encoded = json_encode($data, JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR);
$handle = @fopen($path, 'x+b');
if ($handle === false) throw new RuntimeException('Nie można bezpiecznie utworzyć punktu cofania.');
try {
@chmod($path, 0600);
if (fwrite($handle, $encoded) !== strlen($encoded)) throw new RuntimeException('Błąd zapisu punktu cofania.');
fflush($handle);
} catch (Throwable $e) { fclose($handle); @unlink($path); throw $e; }
fclose($handle);
}
function bridge_import(): string {
$calendar = bridge_calendar_secrets();
$before = pbk_cfg();
$existingId = trim((string)($before['drive_client_id'] ?? ''));
$connected = !empty($before['drive_refresh_token']);
if ($connected && $existingId !== $calendar['client_id']) {
throw new RuntimeException('Backup ma już autoryzowane INNE konto lub klienta OAuth. Nie nadpisuję go.');
}
if ($connected && $existingId === $calendar['client_id']) {
return 'Drive ma już własny token i używa tego samego klienta OAuth. Niczego nie zmieniono.';
}
if ($existingId === $calendar['client_id'] &&
hash_equals($calendar['client_secret'], pbk_secret_decrypt((string)($before['drive_client_secret'] ?? '')))) {
return 'Dane OAuth z Kalendarza są już zapisane w module backupów. Teraz połącz Google Drive przez OAuth.';
}
// Even though the client ID is the same, we never change an already-working Drive connection.
$after = $before;
$after['drive_client_id'] = $calendar['client_id'];
$after['drive_client_secret'] = pbk_secret_encrypt($calendar['client_secret']);
// The Calendar refresh token is never imported; Drive requires fresh consent.
$after['drive_refresh_token'] = '';
$after['drive_folder_id'] = '';
$after['drive_enabled'] = false;
bridge_write_rollback($before, $after);
try { pbk_save_cfg($after); }
catch (Throwable $e) { @unlink(bridge_rollback_file()); throw $e; }
return 'Przyjęto bezpiecznie zapisane Client ID i Client Secret z Kalendarza. Tokeny Kalendarza i ustawienia rezerwacji pozostają bez zmian.';
}
function bridge_rollback(): string {
$path = bridge_rollback_file();
if (!is_file($path)) throw new RuntimeException('Brak punktu cofania V18.10.');
$snapshot = json_decode((string)file_get_contents($path), true);
if (!is_array($snapshot) || ($snapshot['version'] ?? '') !== '18.10' || !isset($snapshot['before']) || !is_array($snapshot['before'])) {
throw new RuntimeException('Punkt cofania jest uszkodzony. Nie wykonuję zmian.');
}
$cfg = pbk_cfg();
if (!hash_equals((string)($snapshot['after_client_id'] ?? ''), (string)($cfg['drive_client_id'] ?? '')) ||
!hash_equals((string)($snapshot['after_encrypted_secret'] ?? ''), (string)($cfg['drive_client_secret'] ?? '')) ||
!empty($cfg['drive_refresh_token']) || !empty($cfg['drive_enabled'])) {
throw new RuntimeException('Ustawienia Drive zmieniły się po imporcie lub konto zostało połączone. Rollback zablokowany, aby nie zerwać aktywnego połączenia.');
}
$keys = ['drive_client_id','drive_client_secret','drive_refresh_token','drive_folder_id','drive_enabled'];
foreach ($keys as $key) {
if (!isset($snapshot['before'][$key]) || !is_array($snapshot['before'][$key])) throw new RuntimeException('Niekompletny punkt cofania.');
if ($snapshot['before'][$key]['exists'] === true) $cfg[$key] = $snapshot['before'][$key]['value'];
else unset($cfg[$key]);
}
pbk_save_cfg($cfg);
@unlink($path);
return 'Przywrócono wcześniejsze ustawienia Google Drive. Połączenie Kalendarza pozostaje bez zmian.';
}
$error = ''; $notice = ''; $calendarStatus = 'nieodczytany'; $driveStatus = 'nieodczytany';
try {
if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST') {
verify_csrf();
$action = (string)($_POST['action'] ?? '');
if ($action === 'import') $notice = bridge_import();
elseif ($action === 'rollback') $notice = bridge_rollback();
else throw new RuntimeException('Nieznana operacja.');
}
$calendar = bridge_calendar_secrets();
$calendarStatus = 'Połączony — zapisane OAuth jest dostępne';
$cfg = pbk_cfg();
$driveStatus = !empty($cfg['drive_refresh_token']) ? 'Połączony — token Drive zapisany' :
(empty($cfg['drive_client_id']) ? 'Nie skonfigurowano' : 'Client ID i Secret zapisane — oczekuje na zgodę Drive');
} catch (Throwable $e) { $error = $e->getMessage(); }
render_admin_header('Google OAuth — jeden zestaw danych');
?>
<style>
.bridge10{max-width:960px;margin:24px auto 56px;padding:0 16px;color:#26382e}.bridge10 *{box-sizing:border-box}.bridge10 h1{font-size:clamp(25px,3vw,35px)}.bridge10 h2{font-size:20px}.bridge10 section{margin:16px 0;padding:22px;border:1px solid #dbe5dd;border-radius:15px;background:#fff}.bridge10 p{line-height:1.65}.bridge10 .buttons{display:flex;gap:10px;flex-wrap:wrap;align-items:center}.bridge10 button,.bridge10 .link{font:inherit;font-weight:700;border-radius:9px;background:#18442c;color:#fff;border:none;padding:11px 17px;cursor:pointer;display:inline-block;text-decoration:none}.bridge10 .secondary{background:#e8eee9;color:#18442c}.bridge10 .alert{background:#fff2e7;border-left:4px solid #bf7234;border-radius:8px;padding:12px 14px}.bridge10 .ok{background:#edf7e9;border-left:4px solid #4a9b55;border-radius:8px;padding:12px 14px}.bridge10 code{overflow-wrap:anywhere}.bridge10 .muted{color:#607266}.bridge10 ol{padding-left:25px;line-height:1.9}@media(max-width:620px){.bridge10 section{padding:16px}.bridge10 .buttons>*{width:100%;text-align:center}}
</style>
<div class="bridge10">
<h1>Google — te same dane OAuth</h1>
<p>POLANA V18.10: jednorazowy most między już skonfigurowanym Kalendarzem Google a automatycznymi kopiami na Dysk Google. <b>Bez przepisywania Client ID ani Client Secret.</b></p>
<?php if ($notice): ?><p class="ok" role="status"><?=bridge_h($notice)?></p><?php endif; ?>
<?php if ($error): ?><p class="alert" role="alert"><?=bridge_h($error)?></p><?php endif; ?>
<section>
<h2>Stan połączeń</h2>
<p><b>Kalendarz:</b> <?=bridge_h($calendarStatus)?></p>
<p><b>Google Drive:</b> <?=bridge_h($driveStatus)?></p>
<p class="muted">Kopiujemy wyłącznie dane aplikacji OAuth. Nigdy nie kopiujemy tokenów ani kluczy szyfrujących Kalendarza. Kopie SQL pozostają nietknięte.</p>
<div class="buttons">
<form method="post"><?=csrf_field()?><input type="hidden" name="action" value="import"><button type="submit">Użyj danych Google z Kalendarza</button></form>
<a class="link secondary" href="/admin/polana-backup-automat.php">Otwórz kopie zapasowe</a>
</div>
</section>
<section>
<h2>Jednorazowe zezwolenie na Google Drive</h2>
<ol>
<li>W projekcie Google Cloud włącz <b>Google Drive API</b>.</li>
<li>W tym samym kliencie OAuth (Web application), co Kalendarz, dodaj drugi <b>Authorized redirect URI</b>:<br><code><?=bridge_h(pbk_oauth_redirect_uri())?></code>. Nie usuwaj adresu callback Kalendarza.</li>
<li>W <b>Automatyczne kopie</b> kliknij <b>Połącz Google Drive (OAuth)</b> i wybierz to samo konto Google, które ma kalendarz.</li>
<li>Po wyrażeniu zgody zaznacz <b>Wysyłaj kopie na Google Drive</b>, zapisz i kliknij <b>Sprawdź połączenie Drive</b>.</li>
</ol>
<p class="muted">Uprawnienie Drive to <code>drive.file</code>, inne niż wcześniej przyznany dostęp do Kalendarza. Kalendarz i Drive korzystają z jednego klienta OAuth, ale mają osobne tokeny.</p>
</section>
<section>
<h2>Rollback — tylko przed połączeniem Drive</h2>
<p class="muted">Przywraca wyłącznie wcześniejsze ustawienia Drive. Jeśli Google Drive został już połączony, rollback jest celowo zablokowany, aby nie utracić dostępu.</p>
<form method="post"><?=csrf_field()?><input type="hidden" name="action" value="rollback"><button type="submit" class="secondary">Cofnij import danych OAuth</button></form>
</section>
</div>
<?php render_admin_footer(); ?>