[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

/inc/ -> JWT.php (source)

   1  <?php
   2  
   3  namespace dokuwiki;
   4  
   5  /**
   6   * Minimal JWT implementation
   7   */
   8  class JWT
   9  {
  10      protected $user;
  11      protected $issued;
  12      protected $secret;
  13  
  14      /**
  15       * Create a new JWT object
  16       *
  17       * Use validate() or create() to create a new instance
  18       *
  19       * @param string $user
  20       * @param int $issued
  21       */
  22      protected function __construct($user, $issued)
  23      {
  24          $this->user = $user;
  25          $this->issued = $issued;
  26      }
  27  
  28      /**
  29       * Load the cookiesalt as secret
  30       *
  31       * @return string
  32       */
  33      protected static function getSecret()
  34      {
  35          return auth_cookiesalt(false, true);
  36      }
  37  
  38      /**
  39       * Create a new instance from a token
  40       *
  41       * @param $token
  42       * @return self
  43       * @throws \Exception
  44       */
  45      public static function validate($token)
  46      {
  47          [$header, $payload, $signature] = sexplode('.', $token, 3, '');
  48          $signature = base64_decode($signature);
  49  
  50          if (!hash_equals($signature, hash_hmac('sha256', "$header.$payload", self::getSecret(), true))) {
  51              throw new \Exception('Invalid JWT signature');
  52          }
  53  
  54          try {
  55              $header = json_decode(base64_decode($header), true, 512, JSON_THROW_ON_ERROR);
  56              $payload = json_decode(base64_decode($payload), true, 512, JSON_THROW_ON_ERROR);
  57          } catch (\Exception $e) {
  58              throw new \Exception('Invalid JWT', $e->getCode(), $e);
  59          }
  60  
  61          if (!$header || !$payload || !$signature) {
  62              throw new \Exception('Invalid JWT');
  63          }
  64  
  65          if ($header['alg'] !== 'HS256') {
  66              throw new \Exception('Unsupported JWT algorithm');
  67          }
  68          if ($header['typ'] !== 'JWT') {
  69              throw new \Exception('Unsupported JWT type');
  70          }
  71          if ($payload['iss'] !== 'dokuwiki') {
  72              throw new \Exception('Unsupported JWT issuer');
  73          }
  74          if (isset($payload['exp']) && $payload['exp'] < time()) {
  75              throw new \Exception('JWT expired');
  76          }
  77  
  78          $user = $payload['sub'];
  79          $file = self::getStorageFile($user);
  80          if (!file_exists($file)) {
  81              throw new \Exception('JWT not found, maybe it expired?');
  82          }
  83  
  84          if (file_get_contents($file) !== $token) {
  85              throw new \Exception('JWT invalid, maybe it expired?');
  86          }
  87  
  88          return new self($user, $payload['iat']);
  89      }
  90  
  91      /**
  92       * Create a new instance from a user
  93       *
  94       * Loads an existing token if available
  95       *
  96       * @param $user
  97       * @return self
  98       */
  99      public static function fromUser($user)
 100      {
 101          $file = self::getStorageFile($user);
 102  
 103          if (file_exists($file)) {
 104              try {
 105                  return self::validate(io_readFile($file));
 106              } catch (\Exception $ignored) {
 107              }
 108          }
 109  
 110          $token = new self($user, time());
 111          $token->save();
 112          return $token;
 113      }
 114  
 115  
 116      /**
 117       * Get the JWT token for this instance
 118       *
 119       * @return string
 120       */
 121      public function getToken()
 122      {
 123          $header = [
 124              'alg' => 'HS256',
 125              'typ' => 'JWT',
 126          ];
 127          $header = base64_encode(json_encode($header));
 128  
 129          $payload = [
 130              'iss' => 'dokuwiki',
 131              'sub' => $this->user,
 132              'iat' => $this->issued,
 133          ];
 134          $payload = base64_encode(json_encode($payload, JSON_THROW_ON_ERROR));
 135  
 136          $signature = hash_hmac('sha256', "$header.$payload", self::getSecret(), true);
 137          $signature = base64_encode($signature);
 138          return "$header.$payload.$signature";
 139      }
 140  
 141      /**
 142       * Save the token for the user
 143       *
 144       * Resets the issued timestamp
 145       */
 146      public function save()
 147      {
 148          $this->issued = time();
 149          io_saveFile(self::getStorageFile($this->user), $this->getToken());
 150      }
 151  
 152      /**
 153       * Get the user of this token
 154       *
 155       * @return string
 156       */
 157      public function getUser()
 158      {
 159          return $this->user;
 160      }
 161  
 162      /**
 163       * Get the issued timestamp of this token
 164       *
 165       * @return int
 166       */
 167      public function getIssued()
 168      {
 169          return $this->issued;
 170      }
 171  
 172      /**
 173       * Get the storage file for this token
 174       *
 175       * Tokens are stored to be able to invalidate them
 176       *
 177       * @param string $user The user the token is for
 178       * @return string
 179       */
 180      public static function getStorageFile($user)
 181      {
 182          return getCacheName($user, '.token');
 183      }
 184  }