Dateien nach "/" hochladen

This commit is contained in:
2025-06-30 03:57:31 +00:00
parent c19c294122
commit bb7b039a32

419
WP Multi 2FA.php Normal file
View File

@@ -0,0 +1,419 @@
<?php
/*
* Plugin Name: WP Multi 2FA
* Plugin URI: https://git.viper.ipv64.net/M_Viper/WP-Multi-2FA
* Description: 2FA mit Backup-Codes und Admin-Erzwingung.
* Version: 1.0
* Author: M_Viper
* Author URI: https://m-viper.de
* Requires at least: 6.7.2
* Tested up to: 6.7.2
* PHP Version: 7.2
* License: GPL2
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
* Support: [Microsoft Teams Support](https://teams.live.com/l/community/FEAzokphpZTJ2u6OgI)
* Support: [Telegram Support](https://t.me/M_Viper04)
*/
if (!defined('ABSPATH')) exit;
// === 1. Plugin-Zeile in der Plugin-Liste erweitern ===
add_filter('plugin_row_meta', 'wp_multi_2fa_plugin_meta_links', 10, 2);
function wp_multi_2fa_plugin_meta_links($links, $file) {
if (plugin_basename(__FILE__) === $file) {
$update_link = '<a href="https://git.viper.ipv64.net/M_Viper/WP-Multi-2FA/releases" target="_blank" style="color: #d63638; font-weight: bold;">🔴 Neue Updates auf Gitea</a>';
$links[] = $update_link;
}
return $links;
}
// === 2. Admin-Dashboard-Hinweis bei neuer Version ===
add_action('admin_init', 'wp_multi_2fa_check_update_notice');
function wp_multi_2fa_check_update_notice() {
if (!current_user_can('update_plugins')) return;
$transient = get_transient('wp_multi_2fa_gitea_version');
if ($transient === false) {
$response = wp_remote_get('https://git.viper.ipv64.net/api/v1/repos/M_Viper/WP-Multi-2FA/releases/latest');
if (is_wp_error($response)) return;
$data = json_decode(wp_remote_retrieve_body($response));
if (isset($data->tag_name)) {
$latest = ltrim($data->tag_name, 'v'); // Entfernt evtl. "v" vor der Versionsnummer
set_transient('wp_multi_2fa_gitea_version', $latest, 12 * HOUR_IN_SECONDS);
} else {
return;
}
} else {
$latest = $transient;
}
if (!function_exists('get_plugin_data')) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
$plugin_data = get_plugin_data(__FILE__);
$current_version = $plugin_data['Version'];
if (version_compare($latest, $current_version, '>')) {
add_action('admin_notices', function () use ($latest) {
?>
<div class="notice notice-warning is-dismissible">
<p><strong>WP Multi 2FA:</strong> Eine neue Version (<?= esc_html($latest); ?>) ist verfügbar.
<a href="https://git.viper.ipv64.net/M_Viper/WP-Multi-2FA/releases" target="_blank">Jetzt ansehen auf Gitea</a>.</p>
</div>
<?php
});
}
}
class WP_2FA_Plugin {
private $option_name = 'wp2fa_users';
public function __construct() {
add_action('show_user_profile', [$this, 'show_2fa_settings']);
add_action('edit_user_profile', [$this, 'show_2fa_settings']);
add_action('personal_options_update', [$this, 'save_2fa_settings']);
add_action('edit_user_profile_update', [$this, 'save_2fa_settings']);
// Login flow
add_action('wp_login', [$this, 'after_login'], 10, 2);
add_action('init', [$this, 'handle_2fa']);
// Einstellungen > Allgemein
add_action('admin_init', [$this, 'register_settings_field']);
// Session für 2FA-Code speichern
if (!session_id()) {
session_start();
}
}
private function get_user_2fa_data($user_id) {
$all = get_option($this->option_name, []);
return $all[$user_id] ?? null;
}
private function set_user_2fa_data($user_id, $data) {
$all = get_option($this->option_name, []);
$all[$user_id] = $data;
update_option($this->option_name, $all);
}
public function show_2fa_settings($user) {
if (!current_user_can('edit_user', $user->ID)) return;
$data = $this->get_user_2fa_data($user->ID);
$enabled = $data['enabled'] ?? false;
$secret = $data['secret'] ?? null;
$backup_codes = $data['backup_codes'] ?? [];
if (!$secret && $enabled) {
$secret = $this->generate_secret();
$data['secret'] = $secret;
$backup_codes = $this->generate_backup_codes();
$data['backup_codes'] = $backup_codes;
$this->set_user_2fa_data($user->ID, $data);
}
?>
<h2>2-Faktor-Authentifizierung</h2>
<table class="form-table">
<tr>
<th><label for="wp2fa_enabled">2FA aktivieren</label></th>
<td>
<input type="checkbox" name="wp2fa_enabled" id="wp2fa_enabled" value="1" <?php checked($enabled); ?>>
<p class="description">Aktiviere die Zwei-Faktor-Authentifizierung für dein Konto.</p>
</td>
</tr>
<?php if ($enabled && $secret): ?>
<tr>
<th>QR-Code (für Authenticator-App)</th>
<td>
<img src="<?php echo esc_url($this->get_qr_code_url($user->user_email, $secret)); ?>" alt="QR Code" />
<p>Scanne diesen QR-Code mit einer App wie Google Authenticator oder Authy.</p>
</td>
</tr>
<tr>
<th>Backup-Codes</th>
<td>
<textarea readonly rows="5" cols="30" style="font-family: monospace;"><?php echo implode("\n", $backup_codes); ?></textarea><br>
<button type="button" onclick="downloadBackupCodes()">Backup-Codes herunterladen</button>
<script>
function downloadBackupCodes() {
const codes = <?php echo json_encode($backup_codes); ?>;
const blob = new Blob([codes.join("\n")], {type: 'text/plain'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'backup-codes.txt';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
</script>
<p>Bewahre diese Codes sicher auf. Jeder Code kann einmal zur Anmeldung verwendet werden.</p>
</td>
</tr>
<?php endif; ?>
</table>
<?php
}
public function save_2fa_settings($user_id) {
if (!current_user_can('edit_user', $user_id)) return;
$data = $this->get_user_2fa_data($user_id) ?? [];
$enabled = isset($_POST['wp2fa_enabled']) && $_POST['wp2fa_enabled'] == '1';
if ($enabled && empty($data['secret'])) {
$data['secret'] = $this->generate_secret();
$data['backup_codes'] = $this->generate_backup_codes();
}
if (!$enabled) {
// Deaktivieren: löschen
$data = [];
} else {
$data['enabled'] = true;
}
$this->set_user_2fa_data($user_id, $data);
}
private function generate_secret() {
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
$secret = '';
for ($i=0; $i < 16; $i++) {
$secret .= $chars[random_int(0, 31)];
}
return $secret;
}
private function generate_backup_codes() {
$codes = [];
for ($i = 0; $i < 10; $i++) {
$codes[] = strtoupper(bin2hex(random_bytes(4)));
}
return $codes;
}
private function get_qr_code_url($email, $secret) {
$issuer = rawurlencode(get_bloginfo('name'));
$label = rawurlencode(get_bloginfo('name') . ':' . $email);
$otpauth = "otpauth://totp/{$label}?secret={$secret}&issuer={$issuer}";
$chl = urlencode($otpauth);
return "https://api.qrserver.com/v1/create-qr-code/?size=200x200&data={$chl}";
}
// Nach Login prüfen, ob 2FA nötig
public function after_login($user_login, $user) {
if (!$this->needs_2fa($user->ID)) return;
$_SESSION['wp2fa_user'] = $user->ID;
wp_logout();
wp_redirect(site_url('/wp-login.php?action=wp2fa'));
exit;
}
private function needs_2fa($user_id) {
$data = $this->get_user_2fa_data($user_id);
$admin_forced = get_option('wp2fa_admin_forced', false);
return ($data['enabled'] ?? false) || $admin_forced;
}
// 2FA Handler: Formular verarbeiten und ggf. anzeigen (Popup)
public function handle_2fa() {
if (!isset($_GET['action']) || $_GET['action'] !== 'wp2fa') return;
$user_id = $_SESSION['wp2fa_user'] ?? 0;
if (!$user_id) {
wp_redirect(wp_login_url());
exit;
}
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$code = strtoupper(trim($_POST['wp2fa_code'] ?? ''));
$data = $this->get_user_2fa_data($user_id);
if (!$data) {
wp_redirect(wp_login_url());
exit;
}
$secret = $data['secret'] ?? '';
$backup_codes = $data['backup_codes'] ?? [];
if ($this->verify_code($secret, $code)) {
// Erfolgreicher Authenticator-Code
wp_set_auth_cookie($user_id);
unset($_SESSION['wp2fa_user']);
wp_redirect(admin_url());
exit;
} elseif (in_array($code, $backup_codes, true)) {
// Backup-Code erfolgreich, diesen Code löschen
$backup_codes = array_diff($backup_codes, [$code]);
$data['backup_codes'] = $backup_codes;
$this->set_user_2fa_data($user_id, $data);
wp_set_auth_cookie($user_id);
unset($_SESSION['wp2fa_user']);
wp_redirect(admin_url());
exit;
} else {
$error = 'Falscher Code!';
}
}
$this->show_2fa_form($error);
exit;
}
private function show_2fa_form($error = '') {
?>
<!DOCTYPE html>
<html>
<head>
<title>2-Faktor-Authentifizierung</title>
<style>
body {
font-family: Arial, sans-serif;
background: #222;
margin: 0; padding: 0; height: 100vh;
display: flex; align-items: center; justify-content: center;
}
#wp2fa-modal {
background: #fff;
padding: 30px;
border-radius: 8px;
box-shadow: 0 0 15px rgba(0,0,0,0.5);
width: 320px;
text-align: center;
position: relative;
}
#wp2fa-modal h1 {
margin-top: 0;
margin-bottom: 20px;
}
#wp2fa-modal input[type="text"] {
font-size: 18px;
padding: 10px;
width: 100%;
box-sizing: border-box;
margin-bottom: 20px;
text-align: center;
letter-spacing: 4px;
font-family: monospace;
}
#wp2fa-modal button {
background-color: #0073aa;
border: none;
color: white;
padding: 10px 20px;
font-size: 16px;
border-radius: 4px;
cursor: pointer;
}
#wp2fa-modal .error {
color: red;
margin-bottom: 15px;
}
</style>
</head>
<body>
<div id="wp2fa-modal" role="dialog" aria-modal="true" aria-labelledby="wp2fa-title">
<h1 id="wp2fa-title">2-Faktor-Authentifizierung</h1>
<?php if ($error): ?>
<div class="error"><?php echo esc_html($error); ?></div>
<?php endif; ?>
<form method="post">
<label for="wp2fa_code">Gib deinen Authenticator- oder Backup-Code ein:</label><br>
<input type="text" name="wp2fa_code" id="wp2fa_code" autocomplete="one-time-code" required maxlength="8" autofocus><br>
<button type="submit">Bestätigen</button>
</form>
</div>
</body>
</html>
<?php
}
private function verify_code($secret, $code) {
// TOTP-Verifikation (30-Sekunden Fenster)
$timeSlice = floor(time() / 30);
for ($i = -1; $i <= 1; $i++) {
$calc = $this->get_totp_code($secret, $timeSlice + $i);
if ($calc === $code) return true;
}
return false;
}
private function get_totp_code($secret, $timeSlice) {
$secretkey = $this->base32_decode($secret);
$time = pack('N*', 0) . pack('N*', $timeSlice);
$hash = hash_hmac('sha1', $time, $secretkey, true);
$offset = ord(substr($hash, -1)) & 0x0F;
$truncatedHash = substr($hash, $offset, 4);
$code = unpack('N', $truncatedHash)[1] & 0x7FFFFFFF;
$code = $code % 1000000;
return str_pad($code, 6, '0', STR_PAD_LEFT);
}
private function base32_decode($b32) {
$alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
$b32 = strtoupper($b32);
$l = strlen($b32);
$n = 0;
$j = 0;
$binary = '';
for ($i = 0; $i < $l; $i++) {
$n = $n << 5; // 5 bits per char
$n = $n + strpos($alphabet, $b32[$i]);
$j += 5;
if ($j >= 8) {
$j -= 8;
$binary .= chr(($n & (0xFF << $j)) >> $j);
}
}
return $binary;
}
// Registrierung des Admin-Feldes in Einstellungen > Allgemein
public function register_settings_field() {
register_setting('general', 'wp2fa_admin_forced', [
'type' => 'boolean',
'description' => '2FA für alle Benutzer erzwingen',
'sanitize_callback' => [$this, 'sanitize_bool'],
'default' => false,
]);
add_settings_field(
'wp2fa_admin_forced',
'2FA für alle Benutzer erzwingen',
[$this, 'render_admin_forced_field'],
'general'
);
}
public function render_admin_forced_field() {
$forced = get_option('wp2fa_admin_forced', false);
?>
<input type="checkbox" id="wp2fa_admin_forced" name="wp2fa_admin_forced" value="1" <?php checked($forced, true); ?> />
<label for="wp2fa_admin_forced">Wenn aktiviert, müssen alle Benutzer 2FA nutzen, auch wenn sie es nicht selbst aktiviert haben.</label>
<?php
}
public function sanitize_bool($value) {
return ($value === '1' || $value === 1 || $value === true) ? true : false;
}
}
new WP_2FA_Plugin();