@@ -106,6 +106,14 @@ function process_login_request(app $app, &$error, $conf, $module)
106106 // Maintenance mode - allow logins only when maintenance mode is off or if the user is admin
107107 if ($ app ->is_under_maintenance () && $ user ['typ ' ] != 'admin ' ) return ;
108108
109+ if ($ user ['typ ' ] == 'admin ' && !is_admin_ip_whitelisted ($ _SERVER ['REMOTE_ADDR ' ], $ conf )) {
110+ // TODO: if it's not a security risk (information disclosure) to
111+ // let the user know they are not whitelisted, then change this
112+ // error message to a more appropriate one
113+ $ error = $ app ->lng ('error_user_password_incorrect ' );
114+ return ;
115+ }
116+
109117 // User login right, so attempts can be deleted
110118 $ sql = "DELETE FROM `attempts_login` WHERE `ip`=? " ;
111119 $ app ->db ->query ($ sql , $ ip );
@@ -189,6 +197,54 @@ function process_login_request(app $app, &$error, $conf, $module)
189197 }
190198}
191199
200+ /**
201+ * Checks if the given admin's IP address is whitelisted.
202+ * @param string $ip
203+ * @return bool
204+ */
205+ function is_admin_ip_whitelisted ($ ip , $ conf )
206+ {
207+ // if there is no config value, we assume that webmaster doesn't use this feature
208+ if (!isset ($ conf ['admin_ip_whitelist_file ' ])) return true ;
209+
210+ // if the file doesn't exist, we assume that webmaster doesn't use this feature
211+ if (!file_exists ($ conf ['admin_ip_whitelist_file ' ])) return true ;
212+
213+ $ file_content = file_get_contents ($ conf ['admin_ip_whitelist_file ' ]);
214+ $ file_lines = explode ("\n" , $ file_content );
215+
216+ $ matches = array_filter ($ file_lines , function ($ v ) use ($ ip ) {
217+ $ line = trim ($ v );
218+
219+ // exclude empty lines and comments
220+ if ($ line === '' || $ line [0 ] === '# ' ) return false ;
221+
222+ return ip_matches_cidr ($ ip , $ line );
223+ });
224+
225+ return count ($ matches ) > 0 ;
226+ }
227+
228+ // based on https://www.php.net/manual/en/ref.network.php (comments)
229+ /**
230+ * Checks if the given IP address matches the given CIDR.
231+ * @param $ip
232+ * @param $cidr
233+
234+ * @return bool
235+ */
236+ function ip_matches_cidr ($ ip , $ cidr ) {
237+ list ($ net , $ mask ) = explode ('/ ' , $ cidr );
238+ if (!$ mask ) $ mask = 32 ;
239+
240+ $ ip_net = ip2long ($ net );
241+ $ ip_mask = ~((1 << (32 - $ mask )) - 1 );
242+
243+ $ ip_ip = ip2long ($ ip );
244+
245+ return (($ ip_ip & $ ip_mask ) == ($ ip_net & $ ip_mask ));
246+ }
247+
192248/**
193249 * Validates user credentials and fetches the user if validation succeeded
194250 * @param app $app
0 commit comments