@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
1<?php
2
3/**
4 * NOTE: When loading ExternalAccounts for use in an authentication context
5 * (that is, you're going to act as the account or link identities or anything
6 * like that) you should require CAN_EDIT capability even if you aren't actually
7 * editing the ExternalAccount.
8 *
9 * ExternalAccounts have a permissive CAN_VIEW policy (like users) because they
10 * interact directly with objects and can leave comments, sign documents, etc.
11 * However, CAN_EDIT is restricted to users who own the accounts.
12 *
13 * @extends PhabricatorCursorPagedPolicyAwareQuery<PhabricatorExternalAccount>
14 */
15final class PhabricatorExternalAccountQuery
16 extends PhabricatorCursorPagedPolicyAwareQuery {
17
18 private $ids;
19 private $phids;
20 private $userPHIDs;
21 private $needImages;
22 private $accountSecrets;
23 private $providerConfigPHIDs;
24 private $needAccountIdentifiers;
25 private $rawAccountIdentifiers;
26
27 public function withUserPHIDs(array $user_phids) {
28 $this->userPHIDs = $user_phids;
29 return $this;
30 }
31
32 public function withPHIDs(array $phids) {
33 $this->phids = $phids;
34 return $this;
35 }
36
37 public function withIDs($ids) {
38 $this->ids = $ids;
39 return $this;
40 }
41
42 public function withAccountSecrets(array $secrets) {
43 $this->accountSecrets = $secrets;
44 return $this;
45 }
46
47 public function needImages($need) {
48 $this->needImages = $need;
49 return $this;
50 }
51
52 public function needAccountIdentifiers($need) {
53 $this->needAccountIdentifiers = $need;
54 return $this;
55 }
56
57 public function withProviderConfigPHIDs(array $phids) {
58 $this->providerConfigPHIDs = $phids;
59 return $this;
60 }
61
62 public function withRawAccountIdentifiers(array $identifiers) {
63 $this->rawAccountIdentifiers = $identifiers;
64 return $this;
65 }
66
67 public function newResultObject() {
68 return new PhabricatorExternalAccount();
69 }
70
71 protected function willFilterPage(array $accounts) {
72 $viewer = $this->getViewer();
73
74 $configs = id(new PhabricatorAuthProviderConfigQuery())
75 ->setViewer($viewer)
76 ->withPHIDs(mpull($accounts, 'getProviderConfigPHID'))
77 ->execute();
78 $configs = mpull($configs, null, 'getPHID');
79
80 foreach ($accounts as $key => $account) {
81 $config_phid = $account->getProviderConfigPHID();
82 $config = idx($configs, $config_phid);
83
84 if (!$config) {
85 unset($accounts[$key]);
86 continue;
87 }
88
89 $account->attachProviderConfig($config);
90 }
91
92 if ($this->needImages) {
93 $file_phids = mpull($accounts, 'getProfileImagePHID');
94 $file_phids = array_filter($file_phids);
95
96 if ($file_phids) {
97 // NOTE: We use the omnipotent viewer here because these files are
98 // usually created during registration and can't be associated with
99 // the correct policies, since the relevant user account does not exist
100 // yet. In effect, if you can see an ExternalAccount, you can see its
101 // profile image.
102 $files = id(new PhabricatorFileQuery())
103 ->setViewer(PhabricatorUser::getOmnipotentUser())
104 ->withPHIDs($file_phids)
105 ->execute();
106 $files = mpull($files, null, 'getPHID');
107 } else {
108 $files = array();
109 }
110
111 $default_file = null;
112 foreach ($accounts as $account) {
113 $image_phid = $account->getProfileImagePHID();
114 if ($image_phid && isset($files[$image_phid])) {
115 $account->attachProfileImageFile($files[$image_phid]);
116 } else {
117 if ($default_file === null) {
118 $default_file = PhabricatorFile::loadBuiltin(
119 $this->getViewer(),
120 'profile.png');
121 }
122 $account->attachProfileImageFile($default_file);
123 }
124 }
125 }
126
127 if ($this->needAccountIdentifiers) {
128 $account_phids = mpull($accounts, 'getPHID');
129
130 $identifiers = id(new PhabricatorExternalAccountIdentifierQuery())
131 ->setViewer($viewer)
132 ->setParentQuery($this)
133 ->withExternalAccountPHIDs($account_phids)
134 ->execute();
135
136 $identifiers = mgroup($identifiers, 'getExternalAccountPHID');
137 foreach ($accounts as $account) {
138 $account_phid = $account->getPHID();
139 $account_identifiers = idx($identifiers, $account_phid, array());
140 $account->attachAccountIdentifiers($account_identifiers);
141 }
142 }
143
144 return $accounts;
145 }
146
147 protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
148 $where = parent::buildWhereClauseParts($conn);
149
150 if ($this->ids !== null) {
151 $where[] = qsprintf(
152 $conn,
153 'account.id IN (%Ld)',
154 $this->ids);
155 }
156
157 if ($this->phids !== null) {
158 $where[] = qsprintf(
159 $conn,
160 'account.phid IN (%Ls)',
161 $this->phids);
162 }
163
164 if ($this->userPHIDs !== null) {
165 $where[] = qsprintf(
166 $conn,
167 'account.userPHID IN (%Ls)',
168 $this->userPHIDs);
169 }
170
171 if ($this->accountSecrets !== null) {
172 $where[] = qsprintf(
173 $conn,
174 'account.accountSecret IN (%Ls)',
175 $this->accountSecrets);
176 }
177
178 if ($this->providerConfigPHIDs !== null) {
179 $where[] = qsprintf(
180 $conn,
181 'account.providerConfigPHID IN (%Ls)',
182 $this->providerConfigPHIDs);
183
184 // If we have a list of ProviderConfig PHIDs and are joining the
185 // identifiers table, also include the list as an additional constraint
186 // on the identifiers table.
187
188 // This does not change the query results (an Account and its
189 // Identifiers always have the same ProviderConfig PHID) but it allows
190 // us to use keys on the Identifier table more efficiently.
191
192 if ($this->shouldJoinIdentifiersTable()) {
193 $where[] = qsprintf(
194 $conn,
195 'identifier.providerConfigPHID IN (%Ls)',
196 $this->providerConfigPHIDs);
197 }
198 }
199
200 if ($this->rawAccountIdentifiers !== null) {
201 $hashes = array();
202
203 foreach ($this->rawAccountIdentifiers as $raw_identifier) {
204 $hashes[] = PhabricatorHash::digestForIndex($raw_identifier);
205 }
206
207 $where[] = qsprintf(
208 $conn,
209 'identifier.identifierHash IN (%Ls)',
210 $hashes);
211 }
212
213 return $where;
214 }
215
216 protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
217 $joins = parent::buildJoinClauseParts($conn);
218
219 if ($this->shouldJoinIdentifiersTable()) {
220 $joins[] = qsprintf(
221 $conn,
222 'JOIN %R identifier ON account.phid = identifier.externalAccountPHID',
223 new PhabricatorExternalAccountIdentifier());
224 }
225
226 return $joins;
227 }
228
229 protected function shouldJoinIdentifiersTable() {
230 return ($this->rawAccountIdentifiers !== null);
231 }
232
233 protected function shouldGroupQueryResultRows() {
234 if ($this->shouldJoinIdentifiersTable()) {
235 return true;
236 }
237
238 return parent::shouldGroupQueryResultRows();
239 }
240
241 protected function getPrimaryTableAlias() {
242 return 'account';
243 }
244
245 public function getQueryApplicationClass() {
246 return PhabricatorPeopleApplication::class;
247 }
248
249}