diff --git a/resources/sql/autopatches/20150602.mlist.1.sql b/resources/sql/autopatches/20150602.mlist.1.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20150602.mlist.1.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_user.user + ADD isMailingList BOOL NOT NULL; diff --git a/scripts/ssh/ssh-exec.php b/scripts/ssh/ssh-exec.php --- a/scripts/ssh/ssh-exec.php +++ b/scripts/ssh/ssh-exec.php @@ -182,11 +182,11 @@ 'P' => $user->getPHID(), )); - if (!$user->isUserActivated()) { + if (!$user->canEstablishSSHSessions()) { throw new Exception( pht( - 'Your account ("%s") is not activated. Visit the web interface '. - 'for more information.', + 'Your account ("%s") does not have permission to establish SSH '. + 'sessions. Visit the web interface for more information.', $user->getUsername())); } diff --git a/scripts/user/account_admin.php b/scripts/user/account_admin.php --- a/scripts/user/account_admin.php +++ b/scripts/user/account_admin.php @@ -125,7 +125,7 @@ $is_system_agent = $user->getIsSystemAgent(); $set_system_agent = phutil_console_confirm( - pht('Is this user a bot/script?'), + pht('Is this user a bot?'), $default_no = !$is_system_agent); $verify_email = null; @@ -165,7 +165,7 @@ printf( $tpl, - pht('Bot/Script'), + pht('Bot'), $original->getIsSystemAgent() ? 'Y' : 'N', $set_system_agent ? 'Y' : 'N'); diff --git a/src/applications/auth/engine/PhabricatorAuthSessionEngine.php b/src/applications/auth/engine/PhabricatorAuthSessionEngine.php --- a/src/applications/auth/engine/PhabricatorAuthSessionEngine.php +++ b/src/applications/auth/engine/PhabricatorAuthSessionEngine.php @@ -158,6 +158,21 @@ $session_dict[substr($key, 2)] = $value; } } + + $user = $user_table->loadFromArray($info); + switch ($session_type) { + case PhabricatorAuthSession::TYPE_WEB: + // Explicitly prevent bots and mailing lists from establishing web + // sessions. It's normally impossible to attach authentication to these + // accounts, and likewise impossible to generate sessions, but it's + // technically possible that a session could exist in the database. If + // one does somehow, refuse to load it. + if (!$user->canEstablishWebSessions()) { + return null; + } + break; + } + $session = id(new PhabricatorAuthSession())->loadFromArray($session_dict); $ttl = PhabricatorAuthSession::getSessionTypeTTL($session_type); @@ -181,7 +196,6 @@ unset($unguarded); } - $user = $user_table->loadFromArray($info); $user->attachSession($session); return $user; } diff --git a/src/applications/conduit/controller/PhabricatorConduitAPIController.php b/src/applications/conduit/controller/PhabricatorConduitAPIController.php --- a/src/applications/conduit/controller/PhabricatorConduitAPIController.php +++ b/src/applications/conduit/controller/PhabricatorConduitAPIController.php @@ -475,10 +475,10 @@ ConduitAPIRequest $request, PhabricatorUser $user) { - if (!$user->isUserActivated()) { + if (!$user->canEstablishAPISessions()) { return array( - 'ERR-USER-DISABLED', - pht('User account is not activated.'), + 'ERR-INVALID-AUTH', + pht('User account is not permitted to use the API.'), ); } diff --git a/src/applications/conduit/settings/PhabricatorConduitTokensSettingsPanel.php b/src/applications/conduit/settings/PhabricatorConduitTokensSettingsPanel.php --- a/src/applications/conduit/settings/PhabricatorConduitTokensSettingsPanel.php +++ b/src/applications/conduit/settings/PhabricatorConduitTokensSettingsPanel.php @@ -20,6 +20,10 @@ } public function isEnabled() { + if ($this->getUser()->getIsMailingList()) { + return false; + } + return true; } diff --git a/src/applications/diffusion/panel/DiffusionSetPasswordSettingsPanel.php b/src/applications/diffusion/panel/DiffusionSetPasswordSettingsPanel.php --- a/src/applications/diffusion/panel/DiffusionSetPasswordSettingsPanel.php +++ b/src/applications/diffusion/panel/DiffusionSetPasswordSettingsPanel.php @@ -19,6 +19,10 @@ } public function isEnabled() { + if ($this->getUser()->getIsMailingList()) { + return false; + } + return PhabricatorEnv::getEnvConfig('diffusion.allow-http-auth'); } diff --git a/src/applications/people/conduit/UserConduitAPIMethod.php b/src/applications/people/conduit/UserConduitAPIMethod.php --- a/src/applications/people/conduit/UserConduitAPIMethod.php +++ b/src/applications/people/conduit/UserConduitAPIMethod.php @@ -18,6 +18,9 @@ if ($user->getIsSystemAgent()) { $roles[] = 'agent'; } + if ($user->getIsMailingList()) { + $roles[] = 'list'; + } if ($user->getIsAdmin()) { $roles[] = 'admin'; } diff --git a/src/applications/people/controller/PhabricatorPeopleCreateController.php b/src/applications/people/controller/PhabricatorPeopleCreateController.php --- a/src/applications/people/controller/PhabricatorPeopleCreateController.php +++ b/src/applications/people/controller/PhabricatorPeopleCreateController.php @@ -4,8 +4,6 @@ extends PhabricatorPeopleController { public function handleRequest(AphrontRequest $request) { - $this->requireApplicationCapability( - PeopleCreateUsersCapability::CAPABILITY); $admin = $request->getUser(); id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession( @@ -17,7 +15,7 @@ if ($request->isFormPost()) { $v_type = $request->getStr('type'); - if ($v_type == 'standard' || $v_type == 'bot') { + if ($v_type == 'standard' || $v_type == 'bot' || $v_type == 'list') { return id(new AphrontRedirectResponse())->setURI( $this->getApplicationURI('new/'.$v_type.'/')); } @@ -41,6 +39,41 @@ $bot_admin = pht( 'Administrators have greater access to edit these accounts.'); + $types = array(); + + $can_create = $this->hasApplicationCapability( + PeopleCreateUsersCapability::CAPABILITY); + if ($can_create) { + $types[] = array( + 'type' => 'standard', + 'name' => pht('Create Standard User'), + 'help' => pht('Create a standard user account.'), + ); + } + + $types[] = array( + 'type' => 'bot', + 'name' => pht('Create Bot User'), + 'help' => pht('Create a new user for use with automated scripts.'), + ); + + $types[] = array( + 'type' => 'list', + 'name' => pht('Create Mailing List User'), + 'help' => pht( + 'Create a mailing list user to represent an existing, external '. + 'mailing list like a Google Group or a Mailman list.'), + ); + + $buttons = id(new AphrontFormRadioButtonControl()) + ->setLabel(pht('Account Type')) + ->setName('type') + ->setValue($v_type); + + foreach ($types as $type) { + $buttons->addButton($type['type'], $type['name'], $type['help']); + } + $form = id(new AphrontFormView()) ->setUser($admin) ->appendRemarkupInstructions( @@ -49,19 +82,7 @@ 'explanation of user account types, see [[ %s | User Guide: '. 'Account Roles ]].', PhabricatorEnv::getDoclink('User Guide: Account Roles'))) - ->appendChild( - id(new AphrontFormRadioButtonControl()) - ->setLabel(pht('Account Type')) - ->setName('type') - ->setValue($v_type) - ->addButton( - 'standard', - pht('Create Standard User'), - hsprintf('%s

%s', $standard_caption, $standard_admin)) - ->addButton( - 'bot', - pht('Create Bot/Script User'), - hsprintf('%s

%s', $bot_caption, $bot_admin))) + ->appendChild($buttons) ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton($this->getApplicationURI()) diff --git a/src/applications/people/controller/PhabricatorPeopleListController.php b/src/applications/people/controller/PhabricatorPeopleListController.php --- a/src/applications/people/controller/PhabricatorPeopleListController.php +++ b/src/applications/people/controller/PhabricatorPeopleListController.php @@ -33,20 +33,12 @@ $crumbs = parent::buildApplicationCrumbs(); $viewer = $this->getRequest()->getUser(); - $can_create = $this->hasApplicationCapability( - PeopleCreateUsersCapability::CAPABILITY); - if ($can_create) { + if ($viewer->getIsAdmin()) { $crumbs->addAction( id(new PHUIListItemView()) ->setName(pht('Create New User')) ->setHref($this->getApplicationURI('create/')) ->setIcon('fa-plus-square')); - } else if ($viewer->getIsAdmin()) { - $crumbs->addAction( - id(new PHUIListItemView()) - ->setName(pht('Create New Bot')) - ->setHref($this->getApplicationURI('new/bot/')) - ->setIcon('fa-plus-square')); } return $crumbs; diff --git a/src/applications/people/controller/PhabricatorPeopleNewController.php b/src/applications/people/controller/PhabricatorPeopleNewController.php --- a/src/applications/people/controller/PhabricatorPeopleNewController.php +++ b/src/applications/people/controller/PhabricatorPeopleNewController.php @@ -7,15 +7,19 @@ $type = $request->getURIData('type'); $admin = $request->getUser(); + $is_bot = false; + $is_list = false; switch ($type) { case 'standard': $this->requireApplicationCapability( PeopleCreateUsersCapability::CAPABILITY); - $is_bot = false; break; case 'bot': $is_bot = true; break; + case 'list': + $is_list = true; + break; default: return new Aphront404Response(); } @@ -77,8 +81,8 @@ // Automatically approve the user, since an admin is creating them. $user->setIsApproved(1); - // If the user is a bot, approve their email too. - if ($is_bot) { + // If the user is a bot or list, approve their email too. + if ($is_bot || $is_list) { $email->setIsVerified(1); } @@ -92,7 +96,13 @@ ->makeSystemAgentUser($user, true); } - if ($welcome_checked && !$is_bot) { + if ($is_list) { + id(new PhabricatorUserEditor()) + ->setActor($admin) + ->makeMailingListUser($user, true); + } + + if ($welcome_checked && !$is_bot && !$is_list) { $user->sendWelcomeEmail($admin); } @@ -123,7 +133,10 @@ if ($is_bot) { $form->appendRemarkupInstructions( - pht('You are creating a new **bot/script** user account.')); + pht('You are creating a new **bot** user account.')); + } else if ($is_list) { + $form->appendRemarkupInstructions( + pht('You are creating a new **mailing list** user account.')); } else { $form->appendRemarkupInstructions( pht('You are creating a new **standard** user account.')); @@ -150,7 +163,7 @@ ->setCaption(PhabricatorUserEmail::describeAllowedAddresses()) ->setError($e_email)); - if (!$is_bot) { + if (!$is_bot && !$is_list) { $form->appendChild( id(new AphrontFormCheckboxControl()) ->addCheckbox( @@ -171,7 +184,7 @@ ->appendChild(id(new AphrontFormDividerControl())) ->appendRemarkupInstructions( pht( - '**Why do bot/script accounts need an email address?**'. + '**Why do bot accounts need an email address?**'. "\n\n". 'Although bots do not normally receive email from Phabricator, '. 'they can interact with other systems which require an email '. diff --git a/src/applications/people/customfield/PhabricatorUserRolesField.php b/src/applications/people/customfield/PhabricatorUserRolesField.php --- a/src/applications/people/customfield/PhabricatorUserRolesField.php +++ b/src/applications/people/customfield/PhabricatorUserRolesField.php @@ -37,6 +37,9 @@ if ($user->getIsSystemAgent()) { $roles[] = pht('Bot'); } + if ($user->getIsMailingList()) { + $roles[] = pht('Mailing List'); + } if ($roles) { return implode(', ', $roles); diff --git a/src/applications/people/editor/PhabricatorUserEditor.php b/src/applications/people/editor/PhabricatorUserEditor.php --- a/src/applications/people/editor/PhabricatorUserEditor.php +++ b/src/applications/people/editor/PhabricatorUserEditor.php @@ -278,6 +278,43 @@ return $this; } + /** + * @task role + */ + public function makeMailingListUser(PhabricatorUser $user, $mailing_list) { + $actor = $this->requireActor(); + + if (!$user->getID()) { + throw new Exception(pht('User has not been created yet!')); + } + + $user->openTransaction(); + $user->beginWriteLocking(); + + $user->reload(); + if ($user->getIsMailingList() == $mailing_list) { + $user->endWriteLocking(); + $user->killTransaction(); + return $this; + } + + $log = PhabricatorUserLog::initializeNewLog( + $actor, + $user->getPHID(), + PhabricatorUserLog::ACTION_MAILING_LIST); + $log->setOldValue($user->getIsMailingList()); + $log->setNewValue($mailing_list); + + $user->setIsMailingList((int)$mailing_list); + $user->save(); + + $log->save(); + + $user->endWriteLocking(); + $user->saveTransaction(); + + return $this; + } /** * @task role diff --git a/src/applications/people/phid/PhabricatorPeopleUserPHIDType.php b/src/applications/people/phid/PhabricatorPeopleUserPHIDType.php --- a/src/applications/people/phid/PhabricatorPeopleUserPHIDType.php +++ b/src/applications/people/phid/PhabricatorPeopleUserPHIDType.php @@ -44,6 +44,10 @@ $handle->setFullName($user->getFullName()); $handle->setImageURI($user->getProfileImageURI()); + if ($user->getIsMailingList()) { + $handle->setIcon('fa-envelope-o'); + } + $availability = null; if (!$user->isUserActivated()) { $availability = PhabricatorObjectHandle::AVAILABILITY_DISABLED; diff --git a/src/applications/people/query/PhabricatorPeopleQuery.php b/src/applications/people/query/PhabricatorPeopleQuery.php --- a/src/applications/people/query/PhabricatorPeopleQuery.php +++ b/src/applications/people/query/PhabricatorPeopleQuery.php @@ -12,6 +12,7 @@ private $dateCreatedBefore; private $isAdmin; private $isSystemAgent; + private $isMailingList; private $isDisabled; private $isApproved; private $nameLike; @@ -67,6 +68,11 @@ return $this; } + public function withIsMailingList($mailing_list) { + $this->isMailingList = $mailing_list; + return $this; + } + public function withIsDisabled($disabled) { $this->isDisabled = $disabled; return $this; @@ -340,6 +346,13 @@ (int)$this->isSystemAgent); } + if ($this->isMailingList !== null) { + $where[] = qsprintf( + $conn_r, + 'user.isMailingList = %d', + (int)$this->isMailingList); + } + if (strlen($this->nameLike)) { $where[] = qsprintf( $conn_r, diff --git a/src/applications/people/query/PhabricatorPeopleSearchEngine.php b/src/applications/people/query/PhabricatorPeopleSearchEngine.php --- a/src/applications/people/query/PhabricatorPeopleSearchEngine.php +++ b/src/applications/people/query/PhabricatorPeopleSearchEngine.php @@ -34,6 +34,10 @@ $this->readBoolFromRequest($request, 'isSystemAgent')); $saved->setParameter( + 'isMailingList', + $this->readBoolFromRequest($request, 'isMailingList')); + + $saved->setParameter( 'needsApproval', $this->readBoolFromRequest($request, 'needsApproval')); @@ -77,6 +81,7 @@ $is_admin = $saved->getParameter('isAdmin'); $is_disabled = $saved->getParameter('isDisabled'); $is_system_agent = $saved->getParameter('isSystemAgent'); + $is_mailing_list = $saved->getParameter('isMailingList'); $needs_approval = $saved->getParameter('needsApproval'); if ($is_admin !== null) { @@ -91,6 +96,10 @@ $query->withIsSystemAgent($is_system_agent); } + if ($is_mailing_list !== null) { + $query->withIsMailingList($is_mailing_list); + } + if ($needs_approval !== null) { $query->withIsApproved(!$needs_approval); } @@ -121,6 +130,7 @@ $is_admin = $this->getBoolFromQuery($saved, 'isAdmin'); $is_disabled = $this->getBoolFromQuery($saved, 'isDisabled'); $is_system_agent = $this->getBoolFromQuery($saved, 'isSystemAgent'); + $is_mailing_list = $this->getBoolFromQuery($saved, 'isMailingList'); $needs_approval = $this->getBoolFromQuery($saved, 'needsApproval'); $form @@ -169,6 +179,17 @@ ))) ->appendChild( id(new AphrontFormSelectControl()) + ->setName('isMailingList') + ->setLabel(pht('Mailing Lists')) + ->setValue($is_mailing_list) + ->setOptions( + array( + '' => pht('(Show All)'), + 'true' => pht('Show Only Mailing Lists'), + 'false' => pht('Hide Mailing Lists'), + ))) + ->appendChild( + id(new AphrontFormSelectControl()) ->setName('needsApproval') ->setLabel(pht('Needs Approval')) ->setValue($needs_approval) @@ -278,6 +299,10 @@ $item->addIcon('fa-desktop', pht('Bot')); } + if ($user->getIsMailingList()) { + $item->addIcon('fa-envelope-o', pht('Mailing List')); + } + if ($viewer->getIsAdmin()) { $user_id = $user->getID(); if ($is_approval) { diff --git a/src/applications/people/storage/PhabricatorUser.php b/src/applications/people/storage/PhabricatorUser.php --- a/src/applications/people/storage/PhabricatorUser.php +++ b/src/applications/people/storage/PhabricatorUser.php @@ -38,6 +38,7 @@ protected $conduitCertificate; protected $isSystemAgent = 0; + protected $isMailingList = 0; protected $isAdmin = 0; protected $isDisabled = 0; protected $isEmailVerified = 0; @@ -73,6 +74,8 @@ return (bool)$this->isDisabled; case 'isSystemAgent': return (bool)$this->isSystemAgent; + case 'isMailingList': + return (bool)$this->isMailingList; case 'isEmailVerified': return (bool)$this->isEmailVerified; case 'isApproved': @@ -112,6 +115,46 @@ return true; } + public function canEstablishWebSessions() { + if (!$this->isUserActivated()) { + return false; + } + + if ($this->getIsMailingList()) { + return false; + } + + if ($this->getIsSystemAgent()) { + return false; + } + + return true; + } + + public function canEstablishAPISessions() { + if (!$this->isUserActivated()) { + return false; + } + + if ($this->getIsMailingList()) { + return false; + } + + return true; + } + + public function canEstablishSSHSessions() { + if (!$this->isUserActivated()) { + return false; + } + + if ($this->getIsMailingList()) { + return false; + } + + return true; + } + /** * Returns `true` if this is a standard user who is logged in. Returns `false` * for logged out, anonymous, or external users. @@ -140,6 +183,7 @@ 'consoleTab' => 'text64', 'conduitCertificate' => 'text255', 'isSystemAgent' => 'bool', + 'isMailingList' => 'bool', 'isDisabled' => 'bool', 'isAdmin' => 'bool', 'timezoneIdentifier' => 'text255', @@ -1032,7 +1076,7 @@ case PhabricatorPolicyCapability::CAN_VIEW: return PhabricatorPolicies::POLICY_PUBLIC; case PhabricatorPolicyCapability::CAN_EDIT: - if ($this->getIsSystemAgent()) { + if ($this->getIsSystemAgent() || $this->getIsMailingList()) { return PhabricatorPolicies::POLICY_ADMIN; } else { return PhabricatorPolicies::POLICY_NOONE; diff --git a/src/applications/people/storage/PhabricatorUserLog.php b/src/applications/people/storage/PhabricatorUserLog.php --- a/src/applications/people/storage/PhabricatorUserLog.php +++ b/src/applications/people/storage/PhabricatorUserLog.php @@ -16,6 +16,7 @@ const ACTION_ADMIN = 'admin'; const ACTION_SYSTEM_AGENT = 'system-agent'; + const ACTION_MAILING_LIST = 'mailing-list'; const ACTION_DISABLE = 'disable'; const ACTION_APPROVE = 'approve'; const ACTION_DELETE = 'delete'; @@ -62,6 +63,7 @@ self::ACTION_EDIT => pht('Edit Account'), self::ACTION_ADMIN => pht('Add/Remove Administrator'), self::ACTION_SYSTEM_AGENT => pht('Add/Remove System Agent'), + self::ACTION_MAILING_LIST => pht('Add/Remove Mailing List'), self::ACTION_DISABLE => pht('Enable/Disable'), self::ACTION_APPROVE => pht('Approve Registration'), self::ACTION_DELETE => pht('Delete User'), diff --git a/src/applications/people/typeahead/PhabricatorPeopleDatasource.php b/src/applications/people/typeahead/PhabricatorPeopleDatasource.php --- a/src/applications/people/typeahead/PhabricatorPeopleDatasource.php +++ b/src/applications/people/typeahead/PhabricatorPeopleDatasource.php @@ -54,7 +54,9 @@ if ($user->getIsDisabled()) { $closed = pht('Disabled'); } else if ($user->getIsSystemAgent()) { - $closed = pht('Bot/Script'); + $closed = pht('Bot'); + } else if ($user->getIsMailingList()) { + $closed = pht('Mailing List'); } $result = id(new PhabricatorTypeaheadResult()) @@ -65,6 +67,10 @@ ->setPriorityType('user') ->setClosed($closed); + if ($user->getIsMailingList()) { + $result->setIcon('fa-envelope-o'); + } + if ($this->enrichResults) { $display_type = 'User'; if ($user->getIsAdmin()) { diff --git a/src/applications/settings/controller/PhabricatorSettingsMainController.php b/src/applications/settings/controller/PhabricatorSettingsMainController.php --- a/src/applications/settings/controller/PhabricatorSettingsMainController.php +++ b/src/applications/settings/controller/PhabricatorSettingsMainController.php @@ -97,6 +97,8 @@ $result = array(); foreach ($panels as $key => $panel) { + $panel->setUser($this->user); + if (!$panel->isEnabled()) { continue; } diff --git a/src/applications/settings/panel/PhabricatorConduitCertificateSettingsPanel.php b/src/applications/settings/panel/PhabricatorConduitCertificateSettingsPanel.php --- a/src/applications/settings/panel/PhabricatorConduitCertificateSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorConduitCertificateSettingsPanel.php @@ -19,6 +19,14 @@ return pht('Authentication'); } + public function isEnabled() { + if ($this->getUser()->getIsMailingList()) { + return false; + } + + return true; + } + public function processRequest(AphrontRequest $request) { $user = $this->getUser(); $viewer = $request->getUser(); diff --git a/src/applications/settings/panel/PhabricatorSSHKeysSettingsPanel.php b/src/applications/settings/panel/PhabricatorSSHKeysSettingsPanel.php --- a/src/applications/settings/panel/PhabricatorSSHKeysSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorSSHKeysSettingsPanel.php @@ -19,6 +19,10 @@ } public function isEnabled() { + if ($this->getUser()->getIsMailingList()) { + return false; + } + return true; }