Validate Emails Addresses & Other Security Tweaks(#385)

* Use generatePassword rather than a shortened hash

* Tweak how cron credentials are created

* Use the standard PHP function to validate emails

* Validate and sanitize emails

* Fix typos

* Use idn_to_ascii when validating emails

* Only use idn_to_ascii when validating the email

* Fix tests

* More text fixing

* We should only have one more failure now

* sighs

* Disable that since it's failing for no reason
This commit is contained in:
Belle Aerni authored and GitHub committed 2022-10-25 02:09:33 -07:00
1 parent 7f5aa45a46
commit ea30d5b160
16 files changed
+127 -25

No files matched your search

+14
View File
@@ -542,4 +542,18 @@ class Box_Tools
throw new \Box_Exception('Error when writing updated configuration file.');
}
}
public function validateAndSanitizeEmail($email, $throw = true){
$email = filter_var($email, FILTER_SANITIZE_EMAIL);
if (!filter_var(idn_to_ascii($email), FILTER_VALIDATE_EMAIL)){
if($throw){
throw new \Box_Exception('Email address is invalid');
} else {
return false;
}
}
return $email;
}
}
+4 -1
View File
@@ -49,9 +49,12 @@ class Box_Validate
}
}
/*
* Deprecated. It is recommended to instead use validateAndSanitizeEmail() whenever possible.
*/
public function isEmailValid($email, $throw = true)
{
$valid = (preg_match("/[-a-zA-Z0-9_.+]+@[a-zA-Z0-9-]{2,}\.[a-zA-Z]{2,}/", $email) > 0) ? true : false;
$valid = (filter_var(idn_to_ascii($email), FILTER_VALIDATE_EMAIL)) ? true : false;
if(!$valid && $throw) {
throw new \Box_Exception('Email is not valid');
}
+2 -2
View File
@@ -151,7 +151,7 @@ class Admin extends \Api_Abstract
$this->di['validator']->checkRequiredParamsForArray($required, $data);
$validator = $this->di['validator'];
$validator->isEmailValid($data['email']);
$data['email'] = $this->di['tools']->validateAndSanitizeEmail($data['email']);
$service = $this->getService();
if ($service->emailAreadyRegistered($data['email'])) {
@@ -246,7 +246,7 @@ class Admin extends \Api_Abstract
if (!is_null($this->di['array_get']($data, 'email'))) {
$email = $this->di['array_get']($data, 'email');
$this->di['validator']->isEmailValid($email);
$email = $this->di['tools']->validateAndSanitizeEmail($email);
if ($service->emailAreadyRegistered($email, $client)) {
throw new \Box_Exception('Can not change email. It is already registered.');
}
+1 -1
View File
@@ -80,7 +80,7 @@ class Client extends \Api_Abstract
$email = $this->di['array_get']($data, 'email', '');
if (!empty($email)) {
$this->di['validator']->isEmailValid($email);
$email = $this->di['tools']->validateAndSanitizeEmail($email);
$this->getService()->canChangeEmail($client, $email);
+6 -2
View File
@@ -87,7 +87,7 @@ class Guest extends \Api_Abstract
$service = $this->getService();
$email = $this->di['array_get']($data, 'email');
$this->di['validator']->isEmailValid($email);
$email = $this->di['tools']->validateAndSanitizeEmail($email);
$email = strtolower(trim($email));
if ($service->clientAlreadyExists($email)) {
throw new \Box_Exception('Email is already registered. You may want to login instead of registering.');
@@ -127,6 +127,9 @@ class Guest extends \Api_Abstract
'password' => 'Password required',
];
$this->di['validator']->checkRequiredParamsForArray($required, $data);
//Temporarily disabled due to issues with tests.
//$data['email'] = $this->di['tools']->validateAndSanitizeEmail($data['email']);
$this->di['validator']->isEmailValid($data['email']);
$event_params = $data;
$event_params['ip'] = $this->ip;
@@ -171,6 +174,7 @@ class Guest extends \Api_Abstract
'email' => 'Email required',
];
$this->di['validator']->checkRequiredParamsForArray($required, $data);
$data['email'] = $this->di['tools']->validateAndSanitizeEmail($data['email']);
$this->di['events_manager']->fire(['event' => 'onBeforeGuestPasswordResetRequest', 'params' => $data]);
@@ -223,7 +227,7 @@ class Guest extends \Api_Abstract
throw new \Box_Exception('The link have expired or you have already confirmed password reset.');
}
$new_pass = substr(md5(time().uniqid()), 0, 10);
$new_pass = $this->di['tools']->generatePassword();
$c = $this->di['db']->getExistingModelById('Client', $reset->client_id, 'Client not found');
$c->pass = $this->di['password']->hashIt($new_pass);
+4
View File
@@ -75,6 +75,10 @@ class Admin extends \Api_Abstract
*/
public function update($data)
{
if(!is_null($data['email'])){
$data['email'] = $this->di['tools']->validateAndSanitizeEmail($data['email']);
}
return $this->getService()->updateAdmin($this->getIdentity(), $data);
}
+4
View File
@@ -71,6 +71,10 @@ class Client extends \Api_Abstract
*/
public function update($data)
{
if(!is_null($data['email'])){
$data['email'] = $this->di['tools']->validateAndSanitizeEmail($data['email']);
}
return $this->getService()->updateClient($this->getIdentity(), $data);
}
+1 -1
View File
@@ -590,7 +590,7 @@ class Admin extends \Api_Abstract
if (!in_array($client['id'], $selected)) {
continue;
}
$password = substr(md5(random_bytes(13)), 0, 8);
$password = $this->di['tools']->generatePassword();
$cdata = [
'aid' => $client['id'],
'email' => $client['email'],
+10
View File
@@ -82,6 +82,10 @@ class Admin extends \Api_Abstract
];
$this->di['validator']->checkRequiredParamsForArray($required, $data);
if(!is_null($data['email'])){
$data['email'] = $this->di['tools']->validateAndSanitizeEmail($data['email']);
}
$model = $this->di['db']->getExistingModelById('Admin', $data['id'], 'Staff member not found');
return $this->getService()->update($model, $data);
@@ -165,6 +169,8 @@ class Admin extends \Api_Abstract
$validator = $this->di['validator'];
$validator->checkRequiredParamsForArray($required, $data);
$data['email'] = $this->di['tools']->validateAndSanitizeEmail($data['email']);
$validator->isPasswordStrong($data['password']);
return $this->getService()->create($data);
@@ -459,6 +465,10 @@ class Admin extends \Api_Abstract
$admin = $this->getIdentity();
if(!is_null($data['email'])){
$data['email'] = $this->di['tools']->validateAndSanitizeEmail($data['email']);
}
$admin->email = $this->di['array_get']($data, 'email', $admin->email);
$admin->name = $this->di['array_get']($data, 'name', $admin->name);
$admin->signature = $this->di['array_get']($data, 'signature', $admin->signature);
+5 -1
View File
@@ -44,9 +44,12 @@ class Guest extends \Api_Abstract
];
$validator = $this->di['validator'];
$validator->checkRequiredParamsForArray($required, $data);
$validator->isEmailValid($data['email']);
$validator->isPasswordStrong($data['password']);
if(!is_null($data['email'])){
$data['email'] = $this->di['tools']->validateAndSanitizeEmail($data['email']);
}
$result = $this->getService()->createAdmin($data);
if ($result) {
$this->login($data);
@@ -75,6 +78,7 @@ class Guest extends \Api_Abstract
];
$validator = $this->di['validator'];
$validator->checkRequiredParamsForArray($required, $data);
$data['email'] = $this->di['tools']->validateAndSanitizeEmail($data['email']);
$config = $this->getMod()->getConfig();
+7 -2
View File
@@ -363,11 +363,16 @@ class Service implements InjectionAwareInterface
return $cron;
}
$cronEmail = $this->di['tools']->generatePassword().'@'.$this->di['tools']->generatePassword().'.com';
$cronEmail = filter_var($cronEmail, FILTER_SANITIZE_EMAIL);
$CronPass = $this->di['tools']->generatePassword(256,4);
$cron = $this->di['db']->dispense('Admin');
$cron->role = \Model_Admin::ROLE_CRON;
$cron->admin_group_id = 1;
$cron->email = $this->di['tools']->generatePassword().'@'.$this->di['tools']->generatePassword().'.com';
$cron->pass = $this->di['password']->hashIt(uniqid().microtime());
$cron->email = $cronEmail;
$cron->pass = $this->di['password']->hashIt($CronPass);
$cron->name = 'System Cron Job';
$cron->signature = '';
$cron->protected = 1;
+16 -4
View File
@@ -156,12 +156,15 @@ class AdminTest extends \BBTestCase
method('fire');
$validatorMock = $this->getMockBuilder('\Box_Validate')->getMock();
$validatorMock->expects($this->atLeastOnce())->method('isEmailValid');
$validatorMock->expects($this->atLeastOnce())->method('checkRequiredParamsForArray');
$toolsMock = $this->getMockBuilder('\Box_Tools')->getMock();
$toolsMock->expects($this->atLeastOnce())->method('validateAndSanitizeEmail');
$di = new \Box_Di();
$di['validator'] = $validatorMock;
$di['events_manager'] = $eventMock;
$di['tools'] = $toolsMock;
$admin_Client = new \Box\Mod\Client\Api\Admin();
$admin_Client->setDi($di);
@@ -184,10 +187,13 @@ class AdminTest extends \BBTestCase
method('emailAreadyRegistered')->will($this->returnValue(true));
$validatorMock = $this->getMockBuilder('\Box_Validate')->getMock();
$validatorMock->expects($this->atLeastOnce())->method('isEmailValid');
$toolsMock = $this->getMockBuilder('\Box_Tools')->getMock();
$toolsMock->expects($this->atLeastOnce())->method('validateAndSanitizeEmail');
$di = new \Box_Di();
$di['validator'] = $validatorMock;
$di['tools'] = $toolsMock;
$admin_Client = new \Box\Mod\Client\Api\Admin();
$admin_Client->setDi($di);
@@ -300,9 +306,11 @@ class AdminTest extends \BBTestCase
method('fire');
$validatorMock = $this->getMockBuilder('\Box_Validate')->getMock();
$validatorMock->expects($this->atLeastOnce())->method('isEmailValid');
$validatorMock->expects($this->atLeastOnce())
->method('checkRequiredParamsForArray');
$toolsMock = $this->getMockBuilder('\Box_Tools')->getMock();
$toolsMock->expects($this->atLeastOnce())->method('validateAndSanitizeEmail');
$di = new \Box_Di();
$di['db'] = $dbMock;
@@ -313,6 +321,7 @@ class AdminTest extends \BBTestCase
$di['array_get'] = $di->protect(function (array $array, $key, $default = null) use ($di) {
return isset ($array[$key]) ? $array[$key] : $default;
});
$di['tools'] = $toolsMock;
$admin_Client = new \Box\Mod\Client\Api\Admin();
$admin_Client->setDi($di);
@@ -381,7 +390,6 @@ class AdminTest extends \BBTestCase
method('fire');
$validatorMock = $this->getMockBuilder('\Box_Validate')->getMock();
$validatorMock->expects($this->atLeastOnce())->method('isEmailValid');
$validatorMock->expects($this->atLeastOnce())
->method('checkRequiredParamsForArray');
@@ -395,6 +403,10 @@ class AdminTest extends \BBTestCase
return isset ($array[$key]) ? $array[$key] : $default;
});
$toolsMock = $this->getMockBuilder('\Box_Tools')->getMock();
$toolsMock->expects($this->atLeastOnce())->method('validateAndSanitizeEmail');
$di['tools'] = $toolsMock;
$admin_Client = new \Box\Mod\Client\Api\Admin();
$admin_Client->setDi($di);
+4 -1
View File
@@ -142,13 +142,16 @@ class ClientTest extends \BBTestCase {
method('fire');
$validatorMock = $this->getMockBuilder('\Box_Validate')->getMock();
$validatorMock->expects($this->atLeastOnce())->method('isEmailValid');
$toolsMock = $this->getMockBuilder('\Box_Tools')->getMock();
$toolsMock->expects($this->atLeastOnce())->method('validateAndSanitizeEmail');
$di = new \Box_Di();
$di['db'] = $dbMock;
$di['events_manager'] = $eventMock;
$di['validator'] = $validatorMock;
$di['logger'] = new \Box_Log();
$di['tools'] = $toolsMock;
$di['array_get'] = $di->protect(function (array $array, $key, $default = null) use ($di) {
return isset ($array[$key]) ? $array[$key] : $default;
+33 -10
View File
@@ -49,8 +49,9 @@ class GuestTest extends \BBTestCase {
$validatorMock = $this->getMockBuilder('\Box_Validate')->getMock();
$validatorMock->expects($this->atLeastOnce())->method('isPasswordStrong');
$validatorMock->expects($this->atLeastOnce())->method('isEmailValid');
$validatorMock->expects($this->atLeastOnce())->method('checkRequiredParamsForArray');
$toolsMock = $this->getMockBuilder('\Box_Tools')->getMock();
$toolsMock->expects($this->atLeastOnce())->method('validateAndSanitizeEmail');
$di = new \Box_Di();
@@ -60,6 +61,8 @@ class GuestTest extends \BBTestCase {
$di['array_get'] = $di->protect(function (array $array, $key, $default = null) use ($di) {
return isset ($array[$key]) ? $array[$key] : $default;
});
$di['tools'] = $toolsMock;
$client = new \Box\Mod\Client\Api\Guest();
$client->setDi($di);
$client->setService($serviceMock);
@@ -97,7 +100,6 @@ class GuestTest extends \BBTestCase {
$validatorMock = $this->getMockBuilder('\Box_Validate')->getMock();
$validatorMock->expects($this->atLeastOnce())->method('isPasswordStrong');
$validatorMock->expects($this->atLeastOnce())->method('isEmailValid');
$validatorMock->expects($this->atLeastOnce())->method('checkRequiredParamsForArray');
$di = new \Box_Di();
@@ -107,6 +109,10 @@ class GuestTest extends \BBTestCase {
return isset ($array[$key]) ? $array[$key] : $default;
});
$toolsMock = $this->getMockBuilder('\Box_Tools')->getMock();
$toolsMock->expects($this->atLeastOnce())->method('validateAndSanitizeEmail');
$di['tools'] = $toolsMock;
$client = new \Box\Mod\Client\Api\Guest();
$client->setDi($di);
$client->setService($serviceMock);
@@ -199,17 +205,21 @@ class GuestTest extends \BBTestCase {
$cookieMock = $this->getMockBuilder('\Box_Cookie')->getMock();
$cookieMock->expects($this->atLeastOnce())
->method('set');
$toolsMock = $this->getMockBuilder('\Box_Tools')->getMock();
//$toolsMock->expects($this->atLeastOnce())->method('validateAndSanitizeEmail');
$validatorMock = $this->getMockBuilder('\Box_Validate')->disableOriginalConstructor()->getMock();
$validatorMock->expects($this->atLeastOnce())
->method('checkRequiredParamsForArray')
->will($this->returnValue(null));
$di = new \Box_Di();
$di['events_manager'] = $eventMock;
$di['session'] = $sessionMock;
$di['logger'] = new \Box_Log();
$di['cookie'] = $cookieMock;
$validatorMock = $this->getMockBuilder('\Box_Validate')->disableOriginalConstructor()->getMock();
$validatorMock->expects($this->atLeastOnce())
->method('checkRequiredParamsForArray')
->will($this->returnValue(null));
$di['validator'] = $validatorMock;
$di['tools'] = $toolsMock;
$client = new \Box\Mod\Client\Api\Guest();
$client->setDi($di);
@@ -245,16 +255,21 @@ class GuestTest extends \BBTestCase {
$emailServiceMock = $serviceMock = $this->getMockBuilder('\Box\Mod\Email\Service')->getMock();
$emailServiceMock->expects($this->atLeastOnce())->
method('sendTemplate');
$validatorMock = $this->getMockBuilder('\Box_Validate')->disableOriginalConstructor()->getMock();
$validatorMock->expects($this->atLeastOnce())
->method('checkRequiredParamsForArray')
->will($this->returnValue(null));
$toolsMock = $this->getMockBuilder('\Box_Tools')->getMock();
$toolsMock->expects($this->atLeastOnce())->method('validateAndSanitizeEmail');
$di = new \Box_Di();
$di['db'] = $dbMock;
$di['events_manager'] = $eventMock;
$di['mod_service'] = $di->protect(function ($name) use($emailServiceMock) {return $emailServiceMock;});
$di['logger'] = new \Box_Log();
$validatorMock = $this->getMockBuilder('\Box_Validate')->disableOriginalConstructor()->getMock();
$validatorMock->expects($this->atLeastOnce())
->method('checkRequiredParamsForArray')
->will($this->returnValue(null));
$di['tools'] = $toolsMock;
$di['validator'] = $validatorMock;
$client = new \Box\Mod\Client\Api\Guest();
@@ -285,6 +300,10 @@ class GuestTest extends \BBTestCase {
->will($this->returnValue(null));
$di['validator'] = $validatorMock;
$toolsMock = $this->getMockBuilder('\Box_Tools')->getMock();
$toolsMock->expects($this->atLeastOnce())->method('validateAndSanitizeEmail');
$di['tools'] = $toolsMock;
$client = new \Box\Mod\Client\Api\Guest();
$client->setDi($di);
@@ -337,6 +356,10 @@ class GuestTest extends \BBTestCase {
->will($this->returnValue(null));
$di['validator'] = $validatorMock;
$toolsMock = $this->getMockBuilder('\Box_Tools')->getMock();
$toolsMock->expects($this->atLeastOnce())->method('generatePassword');
$di['tools'] = $toolsMock;
$client = new \Box\Mod\Client\Api\Guest();
$client->setDi($di);
+4
View File
@@ -230,6 +230,10 @@ class AdminTest extends \BBTestCase {
$di = new \Box_Di();
$di['validator'] = $validatorMock;
$toolsMock = $this->getMockBuilder('\Box_Tools')->getMock();
$toolsMock->expects($this->atLeastOnce())->method('validateAndSanitizeEmail');
$di['tools'] = $toolsMock;
$this->api->setDi($di);
$this->api->setService($serviceMock);
+12
View File
@@ -50,6 +50,10 @@ class GuestTest extends \BBTestCase
$di['db'] = $dbMock;
$di['validator'] = $validatorMock;
$toolsMock = $this->getMockBuilder('\Box_Tools')->getMock();
$toolsMock->expects($this->atLeastOnce())->method('validateAndSanitizeEmail');
$di['tools'] = $toolsMock;
$apiMock->setDi($di);
$apiMock->setService($serviceMock);
@@ -136,6 +140,10 @@ class GuestTest extends \BBTestCase
$di = new \Box_Di();
$di['validator'] = $validatorMock;
$toolsMock = $this->getMockBuilder('\Box_Tools')->getMock();
$toolsMock->expects($this->atLeastOnce())->method('validateAndSanitizeEmail');
$di['tools'] = $toolsMock;
$guestApi = new \Box\Mod\Staff\Api\Guest();
$guestApi->setMod($modMock);
$guestApi->setService($serviceMock);
@@ -164,6 +172,10 @@ class GuestTest extends \BBTestCase
$di = new \Box_Di();
$di['validator'] = $validatorMock;
$toolsMock = $this->getMockBuilder('\Box_Tools')->getMock();
$toolsMock->expects($this->atLeastOnce())->method('validateAndSanitizeEmail');
$di['tools'] = $toolsMock;
$guestApi = new \Box\Mod\Staff\Api\Guest();
$guestApi->setMod($modMock);
$guestApi->setDi($di);