@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator

Add "Mailing List" users

Summary:
Ref T8387. Adds new mailing list users.

This doesn't migrate anything yet. I also need to update the "Email Addresses" panel to let administrators change the list address.

Test Plan:
- Created and edited a mailing list user.
- Viewed profile.
- Viewed People list.
- Searched for lists / nonlists.
- Grepped for all uses of `getIsDisabled()` / `getIsSystemAgent()` and added relevant corresponding behaviors.
- Hit the web/api/ssh session blocks.

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: eadler, tycho.tatitscheff, epriestley

Maniphest Tasks: T8387

Differential Revision: https://secure.phabricator.com/D13123

+244 -43
+2
resources/sql/autopatches/20150602.mlist.1.sql
··· 1 + ALTER TABLE {$NAMESPACE}_user.user 2 + ADD isMailingList BOOL NOT NULL;
+3 -3
scripts/ssh/ssh-exec.php
··· 182 182 'P' => $user->getPHID(), 183 183 )); 184 184 185 - if (!$user->isUserActivated()) { 185 + if (!$user->canEstablishSSHSessions()) { 186 186 throw new Exception( 187 187 pht( 188 - 'Your account ("%s") is not activated. Visit the web interface '. 189 - 'for more information.', 188 + 'Your account ("%s") does not have permission to establish SSH '. 189 + 'sessions. Visit the web interface for more information.', 190 190 $user->getUsername())); 191 191 } 192 192
+2 -2
scripts/user/account_admin.php
··· 125 125 126 126 $is_system_agent = $user->getIsSystemAgent(); 127 127 $set_system_agent = phutil_console_confirm( 128 - pht('Is this user a bot/script?'), 128 + pht('Is this user a bot?'), 129 129 $default_no = !$is_system_agent); 130 130 131 131 $verify_email = null; ··· 165 165 166 166 printf( 167 167 $tpl, 168 - pht('Bot/Script'), 168 + pht('Bot'), 169 169 $original->getIsSystemAgent() ? 'Y' : 'N', 170 170 $set_system_agent ? 'Y' : 'N'); 171 171
+15 -1
src/applications/auth/engine/PhabricatorAuthSessionEngine.php
··· 158 158 $session_dict[substr($key, 2)] = $value; 159 159 } 160 160 } 161 + 162 + $user = $user_table->loadFromArray($info); 163 + switch ($session_type) { 164 + case PhabricatorAuthSession::TYPE_WEB: 165 + // Explicitly prevent bots and mailing lists from establishing web 166 + // sessions. It's normally impossible to attach authentication to these 167 + // accounts, and likewise impossible to generate sessions, but it's 168 + // technically possible that a session could exist in the database. If 169 + // one does somehow, refuse to load it. 170 + if (!$user->canEstablishWebSessions()) { 171 + return null; 172 + } 173 + break; 174 + } 175 + 161 176 $session = id(new PhabricatorAuthSession())->loadFromArray($session_dict); 162 177 163 178 $ttl = PhabricatorAuthSession::getSessionTypeTTL($session_type); ··· 181 196 unset($unguarded); 182 197 } 183 198 184 - $user = $user_table->loadFromArray($info); 185 199 $user->attachSession($session); 186 200 return $user; 187 201 }
+3 -3
src/applications/conduit/controller/PhabricatorConduitAPIController.php
··· 475 475 ConduitAPIRequest $request, 476 476 PhabricatorUser $user) { 477 477 478 - if (!$user->isUserActivated()) { 478 + if (!$user->canEstablishAPISessions()) { 479 479 return array( 480 - 'ERR-USER-DISABLED', 481 - pht('User account is not activated.'), 480 + 'ERR-INVALID-AUTH', 481 + pht('User account is not permitted to use the API.'), 482 482 ); 483 483 } 484 484
+4
src/applications/conduit/settings/PhabricatorConduitTokensSettingsPanel.php
··· 20 20 } 21 21 22 22 public function isEnabled() { 23 + if ($this->getUser()->getIsMailingList()) { 24 + return false; 25 + } 26 + 23 27 return true; 24 28 } 25 29
+4
src/applications/diffusion/panel/DiffusionSetPasswordSettingsPanel.php
··· 19 19 } 20 20 21 21 public function isEnabled() { 22 + if ($this->getUser()->getIsMailingList()) { 23 + return false; 24 + } 25 + 22 26 return PhabricatorEnv::getEnvConfig('diffusion.allow-http-auth'); 23 27 } 24 28
+3
src/applications/people/conduit/UserConduitAPIMethod.php
··· 18 18 if ($user->getIsSystemAgent()) { 19 19 $roles[] = 'agent'; 20 20 } 21 + if ($user->getIsMailingList()) { 22 + $roles[] = 'list'; 23 + } 21 24 if ($user->getIsAdmin()) { 22 25 $roles[] = 'admin'; 23 26 }
+37 -16
src/applications/people/controller/PhabricatorPeopleCreateController.php
··· 4 4 extends PhabricatorPeopleController { 5 5 6 6 public function handleRequest(AphrontRequest $request) { 7 - $this->requireApplicationCapability( 8 - PeopleCreateUsersCapability::CAPABILITY); 9 7 $admin = $request->getUser(); 10 8 11 9 id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession( ··· 17 15 if ($request->isFormPost()) { 18 16 $v_type = $request->getStr('type'); 19 17 20 - if ($v_type == 'standard' || $v_type == 'bot') { 18 + if ($v_type == 'standard' || $v_type == 'bot' || $v_type == 'list') { 21 19 return id(new AphrontRedirectResponse())->setURI( 22 20 $this->getApplicationURI('new/'.$v_type.'/')); 23 21 } ··· 41 39 $bot_admin = pht( 42 40 'Administrators have greater access to edit these accounts.'); 43 41 42 + $types = array(); 43 + 44 + $can_create = $this->hasApplicationCapability( 45 + PeopleCreateUsersCapability::CAPABILITY); 46 + if ($can_create) { 47 + $types[] = array( 48 + 'type' => 'standard', 49 + 'name' => pht('Create Standard User'), 50 + 'help' => pht('Create a standard user account.'), 51 + ); 52 + } 53 + 54 + $types[] = array( 55 + 'type' => 'bot', 56 + 'name' => pht('Create Bot User'), 57 + 'help' => pht('Create a new user for use with automated scripts.'), 58 + ); 59 + 60 + $types[] = array( 61 + 'type' => 'list', 62 + 'name' => pht('Create Mailing List User'), 63 + 'help' => pht( 64 + 'Create a mailing list user to represent an existing, external '. 65 + 'mailing list like a Google Group or a Mailman list.'), 66 + ); 67 + 68 + $buttons = id(new AphrontFormRadioButtonControl()) 69 + ->setLabel(pht('Account Type')) 70 + ->setName('type') 71 + ->setValue($v_type); 72 + 73 + foreach ($types as $type) { 74 + $buttons->addButton($type['type'], $type['name'], $type['help']); 75 + } 76 + 44 77 $form = id(new AphrontFormView()) 45 78 ->setUser($admin) 46 79 ->appendRemarkupInstructions( ··· 49 82 'explanation of user account types, see [[ %s | User Guide: '. 50 83 'Account Roles ]].', 51 84 PhabricatorEnv::getDoclink('User Guide: Account Roles'))) 52 - ->appendChild( 53 - id(new AphrontFormRadioButtonControl()) 54 - ->setLabel(pht('Account Type')) 55 - ->setName('type') 56 - ->setValue($v_type) 57 - ->addButton( 58 - 'standard', 59 - pht('Create Standard User'), 60 - hsprintf('%s<br /><br />%s', $standard_caption, $standard_admin)) 61 - ->addButton( 62 - 'bot', 63 - pht('Create Bot/Script User'), 64 - hsprintf('%s<br /><br />%s', $bot_caption, $bot_admin))) 85 + ->appendChild($buttons) 65 86 ->appendChild( 66 87 id(new AphrontFormSubmitControl()) 67 88 ->addCancelButton($this->getApplicationURI())
+1 -9
src/applications/people/controller/PhabricatorPeopleListController.php
··· 33 33 $crumbs = parent::buildApplicationCrumbs(); 34 34 $viewer = $this->getRequest()->getUser(); 35 35 36 - $can_create = $this->hasApplicationCapability( 37 - PeopleCreateUsersCapability::CAPABILITY); 38 - if ($can_create) { 36 + if ($viewer->getIsAdmin()) { 39 37 $crumbs->addAction( 40 38 id(new PHUIListItemView()) 41 39 ->setName(pht('Create New User')) 42 40 ->setHref($this->getApplicationURI('create/')) 43 - ->setIcon('fa-plus-square')); 44 - } else if ($viewer->getIsAdmin()) { 45 - $crumbs->addAction( 46 - id(new PHUIListItemView()) 47 - ->setName(pht('Create New Bot')) 48 - ->setHref($this->getApplicationURI('new/bot/')) 49 41 ->setIcon('fa-plus-square')); 50 42 } 51 43
+20 -7
src/applications/people/controller/PhabricatorPeopleNewController.php
··· 7 7 $type = $request->getURIData('type'); 8 8 $admin = $request->getUser(); 9 9 10 + $is_bot = false; 11 + $is_list = false; 10 12 switch ($type) { 11 13 case 'standard': 12 14 $this->requireApplicationCapability( 13 15 PeopleCreateUsersCapability::CAPABILITY); 14 - $is_bot = false; 15 16 break; 16 17 case 'bot': 17 18 $is_bot = true; 19 + break; 20 + case 'list': 21 + $is_list = true; 18 22 break; 19 23 default: 20 24 return new Aphront404Response(); ··· 77 81 // Automatically approve the user, since an admin is creating them. 78 82 $user->setIsApproved(1); 79 83 80 - // If the user is a bot, approve their email too. 81 - if ($is_bot) { 84 + // If the user is a bot or list, approve their email too. 85 + if ($is_bot || $is_list) { 82 86 $email->setIsVerified(1); 83 87 } 84 88 ··· 92 96 ->makeSystemAgentUser($user, true); 93 97 } 94 98 95 - if ($welcome_checked && !$is_bot) { 99 + if ($is_list) { 100 + id(new PhabricatorUserEditor()) 101 + ->setActor($admin) 102 + ->makeMailingListUser($user, true); 103 + } 104 + 105 + if ($welcome_checked && !$is_bot && !$is_list) { 96 106 $user->sendWelcomeEmail($admin); 97 107 } 98 108 ··· 123 133 124 134 if ($is_bot) { 125 135 $form->appendRemarkupInstructions( 126 - pht('You are creating a new **bot/script** user account.')); 136 + pht('You are creating a new **bot** user account.')); 137 + } else if ($is_list) { 138 + $form->appendRemarkupInstructions( 139 + pht('You are creating a new **mailing list** user account.')); 127 140 } else { 128 141 $form->appendRemarkupInstructions( 129 142 pht('You are creating a new **standard** user account.')); ··· 150 163 ->setCaption(PhabricatorUserEmail::describeAllowedAddresses()) 151 164 ->setError($e_email)); 152 165 153 - if (!$is_bot) { 166 + if (!$is_bot && !$is_list) { 154 167 $form->appendChild( 155 168 id(new AphrontFormCheckboxControl()) 156 169 ->addCheckbox( ··· 171 184 ->appendChild(id(new AphrontFormDividerControl())) 172 185 ->appendRemarkupInstructions( 173 186 pht( 174 - '**Why do bot/script accounts need an email address?**'. 187 + '**Why do bot accounts need an email address?**'. 175 188 "\n\n". 176 189 'Although bots do not normally receive email from Phabricator, '. 177 190 'they can interact with other systems which require an email '.
+3
src/applications/people/customfield/PhabricatorUserRolesField.php
··· 37 37 if ($user->getIsSystemAgent()) { 38 38 $roles[] = pht('Bot'); 39 39 } 40 + if ($user->getIsMailingList()) { 41 + $roles[] = pht('Mailing List'); 42 + } 40 43 41 44 if ($roles) { 42 45 return implode(', ', $roles);
+37
src/applications/people/editor/PhabricatorUserEditor.php
··· 278 278 return $this; 279 279 } 280 280 281 + /** 282 + * @task role 283 + */ 284 + public function makeMailingListUser(PhabricatorUser $user, $mailing_list) { 285 + $actor = $this->requireActor(); 286 + 287 + if (!$user->getID()) { 288 + throw new Exception(pht('User has not been created yet!')); 289 + } 290 + 291 + $user->openTransaction(); 292 + $user->beginWriteLocking(); 293 + 294 + $user->reload(); 295 + if ($user->getIsMailingList() == $mailing_list) { 296 + $user->endWriteLocking(); 297 + $user->killTransaction(); 298 + return $this; 299 + } 300 + 301 + $log = PhabricatorUserLog::initializeNewLog( 302 + $actor, 303 + $user->getPHID(), 304 + PhabricatorUserLog::ACTION_MAILING_LIST); 305 + $log->setOldValue($user->getIsMailingList()); 306 + $log->setNewValue($mailing_list); 307 + 308 + $user->setIsMailingList((int)$mailing_list); 309 + $user->save(); 310 + 311 + $log->save(); 312 + 313 + $user->endWriteLocking(); 314 + $user->saveTransaction(); 315 + 316 + return $this; 317 + } 281 318 282 319 /** 283 320 * @task role
+4
src/applications/people/phid/PhabricatorPeopleUserPHIDType.php
··· 44 44 $handle->setFullName($user->getFullName()); 45 45 $handle->setImageURI($user->getProfileImageURI()); 46 46 47 + if ($user->getIsMailingList()) { 48 + $handle->setIcon('fa-envelope-o'); 49 + } 50 + 47 51 $availability = null; 48 52 if (!$user->isUserActivated()) { 49 53 $availability = PhabricatorObjectHandle::AVAILABILITY_DISABLED;
+13
src/applications/people/query/PhabricatorPeopleQuery.php
··· 12 12 private $dateCreatedBefore; 13 13 private $isAdmin; 14 14 private $isSystemAgent; 15 + private $isMailingList; 15 16 private $isDisabled; 16 17 private $isApproved; 17 18 private $nameLike; ··· 64 65 65 66 public function withIsSystemAgent($system_agent) { 66 67 $this->isSystemAgent = $system_agent; 68 + return $this; 69 + } 70 + 71 + public function withIsMailingList($mailing_list) { 72 + $this->isMailingList = $mailing_list; 67 73 return $this; 68 74 } 69 75 ··· 338 344 $conn_r, 339 345 'user.isSystemAgent = %d', 340 346 (int)$this->isSystemAgent); 347 + } 348 + 349 + if ($this->isMailingList !== null) { 350 + $where[] = qsprintf( 351 + $conn_r, 352 + 'user.isMailingList = %d', 353 + (int)$this->isMailingList); 341 354 } 342 355 343 356 if (strlen($this->nameLike)) {
+25
src/applications/people/query/PhabricatorPeopleSearchEngine.php
··· 34 34 $this->readBoolFromRequest($request, 'isSystemAgent')); 35 35 36 36 $saved->setParameter( 37 + 'isMailingList', 38 + $this->readBoolFromRequest($request, 'isMailingList')); 39 + 40 + $saved->setParameter( 37 41 'needsApproval', 38 42 $this->readBoolFromRequest($request, 'needsApproval')); 39 43 ··· 77 81 $is_admin = $saved->getParameter('isAdmin'); 78 82 $is_disabled = $saved->getParameter('isDisabled'); 79 83 $is_system_agent = $saved->getParameter('isSystemAgent'); 84 + $is_mailing_list = $saved->getParameter('isMailingList'); 80 85 $needs_approval = $saved->getParameter('needsApproval'); 81 86 82 87 if ($is_admin !== null) { ··· 91 96 $query->withIsSystemAgent($is_system_agent); 92 97 } 93 98 99 + if ($is_mailing_list !== null) { 100 + $query->withIsMailingList($is_mailing_list); 101 + } 102 + 94 103 if ($needs_approval !== null) { 95 104 $query->withIsApproved(!$needs_approval); 96 105 } ··· 121 130 $is_admin = $this->getBoolFromQuery($saved, 'isAdmin'); 122 131 $is_disabled = $this->getBoolFromQuery($saved, 'isDisabled'); 123 132 $is_system_agent = $this->getBoolFromQuery($saved, 'isSystemAgent'); 133 + $is_mailing_list = $this->getBoolFromQuery($saved, 'isMailingList'); 124 134 $needs_approval = $this->getBoolFromQuery($saved, 'needsApproval'); 125 135 126 136 $form ··· 169 179 ))) 170 180 ->appendChild( 171 181 id(new AphrontFormSelectControl()) 182 + ->setName('isMailingList') 183 + ->setLabel(pht('Mailing Lists')) 184 + ->setValue($is_mailing_list) 185 + ->setOptions( 186 + array( 187 + '' => pht('(Show All)'), 188 + 'true' => pht('Show Only Mailing Lists'), 189 + 'false' => pht('Hide Mailing Lists'), 190 + ))) 191 + ->appendChild( 192 + id(new AphrontFormSelectControl()) 172 193 ->setName('needsApproval') 173 194 ->setLabel(pht('Needs Approval')) 174 195 ->setValue($needs_approval) ··· 276 297 277 298 if ($user->getIsSystemAgent()) { 278 299 $item->addIcon('fa-desktop', pht('Bot')); 300 + } 301 + 302 + if ($user->getIsMailingList()) { 303 + $item->addIcon('fa-envelope-o', pht('Mailing List')); 279 304 } 280 305 281 306 if ($viewer->getIsAdmin()) {
+45 -1
src/applications/people/storage/PhabricatorUser.php
··· 38 38 protected $conduitCertificate; 39 39 40 40 protected $isSystemAgent = 0; 41 + protected $isMailingList = 0; 41 42 protected $isAdmin = 0; 42 43 protected $isDisabled = 0; 43 44 protected $isEmailVerified = 0; ··· 73 74 return (bool)$this->isDisabled; 74 75 case 'isSystemAgent': 75 76 return (bool)$this->isSystemAgent; 77 + case 'isMailingList': 78 + return (bool)$this->isMailingList; 76 79 case 'isEmailVerified': 77 80 return (bool)$this->isEmailVerified; 78 81 case 'isApproved': ··· 112 115 return true; 113 116 } 114 117 118 + public function canEstablishWebSessions() { 119 + if (!$this->isUserActivated()) { 120 + return false; 121 + } 122 + 123 + if ($this->getIsMailingList()) { 124 + return false; 125 + } 126 + 127 + if ($this->getIsSystemAgent()) { 128 + return false; 129 + } 130 + 131 + return true; 132 + } 133 + 134 + public function canEstablishAPISessions() { 135 + if (!$this->isUserActivated()) { 136 + return false; 137 + } 138 + 139 + if ($this->getIsMailingList()) { 140 + return false; 141 + } 142 + 143 + return true; 144 + } 145 + 146 + public function canEstablishSSHSessions() { 147 + if (!$this->isUserActivated()) { 148 + return false; 149 + } 150 + 151 + if ($this->getIsMailingList()) { 152 + return false; 153 + } 154 + 155 + return true; 156 + } 157 + 115 158 /** 116 159 * Returns `true` if this is a standard user who is logged in. Returns `false` 117 160 * for logged out, anonymous, or external users. ··· 140 183 'consoleTab' => 'text64', 141 184 'conduitCertificate' => 'text255', 142 185 'isSystemAgent' => 'bool', 186 + 'isMailingList' => 'bool', 143 187 'isDisabled' => 'bool', 144 188 'isAdmin' => 'bool', 145 189 'timezoneIdentifier' => 'text255', ··· 1032 1076 case PhabricatorPolicyCapability::CAN_VIEW: 1033 1077 return PhabricatorPolicies::POLICY_PUBLIC; 1034 1078 case PhabricatorPolicyCapability::CAN_EDIT: 1035 - if ($this->getIsSystemAgent()) { 1079 + if ($this->getIsSystemAgent() || $this->getIsMailingList()) { 1036 1080 return PhabricatorPolicies::POLICY_ADMIN; 1037 1081 } else { 1038 1082 return PhabricatorPolicies::POLICY_NOONE;
+2
src/applications/people/storage/PhabricatorUserLog.php
··· 16 16 17 17 const ACTION_ADMIN = 'admin'; 18 18 const ACTION_SYSTEM_AGENT = 'system-agent'; 19 + const ACTION_MAILING_LIST = 'mailing-list'; 19 20 const ACTION_DISABLE = 'disable'; 20 21 const ACTION_APPROVE = 'approve'; 21 22 const ACTION_DELETE = 'delete'; ··· 62 63 self::ACTION_EDIT => pht('Edit Account'), 63 64 self::ACTION_ADMIN => pht('Add/Remove Administrator'), 64 65 self::ACTION_SYSTEM_AGENT => pht('Add/Remove System Agent'), 66 + self::ACTION_MAILING_LIST => pht('Add/Remove Mailing List'), 65 67 self::ACTION_DISABLE => pht('Enable/Disable'), 66 68 self::ACTION_APPROVE => pht('Approve Registration'), 67 69 self::ACTION_DELETE => pht('Delete User'),
+7 -1
src/applications/people/typeahead/PhabricatorPeopleDatasource.php
··· 54 54 if ($user->getIsDisabled()) { 55 55 $closed = pht('Disabled'); 56 56 } else if ($user->getIsSystemAgent()) { 57 - $closed = pht('Bot/Script'); 57 + $closed = pht('Bot'); 58 + } else if ($user->getIsMailingList()) { 59 + $closed = pht('Mailing List'); 58 60 } 59 61 60 62 $result = id(new PhabricatorTypeaheadResult()) ··· 64 66 ->setPriorityString($user->getUsername()) 65 67 ->setPriorityType('user') 66 68 ->setClosed($closed); 69 + 70 + if ($user->getIsMailingList()) { 71 + $result->setIcon('fa-envelope-o'); 72 + } 67 73 68 74 if ($this->enrichResults) { 69 75 $display_type = 'User';
+2
src/applications/settings/controller/PhabricatorSettingsMainController.php
··· 97 97 98 98 $result = array(); 99 99 foreach ($panels as $key => $panel) { 100 + $panel->setUser($this->user); 101 + 100 102 if (!$panel->isEnabled()) { 101 103 continue; 102 104 }
+8
src/applications/settings/panel/PhabricatorConduitCertificateSettingsPanel.php
··· 19 19 return pht('Authentication'); 20 20 } 21 21 22 + public function isEnabled() { 23 + if ($this->getUser()->getIsMailingList()) { 24 + return false; 25 + } 26 + 27 + return true; 28 + } 29 + 22 30 public function processRequest(AphrontRequest $request) { 23 31 $user = $this->getUser(); 24 32 $viewer = $request->getUser();
+4
src/applications/settings/panel/PhabricatorSSHKeysSettingsPanel.php
··· 19 19 } 20 20 21 21 public function isEnabled() { 22 + if ($this->getUser()->getIsMailingList()) { 23 + return false; 24 + } 25 + 22 26 return true; 23 27 } 24 28