diff --git a/auth.php b/auth.php index ae009f3..04502ef 100644 --- a/auth.php +++ b/auth.php @@ -57,6 +57,7 @@ class auth_plugin_userkey extends auth_plugin_base { 'mappingfield' => self::DEFAULT_MAPPING_FIELD, 'keylifetime' => 60, 'iprestriction' => 0, + 'ipwhitelist' => '', 'redirecturl' => '', 'ssourl' => '', 'createuser' => false, diff --git a/classes/core_userkey_manager.php b/classes/core_userkey_manager.php index 8d897b8..c302cb9 100644 --- a/classes/core_userkey_manager.php +++ b/classes/core_userkey_manager.php @@ -124,8 +124,28 @@ class core_userkey_manager implements userkey_manager_interface { if ($key->iprestriction) { $remoteaddr = getremoteaddr(null); - if (empty($remoteaddr) or !address_in_subnet($remoteaddr, $key->iprestriction)) { - print_error('ipmismatch'); + + if (isset($this->config->ipwhitelist)) { + $whitelist = $this->config->ipwhitelist; + } else { + $whitelist = false; + } + + if (empty($remoteaddr) ) { + print_error('noip', 'auth_userkey'); + } else if (!empty($whitelist)) { + $ips = explode(';', $whitelist); + $whitelisted = false; + foreach ($ips as $ip) { + if (address_in_subnet($remoteaddr, $ip)) { + $whitelisted = true; + } + } + if (!$whitelisted) { + print_error('ipmismatch', 'error', '', null, "Remote address: $remoteaddr\nKey IP: $key->iprestriction"); + } + } else if (!address_in_subnet($remoteaddr, $key->iprestriction)) { + print_error('ipmismatch', 'error', '', null, "Remote address: $remoteaddr\nKey IP: $key->iprestriction"); } } diff --git a/lang/en/auth_userkey.php b/lang/en/auth_userkey.php index cf3e55b..ab5db6f 100644 --- a/lang/en/auth_userkey.php +++ b/lang/en/auth_userkey.php @@ -29,6 +29,10 @@ $string['mappingfield_desc'] = 'This user field will be used to find relevant us $string['iprestriction'] = 'IP restriction'; $string['iprestriction_desc'] = 'If enabled, a web call has to contain "ip" parameter when requesting login URL. A user has to have provided IP to be able to use a key to login to LMS.'; +$string['ipwhitelist'] = 'Whitelist IP ranges'; +$string['ipwhitelist_desc'] = "Ignore IP restrictions if the IP address the token was issued for or the login attempt comes from falls within any of these ranges. +\nThis can happen when some users reach Moodle or the system issuing login tokens via a private network or DMZ. +\nIf the route to either the system issuing tokens or this Moodle is via a private address range then set this value to 10.0.0.0/8;172.16.0.0/12;192.168.0.0/16"; $string['keylifetime'] = 'User key life time'; $string['keylifetime_desc'] = 'Life time in seconds of the each user login key.'; $string['incorrectkeylifetime'] = 'User key life time should be a number'; diff --git a/settings.html b/settings.html index 8e95618..d82c526 100644 --- a/settings.html +++ b/settings.html @@ -49,6 +49,13 @@ $fields = get_auth_plugin('userkey')->get_allowed_mapping_fields(); notification($err[$field], 'notifyfailure'); } ?> + + + +
+ notification($err[$field], 'notifyfailure'); } ?> + + diff --git a/tests/auth_plugin_test.php b/tests/auth_plugin_test.php index bde01e7..dfe7985 100644 --- a/tests/auth_plugin_test.php +++ b/tests/auth_plugin_test.php @@ -756,6 +756,7 @@ class auth_plugin_userkey_testcase extends advanced_testcase { $formconfig->mappingfield = 'email'; $formconfig->keylifetime = 100; $formconfig->iprestriction = 0; + $formconfig->ipwhitelist = ''; $formconfig->redirecturl = 'http://google.com/'; $formconfig->ssourl = 'http://google.com/'; $formconfig->createuser = false; @@ -842,6 +843,32 @@ class auth_plugin_userkey_testcase extends advanced_testcase { $this->auth->user_login_userkey(); } + /** + * Test that IP address mismatch exception gets thrown if incorrect IP and outside whitelist. + * + * @expectedException moodle_exception + * @expectedExceptionMessage Client IP address mismatch + */ + public function test_ipmismatch_exception_thrown_if_ip_is_outside_whitelist() { + global $DB; + + set_config('ipwhitelist', '10.0.0.0/8;172.16.0.0/12;192.168.0.0/16', 'auth_userkey'); + + $key = new stdClass(); + $key->value = 'IpmismatchKey'; + $key->script = 'auth/userkey'; + $key->userid = $this->user->id; + $key->instance = $this->user->id; + $key->iprestriction = '192.161.1.1'; + $key->validuntil = time() + 300; + $key->timecreated = time(); + $DB->insert_record('user_private_key', $key); + + $_POST['key'] = 'IpmismatchKey'; + $_SERVER['HTTP_CLIENT_IP'] = '192.161.1.2'; + $this->auth->user_login_userkey(); + } + /** * Test that IP address mismatch exception gets thrown if user id is incorrect. * diff --git a/tests/core_userkey_manager_test.php b/tests/core_userkey_manager_test.php index 97e3f93..14fb4bc 100644 --- a/tests/core_userkey_manager_test.php +++ b/tests/core_userkey_manager_test.php @@ -149,6 +149,45 @@ class core_userkey_manager_testcase extends advanced_testcase { $this->assertEquals(time() + 60, $actualkey->validuntil); } + /** + * Test that IP address mismatch exception gets thrown if incorrect IP and outside whitelist. + * + * @expectedException moodle_exception + * @expectedExceptionMessage Client IP address mismatch + */ + public function test_exception_if_ip_is_outside_whitelist() { + global $DB; + + $this->config->iprestriction = true; + $this->config->ipwhitelist = '10.0.0.0/8;172.16.0.0/12;192.168.0.0/16'; + + $manager = new core_userkey_manager($this->config); + $value = $manager->create_key($this->user->id, '193.168.1.1'); + + $_SERVER['HTTP_CLIENT_IP'] = '193.168.1.2'; + + $manager->validate_key($value); + } + + /** + * Test that key is accepted if incorrect IP and within whitelist. + */ + public function test_create_correct_key_if_ip_is_whitelisted() { + global $DB; + + $this->config->iprestriction = true; + + $this->config->ipwhitelist = '10.0.0.0/8;172.16.0.0/12;192.168.0.0/16'; + + $manager = new core_userkey_manager($this->config); + $value = $manager->create_key($this->user->id, '192.168.1.1'); + + $_SERVER['HTTP_CLIENT_IP'] = '192.168.1.2'; + + $key = $manager->validate_key($value); + $this->assertEquals($this->user->id, $key->userid); + } + /** * Test that key gets created correctly if config option iprestriction is set to false and we set allowedips. */