Skip to content

Commit

Permalink
MDL-48766 iplookup: Update to geoip2 db to support ipv6
Browse files Browse the repository at this point in the history
The previous maxmind geoip database is now legacy and the GeoIP2
version supports ipv6.
  • Loading branch information
danpoltawski committed Sep 19, 2016
1 parent eacc36a commit 3fad0f1
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 53 deletions.
3 changes: 2 additions & 1 deletion admin/settings/location.php
Expand Up @@ -10,7 +10,8 @@
$temp->add(new admin_setting_configtext('defaultcity', new lang_string('defaultcity', 'admin'), new lang_string('defaultcity_help', 'admin'), ''));

$temp->add(new admin_setting_heading('iplookup', new lang_string('iplookup', 'admin'), new lang_string('iplookupinfo', 'admin')));
$temp->add(new admin_setting_configfile('geoipfile', new lang_string('geoipfile', 'admin'), new lang_string('configgeoipfile', 'admin', $CFG->dataroot.'/geoip/'), $CFG->dataroot.'/geoip/GeoLiteCity.dat'));
$temp->add(new admin_setting_configfile('geoip2file', new lang_string('geoipfile', 'admin'),
new lang_string('configgeoipfile', 'admin', $CFG->dataroot.'/geoip/'), $CFG->dataroot.'/geoip/GeoLite2-City.mmdb'));
$temp->add(new admin_setting_configtext('googlemapkey3', new lang_string('googlemapkey3', 'admin'), new lang_string('googlemapkey3_help', 'admin'), '', PARAM_RAW, 60));

$temp->add(new admin_setting_configtext('allcountrycodes', new lang_string('allcountrycodes', 'admin'), new lang_string('configallcountrycodes', 'admin'), '', '/^(?:\w+(?:,\w+)*)?$/'));
Expand Down
10 changes: 3 additions & 7 deletions iplookup/index.php
Expand Up @@ -33,7 +33,7 @@
throw new require_login_exception('Guests are not allowed here.');
}

$ip = optional_param('ip', getremoteaddr(), PARAM_HOST);
$ip = optional_param('ip', getremoteaddr(), PARAM_RAW);
$user = optional_param('user', 0, PARAM_INT);

if (isset($CFG->iplookup)) {
Expand All @@ -48,15 +48,11 @@
$info = array($ip);
$note = array();

if (!preg_match('/(^\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/', $ip, $match)) {
if (cleanremoteaddr($ip) === false) {
print_error('invalidipformat', 'error');
}

if ($match[1] > 255 or $match[2] > 255 or $match[3] > 255 or $match[4] > 255) {
print_error('invalidipformat', 'error');
}

if ($match[1] == '127' or $match[1] == '10' or ($match[1] == '172' and $match[2] >= '16' and $match[2] <= '31') or ($match[1] == '192' and $match[2] == '168')) {
if (!ip_is_public($ip)) {
print_error('iplookupprivate', 'error');
}

Expand Down
49 changes: 23 additions & 26 deletions iplookup/lib.php
Expand Up @@ -36,46 +36,43 @@ function iplookup_find_location($ip) {

$info = array('city'=>null, 'country'=>null, 'longitude'=>null, 'latitude'=>null, 'error'=>null, 'note'=>'', 'title'=>array());

if (!empty($CFG->geoipfile) and file_exists($CFG->geoipfile)) {
require_once('Net/GeoIP.php');
if (!empty($CFG->geoip2file) and file_exists($CFG->geoip2file)) {
$reader = new GeoIp2\Database\Reader($CFG->geoip2file);
$record = $reader->city($ip);

$geoip = Net_GeoIP::getInstance($CFG->geoipfile, Net_GeoIP::STANDARD);
$location = $geoip->lookupLocation($ip);
$geoip->close();

if (empty($location)) {
if (empty($record)) {
$info['error'] = get_string('iplookupfailed', 'error', $ip);
return $info;
}
if (!empty($location->city)) {
$info['city'] = core_text::convert($location->city, 'iso-8859-1', 'utf-8');
$info['title'][] = $info['city'];
}

if (!empty($location->countryCode)) {
$countries = get_string_manager()->get_list_of_countries(true);
if (isset($countries[$location->countryCode])) {
// prefer our localized country names
$info['country'] = $countries[$location->countryCode];
} else {
$info['country'] = $location->countryName;
}
$info['title'][] = $info['country'];

} else if (!empty($location->countryName)) {
$info['country'] = $location->countryName;
$info['title'][] = $info['country'];
$info['city'] = core_text::convert($record->city->name, 'iso-8859-1', 'utf-8');
$info['title'][] = $info['city'];

$countrycode = $record->country->isoCode;
$countries = get_string_manager()->get_list_of_countries(true);
if (isset($countries[$countrycode])) {
// Prefer our localized country names.
$info['country'] = $countries[$countrycode];
} else {
$info['country'] = $record->country->names['en'];
}
$info['title'][] = $info['country'];

$info['longitude'] = $location->longitude;
$info['latitude'] = $location->latitude;
$info['longitude'] = $record->location->longitude;
$info['latitude'] = $record->location->latitude;
$info['note'] = get_string('iplookupmaxmindnote', 'admin');

return $info;

} else {
require_once($CFG->libdir.'/filelib.php');

if (strpos($ip, ':') !== false) {
// IPv6 is not supported by geoplugin.net.
$info['error'] = get_string('invalidipformat', 'error');
return $info;
}

$ipdata = download_file_content('http://www.geoplugin.net/json.gp?ip='.$ip);
if ($ipdata) {
$ipdata = preg_replace('/^geoPlugin\((.*)\)\s*$/s', '$1', $ipdata);
Expand Down
51 changes: 38 additions & 13 deletions iplookup/tests/geoip_test.php
Expand Up @@ -31,45 +31,56 @@
*/
class core_iplookup_geoip_testcase extends advanced_testcase {

public function test_geoip() {
public function setUp() {
global $CFG;
require_once("$CFG->libdir/filelib.php");
require_once("$CFG->dirroot/iplookup/lib.php");

if (!PHPUNIT_LONGTEST) {
// this may take a long time
return;
$this->markTestSkipped('PHPUNIT_LONGTEST is not defined');
}

$this->resetAfterTest();

// let's store the file somewhere
$gzfile = "$CFG->dataroot/phpunit/geoip/GeoLiteCity.dat.gz";
$gzfile = "$CFG->dataroot/phpunit/geoip/GeoLite2-City.mmdb.gz";
check_dir_exists(dirname($gzfile));
if (file_exists($gzfile) and (filemtime($gzfile) < time() - 60*60*24*30)) {
// delete file if older than 1 month
unlink($gzfile);
}

if (!file_exists($gzfile)) {
download_file_content('http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz', null, null, false, 300, 20, false, $gzfile);
download_file_content('http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.mmdb.gz',
null, null, false, 300, 20, false, $gzfile);
}

$this->assertTrue(file_exists($gzfile));

$zd = gzopen($gzfile, "r");
$contents = gzread($zd, 50000000);
gzclose($zd);
$geoipfile = str_replace('.gz', '', $gzfile);

$geoipfile = "$CFG->dataroot/geoip/GeoLiteCity.dat";
check_dir_exists(dirname($geoipfile));
$fp = fopen($geoipfile, 'w');
fwrite($fp, $contents);
fclose($fp);
// Open our files (in binary mode).
$file = gzopen($gzfile, 'rb');
$geoipfilebuf = fopen($geoipfile, 'wb');

// Keep repeating until the end of the input file.
while (!gzeof($file)) {
// Read buffer-size bytes.
// Both fwrite and gzread and binary-safe.
fwrite($geoipfilebuf, gzread($file, 4096));
}

// Files are done, close files.
fclose($geoipfilebuf);
gzclose($file);

$this->assertTrue(file_exists($geoipfile));

$CFG->geoipfile = $geoipfile;
$CFG->geoip2file = $geoipfile;
}

public function test_ipv4() {

$result = iplookup_find_location('147.230.16.1');

Expand All @@ -82,5 +93,19 @@ public function test_geoip() {
$this->assertEquals('Liberec', $result['title'][0]);
$this->assertEquals('Czech Republic', $result['title'][1]);
}

public function test_ipv6() {

$result = iplookup_find_location('2a01:8900:2:3:8c6c:c0db:3d33:9ce6');

$this->assertEquals('array', gettype($result));
$this->assertEquals('Lancaster', $result['city']);
$this->assertEquals(-2.79970, $result['longitude'], '', 0.001);
$this->assertEquals(54.04650, $result['latitude'], '', 0.001);
$this->assertNull($result['error']);
$this->assertEquals('array', gettype($result['title']));
$this->assertEquals('Lancaster', $result['title'][0]);
$this->assertEquals('United Kingdom', $result['title'][1]);
}
}

13 changes: 11 additions & 2 deletions iplookup/tests/geoplugin_test.php
Expand Up @@ -31,20 +31,22 @@
*/
class core_iplookup_geoplugin_testcase extends advanced_testcase {

public function test_geoip() {
public function setUp() {
global $CFG;
require_once("$CFG->libdir/filelib.php");
require_once("$CFG->dirroot/iplookup/lib.php");

if (!PHPUNIT_LONGTEST) {
// we do not want to DDOS their server, right?
return;
$this->markTestSkipped('PHPUNIT_LONGTEST is not defined');
}

$this->resetAfterTest();

$CFG->geoipfile = '';
}

public function test_geoip_ipv4() {
$result = iplookup_find_location('147.230.16.1');

$this->assertEquals('array', gettype($result));
Expand All @@ -56,5 +58,12 @@ public function test_geoip() {
$this->assertEquals('Liberec', $result['title'][0]);
$this->assertEquals('Czech Republic', $result['title'][1]);
}

public function test_geoip_ipv6() {
$result = iplookup_find_location('2a01:8900:2:3:8c6c:c0db:3d33:9ce6');

$this->assertNotNull($result['error']);
$this->assertEquals($result['error'], get_string('invalidipformat', 'error'));
}
}

8 changes: 4 additions & 4 deletions lang/en/admin.php
Expand Up @@ -230,7 +230,7 @@
$string['configfullnamedisplay'] = 'This defines how names are shown when they are displayed in full. The default value, "language", leaves it to the string "fullnamedisplay" in the current language pack to decide. Some languages have different name display conventions.
For most mono-lingual sites the most efficient setting is "firstname lastname", but you may choose to hide surnames altogether. Placeholders that can be used are: firstname, lastname, firstnamephonetic, lastnamephonetic, middlename, and alternatename.';
$string['configgeoipfile'] = 'Location of GeoIP City binary data file. This file is not part of Moodle distribution and must be obtained separately from <a href="http://www.maxmind.com/">MaxMind</a>. You can either buy a commercial version or use the free version. Simply download <a href="http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz" >http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz</a> and extract it into "{$a}" directory on your server.';
$string['configgeoipfile'] = 'Location of GeoLite2 City binary data file. This file is not part of Moodle distribution and must be obtained separately from <a href="http://www.maxmind.com/">MaxMind</a>. You can either buy a commercial version or use the free version. Simply download <a href="http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.mmdb.gz" >http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.mmdb.gz</a> and extract it into "{$a}" directory on your server.';
$string['configgetremoteaddrconf'] = 'If your server is behind a reverse proxy, you can use this setting to specify which HTTP headers can be trusted to contain the remote IP address. The headers are read in order, using the first one that is available.';
$string['configgradebookroles'] = 'This setting allows you to control who appears on the gradebook. Users need to have at least one of these roles in a course to be shown in the gradebook for that course.';
$string['configgradeexport'] = 'Choose which gradebook export formats are your primary methods for exporting grades. Chosen plugins will then set and use a "last exported" field for every grade. For example, this might result in exported records being identified as being "new" or "updated". If you are not sure about this then leave everything unchecked.';
Expand Down Expand Up @@ -540,7 +540,7 @@
$string['fullnamedisplayprivate'] = 'Full name format - private';
$string['gdrequired'] = 'The GD extension is now required by Moodle for image conversion.';
$string['generalsettings'] = 'General settings';
$string['geoipfile'] = 'GeoIP city data file';
$string['geoipfile'] = 'GeoLite2 City MaxMind DB';
$string['getremoteaddrconf'] = 'Logged IP address source';
$string['globalsearch'] = 'Global search';
$string['globalsearchmanage'] = 'Manage global search';
Expand Down Expand Up @@ -598,9 +598,9 @@
$string['iplookup'] = 'IP address lookup';
$string['iplookupgeoplugin'] = '<a href="http://www.geoplugin.com">geoPlugin</a> service is currently being used to look up geographical information. For more accurate results we recommend installing a local copy of the MaxMind GeoLite database.';
$string['iplookupinfo'] = 'By default Moodle uses the free online NetGeo (The Internet Geographic Database) server to lookup location of IP addresses, unfortunately this database is not maintained anymore and may return <em>wildly incorrect</em> data.
It is recommended to install local copy of free GeoLite City database from MaxMind.<br />
It is recommended to install local copy of free GeoLite2 City database from MaxMind.<br />
IP address location is displayed on simple map or using Google Maps. Please note that you need to have a Google account and apply for free Google Maps API key to enable interactive maps.';
$string['iplookupmaxmindnote'] = 'This product includes GeoLite data created by MaxMind, available from <a href="http://www.maxmind.com/">http://www.maxmind.com/</a>.';
$string['iplookupmaxmindnote'] = 'This product includes GeoLite2 data created by MaxMind, available from <a href="http://www.maxmind.com">http://www.maxmind.com</a>.';
$string['keeptagnamecase'] = 'Keep tag name casing';
$string['lang'] = 'Default language';
$string['langcache'] = 'Cache language menu';
Expand Down

0 comments on commit 3fad0f1

Please sign in to comment.