[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/src/applications/differential/landing/ -> DifferentialLandingToGitHub.php (source)

   1  <?php
   2  
   3  final class DifferentialLandingToGitHub
   4    extends DifferentialLandingStrategy {
   5  
   6    private $account;
   7    private $provider;
   8  
   9    public function processLandRequest(
  10      AphrontRequest $request,
  11      DifferentialRevision $revision,
  12      PhabricatorRepository $repository) {
  13  
  14      $viewer = $request->getUser();
  15      $this->init($viewer, $repository);
  16  
  17      $workspace = $this->getGitWorkspace($repository);
  18  
  19      try {
  20        id(new DifferentialLandingToHostedGit())
  21          ->commitRevisionToWorkspace(
  22            $revision,
  23            $workspace,
  24            $viewer);
  25      } catch (Exception $e) {
  26        throw new PhutilProxyException(
  27          'Failed to commit patch',
  28          $e);
  29      }
  30  
  31      try {
  32        $this->pushWorkspaceRepository($repository, $workspace);
  33      } catch (Exception $e) {
  34        // If it's a permission problem, we know more than git.
  35        $dialog = $this->verifyRemotePermissions($viewer, $revision, $repository);
  36        if ($dialog) {
  37          return $dialog;
  38        }
  39  
  40        // Else, throw what git said.
  41        throw new PhutilProxyException(
  42          'Failed to push changes upstream',
  43          $e);
  44      }
  45    }
  46  
  47    /**
  48     * returns PhabricatorActionView or an array of PhabricatorActionView or null.
  49     */
  50    public function createMenuItem(
  51      PhabricatorUser $viewer,
  52      DifferentialRevision $revision,
  53      PhabricatorRepository $repository) {
  54  
  55      $vcs = $repository->getVersionControlSystem();
  56      if ($vcs !== PhabricatorRepositoryType::REPOSITORY_TYPE_GIT) {
  57        return;
  58      }
  59  
  60      if ($repository->isHosted()) {
  61        return;
  62      }
  63  
  64      try {
  65        // These throw when failing.
  66        $this->init($viewer, $repository);
  67        $this->findGitHubRepo($repository);
  68      } catch (Exception $e) {
  69        return;
  70      }
  71  
  72      return $this->createActionView($revision, pht('Land to GitHub'))
  73        ->setIcon('fa-cloud-upload');
  74    }
  75  
  76    public function pushWorkspaceRepository(
  77      PhabricatorRepository $repository,
  78      ArcanistRepositoryAPI $workspace) {
  79  
  80      $token = $this->getAccessToken();
  81  
  82      $github_repo = $this->findGitHubRepo($repository);
  83  
  84      $remote = urisprintf(
  85        'https://%s:x-oauth-basic@%s/%s.git',
  86        $token,
  87        $this->provider->getProviderDomain(),
  88        $github_repo);
  89  
  90      $workspace->execxLocal(
  91        'push %P HEAD:master',
  92        new PhutilOpaqueEnvelope($remote));
  93    }
  94  
  95    private function init($viewer, $repository) {
  96      $repo_uri = $repository->getRemoteURIObject();
  97      $repo_domain = $repo_uri->getDomain();
  98  
  99      $this->account = id(new PhabricatorExternalAccountQuery())
 100        ->setViewer($viewer)
 101        ->withUserPHIDs(array($viewer->getPHID()))
 102        ->withAccountTypes(array('github'))
 103        ->withAccountDomains(array($repo_domain))
 104        ->requireCapabilities(
 105          array(
 106            PhabricatorPolicyCapability::CAN_VIEW,
 107            PhabricatorPolicyCapability::CAN_EDIT,
 108          ))
 109        ->executeOne();
 110  
 111      if (!$this->account) {
 112        throw new Exception(
 113          "No matching GitHub account found for {$repo_domain}.");
 114      }
 115  
 116      $this->provider = PhabricatorAuthProvider::getEnabledProviderByKey(
 117        $this->account->getProviderKey());
 118      if (!$this->provider) {
 119        throw new Exception("GitHub provider for {$repo_domain} is not enabled.");
 120      }
 121    }
 122  
 123    private function findGitHubRepo(PhabricatorRepository $repository) {
 124      $repo_uri = $repository->getRemoteURIObject();
 125  
 126      $repo_path = $repo_uri->getPath();
 127  
 128      if (substr($repo_path, -4) == '.git') {
 129        $repo_path = substr($repo_path, 0, -4);
 130      }
 131      $repo_path = ltrim($repo_path, '/');
 132  
 133      return $repo_path;
 134    }
 135  
 136    private function getAccessToken() {
 137      return $this->provider->getOAuthAccessToken($this->account);
 138    }
 139  
 140    private function verifyRemotePermissions($viewer, $revision, $repository) {
 141      $github_user = $this->account->getUsername();
 142      $github_repo = $this->findGitHubRepo($repository);
 143  
 144      $uri = urisprintf(
 145        'https://api.github.com/repos/%s/collaborators/%s',
 146        $github_repo,
 147        $github_user);
 148  
 149      $uri = new PhutilURI($uri);
 150      $uri->setQueryParam('access_token', $this->getAccessToken());
 151      list($status, $body, $headers) = id(new HTTPSFuture($uri))->resolve();
 152  
 153      // Likely status codes:
 154      // 204 No Content: Has permissions. Token might be too weak.
 155      // 404 Not Found: Not a collaborator.
 156      // 401 Unauthorized: Token is bad/revoked.
 157  
 158      $no_permission = ($status->getStatusCode() == 404);
 159  
 160      if ($no_permission) {
 161        throw new Exception(
 162          "You don't have permission to push to this repository. \n".
 163          "Push permissions for this repository are managed on GitHub.");
 164      }
 165  
 166      $scopes = BaseHTTPFuture::getHeader($headers, 'X-OAuth-Scopes');
 167      if (strpos($scopes, 'public_repo') === false) {
 168        $provider_key = $this->provider->getProviderKey();
 169        $refresh_token_uri = new PhutilURI("/auth/refresh/{$provider_key}/");
 170        $refresh_token_uri->setQueryParam('scope', 'public_repo');
 171  
 172        return id(new AphrontDialogView())
 173          ->setUser($viewer)
 174          ->setTitle(pht('Stronger token needed'))
 175          ->appendChild(pht(
 176              'In order to complete this action, you need a '.
 177              'stronger GitHub token.'))
 178          ->setSubmitURI($refresh_token_uri)
 179          ->addCancelButton('/D'.$revision->getId())
 180          ->setDisableWorkflowOnSubmit(true)
 181          ->addSubmitButton(pht('Refresh Account Link'));
 182      }
 183    }
 184  }


Generated: Sun Nov 30 09:20:46 2014 Cross-referenced by PHPXref 0.7.1