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.
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.
13 final class PhabricatorExternalAccountQuery
14 extends PhabricatorCursorPagedPolicyAwareQuery
{
20 private $accountSecrets;
21 private $providerConfigPHIDs;
22 private $needAccountIdentifiers;
23 private $rawAccountIdentifiers;
25 public function withUserPHIDs(array $user_phids) {
26 $this->userPHIDs
= $user_phids;
30 public function withPHIDs(array $phids) {
31 $this->phids
= $phids;
35 public function withIDs($ids) {
40 public function withAccountSecrets(array $secrets) {
41 $this->accountSecrets
= $secrets;
45 public function needImages($need) {
46 $this->needImages
= $need;
50 public function needAccountIdentifiers($need) {
51 $this->needAccountIdentifiers
= $need;
55 public function withProviderConfigPHIDs(array $phids) {
56 $this->providerConfigPHIDs
= $phids;
60 public function withRawAccountIdentifiers(array $identifiers) {
61 $this->rawAccountIdentifiers
= $identifiers;
65 public function newResultObject() {
66 return new PhabricatorExternalAccount();
69 protected function willFilterPage(array $accounts) {
70 $viewer = $this->getViewer();
72 $configs = id(new PhabricatorAuthProviderConfigQuery())
74 ->withPHIDs(mpull($accounts, 'getProviderConfigPHID'))
76 $configs = mpull($configs, null, 'getPHID');
78 foreach ($accounts as $key => $account) {
79 $config_phid = $account->getProviderConfigPHID();
80 $config = idx($configs, $config_phid);
83 unset($accounts[$key]);
87 $account->attachProviderConfig($config);
90 if ($this->needImages
) {
91 $file_phids = mpull($accounts, 'getProfileImagePHID');
92 $file_phids = array_filter($file_phids);
95 // NOTE: We use the omnipotent viewer here because these files are
96 // usually created during registration and can't be associated with
97 // the correct policies, since the relevant user account does not exist
98 // yet. In effect, if you can see an ExternalAccount, you can see its
100 $files = id(new PhabricatorFileQuery())
101 ->setViewer(PhabricatorUser
::getOmnipotentUser())
102 ->withPHIDs($file_phids)
104 $files = mpull($files, null, 'getPHID');
109 $default_file = null;
110 foreach ($accounts as $account) {
111 $image_phid = $account->getProfileImagePHID();
112 if ($image_phid && isset($files[$image_phid])) {
113 $account->attachProfileImageFile($files[$image_phid]);
115 if ($default_file === null) {
116 $default_file = PhabricatorFile
::loadBuiltin(
120 $account->attachProfileImageFile($default_file);
125 if ($this->needAccountIdentifiers
) {
126 $account_phids = mpull($accounts, 'getPHID');
128 $identifiers = id(new PhabricatorExternalAccountIdentifierQuery())
130 ->setParentQuery($this)
131 ->withExternalAccountPHIDs($account_phids)
134 $identifiers = mgroup($identifiers, 'getExternalAccountPHID');
135 foreach ($accounts as $account) {
136 $account_phid = $account->getPHID();
137 $account_identifiers = idx($identifiers, $account_phid, array());
138 $account->attachAccountIdentifiers($account_identifiers);
145 protected function buildWhereClauseParts(AphrontDatabaseConnection
$conn) {
146 $where = parent
::buildWhereClauseParts($conn);
148 if ($this->ids
!== null) {
151 'account.id IN (%Ld)',
155 if ($this->phids
!== null) {
158 'account.phid IN (%Ls)',
162 if ($this->userPHIDs
!== null) {
165 'account.userPHID IN (%Ls)',
169 if ($this->accountSecrets
!== null) {
172 'account.accountSecret IN (%Ls)',
173 $this->accountSecrets
);
176 if ($this->providerConfigPHIDs
!== null) {
179 'account.providerConfigPHID IN (%Ls)',
180 $this->providerConfigPHIDs
);
182 // If we have a list of ProviderConfig PHIDs and are joining the
183 // identifiers table, also include the list as an additional constraint
184 // on the identifiers table.
186 // This does not change the query results (an Account and its
187 // Identifiers always have the same ProviderConfig PHID) but it allows
188 // us to use keys on the Identifier table more efficiently.
190 if ($this->shouldJoinIdentifiersTable()) {
193 'identifier.providerConfigPHID IN (%Ls)',
194 $this->providerConfigPHIDs
);
198 if ($this->rawAccountIdentifiers
!== null) {
201 foreach ($this->rawAccountIdentifiers
as $raw_identifier) {
202 $hashes[] = PhabricatorHash
::digestForIndex($raw_identifier);
207 'identifier.identifierHash IN (%Ls)',
214 protected function buildJoinClauseParts(AphrontDatabaseConnection
$conn) {
215 $joins = parent
::buildJoinClauseParts($conn);
217 if ($this->shouldJoinIdentifiersTable()) {
220 'JOIN %R identifier ON account.phid = identifier.externalAccountPHID',
221 new PhabricatorExternalAccountIdentifier());
227 protected function shouldJoinIdentifiersTable() {
228 return ($this->rawAccountIdentifiers
!== null);
231 protected function shouldGroupQueryResultRows() {
232 if ($this->shouldJoinIdentifiersTable()) {
236 return parent
::shouldGroupQueryResultRows();
239 protected function getPrimaryTableAlias() {
243 public function getQueryApplicationClass() {
244 return 'PhabricatorPeopleApplication';