PageRenderTime 50ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/src/applications/repository/storage/PhabricatorRepositoryURI.php

http://github.com/facebook/phabricator
PHP | 738 lines | 587 code | 133 blank | 18 comment | 55 complexity | c8c18e49dfd100dbb6aff220803b0c0c MD5 | raw file
Possible License(s): JSON, MPL-2.0-no-copyleft-exception, Apache-2.0, BSD-3-Clause, LGPL-2.0, MIT, LGPL-2.1, LGPL-3.0
  1. <?php
  2. final class PhabricatorRepositoryURI
  3. extends PhabricatorRepositoryDAO
  4. implements
  5. PhabricatorApplicationTransactionInterface,
  6. PhabricatorPolicyInterface,
  7. PhabricatorExtendedPolicyInterface,
  8. PhabricatorConduitResultInterface {
  9. protected $repositoryPHID;
  10. protected $uri;
  11. protected $builtinProtocol;
  12. protected $builtinIdentifier;
  13. protected $credentialPHID;
  14. protected $ioType;
  15. protected $displayType;
  16. protected $isDisabled;
  17. private $repository = self::ATTACHABLE;
  18. const BUILTIN_PROTOCOL_SSH = 'ssh';
  19. const BUILTIN_PROTOCOL_HTTP = 'http';
  20. const BUILTIN_PROTOCOL_HTTPS = 'https';
  21. const BUILTIN_IDENTIFIER_ID = 'id';
  22. const BUILTIN_IDENTIFIER_SHORTNAME = 'shortname';
  23. const BUILTIN_IDENTIFIER_CALLSIGN = 'callsign';
  24. const DISPLAY_DEFAULT = 'default';
  25. const DISPLAY_NEVER = 'never';
  26. const DISPLAY_ALWAYS = 'always';
  27. const IO_DEFAULT = 'default';
  28. const IO_OBSERVE = 'observe';
  29. const IO_MIRROR = 'mirror';
  30. const IO_NONE = 'none';
  31. const IO_READ = 'read';
  32. const IO_READWRITE = 'readwrite';
  33. protected function getConfiguration() {
  34. return array(
  35. self::CONFIG_AUX_PHID => true,
  36. self::CONFIG_COLUMN_SCHEMA => array(
  37. 'uri' => 'text255',
  38. 'builtinProtocol' => 'text32?',
  39. 'builtinIdentifier' => 'text32?',
  40. 'credentialPHID' => 'phid?',
  41. 'ioType' => 'text32',
  42. 'displayType' => 'text32',
  43. 'isDisabled' => 'bool',
  44. ),
  45. self::CONFIG_KEY_SCHEMA => array(
  46. 'key_builtin' => array(
  47. 'columns' => array(
  48. 'repositoryPHID',
  49. 'builtinProtocol',
  50. 'builtinIdentifier',
  51. ),
  52. 'unique' => true,
  53. ),
  54. ),
  55. ) + parent::getConfiguration();
  56. }
  57. public static function initializeNewURI() {
  58. return id(new self())
  59. ->setIoType(self::IO_DEFAULT)
  60. ->setDisplayType(self::DISPLAY_DEFAULT)
  61. ->setIsDisabled(0);
  62. }
  63. public function generatePHID() {
  64. return PhabricatorPHID::generateNewPHID(
  65. PhabricatorRepositoryURIPHIDType::TYPECONST);
  66. }
  67. public function attachRepository(PhabricatorRepository $repository) {
  68. $this->repository = $repository;
  69. return $this;
  70. }
  71. public function getRepository() {
  72. return $this->assertAttached($this->repository);
  73. }
  74. public function getRepositoryURIBuiltinKey() {
  75. if (!$this->getBuiltinProtocol()) {
  76. return null;
  77. }
  78. $parts = array(
  79. $this->getBuiltinProtocol(),
  80. $this->getBuiltinIdentifier(),
  81. );
  82. return implode('.', $parts);
  83. }
  84. public function isBuiltin() {
  85. return (bool)$this->getBuiltinProtocol();
  86. }
  87. public function getEffectiveDisplayType() {
  88. $display = $this->getDisplayType();
  89. if ($display != self::DISPLAY_DEFAULT) {
  90. return $display;
  91. }
  92. return $this->getDefaultDisplayType();
  93. }
  94. public function getDefaultDisplayType() {
  95. switch ($this->getEffectiveIOType()) {
  96. case self::IO_MIRROR:
  97. case self::IO_OBSERVE:
  98. case self::IO_NONE:
  99. return self::DISPLAY_NEVER;
  100. case self::IO_READ:
  101. case self::IO_READWRITE:
  102. // By default, only show the "best" version of the builtin URI, not the
  103. // other redundant versions.
  104. $repository = $this->getRepository();
  105. $other_uris = $repository->getURIs();
  106. $identifier_value = array(
  107. self::BUILTIN_IDENTIFIER_SHORTNAME => 3,
  108. self::BUILTIN_IDENTIFIER_CALLSIGN => 2,
  109. self::BUILTIN_IDENTIFIER_ID => 1,
  110. );
  111. $have_identifiers = array();
  112. foreach ($other_uris as $other_uri) {
  113. if ($other_uri->getIsDisabled()) {
  114. continue;
  115. }
  116. $identifier = $other_uri->getBuiltinIdentifier();
  117. if (!$identifier) {
  118. continue;
  119. }
  120. $have_identifiers[$identifier] = $identifier_value[$identifier];
  121. }
  122. $best_identifier = max($have_identifiers);
  123. $this_identifier = $identifier_value[$this->getBuiltinIdentifier()];
  124. if ($this_identifier < $best_identifier) {
  125. return self::DISPLAY_NEVER;
  126. }
  127. return self::DISPLAY_ALWAYS;
  128. }
  129. return self::DISPLAY_NEVER;
  130. }
  131. public function getEffectiveIOType() {
  132. $io = $this->getIoType();
  133. if ($io != self::IO_DEFAULT) {
  134. return $io;
  135. }
  136. return $this->getDefaultIOType();
  137. }
  138. public function getDefaultIOType() {
  139. if ($this->isBuiltin()) {
  140. $repository = $this->getRepository();
  141. $other_uris = $repository->getURIs();
  142. $any_observe = false;
  143. foreach ($other_uris as $other_uri) {
  144. if ($other_uri->getIoType() == self::IO_OBSERVE) {
  145. $any_observe = true;
  146. break;
  147. }
  148. }
  149. if ($any_observe) {
  150. return self::IO_READ;
  151. } else {
  152. return self::IO_READWRITE;
  153. }
  154. }
  155. return self::IO_NONE;
  156. }
  157. public function getNormalizedURI() {
  158. $vcs = $this->getRepository()->getVersionControlSystem();
  159. $map = array(
  160. PhabricatorRepositoryType::REPOSITORY_TYPE_GIT =>
  161. PhabricatorRepositoryURINormalizer::TYPE_GIT,
  162. PhabricatorRepositoryType::REPOSITORY_TYPE_SVN =>
  163. PhabricatorRepositoryURINormalizer::TYPE_SVN,
  164. PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL =>
  165. PhabricatorRepositoryURINormalizer::TYPE_MERCURIAL,
  166. );
  167. $type = $map[$vcs];
  168. $display = (string)$this->getDisplayURI();
  169. $normal_uri = new PhabricatorRepositoryURINormalizer($type, $display);
  170. return $normal_uri->getNormalizedURI();
  171. }
  172. public function getDisplayURI() {
  173. return $this->getURIObject();
  174. }
  175. public function getEffectiveURI() {
  176. return $this->getURIObject();
  177. }
  178. public function getURIEnvelope() {
  179. $uri = $this->getEffectiveURI();
  180. $command_engine = $this->newCommandEngine();
  181. $is_http = $command_engine->isAnyHTTPProtocol();
  182. // For SVN, we use `--username` and `--password` flags separately in the
  183. // CommandEngine, so we don't need to add any credentials here.
  184. $is_svn = $this->getRepository()->isSVN();
  185. $credential_phid = $this->getCredentialPHID();
  186. if ($is_http && !$is_svn && $credential_phid) {
  187. $key = PassphrasePasswordKey::loadFromPHID(
  188. $credential_phid,
  189. PhabricatorUser::getOmnipotentUser());
  190. $uri->setUser($key->getUsernameEnvelope()->openEnvelope());
  191. $uri->setPass($key->getPasswordEnvelope()->openEnvelope());
  192. }
  193. return new PhutilOpaqueEnvelope((string)$uri);
  194. }
  195. private function getURIObject() {
  196. // Users can provide Git/SCP-style URIs in the form "user@host:path".
  197. // In the general case, these are not equivalent to any "ssh://..." form
  198. // because the path is relative.
  199. if ($this->isBuiltin()) {
  200. $builtin_protocol = $this->getForcedProtocol();
  201. $builtin_domain = $this->getForcedHost();
  202. $raw_uri = "{$builtin_protocol}://{$builtin_domain}";
  203. } else {
  204. $raw_uri = $this->getURI();
  205. }
  206. $port = $this->getForcedPort();
  207. $default_ports = array(
  208. 'ssh' => 22,
  209. 'http' => 80,
  210. 'https' => 443,
  211. );
  212. $uri = new PhutilURI($raw_uri);
  213. // Make sure to remove any password from the URI before we do anything
  214. // with it; this should always be provided by the associated credential.
  215. $uri->setPass(null);
  216. $protocol = $this->getForcedProtocol();
  217. if ($protocol) {
  218. $uri->setProtocol($protocol);
  219. }
  220. if ($port) {
  221. $uri->setPort($port);
  222. }
  223. // Remove any explicitly set default ports.
  224. $uri_port = $uri->getPort();
  225. $uri_protocol = $uri->getProtocol();
  226. $uri_default = idx($default_ports, $uri_protocol);
  227. if ($uri_default && ($uri_default == $uri_port)) {
  228. $uri->setPort(null);
  229. }
  230. $user = $this->getForcedUser();
  231. if ($user) {
  232. $uri->setUser($user);
  233. }
  234. $host = $this->getForcedHost();
  235. if ($host) {
  236. $uri->setDomain($host);
  237. }
  238. $path = $this->getForcedPath();
  239. if ($path) {
  240. $uri->setPath($path);
  241. }
  242. return $uri;
  243. }
  244. private function getForcedProtocol() {
  245. $repository = $this->getRepository();
  246. switch ($this->getBuiltinProtocol()) {
  247. case self::BUILTIN_PROTOCOL_SSH:
  248. if ($repository->isSVN()) {
  249. return 'svn+ssh';
  250. } else {
  251. return 'ssh';
  252. }
  253. case self::BUILTIN_PROTOCOL_HTTP:
  254. return 'http';
  255. case self::BUILTIN_PROTOCOL_HTTPS:
  256. return 'https';
  257. default:
  258. return null;
  259. }
  260. }
  261. private function getForcedUser() {
  262. switch ($this->getBuiltinProtocol()) {
  263. case self::BUILTIN_PROTOCOL_SSH:
  264. return AlmanacKeys::getClusterSSHUser();
  265. default:
  266. return null;
  267. }
  268. }
  269. private function getForcedHost() {
  270. $phabricator_uri = PhabricatorEnv::getURI('/');
  271. $phabricator_uri = new PhutilURI($phabricator_uri);
  272. $phabricator_host = $phabricator_uri->getDomain();
  273. switch ($this->getBuiltinProtocol()) {
  274. case self::BUILTIN_PROTOCOL_SSH:
  275. $ssh_host = PhabricatorEnv::getEnvConfig('diffusion.ssh-host');
  276. if ($ssh_host !== null) {
  277. return $ssh_host;
  278. }
  279. return $phabricator_host;
  280. case self::BUILTIN_PROTOCOL_HTTP:
  281. case self::BUILTIN_PROTOCOL_HTTPS:
  282. return $phabricator_host;
  283. default:
  284. return null;
  285. }
  286. }
  287. private function getForcedPort() {
  288. $protocol = $this->getBuiltinProtocol();
  289. if ($protocol == self::BUILTIN_PROTOCOL_SSH) {
  290. return PhabricatorEnv::getEnvConfig('diffusion.ssh-port');
  291. }
  292. // If Phabricator is running on a nonstandard port, use that as the default
  293. // port for URIs with the same protocol.
  294. $is_http = ($protocol == self::BUILTIN_PROTOCOL_HTTP);
  295. $is_https = ($protocol == self::BUILTIN_PROTOCOL_HTTPS);
  296. if ($is_http || $is_https) {
  297. $uri = PhabricatorEnv::getURI('/');
  298. $uri = new PhutilURI($uri);
  299. $port = $uri->getPort();
  300. if (!$port) {
  301. return null;
  302. }
  303. $uri_protocol = $uri->getProtocol();
  304. $use_port =
  305. ($is_http && ($uri_protocol == 'http')) ||
  306. ($is_https && ($uri_protocol == 'https'));
  307. if (!$use_port) {
  308. return null;
  309. }
  310. return $port;
  311. }
  312. return null;
  313. }
  314. private function getForcedPath() {
  315. if (!$this->isBuiltin()) {
  316. return null;
  317. }
  318. $repository = $this->getRepository();
  319. $id = $repository->getID();
  320. $callsign = $repository->getCallsign();
  321. $short_name = $repository->getRepositorySlug();
  322. $clone_name = $repository->getCloneName();
  323. if ($repository->isGit()) {
  324. $suffix = '.git';
  325. } else if ($repository->isHg()) {
  326. $suffix = '/';
  327. } else {
  328. $suffix = '';
  329. $clone_name = '';
  330. }
  331. switch ($this->getBuiltinIdentifier()) {
  332. case self::BUILTIN_IDENTIFIER_ID:
  333. return "/diffusion/{$id}/{$clone_name}{$suffix}";
  334. case self::BUILTIN_IDENTIFIER_SHORTNAME:
  335. return "/source/{$short_name}{$suffix}";
  336. case self::BUILTIN_IDENTIFIER_CALLSIGN:
  337. return "/diffusion/{$callsign}/{$clone_name}{$suffix}";
  338. default:
  339. return null;
  340. }
  341. }
  342. public function getViewURI() {
  343. $id = $this->getID();
  344. return $this->getRepository()->getPathURI("uri/view/{$id}/");
  345. }
  346. public function getEditURI() {
  347. $id = $this->getID();
  348. return $this->getRepository()->getPathURI("uri/edit/{$id}/");
  349. }
  350. public function getAvailableIOTypeOptions() {
  351. $options = array(
  352. self::IO_DEFAULT,
  353. self::IO_NONE,
  354. );
  355. if ($this->isBuiltin()) {
  356. $options[] = self::IO_READ;
  357. $options[] = self::IO_READWRITE;
  358. } else {
  359. $options[] = self::IO_OBSERVE;
  360. $options[] = self::IO_MIRROR;
  361. }
  362. $map = array();
  363. $io_map = self::getIOTypeMap();
  364. foreach ($options as $option) {
  365. $spec = idx($io_map, $option, array());
  366. $label = idx($spec, 'label', $option);
  367. $short = idx($spec, 'short');
  368. $name = pht('%s: %s', $label, $short);
  369. $map[$option] = $name;
  370. }
  371. return $map;
  372. }
  373. public function getAvailableDisplayTypeOptions() {
  374. $options = array(
  375. self::DISPLAY_DEFAULT,
  376. self::DISPLAY_ALWAYS,
  377. self::DISPLAY_NEVER,
  378. );
  379. $map = array();
  380. $display_map = self::getDisplayTypeMap();
  381. foreach ($options as $option) {
  382. $spec = idx($display_map, $option, array());
  383. $label = idx($spec, 'label', $option);
  384. $short = idx($spec, 'short');
  385. $name = pht('%s: %s', $label, $short);
  386. $map[$option] = $name;
  387. }
  388. return $map;
  389. }
  390. public static function getIOTypeMap() {
  391. return array(
  392. self::IO_DEFAULT => array(
  393. 'label' => pht('Default'),
  394. 'short' => pht('Use default behavior.'),
  395. ),
  396. self::IO_OBSERVE => array(
  397. 'icon' => 'fa-download',
  398. 'color' => 'green',
  399. 'label' => pht('Observe'),
  400. 'note' => pht(
  401. 'Phabricator will observe changes to this URI and copy them.'),
  402. 'short' => pht('Copy from a remote.'),
  403. ),
  404. self::IO_MIRROR => array(
  405. 'icon' => 'fa-upload',
  406. 'color' => 'green',
  407. 'label' => pht('Mirror'),
  408. 'note' => pht(
  409. 'Phabricator will push a copy of any changes to this URI.'),
  410. 'short' => pht('Push a copy to a remote.'),
  411. ),
  412. self::IO_NONE => array(
  413. 'icon' => 'fa-times',
  414. 'color' => 'grey',
  415. 'label' => pht('No I/O'),
  416. 'note' => pht(
  417. 'Phabricator will not push or pull any changes to this URI.'),
  418. 'short' => pht('Do not perform any I/O.'),
  419. ),
  420. self::IO_READ => array(
  421. 'icon' => 'fa-folder',
  422. 'color' => 'blue',
  423. 'label' => pht('Read Only'),
  424. 'note' => pht(
  425. 'Phabricator will serve a read-only copy of the repository from '.
  426. 'this URI.'),
  427. 'short' => pht('Serve repository in read-only mode.'),
  428. ),
  429. self::IO_READWRITE => array(
  430. 'icon' => 'fa-folder-open',
  431. 'color' => 'blue',
  432. 'label' => pht('Read/Write'),
  433. 'note' => pht(
  434. 'Phabricator will serve a read/write copy of the repository from '.
  435. 'this URI.'),
  436. 'short' => pht('Serve repository in read/write mode.'),
  437. ),
  438. );
  439. }
  440. public static function getDisplayTypeMap() {
  441. return array(
  442. self::DISPLAY_DEFAULT => array(
  443. 'label' => pht('Default'),
  444. 'short' => pht('Use default behavior.'),
  445. ),
  446. self::DISPLAY_ALWAYS => array(
  447. 'icon' => 'fa-eye',
  448. 'color' => 'green',
  449. 'label' => pht('Visible'),
  450. 'note' => pht('This URI will be shown to users as a clone URI.'),
  451. 'short' => pht('Show as a clone URI.'),
  452. ),
  453. self::DISPLAY_NEVER => array(
  454. 'icon' => 'fa-eye-slash',
  455. 'color' => 'grey',
  456. 'label' => pht('Hidden'),
  457. 'note' => pht(
  458. 'This URI will be hidden from users.'),
  459. 'short' => pht('Do not show as a clone URI.'),
  460. ),
  461. );
  462. }
  463. public function newCommandEngine() {
  464. $repository = $this->getRepository();
  465. return DiffusionCommandEngine::newCommandEngine($repository)
  466. ->setCredentialPHID($this->getCredentialPHID())
  467. ->setURI($this->getEffectiveURI());
  468. }
  469. public function getURIScore() {
  470. $score = 0;
  471. $io_points = array(
  472. self::IO_READWRITE => 200,
  473. self::IO_READ => 100,
  474. );
  475. $score += idx($io_points, $this->getEffectiveIOType(), 0);
  476. $protocol_points = array(
  477. self::BUILTIN_PROTOCOL_SSH => 30,
  478. self::BUILTIN_PROTOCOL_HTTPS => 20,
  479. self::BUILTIN_PROTOCOL_HTTP => 10,
  480. );
  481. $score += idx($protocol_points, $this->getBuiltinProtocol(), 0);
  482. $identifier_points = array(
  483. self::BUILTIN_IDENTIFIER_SHORTNAME => 3,
  484. self::BUILTIN_IDENTIFIER_CALLSIGN => 2,
  485. self::BUILTIN_IDENTIFIER_ID => 1,
  486. );
  487. $score += idx($identifier_points, $this->getBuiltinIdentifier(), 0);
  488. return $score;
  489. }
  490. /* -( PhabricatorApplicationTransactionInterface )------------------------- */
  491. public function getApplicationTransactionEditor() {
  492. return new DiffusionURIEditor();
  493. }
  494. public function getApplicationTransactionTemplate() {
  495. return new PhabricatorRepositoryURITransaction();
  496. }
  497. /* -( PhabricatorPolicyInterface )----------------------------------------- */
  498. public function getCapabilities() {
  499. return array(
  500. PhabricatorPolicyCapability::CAN_VIEW,
  501. PhabricatorPolicyCapability::CAN_EDIT,
  502. );
  503. }
  504. public function getPolicy($capability) {
  505. switch ($capability) {
  506. case PhabricatorPolicyCapability::CAN_VIEW:
  507. case PhabricatorPolicyCapability::CAN_EDIT:
  508. return PhabricatorPolicies::getMostOpenPolicy();
  509. }
  510. }
  511. public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
  512. return false;
  513. }
  514. /* -( PhabricatorExtendedPolicyInterface )--------------------------------- */
  515. public function getExtendedPolicy($capability, PhabricatorUser $viewer) {
  516. $extended = array();
  517. switch ($capability) {
  518. case PhabricatorPolicyCapability::CAN_EDIT:
  519. // To edit a repository URI, you must be able to edit the
  520. // corresponding repository.
  521. $extended[] = array($this->getRepository(), $capability);
  522. break;
  523. }
  524. return $extended;
  525. }
  526. /* -( PhabricatorConduitResultInterface )---------------------------------- */
  527. public function getFieldSpecificationsForConduit() {
  528. return array(
  529. id(new PhabricatorConduitSearchFieldSpecification())
  530. ->setKey('repositoryPHID')
  531. ->setType('phid')
  532. ->setDescription(pht('The associated repository PHID.')),
  533. id(new PhabricatorConduitSearchFieldSpecification())
  534. ->setKey('uri')
  535. ->setType('map<string, string>')
  536. ->setDescription(pht('The raw and effective URI.')),
  537. id(new PhabricatorConduitSearchFieldSpecification())
  538. ->setKey('io')
  539. ->setType('map<string, const>')
  540. ->setDescription(
  541. pht('The raw, default, and effective I/O Type settings.')),
  542. id(new PhabricatorConduitSearchFieldSpecification())
  543. ->setKey('display')
  544. ->setType('map<string, const>')
  545. ->setDescription(
  546. pht('The raw, default, and effective Display Type settings.')),
  547. id(new PhabricatorConduitSearchFieldSpecification())
  548. ->setKey('credentialPHID')
  549. ->setType('phid?')
  550. ->setDescription(
  551. pht('The associated credential PHID, if one exists.')),
  552. id(new PhabricatorConduitSearchFieldSpecification())
  553. ->setKey('disabled')
  554. ->setType('bool')
  555. ->setDescription(pht('True if the URI is disabled.')),
  556. id(new PhabricatorConduitSearchFieldSpecification())
  557. ->setKey('builtin')
  558. ->setType('map<string, string>')
  559. ->setDescription(
  560. pht('Information about builtin URIs.')),
  561. id(new PhabricatorConduitSearchFieldSpecification())
  562. ->setKey('dateCreated')
  563. ->setType('int')
  564. ->setDescription(
  565. pht('Epoch timestamp when the object was created.')),
  566. id(new PhabricatorConduitSearchFieldSpecification())
  567. ->setKey('dateModified')
  568. ->setType('int')
  569. ->setDescription(
  570. pht('Epoch timestamp when the object was last updated.')),
  571. );
  572. }
  573. public function getFieldValuesForConduit() {
  574. return array(
  575. 'repositoryPHID' => $this->getRepositoryPHID(),
  576. 'uri' => array(
  577. 'raw' => $this->getURI(),
  578. 'display' => (string)$this->getDisplayURI(),
  579. 'effective' => (string)$this->getEffectiveURI(),
  580. 'normalized' => (string)$this->getNormalizedURI(),
  581. ),
  582. 'io' => array(
  583. 'raw' => $this->getIOType(),
  584. 'default' => $this->getDefaultIOType(),
  585. 'effective' => $this->getEffectiveIOType(),
  586. ),
  587. 'display' => array(
  588. 'raw' => $this->getDisplayType(),
  589. 'default' => $this->getDefaultDisplayType(),
  590. 'effective' => $this->getEffectiveDisplayType(),
  591. ),
  592. 'credentialPHID' => $this->getCredentialPHID(),
  593. 'disabled' => (bool)$this->getIsDisabled(),
  594. 'builtin' => array(
  595. 'protocol' => $this->getBuiltinProtocol(),
  596. 'identifier' => $this->getBuiltinIdentifier(),
  597. ),
  598. 'dateCreated' => $this->getDateCreated(),
  599. 'dateModified' => $this->getDateModified(),
  600. );
  601. }
  602. public function getConduitSearchAttachments() {
  603. return array();
  604. }
  605. }