Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/diffusion/editor/DiffusionURIEditor.php
12242 views
1
<?php
2
3
final class DiffusionURIEditor
4
extends PhabricatorApplicationTransactionEditor {
5
6
private $repository;
7
private $repositoryPHID;
8
9
public function getEditorApplicationClass() {
10
return 'PhabricatorDiffusionApplication';
11
}
12
13
public function getEditorObjectsDescription() {
14
return pht('Diffusion URIs');
15
}
16
17
public function getTransactionTypes() {
18
$types = parent::getTransactionTypes();
19
20
$types[] = PhabricatorRepositoryURITransaction::TYPE_REPOSITORY;
21
$types[] = PhabricatorRepositoryURITransaction::TYPE_URI;
22
$types[] = PhabricatorRepositoryURITransaction::TYPE_IO;
23
$types[] = PhabricatorRepositoryURITransaction::TYPE_DISPLAY;
24
$types[] = PhabricatorRepositoryURITransaction::TYPE_CREDENTIAL;
25
$types[] = PhabricatorRepositoryURITransaction::TYPE_DISABLE;
26
27
return $types;
28
}
29
30
protected function getCustomTransactionOldValue(
31
PhabricatorLiskDAO $object,
32
PhabricatorApplicationTransaction $xaction) {
33
34
switch ($xaction->getTransactionType()) {
35
case PhabricatorRepositoryURITransaction::TYPE_URI:
36
return $object->getURI();
37
case PhabricatorRepositoryURITransaction::TYPE_IO:
38
return $object->getIOType();
39
case PhabricatorRepositoryURITransaction::TYPE_DISPLAY:
40
return $object->getDisplayType();
41
case PhabricatorRepositoryURITransaction::TYPE_REPOSITORY:
42
return $object->getRepositoryPHID();
43
case PhabricatorRepositoryURITransaction::TYPE_CREDENTIAL:
44
return $object->getCredentialPHID();
45
case PhabricatorRepositoryURITransaction::TYPE_DISABLE:
46
return (int)$object->getIsDisabled();
47
}
48
49
return parent::getCustomTransactionOldValue($object, $xaction);
50
}
51
52
protected function getCustomTransactionNewValue(
53
PhabricatorLiskDAO $object,
54
PhabricatorApplicationTransaction $xaction) {
55
56
switch ($xaction->getTransactionType()) {
57
case PhabricatorRepositoryURITransaction::TYPE_URI:
58
case PhabricatorRepositoryURITransaction::TYPE_IO:
59
case PhabricatorRepositoryURITransaction::TYPE_DISPLAY:
60
case PhabricatorRepositoryURITransaction::TYPE_REPOSITORY:
61
case PhabricatorRepositoryURITransaction::TYPE_CREDENTIAL:
62
return $xaction->getNewValue();
63
case PhabricatorRepositoryURITransaction::TYPE_DISABLE:
64
return (int)$xaction->getNewValue();
65
}
66
67
return parent::getCustomTransactionNewValue($object, $xaction);
68
}
69
70
protected function applyCustomInternalTransaction(
71
PhabricatorLiskDAO $object,
72
PhabricatorApplicationTransaction $xaction) {
73
74
switch ($xaction->getTransactionType()) {
75
case PhabricatorRepositoryURITransaction::TYPE_URI:
76
if (!$this->getIsNewObject()) {
77
$old_uri = $object->getEffectiveURI();
78
} else {
79
$old_uri = null;
80
81
// When creating a URI via the API, we may not have processed the
82
// repository transaction yet. Attach the repository here to make
83
// sure we have it for the calls below.
84
if ($this->repository) {
85
$object->attachRepository($this->repository);
86
}
87
}
88
89
$object->setURI($xaction->getNewValue());
90
91
// If we've changed the domain or protocol of the URI, remove the
92
// current credential. This improves behavior in several cases:
93
94
// If a user switches between protocols with different credential
95
// types, like HTTP and SSH, the old credential won't be valid anyway.
96
// It's cleaner to remove it than leave a bad credential in place.
97
98
// If a user switches hosts, the old credential is probably not
99
// correct (and potentially confusing/misleading). Removing it forces
100
// users to double check that they have the correct credentials.
101
102
// If an attacker can't see a symmetric credential like a username and
103
// password, they could still potentially capture it by changing the
104
// host for a URI that uses it to `evil.com`, a server they control,
105
// then observing the requests. Removing the credential prevents this
106
// kind of escalation.
107
108
// Since port and path changes are less likely to fall among these
109
// cases, they don't trigger a credential wipe.
110
111
$new_uri = $object->getEffectiveURI();
112
if ($old_uri) {
113
$new_proto = ($old_uri->getProtocol() != $new_uri->getProtocol());
114
$new_domain = ($old_uri->getDomain() != $new_uri->getDomain());
115
if ($new_proto || $new_domain) {
116
$object->setCredentialPHID(null);
117
}
118
}
119
break;
120
case PhabricatorRepositoryURITransaction::TYPE_IO:
121
$object->setIOType($xaction->getNewValue());
122
break;
123
case PhabricatorRepositoryURITransaction::TYPE_DISPLAY:
124
$object->setDisplayType($xaction->getNewValue());
125
break;
126
case PhabricatorRepositoryURITransaction::TYPE_REPOSITORY:
127
$object->setRepositoryPHID($xaction->getNewValue());
128
$object->attachRepository($this->repository);
129
break;
130
case PhabricatorRepositoryURITransaction::TYPE_CREDENTIAL:
131
$object->setCredentialPHID($xaction->getNewValue());
132
break;
133
case PhabricatorRepositoryURITransaction::TYPE_DISABLE:
134
$object->setIsDisabled($xaction->getNewValue());
135
break;
136
}
137
}
138
139
protected function applyCustomExternalTransaction(
140
PhabricatorLiskDAO $object,
141
PhabricatorApplicationTransaction $xaction) {
142
143
switch ($xaction->getTransactionType()) {
144
case PhabricatorRepositoryURITransaction::TYPE_URI:
145
case PhabricatorRepositoryURITransaction::TYPE_IO:
146
case PhabricatorRepositoryURITransaction::TYPE_DISPLAY:
147
case PhabricatorRepositoryURITransaction::TYPE_REPOSITORY:
148
case PhabricatorRepositoryURITransaction::TYPE_CREDENTIAL:
149
case PhabricatorRepositoryURITransaction::TYPE_DISABLE:
150
return;
151
}
152
153
return parent::applyCustomExternalTransaction($object, $xaction);
154
}
155
156
protected function validateTransaction(
157
PhabricatorLiskDAO $object,
158
$type,
159
array $xactions) {
160
161
$errors = parent::validateTransaction($object, $type, $xactions);
162
163
switch ($type) {
164
case PhabricatorRepositoryURITransaction::TYPE_REPOSITORY:
165
// Save this, since we need it to validate TYPE_IO transactions.
166
$this->repositoryPHID = $object->getRepositoryPHID();
167
168
$missing = $this->validateIsEmptyTextField(
169
$object->getRepositoryPHID(),
170
$xactions);
171
if ($missing) {
172
// NOTE: This isn't being marked as a missing field error because
173
// it's a fundamental, required property of the URI.
174
$errors[] = new PhabricatorApplicationTransactionValidationError(
175
$type,
176
pht('Required'),
177
pht(
178
'When creating a repository URI, you must specify which '.
179
'repository the URI will belong to.'),
180
nonempty(last($xactions), null));
181
break;
182
}
183
184
$viewer = $this->getActor();
185
186
foreach ($xactions as $xaction) {
187
$repository_phid = $xaction->getNewValue();
188
189
// If this isn't changing anything, let it through as-is.
190
if ($repository_phid == $object->getRepositoryPHID()) {
191
continue;
192
}
193
194
if (!$this->getIsNewObject()) {
195
$errors[] = new PhabricatorApplicationTransactionValidationError(
196
$type,
197
pht('Invalid'),
198
pht(
199
'The repository a URI is associated with is immutable, and '.
200
'can not be changed after the URI is created.'),
201
$xaction);
202
continue;
203
}
204
205
$repository = id(new PhabricatorRepositoryQuery())
206
->setViewer($viewer)
207
->withPHIDs(array($repository_phid))
208
->requireCapabilities(
209
array(
210
PhabricatorPolicyCapability::CAN_VIEW,
211
PhabricatorPolicyCapability::CAN_EDIT,
212
))
213
->executeOne();
214
if (!$repository) {
215
$errors[] = new PhabricatorApplicationTransactionValidationError(
216
$type,
217
pht('Invalid'),
218
pht(
219
'To create a URI for a repository ("%s"), it must exist and '.
220
'you must have permission to edit it.',
221
$repository_phid),
222
$xaction);
223
continue;
224
}
225
226
$this->repository = $repository;
227
$this->repositoryPHID = $repository_phid;
228
}
229
break;
230
case PhabricatorRepositoryURITransaction::TYPE_CREDENTIAL:
231
$viewer = $this->getActor();
232
foreach ($xactions as $xaction) {
233
$credential_phid = $xaction->getNewValue();
234
235
if ($credential_phid == $object->getCredentialPHID()) {
236
continue;
237
}
238
239
// Anyone who can edit a URI can remove the credential.
240
if ($credential_phid === null) {
241
continue;
242
}
243
244
$credential = id(new PassphraseCredentialQuery())
245
->setViewer($viewer)
246
->withPHIDs(array($credential_phid))
247
->executeOne();
248
if (!$credential) {
249
$errors[] = new PhabricatorApplicationTransactionValidationError(
250
$type,
251
pht('Invalid'),
252
pht(
253
'You can only associate a credential ("%s") with a repository '.
254
'URI if it exists and you have permission to see it.',
255
$credential_phid),
256
$xaction);
257
continue;
258
}
259
}
260
break;
261
case PhabricatorRepositoryURITransaction::TYPE_URI:
262
$missing = $this->validateIsEmptyTextField(
263
$object->getURI(),
264
$xactions);
265
266
if ($missing) {
267
$error = new PhabricatorApplicationTransactionValidationError(
268
$type,
269
pht('Required'),
270
pht('A repository URI must have a nonempty URI.'),
271
nonempty(last($xactions), null));
272
273
$error->setIsMissingFieldError(true);
274
$errors[] = $error;
275
break;
276
}
277
278
foreach ($xactions as $xaction) {
279
$new_uri = $xaction->getNewValue();
280
if ($new_uri == $object->getURI()) {
281
continue;
282
}
283
284
try {
285
PhabricatorRepository::assertValidRemoteURI($new_uri);
286
} catch (Exception $ex) {
287
$errors[] = new PhabricatorApplicationTransactionValidationError(
288
$type,
289
pht('Invalid'),
290
$ex->getMessage(),
291
$xaction);
292
continue;
293
}
294
}
295
296
break;
297
case PhabricatorRepositoryURITransaction::TYPE_IO:
298
$available = $object->getAvailableIOTypeOptions();
299
foreach ($xactions as $xaction) {
300
$new = $xaction->getNewValue();
301
302
if (empty($available[$new])) {
303
$errors[] = new PhabricatorApplicationTransactionValidationError(
304
$type,
305
pht('Invalid'),
306
pht(
307
'Value "%s" is not a valid IO setting for this URI. '.
308
'Available types for this URI are: %s.',
309
$new,
310
implode(', ', array_keys($available))),
311
$xaction);
312
continue;
313
}
314
315
// If we are setting this URI to use "Observe", we must have no
316
// other "Observe" URIs and must also have no "Read/Write" URIs.
317
318
// If we are setting this URI to "Read/Write", we must have no
319
// other "Observe" URIs. It's OK to have other "Read/Write" URIs.
320
321
$no_observers = false;
322
$no_readwrite = false;
323
switch ($new) {
324
case PhabricatorRepositoryURI::IO_OBSERVE:
325
$no_readwrite = true;
326
$no_observers = true;
327
break;
328
case PhabricatorRepositoryURI::IO_READWRITE:
329
$no_observers = true;
330
break;
331
}
332
333
if ($no_observers || $no_readwrite) {
334
$repository = id(new PhabricatorRepositoryQuery())
335
->setViewer(PhabricatorUser::getOmnipotentUser())
336
->withPHIDs(array($this->repositoryPHID))
337
->needURIs(true)
338
->executeOne();
339
$uris = $repository->getURIs();
340
341
$observe_conflict = null;
342
$readwrite_conflict = null;
343
foreach ($uris as $uri) {
344
// If this is the URI being edited, it can not conflict with
345
// itself.
346
if ($uri->getID() == $object->getID()) {
347
continue;
348
}
349
350
$io_type = $uri->getEffectiveIOType();
351
352
if ($io_type == PhabricatorRepositoryURI::IO_READWRITE) {
353
if ($no_readwrite) {
354
$readwrite_conflict = $uri;
355
break;
356
}
357
}
358
359
if ($io_type == PhabricatorRepositoryURI::IO_OBSERVE) {
360
if ($no_observers) {
361
$observe_conflict = $uri;
362
break;
363
}
364
}
365
}
366
367
if ($observe_conflict) {
368
if ($new == PhabricatorRepositoryURI::IO_OBSERVE) {
369
$message = pht(
370
'You can not set this URI to use Observe IO because '.
371
'another URI for this repository is already configured '.
372
'in Observe IO mode. A repository can not observe two '.
373
'different remotes simultaneously. Turn off IO for the '.
374
'other URI first.');
375
} else {
376
$message = pht(
377
'You can not set this URI to use Read/Write IO because '.
378
'another URI for this repository is already configured '.
379
'in Observe IO mode. An observed repository can not be '.
380
'made writable. Turn off IO for the other URI first.');
381
}
382
383
$errors[] = new PhabricatorApplicationTransactionValidationError(
384
$type,
385
pht('Invalid'),
386
$message,
387
$xaction);
388
continue;
389
}
390
391
if ($readwrite_conflict) {
392
$message = pht(
393
'You can not set this URI to use Observe IO because '.
394
'another URI for this repository is already configured '.
395
'in Read/Write IO mode. A repository can not simultaneously '.
396
'be writable and observe a remote. Turn off IO for the '.
397
'other URI first.');
398
399
$errors[] = new PhabricatorApplicationTransactionValidationError(
400
$type,
401
pht('Invalid'),
402
$message,
403
$xaction);
404
continue;
405
}
406
}
407
}
408
409
break;
410
case PhabricatorRepositoryURITransaction::TYPE_DISPLAY:
411
$available = $object->getAvailableDisplayTypeOptions();
412
foreach ($xactions as $xaction) {
413
$new = $xaction->getNewValue();
414
415
if (empty($available[$new])) {
416
$errors[] = new PhabricatorApplicationTransactionValidationError(
417
$type,
418
pht('Invalid'),
419
pht(
420
'Value "%s" is not a valid display setting for this URI. '.
421
'Available types for this URI are: %s.',
422
$new,
423
implode(', ', array_keys($available))));
424
}
425
}
426
break;
427
428
case PhabricatorRepositoryURITransaction::TYPE_DISABLE:
429
$old = $object->getIsDisabled();
430
foreach ($xactions as $xaction) {
431
$new = $xaction->getNewValue();
432
433
if ($old == $new) {
434
continue;
435
}
436
437
if (!$object->isBuiltin()) {
438
continue;
439
}
440
441
$errors[] = new PhabricatorApplicationTransactionValidationError(
442
$type,
443
pht('Invalid'),
444
pht('You can not manually disable builtin URIs.'));
445
}
446
break;
447
}
448
449
return $errors;
450
}
451
452
protected function applyFinalEffects(
453
PhabricatorLiskDAO $object,
454
array $xactions) {
455
456
// Synchronize the repository state based on the presence of an "Observe"
457
// URI.
458
$repository = $object->getRepository();
459
460
$uris = id(new PhabricatorRepositoryURIQuery())
461
->setViewer(PhabricatorUser::getOmnipotentUser())
462
->withRepositories(array($repository))
463
->execute();
464
465
// Reattach the current URIs to the repository: we're going to rebuild
466
// the index explicitly below, and want to include any changes made to
467
// this URI in the index update.
468
$repository->attachURIs($uris);
469
470
$observe_uri = null;
471
foreach ($uris as $uri) {
472
if ($uri->getIoType() != PhabricatorRepositoryURI::IO_OBSERVE) {
473
continue;
474
}
475
476
$observe_uri = $uri;
477
break;
478
}
479
480
$was_hosted = $repository->isHosted();
481
482
if ($observe_uri) {
483
$repository
484
->setHosted(false)
485
->setDetail('remote-uri', (string)$observe_uri->getEffectiveURI())
486
->setCredentialPHID($observe_uri->getCredentialPHID());
487
} else {
488
$repository
489
->setHosted(true)
490
->setDetail('remote-uri', null)
491
->setCredentialPHID(null);
492
}
493
494
$repository->save();
495
496
// Explicitly update the URI index.
497
$repository->updateURIIndex();
498
499
$is_hosted = $repository->isHosted();
500
501
// If we've swapped the repository from hosted to observed or vice versa,
502
// reset all the cluster version clocks.
503
if ($was_hosted != $is_hosted) {
504
$cluster_engine = id(new DiffusionRepositoryClusterEngine())
505
->setViewer($this->getActor())
506
->setRepository($repository)
507
->synchronizeWorkingCopyAfterHostingChange();
508
}
509
510
$repository->writeStatusMessage(
511
PhabricatorRepositoryStatusMessage::TYPE_NEEDS_UPDATE,
512
null);
513
514
return $xactions;
515
}
516
517
}
518
519