From 3857481c65d7cb25b5a2fde3a21d22b4907aafcf Mon Sep 17 00:00:00 2001 From: Yahnis Elsts Date: Tue, 4 Feb 2020 20:29:25 +0200 Subject: [PATCH] GitHub support: Use Basic auth instead of the access_token query parameter See #337, #338 --- Puc/v4p8/Vcs/GitHubApi.php | 88 ++++++++++++++++++++++++-------------- 1 file changed, 56 insertions(+), 32 deletions(-) diff --git a/Puc/v4p8/Vcs/GitHubApi.php b/Puc/v4p8/Vcs/GitHubApi.php index 519d41d1..6bfc16a6 100644 --- a/Puc/v4p8/Vcs/GitHubApi.php +++ b/Puc/v4p8/Vcs/GitHubApi.php @@ -68,7 +68,7 @@ public function getLatestRelease() { $reference = new Puc_v4p8_Vcs_Reference(array( 'name' => $release->tag_name, 'version' => ltrim($release->tag_name, 'v'), //Remove the "v" prefix from "v1.2.3". - 'downloadUrl' => $this->signDownloadUrl($release->zipball_url), + 'downloadUrl' => $release->zipball_url, 'updated' => $release->created_at, 'apiResponse' => $release, )); @@ -84,9 +84,10 @@ public function getLatestRelease() { if ( $this->isAuthenticationEnabled() ) { /** * Keep in mind that we'll need to add an "Accept" header to download this asset. - * @see setReleaseDownloadHeader() + * + * @see setUpdateDownloadHeaders() */ - $reference->downloadUrl = $this->signDownloadUrl($matchingAssets[0]->url); + $reference->downloadUrl = $matchingAssets[0]->url; } else { //It seems that browser_download_url only works for public repositories. //Using an access_token doesn't help. Maybe OAuth would work? @@ -126,7 +127,7 @@ public function getLatestTag() { return new Puc_v4p8_Vcs_Reference(array( 'name' => $tag->name, 'version' => ltrim($tag->name, 'v'), - 'downloadUrl' => $this->signDownloadUrl($tag->zipball_url), + 'downloadUrl' => $tag->zipball_url, 'apiResponse' => $tag, )); } @@ -203,6 +204,10 @@ protected function api($url, $queryParams = array()) { $url = $this->buildApiUrl($url, $queryParams); $options = array('timeout' => 10); + if ( $this->isAuthenticationEnabled() ) { + $options['headers'] = array('Authorization' => $this->getAuthorizationHeader()); + } + if ( !empty($this->httpFilterName) ) { $options = apply_filters($this->httpFilterName, $options); } @@ -245,9 +250,6 @@ protected function buildApiUrl($url, $queryParams) { } $url = 'https://api.github.com' . $url; - if ( !empty($this->accessToken) ) { - $queryParams['access_token'] = $this->accessToken; - } if ( !empty($queryParams) ) { $url = add_query_arg($queryParams, $url); } @@ -285,9 +287,6 @@ public function buildArchiveDownloadUrl($ref = 'master') { urlencode($this->repositoryName), urlencode($ref) ); - if ( !empty($this->accessToken) ) { - $url = $this->signDownloadUrl($url); - } return $url; } @@ -305,6 +304,10 @@ public function getTag($tagName) { public function setAuthentication($credentials) { parent::setAuthentication($credentials); $this->accessToken = is_string($credentials) ? $credentials : null; + + //Optimization: Instead of filtering all HTTP requests, let's do it only when + //WordPress is about to download an update. + add_filter('upgrader_pre_download', array($this, 'addHttpRequestFilter'), 10, 1); //WP 3.7+ } /** @@ -332,17 +335,6 @@ public function chooseReference($configBranch) { return $updateSource; } - /** - * @param string $url - * @return string - */ - public function signDownloadUrl($url) { - if ( empty($this->credentials) ) { - return $url; - } - return add_query_arg('access_token', $this->credentials, $url); - } - /** * Enable updating via release assets. * @@ -361,10 +353,6 @@ public function enableReleaseAssets($fileNameRegex = null) { $this->userName, $this->repositoryName ); - - //Optimization: Instead of filtering all HTTP requests, let's do it only when - //WordPress is about to download an update. - add_filter('upgrader_pre_download', array($this, 'addHttpRequestFilter'), 10, 1); //WP 3.7+ } /** @@ -387,31 +375,67 @@ protected function matchesAssetFilter($releaseAsset) { * @return bool */ public function addHttpRequestFilter($result) { - if ( $this->releaseAssetsEnabled && !$this->downloadFilterAdded && $this->isAuthenticationEnabled() ) { - add_filter('http_request_args', array($this, 'setReleaseDownloadHeader'), 10, 2); + if ( !$this->downloadFilterAdded && $this->isAuthenticationEnabled() ) { + add_filter('http_request_args', array($this, 'setUpdateDownloadHeaders'), 10, 2); + add_action('requests-requests.before_redirect', array($this, 'removeAuthHeaderFromRedirects'), 10, 4); $this->downloadFilterAdded = true; } return $result; } /** - * Set the HTTP header that's necessary to download private release assets. + * Set the HTTP headers that are necessary to download updates from private repositories. * * See GitHub docs: * @link https://developer.github.com/v3/repos/releases/#get-a-single-release-asset + * @link https://developer.github.com/v3/auth/#basic-authentication * * @internal * @param array $requestArgs * @param string $url * @return array */ - public function setReleaseDownloadHeader($requestArgs, $url = '') { - //Is WordPress trying to download one of our assets? - if ( strpos($url, $this->assetApiBaseUrl) !== false ) { - $requestArgs['headers']['accept'] = 'application/octet-stream'; + public function setUpdateDownloadHeaders($requestArgs, $url = '') { + //Is WordPress trying to download one of our release assets? + if ( $this->releaseAssetsEnabled && (strpos($url, $this->assetApiBaseUrl) !== false) ) { + $requestArgs['headers']['Accept'] = 'application/octet-stream'; + } + //Use Basic authentication, but only if the download is from our repository. + $repoApiBaseUrl = $this->buildApiUrl('/repos/:user/:repo/', array()); + if ( $this->isAuthenticationEnabled() && (strpos($url, $repoApiBaseUrl)) === 0 ) { + $requestArgs['headers']['Authorization'] = $this->getAuthorizationHeader(); } return $requestArgs; } + + /** + * When following a redirect, the Requests library will automatically forward + * the authorization header to other hosts. We don't want that because it breaks + * AWS downloads and can leak authorization information. + * + * @internal + * @param string $location + * @param array $headers + */ + public function removeAuthHeaderFromRedirects(&$location, &$headers) { + $repoApiBaseUrl = $this->buildApiUrl('/repos/:user/:repo/', array()); + if ( strpos($location, $repoApiBaseUrl) === 0 ) { + return; //This request is going to GitHub, so it's fine. + } + //Remove the header. + if ( isset($headers['Authorization']) ) { + unset($headers['Authorization']); + } + } + + /** + * Generate the value of the "Authorization" header. + * + * @return string + */ + protected function getAuthorizationHeader() { + return 'Basic ' . base64_encode($this->userName . ':' . $this->accessToken); + } } endif;