<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[9564] sites/trunk/api.wordpress.org/public_html/dotorg/trac/pr: Trac: Github PRs: Switch to using a Github App Token for authentication with Github.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="http://meta.trac.wordpress.org/changeset/9564">9564</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"http://meta.trac.wordpress.org/changeset/9564","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>dd32</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2020-03-04 05:10:57 +0000 (Wed, 04 Mar 2020)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Trac: Github PRs: Switch to using a Github App Token for authentication with Github.

This significantly increases the number of API requests that can be made per hour, and enables access to several additional API endpoints.

This uses the MIT licensed https://github.com/adhocore/php-jwt to generate the JWTs as required by the Github App API.

See <a href="http://meta.trac.wordpress.org/ticket/4903">#4903</a>, <a href="http://meta.trac.wordpress.org/ticket/5052">#5052</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#sitestrunkapiwordpressorgpublic_htmldotorgtracprfunctionsphp">sites/trunk/api.wordpress.org/public_html/dotorg/trac/pr/functions.php</a></li>
<li><a href="#sitestrunkapiwordpressorgpublic_htmldotorgtracprwebhookphp">sites/trunk/api.wordpress.org/public_html/dotorg/trac/pr/webhook.php</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li>sites/trunk/api.wordpress.org/public_html/dotorg/trac/pr/adhocore-php-jwt/</li>
<li><a href="#sitestrunkapiwordpressorgpublic_htmldotorgtracpradhocorephpjwtJWTphp">sites/trunk/api.wordpress.org/public_html/dotorg/trac/pr/adhocore-php-jwt/JWT.php</a></li>
<li><a href="#sitestrunkapiwordpressorgpublic_htmldotorgtracpradhocorephpjwtJWTExceptionphp">sites/trunk/api.wordpress.org/public_html/dotorg/trac/pr/adhocore-php-jwt/JWTException.php</a></li>
<li><a href="#sitestrunkapiwordpressorgpublic_htmldotorgtracpradhocorephpjwtValidatesJWTphp">sites/trunk/api.wordpress.org/public_html/dotorg/trac/pr/adhocore-php-jwt/ValidatesJWT.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="sitestrunkapiwordpressorgpublic_htmldotorgtracpradhocorephpjwtJWTphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/api.wordpress.org/public_html/dotorg/trac/pr/adhocore-php-jwt/JWT.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/api.wordpress.org/public_html/dotorg/trac/pr/adhocore-php-jwt/JWT.php                         (rev 0)
+++ sites/trunk/api.wordpress.org/public_html/dotorg/trac/pr/adhocore-php-jwt/JWT.php   2020-03-04 05:10:57 UTC (rev 9564)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,273 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the PHP-JWT package.
+ *
+ * (c) Jitendra Adhikari <jiten.adhikary@gmail.com>
+ *     <https://github.com/adhocore>
+ *
+ * Licensed under MIT license.
+ */
+
+namespace Ahc\Jwt;
+
+/**
+ * JSON Web Token (JWT) implementation in PHP5.5+.
+ *
+ * @author   Jitendra Adhikari <jiten.adhikary@gmail.com>
+ * @license  MIT
+ *
+ * @link     https://github.com/adhocore/jwt
+ */
+class JWT
+{
+    use ValidatesJWT;
+
+    const ERROR_KEY_EMPTY        = 10;
+    const ERROR_KEY_INVALID      = 12;
+    const ERROR_ALGO_UNSUPPORTED = 20;
+    const ERROR_ALGO_MISSING     = 22;
+    const ERROR_INVALID_MAXAGE   = 30;
+    const ERROR_INVALID_LEEWAY   = 32;
+    const ERROR_JSON_FAILED      = 40;
+    const ERROR_TOKEN_INVALID    = 50;
+    const ERROR_TOKEN_EXPIRED    = 52;
+    const ERROR_TOKEN_NOT_NOW    = 54;
+    const ERROR_SIGNATURE_FAILED = 60;
+    const ERROR_KID_UNKNOWN      = 70;
+
+    /** @var array Supported Signing algorithms. */
+    protected $algos = [
+        'HS256' => 'sha256',
+        'HS384' => 'sha384',
+        'HS512' => 'sha512',
+        'RS256' => \OPENSSL_ALGO_SHA256,
+        'RS384' => \OPENSSL_ALGO_SHA384,
+        'RS512' => \OPENSSL_ALGO_SHA512,
+    ];
+
+    /** @var string|resource The signature key. */
+    protected $key;
+
+    /** @var array The list of supported keys with id. */
+    protected $keys = [];
+
+    /** @var int|null Use setTestTimestamp() to set custom value for time(). Useful for testability. */
+    protected $timestamp = null;
+
+    /** @var string The JWT signing algorithm. Defaults to HS256. */
+    protected $algo = 'HS256';
+
+    /** @var int The JWT TTL in seconds. Defaults to 1 hour. */
+    protected $maxAge = 3600;
+
+    /** @var int Grace period in seconds to allow for clock skew. Defaults to 0 seconds. */
+    protected $leeway = 0;
+
+    /** @var string|null The passphrase for RSA signing (optional). */
+    protected $passphrase;
+
+    /**
+     * Constructor.
+     *
+     * @param string|resource $key    The signature key. For RS* it should be file path or resource of private key.
+     * @param string          $algo   The algorithm to sign/verify the token.
+     * @param int             $maxAge The TTL of token to be used to determine expiry if `iat` claim is present.
+     *                                This is also used to provide default `exp` claim in case it is missing.
+     * @param int             $leeway Leeway for clock skew. Shouldnot be more than 2 minutes (120s).
+     * @param string          $pass   The passphrase (only for RS* algos).
+     */
+    public function __construct(
+        $key,
+        string $algo = 'HS256',
+        int $maxAge = 3600,
+        int $leeway = 0,
+        string $pass = null
+    ) {
+        $this->validateConfig($key, $algo, $maxAge, $leeway);
+
+        if (\is_array($key)) {
+            $this->registerKeys($key);
+            $key = \reset($key); // use first one!
+        }
+
+        $this->key        = $key;
+        $this->algo       = $algo;
+        $this->maxAge     = $maxAge;
+        $this->leeway     = $leeway;
+        $this->passphrase = $pass;
+    }
+
+    /**
+     * Register keys for `kid` support.
+     *
+     * @param array $keys Use format: ['<kid>' => '<key data>', '<kid2>' => '<key data2>']
+     *
+     * @return self
+     */
+    public function registerKeys(array $keys): self
+    {
+        $this->keys = \array_merge($this->keys, $keys);
+
+        return $this;
+    }
+
+    /**
+     * Encode payload as JWT token.
+     *
+     * @param array $payload
+     * @param array $header  Extra header (if any) to append.
+     *
+     * @return string URL safe JWT token.
+     */
+    public function encode(array $payload, array $header = []): string
+    {
+        $header = ['typ' => 'JWT', 'alg' => $this->algo] + $header;
+
+        $this->validateKid($header);
+
+        if (!isset($payload['iat']) && !isset($payload['exp'])) {
+            $payload['exp'] = ($this->timestamp ?: \time()) + $this->maxAge;
+        }
+
+        $header    = $this->urlSafeEncode($header);
+        $payload   = $this->urlSafeEncode($payload);
+        $signature = $this->urlSafeEncode($this->sign($header . '.' . $payload));
+
+        return $header . '.' . $payload . '.' . $signature;
+    }
+
+    /**
+     * Decode JWT token and return original payload.
+     *
+     * @param string $token
+     *
+     * @throws JWTException
+     *
+     * @return array
+     */
+    public function decode(string $token): array
+    {
+        if (\substr_count($token, '.') < 2) {
+            throw new JWTException('Invalid token: Incomplete segments', static::ERROR_TOKEN_INVALID);
+        }
+
+        $token = \explode('.', $token, 3);
+        $this->validateHeader((array) $this->urlSafeDecode($token[0]));
+
+        // Validate signature.
+        if (!$this->verify($token[0] . '.' . $token[1], $token[2])) {
+            throw new JWTException('Invalid token: Signature failed', static::ERROR_SIGNATURE_FAILED);
+        }
+
+        $payload = (array) $this->urlSafeDecode($token[1]);
+
+        $this->validateTimestamps($payload);
+
+        return $payload;
+    }
+
+    /**
+     * Spoof current timestamp for testing.
+     *
+     * @param int|null $timestamp
+     */
+    public function setTestTimestamp(int $timestamp = null): self
+    {
+        $this->timestamp = $timestamp;
+
+        return $this;
+    }
+
+    /**
+     * Sign the input with configured key and return the signature.
+     *
+     * @param string $input
+     *
+     * @return string
+     */
+    protected function sign(string $input): string
+    {
+        // HMAC SHA.
+        if (\substr($this->algo, 0, 2) === 'HS') {
+            return \hash_hmac($this->algos[$this->algo], $input, $this->key, true);
+        }
+
+        $this->validateKey();
+
+        \openssl_sign($input, $signature, $this->key, $this->algos[$this->algo]);
+
+        return $signature;
+    }
+
+    /**
+     * Verify the signature of given input.
+     *
+     * @param string $input
+     * @param string $signature
+     *
+     * @throws JWTException When key is invalid.
+     *
+     * @return bool
+     */
+    protected function verify(string $input, string $signature): bool
+    {
+        $algo = $this->algos[$this->algo];
+
+        // HMAC SHA.
+        if (\substr($this->algo, 0, 2) === 'HS') {
+            return \hash_equals($this->urlSafeEncode(\hash_hmac($algo, $input, $this->key, true)), $signature);
+        }
+
+        $this->validateKey();
+
+        $pubKey = \openssl_pkey_get_details($this->key)['key'];
+
+        return \openssl_verify($input, $this->urlSafeDecode($signature, false), $pubKey, $algo) === 1;
+    }
+
+    /**
+     * URL safe base64 encode.
+     *
+     * First serialized the payload as json if it is an array.
+     *
+     * @param array|string $data
+     *
+     * @throws JWTException When JSON encode fails.
+     *
+     * @return string
+     */
+    protected function urlSafeEncode($data): string
+    {
+        if (\is_array($data)) {
+            $data = \json_encode($data, \JSON_UNESCAPED_SLASHES);
+            $this->validateLastJson();
+        }
+
+        return \rtrim(\strtr(\base64_encode($data), '+/', '-_'), '=');
+    }
+
+    /**
+     * URL safe base64 decode.
+     *
+     * @param array|string $data
+     * @param bool         $asJson Whether to parse as JSON (defaults to true).
+     *
+     * @throws JWTException When JSON encode fails.
+     *
+     * @return array|\stdClass|string
+     */
+    protected function urlSafeDecode($data, bool $asJson = true)
+    {
+        if (!$asJson) {
+            return \base64_decode(\strtr($data, '-_', '+/'));
+        }
+
+        $data = \json_decode(\base64_decode(\strtr($data, '-_', '+/')));
+        $this->validateLastJson();
+
+        return $data;
+    }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/api.wordpress.org/public_html/dotorg/trac/pr/adhocore-php-jwt/JWT.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="sitestrunkapiwordpressorgpublic_htmldotorgtracpradhocorephpjwtJWTExceptionphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/api.wordpress.org/public_html/dotorg/trac/pr/adhocore-php-jwt/JWTException.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/api.wordpress.org/public_html/dotorg/trac/pr/adhocore-php-jwt/JWTException.php                                (rev 0)
+++ sites/trunk/api.wordpress.org/public_html/dotorg/trac/pr/adhocore-php-jwt/JWTException.php  2020-03-04 05:10:57 UTC (rev 9564)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,17 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+/*
+ * This file is part of the PHP-JWT package.
+ *
+ * (c) Jitendra Adhikari <jiten.adhikary@gmail.com>
+ *     <https://github.com/adhocore>
+ *
+ * Licensed under MIT license.
+ */
+
+namespace Ahc\Jwt;
+
+class JWTException extends \InvalidArgumentException
+{
+    // ;)
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/api.wordpress.org/public_html/dotorg/trac/pr/adhocore-php-jwt/JWTException.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="sitestrunkapiwordpressorgpublic_htmldotorgtracpradhocorephpjwtValidatesJWTphp"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: sites/trunk/api.wordpress.org/public_html/dotorg/trac/pr/adhocore-php-jwt/ValidatesJWT.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/api.wordpress.org/public_html/dotorg/trac/pr/adhocore-php-jwt/ValidatesJWT.php                                (rev 0)
+++ sites/trunk/api.wordpress.org/public_html/dotorg/trac/pr/adhocore-php-jwt/ValidatesJWT.php  2020-03-04 05:10:57 UTC (rev 9564)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -0,0 +1,133 @@
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the PHP-JWT package.
+ *
+ * (c) Jitendra Adhikari <jiten.adhikary@gmail.com>
+ *     <https://github.com/adhocore>
+ *
+ * Licensed under MIT license.
+ */
+
+namespace Ahc\Jwt;
+
+/**
+ * JSON Web Token (JWT) implementation in PHP7.
+ *
+ * @author   Jitendra Adhikari <jiten.adhikary@gmail.com>
+ * @license  MIT
+ *
+ * @link     https://github.com/adhocore/jwt
+ */
+trait ValidatesJWT
+{
+    /**
+     * Throw up if input parameters invalid.
+     *
+     * @codeCoverageIgnore
+     */
+    protected function validateConfig($key, string $algo, int $maxAge, int $leeway)
+    {
+        if (empty($key)) {
+            throw new JWTException('Signing key cannot be empty', static::ERROR_KEY_EMPTY);
+        }
+
+        if (!isset($this->algos[$algo])) {
+            throw new JWTException('Unsupported algo ' . $algo, static::ERROR_ALGO_UNSUPPORTED);
+        }
+
+        if ($maxAge < 1) {
+            throw new JWTException('Invalid maxAge: Should be greater than 0', static::ERROR_INVALID_MAXAGE);
+        }
+
+        if ($leeway < 0 || $leeway > 120) {
+            throw new JWTException('Invalid leeway: Should be between 0-120', static::ERROR_INVALID_LEEWAY);
+        }
+    }
+
+    /**
+     * Throw up if header invalid.
+     */
+    protected function validateHeader(array $header)
+    {
+        if (empty($header['alg'])) {
+            throw new JWTException('Invalid token: Missing header algo', static::ERROR_ALGO_MISSING);
+        }
+        if (empty($this->algos[$header['alg']])) {
+            throw new JWTException('Invalid token: Unsupported header algo', static::ERROR_ALGO_UNSUPPORTED);
+        }
+
+        $this->validateKid($header);
+    }
+
+    /**
+     * Throw up if kid exists and invalid.
+     */
+    protected function validateKid(array $header)
+    {
+        if (!isset($header['kid'])) {
+            return;
+        }
+        if (empty($this->keys[$header['kid']])) {
+            throw new JWTException('Invalid token: Unknown key ID', static::ERROR_KID_UNKNOWN);
+        }
+
+        $this->key = $this->keys[$header['kid']];
+    }
+
+    /**
+     * Throw up if timestamp claims like iat, exp, nbf are invalid.
+     */
+    protected function validateTimestamps(array $payload)
+    {
+        $timestamp = $this->timestamp ?: \time();
+        $checks    = [
+            ['exp', $this->leeway /*          */ , static::ERROR_TOKEN_EXPIRED, 'Expired'],
+            ['iat', $this->maxAge - $this->leeway, static::ERROR_TOKEN_EXPIRED, 'Expired'],
+            ['nbf', $this->maxAge - $this->leeway, static::ERROR_TOKEN_NOT_NOW, 'Not now'],
+        ];
+
+        foreach ($checks as list($key, $offset, $code, $error)) {
+            if (isset($payload[$key])) {
+                $offset += $payload[$key];
+                $fail    = $key === 'nbf' ? $timestamp <= $offset : $timestamp >= $offset;
+
+                if ($fail) {
+                    throw new JWTException('Invalid token: ' . $error, $code);
+                }
+            }
+        }
+    }
+
+    /**
+     * Throw up if key is not resource or file path to private key.
+     */
+    protected function validateKey()
+    {
+        if (\is_string($key = $this->key)) {
+            if (\substr($key, 0, 7) !== 'file://') {
+                $key = 'file://' . $key;
+            }
+
+            $this->key = \openssl_get_privatekey($key, $this->passphrase ?: '');
+        }
+
+        if (!\is_resource($this->key)) {
+            throw new JWTException('Invalid key: Should be resource of private key', static::ERROR_KEY_INVALID);
+        }
+    }
+
+    /**
+     * Throw up if last json_encode/decode was a failure.
+     */
+    protected function validateLastJson()
+    {
+        if (\JSON_ERROR_NONE === \json_last_error()) {
+            return;
+        }
+
+        throw new JWTException('JSON failed: ' . \json_last_error_msg(), static::ERROR_JSON_FAILED);
+    }
+}
</ins><span class="cx" style="display: block; padding: 0 10px">Property changes on: sites/trunk/api.wordpress.org/public_html/dotorg/trac/pr/adhocore-php-jwt/ValidatesJWT.php
</span><span class="cx" style="display: block; padding: 0 10px">___________________________________________________________________
</span></span></pre></div>
<a id="svneolstyle"></a>
<div class="addfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Added: svn:eol-style</h4></div>
<ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+native
</ins><span class="cx" style="display: block; padding: 0 10px">\ No newline at end of property
</span><a id="sitestrunkapiwordpressorgpublic_htmldotorgtracprfunctionsphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: sites/trunk/api.wordpress.org/public_html/dotorg/trac/pr/functions.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/api.wordpress.org/public_html/dotorg/trac/pr/functions.php    2020-03-03 22:06:18 UTC (rev 9563)
+++ sites/trunk/api.wordpress.org/public_html/dotorg/trac/pr/functions.php      2020-03-04 05:10:57 UTC (rev 9564)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -71,7 +71,7 @@
</span><span class="cx" style="display: block; padding: 0 10px">                'header'        => array_merge(
</span><span class="cx" style="display: block; padding: 0 10px">                        [
</span><span class="cx" style="display: block; padding: 0 10px">                                'Accept: application/json',
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                'Authorization: ' . get_authorization_token(),
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         'Authorization: ' . get_authorization_token( $url ),
</ins><span class="cx" style="display: block; padding: 0 10px">                         ],
</span><span class="cx" style="display: block; padding: 0 10px">                        $headers
</span><span class="cx" style="display: block; padding: 0 10px">                ),
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -88,15 +88,85 @@
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="cx" style="display: block; padding: 0 10px">  * Fetch an Authorization token for a Github API request.
</span><span class="cx" style="display: block; padding: 0 10px">  */
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-function get_authorization_token() {
-       global $wpdb;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+function get_authorization_token( $url ) {
+       // There are two different tokens used, JWT and App Installation tokens.
+       if ( false !== stripos( $url, 'api.github.com/app' ) ) {
+               // App Endpoint, that's a JWT token
+               return 'BEARER ' . get_jwt_app_token();
+       } else {
+               // Regular Endpoint, use an App Installation token
+               return 'BEARER ' . get_app_install_token();
+       }
+}
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        // TODO: This needs to be switched to a Github App token.
-       // This works temporarily to avoid the low unauthenticated limits.
-       return 'BEARER ' . $wpdb->get_var( "SELECT access_token FROM wporg_github_users WHERE github_user = 'dd32'");
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+/**
+ * Fetch a JWT Authorization token for the Github /app API endpoints.
+ */
+function get_jwt_app_token() {
+       $token = wp_cache_get( GH_TRAC_APP_ID, 'API:JWT-token' );
+       if ( $token ) {
+               return $token;
+       }
+
+       include_once __DIR__ . '/adhocore-php-jwt/ValidatesJWT.php';
+       include_once __DIR__ . '/adhocore-php-jwt/JWTException.php';
+       include_once __DIR__ . '/adhocore-php-jwt/JWT.php';
+
+       $key = openssl_pkey_get_private( base64_decode( GH_TRAC_APP_PRIV_KEY ) );
+       $jwt = new \Ahc\Jwt\JWT( $key, 'RS256' );
+
+       $token = $jwt->encode([
+               'iat' => time(),
+               'exp' => time() + 10*60,
+               'iss' => GH_TRAC_APP_ID,
+       ]);
+
+       // Cache it for 9 mins (It's valid for 10min)
+       wp_cache_set( GH_TRAC_APP_ID, $token, 'API:JWT-token', 9 * 60 );
+
+       return $token;
</ins><span class="cx" style="display: block; padding: 0 10px"> }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+ * Fetch an App Authorization token for accessing Github Resources.
+ * 
+ * This assumes that the Github App will only ever be installed on the @WordPress organization.
+ */
+function get_app_install_token() {
+       $token = wp_cache_get( GH_TRAC_APP_ID . '-install-token', 'API:JWT-token' );
+       if ( $token ) {
+               return $token;
+       }
+
+       $installs = api_request(
+               '/app/installations',
+               null,
+               [ 'Accept: application/vnd.github.machine-man-preview+json' ]
+       );
+       if ( ! $installs || empty( $installs[0]->access_tokens_url ) ) {
+               return false;
+       }
+
+       $access_token = api_request(
+               $installs[0]->access_tokens_url,
+               null,
+               [ 'Accept: application/vnd.github.machine-man-preview+json' ],
+               'POST'
+       );
+       if ( ! $access_token || empty( $access_token->token ) ) {
+               return false;
+       }
+
+       $token     = $access_token->token;
+       $token_exp = strtotime( $access_token->expires_at );
+
+       // Cache the token for 1 minute less than what it's valid for.
+       wp_cache_set( GH_TRAC_APP_ID . '-install-token', $token, 'API:JWT-token', $token_exp - time() - 60 );
+
+       return $token;
+}
+
+/**
</ins><span class="cx" style="display: block; padding: 0 10px">  * Use some rough heuristics to find the Trac ticket for a given PR.
</span><span class="cx" style="display: block; padding: 0 10px">  * 
</span><span class="cx" style="display: block; padding: 0 10px">  * TODO: This should probably support multiple Trac Tickets, but once you start to use the final few regexes it can start to match Gutenberg references.
</span></span></pre></div>
<a id="sitestrunkapiwordpressorgpublic_htmldotorgtracprwebhookphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: sites/trunk/api.wordpress.org/public_html/dotorg/trac/pr/webhook.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/api.wordpress.org/public_html/dotorg/trac/pr/webhook.php      2020-03-03 22:06:18 UTC (rev 9563)
+++ sites/trunk/api.wordpress.org/public_html/dotorg/trac/pr/webhook.php        2020-03-04 05:10:57 UTC (rev 9564)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -3,6 +3,8 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> require dirname( dirname( dirname( __DIR__ ) ) ) . '/init.php';
</span><span class="cx" style="display: block; padding: 0 10px"> require dirname( dirname( dirname( __DIR__ ) ) ) . '/includes/hyperdb/bb-10-hyper-db.php';
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+require dirname( dirname( dirname( __DIR__ ) ) ) . '/includes/object-cache.php';
+\wp_cache_init();
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> require __DIR__ . '/functions.php';
</span><span class="cx" style="display: block; padding: 0 10px"> require __DIR__ . '/class-trac.php';
</span></span></pre>
</div>
</div>

</body>
</html>