PluginProbe
Easy Basic Authentication – Add basic auth to site or admin area / 4.1.0
Easy Basic Authentication – Add basic auth to site or admin area v4.1.0
4.1.0 trunk 1.1 1.2 1.3 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.4 1.4.1 1.5 1.5.1 1.5.2 1.5.3 1.6 1.6.1 1.6.2 1.6.3 1.6.4 1.7 1.7.1 1.8 1.8.1 All 51 releases
← All changes | class/easy-basic-authentication-class.php +261 -200 1.44.1.0 View file →
@@ -1,72 +1,280 @@
1 -<?php
1 +<?php
2 +if ( ! defined( 'ABSPATH' ) ) {
3 + exit;
4 +}
2 5
6 +
3 7 require_once (dirname(__FILE__).'/easy-basic-authentication-log-class.php');
4 8 require_once (dirname(__FILE__).'/easy-basic-authentication-emailalert-class.php');
9 +require_once (dirname(__FILE__).'/easy-basic-authentication-form-class.php');
10 +require_once (dirname(__FILE__).'/easy-basic-authentication-notice-class.php');
11 +require_once (dirname(__FILE__).'/easy-basic-authentication-compatcheck-class.php');
5 12
6 13 class easy_basic_authentication_class {
7 14
8 15 private $log;
9 16 private $email;
17 + private $form;
18 + private $compatcheck;
10 19
11 20 public function __construct()
12 21 {
13 22 $this->log = new easy_basic_authentication_log_class();
14 23 $this->email = new easy_basic_authentication_emailalert_class();
24 + $this->form = new easy_basic_authentication_form_class();
25 + $notice = new easy_basic_authentication_notice_class();
26 + $this->compatcheck = new easy_basic_authentication_compatcheck_class();
27 + $this->compatcheck->register_hooks();
15 28
16 - if(get_option( 'basic_auth_plugin_admin_enable' )) {
17 - if (in_array($GLOBALS['pagenow'], array('wp-login.php', 'wp-register.php'))) {
18 - add_action( 'init', array($this,'basic_auth_admin') );
29 + // Fuori da una richiesta HTTP la sfida 401 non ha alcun senso: da riga di comando
30 + // diventa un exit() silenzioso che blocca WP-CLI, il cron di sistema e qualunque
31 + // script che faccia require di wp-load.php.
32 + if ( ! self::is_cli() ) {
33 + $pagenow = isset( $GLOBALS['pagenow'] ) ? $GLOBALS['pagenow'] : '';
34 +
35 + if(get_option( 'basic_auth_plugin_admin_enable' )) {
36 + if (in_array($pagenow, array('wp-login.php', 'wp-register.php'))) {
37 + add_action( 'init', array($this,'basic_auth_root') );
38 + }
19 39 }
20 - }
21 40
22 - if(get_option( 'basic_auth_plugin_enable' ) && get_option( 'basic_auth_plugin_admin_enable' )){
23 - add_action( 'init', array($this,'basic_auth_root') );
41 + if(get_option( 'basic_auth_plugin_enable' ) && get_option( 'basic_auth_plugin_admin_enable' )){
42 + add_action( 'init', array($this,'basic_auth_root') );
43 + }
24 44 }
25 45
46 + add_action( 'init', array($this, 'maybe_upgrade'), 1 );
47 +
26 48 add_action( 'admin_menu', array($this,'basic_auth_plugin_menu' ));
27 - add_action( 'admin_init', array($this,'basic_auth_plugin_settings_init' ));
49 + add_action( 'admin_init', array($this->form,'basic_auth_plugin_settings_init' ));
28 50
29 - add_action( 'admin_init', array($this,'basic_auth_plugin_save_settings') );
51 + add_action('admin_init', function () {
52 + if ( ! current_user_can( 'manage_options' ) ) {
53 + return;
54 + }
55 +
56 + // Il nonce e verificato dentro basic_auth_plugin_save_settings(), che riceve
57 + // i dati grezzi perche deve distinguere un campo assente da uno vuoto (le
58 + // checkbox non spuntate non vengono inviate).
59 + // phpcs:ignore WordPress.Security.NonceVerification.Missing
60 + $post_data = $_POST;
61 + $this->form->basic_auth_plugin_save_settings($post_data);
62 + });
30 63
31 64 }
32 65
33 - public function basic_auth_root() {
34 - $user = get_option( 'basic_auth_plugin_username' );
35 - $pass = get_option( 'basic_auth_plugin_password' );
66 + /**
67 + * Se stiamo girando fuori da una richiesta HTTP (WP-CLI, cron di sistema, script).
68 + *
69 + * @return bool
70 + */
71 + public static function is_cli() {
72 + if ( defined( 'WP_CLI' ) && WP_CLI ) {
73 + return true;
74 + }
75 +
76 + return 'cli' === PHP_SAPI || 'phpdbg' === PHP_SAPI;
77 + }
78 +
79 + public function basic_auth_root()
80 + {
81 + if ( self::is_cli() ) {
82 + return;
83 + }
84 +
85 + $user = get_option('basic_auth_plugin_username');
86 + $pass = get_option('basic_auth_plugin_password');
87 +
88 + if ($this->whiteListChecker()) {
89 + return;
90 + }
91 +
92 + if ($this->urlWhiteListChecker()) {
93 + return;
94 + }
95 +
96 + // Alcuni server (nginx + FastCGI su tutti) non popolano PHP_AUTH_USER da soli:
97 + // le credenziali arrivano solo nell'header Authorization e vanno estratte a mano.
98 + if (!isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['HTTP_AUTHORIZATION'])) {
99 + $authorization = sanitize_text_field( wp_unslash( $_SERVER['HTTP_AUTHORIZATION'] ) );
100 +
101 + if ( preg_match( '#^Basic\s+([A-Za-z0-9+/=]+)$#i', $authorization, $matches ) ) {
102 + $decoded = base64_decode( $matches[1], true );
103 +
104 + // Senza i due punti non ci sono due campi: la list() originale
105 + // generava un warning "Undefined array key 1" su PHP 8.
106 + if ( false !== $decoded && false !== strpos( $decoded, ':' ) ) {
107 + list( $sent_user, $sent_pass ) = explode( ':', $decoded, 2 );
108 + $_SERVER['PHP_AUTH_USER'] = $sent_user;
109 + $_SERVER['PHP_AUTH_PW'] = $sent_pass;
110 + }
111 + }
112 + }
113 +
114 + // Basic Auth prevede sempre un primo giro senza credenziali: il browser chiede,
115 + // riceve 401, e solo allora rimanda con utente e password. Quel 401 non e un
116 + // tentativo fallito, e registrarlo significherebbe una voce di log (e una mail)
117 + // per ogni visitatore e ogni bot che passa.
118 + $credentials_sent = isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW']);
119 +
120 + if (!$credentials_sent) {
121 + $this->do_exit(true, false);
122 + return;
123 + }
124 +
125 + // L'utente e salvato passando per sanitize_text_field(), quindi quello in arrivo
126 + // va normalizzato allo stesso modo perche il confronto sia sensato.
127 + $sent_user = sanitize_text_field( wp_unslash( $_SERVER['PHP_AUTH_USER'] ) );
128 +
129 + // La password invece NON va sanitizzata: alterarla farebbe fallire l'accesso a
130 + // chiunque ne usi una con caratteri speciali. Non viene mai stampata, solo
131 + // confrontata con l'hash.
132 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
133 + $sent_pass = wp_unslash( $_SERVER['PHP_AUTH_PW'] );
134 +
135 + $credentials_valid = hash_equals( (string) $user, $sent_user )
136 + && wp_check_password( $sent_pass, $pass );
137 +
138 + if (!$credentials_valid) {
139 + // Credenziali inviate e sbagliate: questo si che va segnalato.
140 + $this->do_exit(true, true);
141 + }
142 + }
143 +
144 + public function urlWhiteListChecker() {
145 + if (empty($_SERVER['HTTP_HOST']) || empty($_SERVER['REQUEST_URI'])) {
146 + return false;
147 + }
148 +
149 + $scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
150 +
151 + $host = sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) );
152 +
153 + // L'URI serve solo al confronto con la whitelist, non viene mai stampato.
154 + // sanitize_text_field() qui sarebbe dannoso: rimuove le sequenze %xx e
155 + // cambierebbe il confronto su qualsiasi percorso con caratteri codificati.
156 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
157 + $request_uri = wp_unslash( $_SERVER['REQUEST_URI'] );
158 +
159 + $currentUrl = $scheme . '://' . $host . $request_uri;
160 +
161 + $whitelist = $this->getUrlWhiteList();
162 +
163 + foreach ($whitelist as $entry) {
164 + if ($this->isUrlAllowed($currentUrl, $entry)) {
165 + return true;
166 + }
167 + }
168 +
169 + return false;
170 + }
171 +
172 + private function isUrlAllowed($currentUrl, $entry) {
173 + $currentUrl = rtrim($currentUrl, '/');
174 + $entry = rtrim($entry, '/');
175 +
176 + if (strpos($entry, '/') === 0) {
177 + $path = wp_parse_url($currentUrl, PHP_URL_PATH);
178 + return stripos($path, $entry) === 0;
179 + }
180 +
181 + if (!preg_match('#^https?://#i', $entry)) {
182 + $scheme = wp_parse_url($currentUrl, PHP_URL_SCHEME) ?: 'https';
183 + $entry = $scheme . '://' . $entry;
184 + }
185 +
186 + return stripos($currentUrl, $entry) === 0;
187 + }
188 +
189 + public function whiteListChecker() {
190 + if (!isset($_SERVER['REMOTE_ADDR'])) {
191 + return false;
192 + }
36 193
37 - if ( !isset( $_SERVER['PHP_AUTH_USER'] ) || !isset( $_SERVER['PHP_AUTH_PW'] ) ||
38 - $_SERVER['PHP_AUTH_USER'] != $user || !wp_check_password( $_SERVER['PHP_AUTH_PW'], $pass ) ) {
39 -
40 - $this->basic_auth_action_failed_access();
194 + // Un REMOTE_ADDR malformato non deve arrivare ai confronti della whitelist:
195 + // ip2long() restituirebbe false e il risultato sarebbe imprevedibile.
196 + $ip = filter_var( wp_unslash( $_SERVER['REMOTE_ADDR'] ), FILTER_VALIDATE_IP );
41 197
42 - do_action('basic_auth_before_401');
43 -
44 - header( 'WWW-Authenticate: Basic realm="My Website"' );
45 - header( 'HTTP/1.0 401 Unauthorized' );
46 - echo 'Autenticazione richiesta';
47 - exit;
198 + if ( false === $ip ) {
199 + return false;
48 200 }
201 +
202 + $whitelist = $this->getWhiteList();
203 +
204 + foreach ($whitelist as $entry) {
205 + if ($this->isIpAllowed($ip, $entry)) {
206 + return true;
207 + }
208 + }
209 + return false;
49 210 }
50 211
51 - public function basic_auth_admin() {
52 - $user = get_option( 'basic_auth_plugin_username' );
53 - $pass = get_option( 'basic_auth_plugin_password' );
212 + private function isIpAllowed($ip, $entry) {
213 + if (filter_var($entry, FILTER_VALIDATE_IP)) {
214 + return $ip === $entry;
215 + } elseif (strpos($entry, '/') !== false) {
216 + return $this->isIpInCidr($ip, $entry);
217 + } elseif (strpos($entry, '-') !== false) {
218 + return $this->isIpInRange($ip, $entry);
219 + }
220 + return false;
221 + }
54 222
55 - if ( !isset( $_SERVER['PHP_AUTH_USER'] ) || !isset( $_SERVER['PHP_AUTH_PW'] ) ||
56 - $_SERVER['PHP_AUTH_USER'] != $user || !wp_check_password( $_SERVER['PHP_AUTH_PW'], $pass ) ) {
57 -
223 + private function isIpInCidr($ip, $cidr) {
224 + list($subnet, $mask) = explode('/', $cidr);
225 + $ipLong = ip2long($ip);
226 + $subnetLong = ip2long($subnet);
227 + $maskLong = -1 << (32 - $mask);
228 + return ($ipLong & $maskLong) === ($subnetLong & $maskLong);
229 + }
230 +
231 + private function isIpInRange($ip, $range) {
232 + list($start, $end) = array_map('trim', explode('-', $range));
233 + $ipLong = ip2long($ip);
234 + $startLong = ip2long($start);
235 + $endLong = ip2long($end);
236 + return ($ipLong >= $startLong && $ipLong <= $endLong);
237 + }
238 +
239 + /**
240 + * Manda la sfida 401.
241 + *
242 + * @param bool $admin_area Se la richiesta riguarda l'area di amministrazione.
243 + * @param bool $log_attempt Se registrare l'accesso fra i tentativi falliti.
244 + * False per il 401 iniziale, che fa parte del protocollo.
245 + */
246 + public function do_exit($admin_area = false, $log_attempt = true) {
247 +
248 + if ($log_attempt) {
58 249 $this->basic_auth_action_failed_access();
59 -
60 - do_action('basic_auth_before_401');
250 + }
61 251
62 - header( 'HTTP/1.1 401 Unauthorized' );
63 - header( 'WWW-Authenticate: Basic realm="Admin Area"' );
64 - exit;
65 - }
252 + do_action('basic_auth_before_401');
253 +
254 + if ($admin_area) {
255 + do_action('basic_auth_before_401_admin_area');
256 + }
257 +
258 + // I browser memorizzano le credenziali per realm: cambiare questa stringa fa
259 + // ricomparire la richiesta di accesso a chi era gia autenticato. Il valore
260 + // predefinito resta quello storico; chi vuole cambiarlo usa il filtro.
261 + $realm = apply_filters('basic_auth_realm', 'My Website', $admin_area);
262 + $realm = str_replace(array('"', "\r", "\n"), '', (string) $realm);
263 +
264 + header('WWW-Authenticate: Basic realm="' . $realm . '"');
265 + status_header(401);
266 + exit;
66 267 }
67 -
68 -
268 +
269 + public function getWhiteList() {
270 + return get_option( 'basic_auth_plugin_whitelist' )?explode(',',get_option( 'basic_auth_plugin_whitelist' )):[];
271 + }
272 +
273 + public function getUrlWhiteList() {
274 + return get_option( 'basic_auth_plugin_urlwhitelist' )?explode(',',get_option( 'basic_auth_plugin_urlwhitelist' )):[];
275 + }
276 +
69 277 public function basic_auth_plugin_menu() {
70 278 add_menu_page(
71 279 __('Configurations for Easy Basic Authentication', 'easy-basic-authentication'),
72 280 __('Easy Basic A.', 'easy-basic-authentication'),
@@ -71,180 +279,33 @@
71 279 __('Configurations for Easy Basic Authentication', 'easy-basic-authentication'),
72 280 __('Easy Basic A.', 'easy-basic-authentication'),
73 281 'manage_options',
74 282 'basic-auth-plugin',
75 - array($this, 'basic_auth_plugin_settings_page'),
283 + array($this->form, 'basic_auth_plugin_settings_page'),
76 284 'dashicons-lock'
77 285 );
78 286 if($this->log->is_enabled()) {
79 287 $this->log->getMenu();
80 288 }
81 - }
289 + }
82 290
83 - public function basic_auth_plugin_settings_page() {
84 - include plugin_dir_path( __FILE__ ) . '../template/settings_page.php';
85 - }
86 -
87 - public function basic_auth_plugin_settings_init() {
88 - register_setting( 'basic-auth-plugin-settings', 'basic_auth_plugin_admin_enable' );
89 - register_setting( 'basic-auth-plugin-settings', 'basic_auth_plugin_enable' );
90 - register_setting( 'basic-auth-plugin-settings', 'basic_auth_plugin_username' );
91 - register_setting( 'basic-auth-plugin-settings', 'basic_auth_plugin_admin_log_enable' );
92 -
93 - add_settings_section(
94 - 'basic-auth-plugin-section',
95 - __('Configurations for Easy Basic Authentication', 'easy-basic-authentication'),
96 - array($this,'basic_auth_plugin_section_cb'),
97 - 'basic-auth-plugin-settings'
98 - );
99 -
100 - add_settings_field(
101 - 'basic-auth-plugin-admin-enable',
102 - __('Enable for wp-admin', 'easy-basic-authentication'),
103 - array($this,'basic_auth_plugin_admin_enable_cb'),
104 - 'basic-auth-plugin-settings',
105 - 'basic-auth-plugin-section'
106 - );
107 -
108 - add_settings_field(
109 - 'basic-auth-plugin-enable',
110 - __('Enable for the entire site (only if wp-admin is enabled)', 'easy-basic-authentication'),
111 - array($this,'basic_auth_plugin_enable_cb'),
112 - 'basic-auth-plugin-settings',
113 - 'basic-auth-plugin-section'
114 - );
115 -
116 - add_settings_field(
117 - 'basic-auth-plugin-username',
118 - __('Username', 'easy-basic-authentication'),
119 - array($this,'basic_auth_plugin_username_cb'),
120 - 'basic-auth-plugin-settings',
121 - 'basic-auth-plugin-section'
122 - );
123 -
124 - add_settings_field(
125 - 'basic-auth-plugin-password',
126 - __('Password', 'easy-basic-authentication'),
127 - array($this,'basic_auth_plugin_password_cb'),
128 - 'basic-auth-plugin-settings',
129 - 'basic-auth-plugin-section'
130 - );
131 -
132 - add_settings_field(
133 - 'basic-auth-plugin-admin-log-enable',
134 - __('Enable access logs', 'easy-basic-authentication'),
135 - array($this,'basic_auth_plugin_admin_log_enable_cb'),
136 - 'basic-auth-plugin-settings',
137 - 'basic-auth-plugin-section'
138 - );
139 -
140 - add_settings_field(
141 - 'basic-auth-plugin-alert-enable',
142 - __('Enable email alert', 'easy-basic-authentication'),
143 - array($this,'basic_auth_plugin_alert_enable_cb'),
144 - 'basic-auth-plugin-settings',
145 - 'basic-auth-plugin-section'
146 - );
147 -
148 - add_settings_field(
149 - 'basic-auth-plugin-email-alert',
150 - __('Email for alert', 'easy-basic-authentication'),
151 - array($this,'basic_auth_plugin_alertemail_cb'),
152 - 'basic-auth-plugin-settings',
153 - 'basic-auth-plugin-section'
154 - );
155 -
156 - }
157 -
158 - public function basic_auth_plugin_section_cb() {
159 - echo __('Configure basic authentication', 'easy-basic-authentication');
160 - }
161 -
162 - public function basic_auth_plugin_enable_cb() {
163 - $admin_enable = get_option( 'basic_auth_plugin_admin_enable' );
164 - $enable = get_option( 'basic_auth_plugin_enable' );
165 - ?>
166 - <input type="checkbox" name="basic_auth_plugin_enable" value="1" <?php checked( $enable, 1 ); ?><?php disabled( !$admin_enable ); ?>>
167 - <?php
168 - }
169 -
170 - public function basic_auth_plugin_admin_enable_cb() {
171 - $enable = get_option( 'basic_auth_plugin_admin_enable' );
172 - ?>
173 - <input type="checkbox" name="basic_auth_plugin_admin_enable" value="1" <?php checked( $enable, 1 ); ?>>
174 - <?php
175 - }
176 -
177 - public function basic_auth_plugin_username_cb() {
178 - $username = get_option( 'basic_auth_plugin_username' );
179 - ?>
180 - <input type="text" name="basic_auth_plugin_username" value="<?php echo esc_attr( $username ); ?>">
181 - <?php
182 - }
183 -
184 - public function basic_auth_plugin_password_cb() {
185 - $password = get_option( 'basic_auth_plugin_password' )
186 - ? __('Password entered', 'easy-basic-authentication')
187 - : __('Enter the password', 'easy-basic-authentication');
188 - ?>
189 - <input type="password" name="basic_auth_plugin_password" value="" placeholder="<?php echo $password; ?>">
190 - <?php
191 - }
192 -
193 - public function basic_auth_plugin_admin_log_enable_cb() {
194 - $enable = get_option( 'basic_auth_plugin_admin_log_enable' );
195 - ?>
196 - <input type="checkbox" name="basic_auth_plugin_admin_log_enable" value="1" <?php checked( $enable, 1 ); ?>>
197 - <?php
198 - }
199 -
200 - public function basic_auth_plugin_alert_enable_cb() {
201 - $enable = get_option( 'basic_auth_plugin_alert_enable' );
202 - ?>
203 - <input type="checkbox" name="basic_auth_plugin_alert_enable" value="1" <?php checked( $enable, 1 ); ?>>
204 - <?php
205 - }
206 -
207 - public function basic_auth_plugin_alertemail_cb() {
208 - $email_alert = get_option( 'basic_auth_plugin_alertemail' );
209 - ?>
210 - <input type="text" name="basic_auth_plugin_alertemail" value="<?php echo esc_attr( $email_alert ); ?>" placeholder="<?php echo __('Enter the email', 'easy-basic-authentication'); ?>">
211 - <?php
212 - }
213 -
214 -
215 - public function basic_auth_plugin_save_settings() {
216 - if ( isset( $_POST['eba_submit'] ) ) {
217 - $admin_enable = isset( $_POST['basic_auth_plugin_admin_enable'] ) ? 1 : 0;
218 - $enable = isset( $_POST['basic_auth_plugin_enable'] ) ? 1 : 0;
219 - $username = sanitize_text_field( $_POST['basic_auth_plugin_username'] );
220 - $password = sanitize_text_field( $_POST['basic_auth_plugin_password'] );
221 - $log_enable = isset( $_POST['basic_auth_plugin_admin_log_enable'] ) ? 1 : 0;
222 - $alert_enable = isset( $_POST['basic_auth_plugin_alert_enable'] ) ? 1 : 0;
223 - $alert_email = sanitize_text_field( $_POST['basic_auth_plugin_alertemail'] );
291 + /**
292 + * Migrazioni una tantum, eseguite quando cambia la versione del plugin.
293 + *
294 + * Fino alla 4.0.0 il log degli accessi stava in un'opzione in autoload: fino a
295 + * 500 voci, circa 144 KB, deserializzate a ogni richiesta del sito.
296 + */
297 + public function maybe_upgrade() {
298 + if ( get_option( 'basic_auth_plugin_db_version' ) === EASY_BASIC_AUTHENTICATION_VERSION ) {
299 + return;
300 + }
224 301
225 - if ( !is_email( $alert_email ) ) {
226 - add_settings_error(
227 - 'basic_auth_plugin_alertemail',
228 - 'basic_auth_plugin_alertemail_error',
229 - __('Invalid email address', 'easy-basic-authentication'),
230 - 'error'
231 - );
232 - } else {
233 - update_option( 'basic_auth_plugin_alertemail', $alert_email );
234 - }
235 -
236 - update_option( 'basic_auth_plugin_admin_enable', $admin_enable );
237 - update_option( 'basic_auth_plugin_enable', $enable );
238 - update_option( 'basic_auth_plugin_username', $username );
239 - update_option( 'basic_auth_plugin_admin_log_enable', $log_enable );
240 - update_option( 'basic_auth_plugin_alert_enable', $alert_enable );
241 -
242 - if ( ! empty( $password ) ) {
243 - $hashed_password = wp_hash_password( $password );
244 - update_option( 'basic_auth_plugin_password', $hashed_password );
245 - }
302 + $logs = get_option( 'basic_auth_failure_logs', null );
303 + if ( null !== $logs ) {
304 + update_option( 'basic_auth_failure_logs', $logs, false );
246 305 }
306 +
307 + update_option( 'basic_auth_plugin_db_version', EASY_BASIC_AUTHENTICATION_VERSION, false );
247 308 }
248 309
249 310 public function basic_auth_action_failed_access() {
250 311 if($this->log->is_enabled()) {