mirror of
https://we.phorge.it/source/phorge.git
synced 2025-02-18 01:38:39 +01:00
Use OpaqueEnvelopes for all passwords in Phabricator
Summary: See D2991 / T1526. Two major changes here: - PHP just straight-up logs passwords on ldap_bind() failures. Suppress that with "@" and keep them out of DarkConsole by enabling discard mode. - Use PhutilOpaqueEnvelope whenever we send a password into a call stack. Test Plan: - Created a new account. - Reset password. - Changed password. - Logged in with valid password. - Tried to login with bad password. - Changed password via accountadmin. - Hit various LDAP errors and made sure nothing appears in the logs. Reviewers: vrana, btrahan Reviewed By: vrana CC: aran Differential Revision: https://secure.phabricator.com/D2993
This commit is contained in:
parent
ae2e73ce80
commit
dd70c59465
8 changed files with 50 additions and 31 deletions
|
@ -166,7 +166,8 @@ $user->openTransaction();
|
||||||
$editor->makeAdminUser($user, $set_admin);
|
$editor->makeAdminUser($user, $set_admin);
|
||||||
|
|
||||||
if ($changed_pass !== false) {
|
if ($changed_pass !== false) {
|
||||||
$editor->changePassword($user, $changed_pass);
|
$envelope = new PhutilOpaqueEnvelope($changed_pass);
|
||||||
|
$editor->changePassword($user, $envelope);
|
||||||
}
|
}
|
||||||
|
|
||||||
$user->saveTransaction();
|
$user->saveTransaction();
|
||||||
|
|
|
@ -29,6 +29,10 @@ final class DarkConsoleErrorLogPluginAPI {
|
||||||
self::$discardMode = true;
|
self::$discardMode = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function disableDiscardMode() {
|
||||||
|
self::$discardMode = false;
|
||||||
|
}
|
||||||
|
|
||||||
public static function getErrors() {
|
public static function getErrors() {
|
||||||
return self::$errors;
|
return self::$errors;
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,9 +37,8 @@ final class PhabricatorLDAPLoginController extends PhabricatorAuthController {
|
||||||
|
|
||||||
if ($request->isFormPost()) {
|
if ($request->isFormPost()) {
|
||||||
try {
|
try {
|
||||||
$this->provider->auth($request->getStr('username'),
|
$envelope = new PhutilOpaqueEnvelope($request->getStr('password'));
|
||||||
$request->getStr('password'));
|
$this->provider->auth($request->getStr('username'), $envelope);
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
$errors[] = $e->getMessage();
|
$errors[] = $e->getMessage();
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,7 +119,10 @@ final class PhabricatorLoginController
|
||||||
if (!$errors) {
|
if (!$errors) {
|
||||||
// Perform username/password tests only if we didn't get rate limited
|
// Perform username/password tests only if we didn't get rate limited
|
||||||
// by the CAPTCHA.
|
// by the CAPTCHA.
|
||||||
if (!$user || !$user->comparePassword($request->getStr('password'))) {
|
|
||||||
|
$envelope = new PhutilOpaqueEnvelope($request->getStr('password'));
|
||||||
|
|
||||||
|
if (!$user || !$user->comparePassword($envelope)) {
|
||||||
$errors[] = 'Bad username/password.';
|
$errors[] = 'Bad username/password.';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,7 @@ final class PhabricatorLDAPProvider {
|
||||||
public function retrieveUserEmail() {
|
public function retrieveUserEmail() {
|
||||||
return $this->userData['mail'][0];
|
return $this->userData['mail'][0];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function retrieveUserRealName() {
|
public function retrieveUserRealName() {
|
||||||
return $this->retrieveUserRealNameFromData($this->userData);
|
return $this->retrieveUserRealNameFromData($this->userData);
|
||||||
}
|
}
|
||||||
|
@ -106,9 +106,9 @@ final class PhabricatorLDAPProvider {
|
||||||
return $this->userData;
|
return $this->userData;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function auth($username, $password) {
|
public function auth($username, PhutilOpaqueEnvelope $password) {
|
||||||
if (strlen(trim($username)) == 0 || strlen(trim($password)) == 0) {
|
if (strlen(trim($username)) == 0) {
|
||||||
throw new Exception('Username and/or password can not be empty');
|
throw new Exception('Username can not be empty');
|
||||||
}
|
}
|
||||||
|
|
||||||
$activeDirectoryDomain =
|
$activeDirectoryDomain =
|
||||||
|
@ -121,10 +121,17 @@ final class PhabricatorLDAPProvider {
|
||||||
$this->getBaseDN();
|
$this->getBaseDN();
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = ldap_bind($this->getConnection(), $dn, $password);
|
$conn = $this->getConnection();
|
||||||
|
|
||||||
|
// NOTE: It is very important we suppress any messages that occur here,
|
||||||
|
// because it logs passwords if it reaches an error log of any sort.
|
||||||
|
DarkConsoleErrorLogPluginAPI::enableDiscardMode();
|
||||||
|
$result = @ldap_bind($conn, $dn, $password->openEnvelope());
|
||||||
|
DarkConsoleErrorLogPluginAPI::disableDiscardMode();
|
||||||
|
|
||||||
if (!$result) {
|
if (!$result) {
|
||||||
throw new Exception('Bad username/password.');
|
throw new Exception(
|
||||||
|
"LDAP Error #".ldap_errno($conn).": ".ldap_error($conn));
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->userData = $this->getUser($username);
|
$this->userData = $this->getUser($username);
|
||||||
|
@ -184,14 +191,14 @@ final class PhabricatorLDAPProvider {
|
||||||
$row = array();
|
$row = array();
|
||||||
$entry = $entries[$i];
|
$entry = $entries[$i];
|
||||||
|
|
||||||
// Get username, email and realname
|
// Get username, email and realname
|
||||||
$username = $entry[$this->getSearchAttribute()][0];
|
$username = $entry[$this->getSearchAttribute()][0];
|
||||||
if(empty($username)) {
|
if(empty($username)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$row[] = $username;
|
$row[] = $username;
|
||||||
$row[] = $entry['mail'][0];
|
$row[] = $entry['mail'][0];
|
||||||
$row[] = $this->retrieveUserRealNameFromData($entry);
|
$row[] = $this->retrieveUserRealNameFromData($entry);
|
||||||
|
|
||||||
|
|
||||||
$rows[] = $row;
|
$rows[] = $row;
|
||||||
|
|
|
@ -127,7 +127,10 @@ final class PhabricatorUserEditor {
|
||||||
/**
|
/**
|
||||||
* @task edit
|
* @task edit
|
||||||
*/
|
*/
|
||||||
public function changePassword(PhabricatorUser $user, $password) {
|
public function changePassword(
|
||||||
|
PhabricatorUser $user,
|
||||||
|
PhutilOpaqueEnvelope $envelope) {
|
||||||
|
|
||||||
if (!$user->getID()) {
|
if (!$user->getID()) {
|
||||||
throw new Exception("User has not been created yet!");
|
throw new Exception("User has not been created yet!");
|
||||||
}
|
}
|
||||||
|
@ -135,7 +138,7 @@ final class PhabricatorUserEditor {
|
||||||
$user->openTransaction();
|
$user->openTransaction();
|
||||||
$user->reload();
|
$user->reload();
|
||||||
|
|
||||||
$user->setPassword($password);
|
$user->setPassword($envelope);
|
||||||
$user->save();
|
$user->save();
|
||||||
|
|
||||||
$log = PhabricatorUserLog::newLog(
|
$log = PhabricatorUserLog::newLog(
|
||||||
|
|
|
@ -59,7 +59,8 @@ final class PhabricatorUserPasswordSettingsPanelController
|
||||||
$errors = array();
|
$errors = array();
|
||||||
if ($request->isFormPost()) {
|
if ($request->isFormPost()) {
|
||||||
if (!$valid_token) {
|
if (!$valid_token) {
|
||||||
if (!$user->comparePassword($request->getStr('old_pw'))) {
|
$envelope = new PhutilOpaqueEnvelope($request->getStr('old_pw'));
|
||||||
|
if (!$user->comparePassword($envelope)) {
|
||||||
$errors[] = 'The old password you entered is incorrect.';
|
$errors[] = 'The old password you entered is incorrect.';
|
||||||
$e_old = 'Invalid';
|
$e_old = 'Invalid';
|
||||||
}
|
}
|
||||||
|
@ -85,9 +86,10 @@ final class PhabricatorUserPasswordSettingsPanelController
|
||||||
// is changed here the CSRF token check will fail.
|
// is changed here the CSRF token check will fail.
|
||||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||||
|
|
||||||
|
$envelope = new PhutilOpaqueEnvelope($pass);
|
||||||
id(new PhabricatorUserEditor())
|
id(new PhabricatorUserEditor())
|
||||||
->setActor($user)
|
->setActor($user)
|
||||||
->changePassword($user, $pass);
|
->changePassword($user, $envelope);
|
||||||
|
|
||||||
unset($unguarded);
|
unset($unguarded);
|
||||||
|
|
||||||
|
|
|
@ -74,18 +74,18 @@ final class PhabricatorUser extends PhabricatorUserDAO implements PhutilPerson {
|
||||||
PhabricatorPHIDConstants::PHID_TYPE_USER);
|
PhabricatorPHIDConstants::PHID_TYPE_USER);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setPassword($password) {
|
public function setPassword(PhutilOpaqueEnvelope $envelope) {
|
||||||
if (!$this->getPHID()) {
|
if (!$this->getPHID()) {
|
||||||
throw new Exception(
|
throw new Exception(
|
||||||
"You can not set a password for an unsaved user because their PHID ".
|
"You can not set a password for an unsaved user because their PHID ".
|
||||||
"is a salt component in the password hash.");
|
"is a salt component in the password hash.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!strlen($password)) {
|
if (!strlen($envelope->openEnvelope())) {
|
||||||
$this->setPasswordHash('');
|
$this->setPasswordHash('');
|
||||||
} else {
|
} else {
|
||||||
$this->setPasswordSalt(md5(mt_rand()));
|
$this->setPasswordSalt(md5(mt_rand()));
|
||||||
$hash = $this->hashPassword($password);
|
$hash = $this->hashPassword($envelope);
|
||||||
$this->setPasswordHash($hash);
|
$this->setPasswordHash($hash);
|
||||||
}
|
}
|
||||||
return $this;
|
return $this;
|
||||||
|
@ -129,26 +129,26 @@ final class PhabricatorUser extends PhabricatorUserDAO implements PhutilPerson {
|
||||||
return Filesystem::readRandomCharacters(255);
|
return Filesystem::readRandomCharacters(255);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function comparePassword($password) {
|
public function comparePassword(PhutilOpaqueEnvelope $envelope) {
|
||||||
if (!strlen($password)) {
|
if (!strlen($envelope->openEnvelope())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!strlen($this->getPasswordHash())) {
|
if (!strlen($this->getPasswordHash())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$password = $this->hashPassword($password);
|
$password_hash = $this->hashPassword($envelope);
|
||||||
return ($password === $this->getPasswordHash());
|
return ($password_hash === $this->getPasswordHash());
|
||||||
}
|
}
|
||||||
|
|
||||||
private function hashPassword($password) {
|
private function hashPassword(PhutilOpaqueEnvelope $envelope) {
|
||||||
$password = $this->getUsername().
|
$hash = $this->getUsername().
|
||||||
$password.
|
$envelope->openEnvelope().
|
||||||
$this->getPHID().
|
$this->getPHID().
|
||||||
$this->getPasswordSalt();
|
$this->getPasswordSalt();
|
||||||
for ($ii = 0; $ii < 1000; $ii++) {
|
for ($ii = 0; $ii < 1000; $ii++) {
|
||||||
$password = md5($password);
|
$hash = md5($hash);
|
||||||
}
|
}
|
||||||
return $password;
|
return $hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CSRF_CYCLE_FREQUENCY = 3600;
|
const CSRF_CYCLE_FREQUENCY = 3600;
|
||||||
|
|
Loading…
Add table
Reference in a new issue