mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-10 00:42:41 +01:00
Allow restriction of permitted email domains
Summary: Allow allowed email addresses to be restricted to certain domains. This implies email must be verified. This probably isn't QUITE ready for prime-time without a few other tweaks (better administrative tools, notably) but we're nearly there. Test Plan: - With no restrictions: - Registered with OAuth - Created an account with accountadmin - Added an email - With restrictions: - Tried to OAuth register with a restricted address, was prompted to provide a valid one. - Tried to OAuth register with a valid address, worked fine. - Tried to accountadmin a restricted address, got blocked. - Tried to accountadmin a valid address, worked fine. - Tried to add a restricted address, blocked. - Tried to add a valid address, worked fine. - Created a user with People with an invalid address, got blocked. - Created a user with People with a valid address, worked fine. Reviewers: btrahan, csilvers Reviewed By: csilvers CC: aran, joe, csilvers Maniphest Tasks: T1184 Differential Revision: https://secure.phabricator.com/D2581
This commit is contained in:
parent
648c8aa499
commit
557e508656
12 changed files with 147 additions and 14 deletions
|
@ -454,9 +454,28 @@ return array(
|
||||||
'auth.sshkeys.enabled' => false,
|
'auth.sshkeys.enabled' => false,
|
||||||
|
|
||||||
// If true, email addresses must be verified (by clicking a link in an
|
// If true, email addresses must be verified (by clicking a link in an
|
||||||
// email) before a user can login. By default, verification is optional.
|
// email) before a user can login. By default, verification is optional
|
||||||
|
// unless 'auth.email-domains' is nonempty (see below).
|
||||||
'auth.require-email-verification' => false,
|
'auth.require-email-verification' => false,
|
||||||
|
|
||||||
|
// You can restrict allowed email addresses to certain domains (like
|
||||||
|
// "yourcompany.com") by setting a list of allowed domains here. Users will
|
||||||
|
// only be allowed to register using email addresses at one of the domains,
|
||||||
|
// and will only be able to add new email addresses for these domains. If
|
||||||
|
// you configure this, it implies 'auth.require-email-verification'.
|
||||||
|
//
|
||||||
|
// To configure email domains, set a list of domains like this:
|
||||||
|
//
|
||||||
|
// array(
|
||||||
|
// 'yourcompany.com',
|
||||||
|
// 'yourcompany.co.uk',
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// You should omit the "@" from domains. Note that the domain must match
|
||||||
|
// exactly. If you allow "yourcompany.com", that permits "joe@yourcompany.com"
|
||||||
|
// but rejects "joe@mail.yourcompany.com".
|
||||||
|
'auth.email-domains' => array(),
|
||||||
|
|
||||||
|
|
||||||
// -- Accounts -------------------------------------------------------------- //
|
// -- Accounts -------------------------------------------------------------- //
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,26 @@ final class PhabricatorOAuthDefaultRegistrationController
|
||||||
|
|
||||||
$new_email = $provider->retrieveUserEmail();
|
$new_email = $provider->retrieveUserEmail();
|
||||||
|
|
||||||
|
if ($new_email) {
|
||||||
|
// If the user's OAuth provider account has an email address but the
|
||||||
|
// email address domain is not allowed by the Phabricator configuration,
|
||||||
|
// we just pretend the provider did not supply an address.
|
||||||
|
//
|
||||||
|
// For instance, if the user uses Google OAuth and their Google address
|
||||||
|
// is "joe@personal.com" but Phabricator is configured to require users
|
||||||
|
// use "@company.com" addresses, we show a prompt below and tell the user
|
||||||
|
// to provide their "@company.com" address. They can still use the OAuth
|
||||||
|
// account to login, they just need to associate their account with an
|
||||||
|
// allowed address.
|
||||||
|
//
|
||||||
|
// If the OAuth address is fine, we just use it and don't prompt the user.
|
||||||
|
if (!PhabricatorUserEmail::isAllowedAddress($new_email)) {
|
||||||
|
$new_email = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$show_email_input = ($new_email === null);
|
||||||
|
|
||||||
if ($request->isFormPost()) {
|
if ($request->isFormPost()) {
|
||||||
|
|
||||||
$user->setUsername($request->getStr('username'));
|
$user->setUsername($request->getStr('username'));
|
||||||
|
@ -60,6 +80,14 @@ final class PhabricatorOAuthDefaultRegistrationController
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($new_email) {
|
||||||
|
$email_ok = PhabricatorUserEmail::isAllowedAddress($new_email);
|
||||||
|
if (!$email_ok) {
|
||||||
|
$e_email = 'Invalid';
|
||||||
|
$errors[] = PhabricatorUserEmail::describeAllowedAddresses();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!strlen($user->getRealName())) {
|
if (!strlen($user->getRealName())) {
|
||||||
$user->setRealName($request->getStr('realname'));
|
$user->setRealName($request->getStr('realname'));
|
||||||
if (!strlen($user->getRealName())) {
|
if (!strlen($user->getRealName())) {
|
||||||
|
@ -160,16 +188,17 @@ final class PhabricatorOAuthDefaultRegistrationController
|
||||||
->setValue($user->getUsername())
|
->setValue($user->getUsername())
|
||||||
->setError($e_username));
|
->setError($e_username));
|
||||||
|
|
||||||
if ($provider->retrieveUserEmail() === null) {
|
if ($show_email_input) {
|
||||||
$form->appendChild(
|
$form->appendChild(
|
||||||
id(new AphrontFormTextControl())
|
id(new AphrontFormTextControl())
|
||||||
->setLabel('Email')
|
->setLabel('Email')
|
||||||
->setName('email')
|
->setName('email')
|
||||||
->setValue($request->getStr('email'))
|
->setValue($request->getStr('email'))
|
||||||
|
->setCaption(PhabricatorUserEmail::describeAllowedAddresses())
|
||||||
->setError($e_email));
|
->setError($e_email));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($provider->retrieveUserRealName () === null) {
|
if ($provider->retrieveUserRealName() === null) {
|
||||||
$form->appendChild(
|
$form->appendChild(
|
||||||
id(new AphrontFormTextControl())
|
id(new AphrontFormTextControl())
|
||||||
->setLabel('Real Name')
|
->setLabel('Real Name')
|
||||||
|
|
|
@ -31,9 +31,7 @@ abstract class PhabricatorController extends AphrontController {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function shouldRequireEmailVerification() {
|
public function shouldRequireEmailVerification() {
|
||||||
$config_key = 'auth.require-email-verification';
|
$need_verify = PhabricatorUserEmail::isEmailVerificationRequired();
|
||||||
|
|
||||||
$need_verify = PhabricatorEnv::getEnvConfig($config_key);
|
|
||||||
$need_login = $this->shouldRequireLogin();
|
$need_login = $this->shouldRequireLogin();
|
||||||
|
|
||||||
return ($need_login && $need_verify);
|
return ($need_login && $need_verify);
|
||||||
|
@ -79,6 +77,11 @@ abstract class PhabricatorController extends AphrontController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->shouldRequireLogin() && !$user->getPHID()) {
|
||||||
|
$login_controller = newv('PhabricatorLoginController', array($request));
|
||||||
|
return $this->delegateToController($login_controller);
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->shouldRequireEmailVerification()) {
|
if ($this->shouldRequireEmailVerification()) {
|
||||||
$email = $user->loadPrimaryEmail();
|
$email = $user->loadPrimaryEmail();
|
||||||
if (!$email) {
|
if (!$email) {
|
||||||
|
@ -93,11 +96,6 @@ abstract class PhabricatorController extends AphrontController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->shouldRequireLogin() && !$user->getPHID()) {
|
|
||||||
$login_controller = newv('PhabricatorLoginController', array($request));
|
|
||||||
return $this->delegateToController($login_controller);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->shouldRequireAdmin() && !$user->getIsAdmin()) {
|
if ($this->shouldRequireAdmin() && !$user->getIsAdmin()) {
|
||||||
return new Aphront403Response();
|
return new Aphront403Response();
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ phutil_require_module('phabricator', 'aphront/console/core');
|
||||||
phutil_require_module('phabricator', 'aphront/controller');
|
phutil_require_module('phabricator', 'aphront/controller');
|
||||||
phutil_require_module('phabricator', 'aphront/response/403');
|
phutil_require_module('phabricator', 'aphront/response/403');
|
||||||
phutil_require_module('phabricator', 'aphront/response/webpage');
|
phutil_require_module('phabricator', 'aphront/response/webpage');
|
||||||
|
phutil_require_module('phabricator', 'applications/people/storage/email');
|
||||||
phutil_require_module('phabricator', 'applications/people/storage/user');
|
phutil_require_module('phabricator', 'applications/people/storage/user');
|
||||||
phutil_require_module('phabricator', 'infrastructure/env');
|
phutil_require_module('phabricator', 'infrastructure/env');
|
||||||
phutil_require_module('phabricator', 'storage/queryfx');
|
phutil_require_module('phabricator', 'storage/queryfx');
|
||||||
|
|
|
@ -387,7 +387,7 @@ final class PhabricatorConduitAPIController
|
||||||
'User is disabled.');
|
'User is disabled.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PhabricatorEnv::getEnvConfig('auth.require-email-verification')) {
|
if (PhabricatorUserEmail::isEmailVerificationRequired()) {
|
||||||
$email = $user->loadPrimaryEmail();
|
$email = $user->loadPrimaryEmail();
|
||||||
if (!$email) {
|
if (!$email) {
|
||||||
return array(
|
return array(
|
||||||
|
|
|
@ -16,9 +16,9 @@ phutil_require_module('phabricator', 'applications/conduit/storage/methodcalllog
|
||||||
phutil_require_module('phabricator', 'applications/oauthserver/scope');
|
phutil_require_module('phabricator', 'applications/oauthserver/scope');
|
||||||
phutil_require_module('phabricator', 'applications/oauthserver/server');
|
phutil_require_module('phabricator', 'applications/oauthserver/server');
|
||||||
phutil_require_module('phabricator', 'applications/oauthserver/storage/accesstoken');
|
phutil_require_module('phabricator', 'applications/oauthserver/storage/accesstoken');
|
||||||
|
phutil_require_module('phabricator', 'applications/people/storage/email');
|
||||||
phutil_require_module('phabricator', 'applications/people/storage/user');
|
phutil_require_module('phabricator', 'applications/people/storage/user');
|
||||||
phutil_require_module('phabricator', 'infrastructure/accesslog');
|
phutil_require_module('phabricator', 'infrastructure/accesslog');
|
||||||
phutil_require_module('phabricator', 'infrastructure/env');
|
|
||||||
phutil_require_module('phabricator', 'storage/queryfx');
|
phutil_require_module('phabricator', 'storage/queryfx');
|
||||||
phutil_require_module('phabricator', 'view/control/table');
|
phutil_require_module('phabricator', 'view/control/table');
|
||||||
phutil_require_module('phabricator', 'view/layout/panel');
|
phutil_require_module('phabricator', 'view/layout/panel');
|
||||||
|
|
|
@ -136,6 +136,9 @@ final class PhabricatorPeopleEditController
|
||||||
if (!strlen($new_email)) {
|
if (!strlen($new_email)) {
|
||||||
$errors[] = 'Email is required.';
|
$errors[] = 'Email is required.';
|
||||||
$e_email = 'Required';
|
$e_email = 'Required';
|
||||||
|
} else if (!PhabricatorUserEmail::isAllowedAddress($new_email)) {
|
||||||
|
$e_email = 'Invalid';
|
||||||
|
$errors[] = PhabricatorUserEmail::describeAllowedAddresses();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->getStr('role') == 'agent') {
|
if ($request->getStr('role') == 'agent') {
|
||||||
|
@ -249,6 +252,7 @@ final class PhabricatorPeopleEditController
|
||||||
->setName('email')
|
->setName('email')
|
||||||
->setDisabled($is_immutable)
|
->setDisabled($is_immutable)
|
||||||
->setValue($new_email)
|
->setValue($new_email)
|
||||||
|
->setCaption(PhabricatorUserEmail::describeAllowedAddresses())
|
||||||
->setError($e_email));
|
->setError($e_email));
|
||||||
} else {
|
} else {
|
||||||
$email = $user->loadPrimaryEmail();
|
$email = $user->loadPrimaryEmail();
|
||||||
|
|
|
@ -171,6 +171,9 @@ final class PhabricatorUserEmailSettingsPanelController
|
||||||
if (!strlen($email)) {
|
if (!strlen($email)) {
|
||||||
$e_email = 'Required';
|
$e_email = 'Required';
|
||||||
$errors[] = 'Email is required.';
|
$errors[] = 'Email is required.';
|
||||||
|
} else if (!PhabricatorUserEmail::isAllowedAddress($email)) {
|
||||||
|
$e_email = 'Invalid';
|
||||||
|
$errors[] = PhabricatorUserEmail::describeAllowedAddresses();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$errors) {
|
if (!$errors) {
|
||||||
|
@ -216,6 +219,7 @@ final class PhabricatorUserEmailSettingsPanelController
|
||||||
->setLabel('Email')
|
->setLabel('Email')
|
||||||
->setName('email')
|
->setName('email')
|
||||||
->setValue($request->getStr('email'))
|
->setValue($request->getStr('email'))
|
||||||
|
->setCaption(PhabricatorUserEmail::describeAllowedAddresses())
|
||||||
->setError($e_email));
|
->setError($e_email));
|
||||||
|
|
||||||
$dialog = id(new AphrontDialogView())
|
$dialog = id(new AphrontDialogView())
|
||||||
|
|
|
@ -65,6 +65,8 @@ final class PhabricatorUserEditor {
|
||||||
// Always set a new user's email address to primary.
|
// Always set a new user's email address to primary.
|
||||||
$email->setIsPrimary(1);
|
$email->setIsPrimary(1);
|
||||||
|
|
||||||
|
$this->willAddEmail($email);
|
||||||
|
|
||||||
$user->openTransaction();
|
$user->openTransaction();
|
||||||
$user->save();
|
$user->save();
|
||||||
|
|
||||||
|
@ -241,6 +243,8 @@ final class PhabricatorUserEditor {
|
||||||
$email->setIsPrimary(0);
|
$email->setIsPrimary(0);
|
||||||
$email->setUserPHID($user->getPHID());
|
$email->setUserPHID($user->getPHID());
|
||||||
|
|
||||||
|
$this->willAddEmail($email);
|
||||||
|
|
||||||
$user->openTransaction();
|
$user->openTransaction();
|
||||||
$user->beginWriteLocking();
|
$user->beginWriteLocking();
|
||||||
|
|
||||||
|
@ -390,4 +394,20 @@ final class PhabricatorUserEditor {
|
||||||
return $this->actor;
|
return $this->actor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @task internal
|
||||||
|
*/
|
||||||
|
private function willAddEmail(PhabricatorUserEmail $email) {
|
||||||
|
|
||||||
|
// Hard check before write to prevent creation of disallowed email
|
||||||
|
// addresses. Normally, the application does checks and raises more
|
||||||
|
// user friendly errors for us, but we omit the courtesy checks on some
|
||||||
|
// pathways like administrative scripts for simplicity.
|
||||||
|
|
||||||
|
if (!PhabricatorUserEmail::isAllowedAddress($email->getAddress())) {
|
||||||
|
throw new Exception(PhabricatorUserEmail::describeAllowedAddresses());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
phutil_require_module('phabricator', 'applications/people/storage/email');
|
||||||
phutil_require_module('phabricator', 'applications/people/storage/log');
|
phutil_require_module('phabricator', 'applications/people/storage/log');
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @task email Email About Email
|
* @task restrictions Domain Restrictions
|
||||||
|
* @task email Email About Email
|
||||||
*/
|
*/
|
||||||
final class PhabricatorUserEmail extends PhabricatorUserDAO {
|
final class PhabricatorUserEmail extends PhabricatorUserDAO {
|
||||||
|
|
||||||
|
@ -39,6 +40,61 @@ final class PhabricatorUserEmail extends PhabricatorUserDAO {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* -( Domain Restrictions )------------------------------------------------ */
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @task restrictions
|
||||||
|
*/
|
||||||
|
public static function isAllowedAddress($address) {
|
||||||
|
$allowed_domains = PhabricatorEnv::getEnvConfig('auth.email-domains');
|
||||||
|
if (!$allowed_domains) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$addr_obj = new PhutilEmailAddress($address);
|
||||||
|
|
||||||
|
$domain = $addr_obj->getDomainName();
|
||||||
|
if (!$domain) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return in_array($domain, $allowed_domains);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @task restrictions
|
||||||
|
*/
|
||||||
|
public static function describeAllowedAddresses() {
|
||||||
|
$domains = PhabricatorEnv::getEnvConfig('auth.email-domains');
|
||||||
|
if (!$domains) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($domains) == 1) {
|
||||||
|
return 'Email address must be @'.head($domains);
|
||||||
|
} else {
|
||||||
|
return 'Email address must be at one of: '.
|
||||||
|
implode(', ', $domains);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this install requires email verification.
|
||||||
|
*
|
||||||
|
* @return bool True if email addresses must be verified.
|
||||||
|
*
|
||||||
|
* @task restrictions
|
||||||
|
*/
|
||||||
|
public static function isEmailVerificationRequired() {
|
||||||
|
// NOTE: Configuring required email domains implies required verification.
|
||||||
|
return PhabricatorEnv::getEnvConfig('auth.require-email-verification') ||
|
||||||
|
PhabricatorEnv::getEnvConfig('auth.email-domains');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* -( Email About Email )-------------------------------------------------- */
|
/* -( Email About Email )-------------------------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ phutil_require_module('phabricator', 'applications/people/storage/base');
|
||||||
phutil_require_module('phabricator', 'infrastructure/env');
|
phutil_require_module('phabricator', 'infrastructure/env');
|
||||||
|
|
||||||
phutil_require_module('phutil', 'filesystem');
|
phutil_require_module('phutil', 'filesystem');
|
||||||
|
phutil_require_module('phutil', 'parser/emailaddress');
|
||||||
phutil_require_module('phutil', 'utils');
|
phutil_require_module('phutil', 'utils');
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue