Overview

Namespaces

  • Antavo
    • RestClient
      • Exceptions
    • SignedToken
      • Exceptions
  • PHP

Classes

  • Antavo\ApiClient
  • Antavo\CustomerToken
  • Antavo\RestClient\RestClient
  • Antavo\SignedToken\SignedToken

Exceptions

  • Antavo\ApiClientException
  • Antavo\RestClient\Exceptions\Exception
  • Antavo\RestClient\Exceptions\ParserException
  • Antavo\RestClient\Exceptions\StatusCodeException
  • Antavo\SignedToken\Exceptions\Exception
  • Antavo\SignedToken\Exceptions\ExpiredException
  • Antavo\SignedToken\Exceptions\InvalidDigestException
  • Antavo\SignedToken\Exceptions\InvalidPayloadException
  • Exception
  • RuntimeException
  • Overview
  • Namespace
  • Class
  1:   2:   3:   4:   5:   6:   7:   8:   9:  10:  11:  12:  13:  14:  15:  16:  17:  18:  19:  20:  21:  22:  23:  24:  25:  26:  27:  28:  29:  30:  31:  32:  33:  34:  35:  36:  37:  38:  39:  40:  41:  42:  43:  44:  45:  46:  47:  48:  49:  50:  51:  52:  53:  54:  55:  56:  57:  58:  59:  60:  61:  62:  63:  64:  65:  66:  67:  68:  69:  70:  71:  72:  73:  74:  75:  76:  77:  78:  79:  80:  81:  82:  83:  84:  85:  86:  87:  88:  89:  90:  91:  92:  93:  94:  95:  96:  97:  98:  99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 
<?php
namespace Antavo\SignedToken;

/**
 * Packs/unpacks a signed token with custom payload.
 *
 * @example Creating a new token
 * <pre>
 * $token = new Antavo\SignedToken\SignedToken($token_secret);
 * $token->setPayload(array('some_key' => 'some_value'));
 * echo $token->getToken();
 * </pre>
 * @example Extracting payload from a token string
 * <pre>
 * try {
 *     $token = new Antavo\SignedToken\SignedToken($token_secret);
 *     $token->setToken($input_token);
 *     var_dump($token->getPayload());
 * } catch (Antavo\SignedToken\Exceptions\Exception $e) {
 *     echo $e->getMessage();
 * }
 * </pre>
 */
class SignedToken {
    /**
     * @var string  Algorithm used for creating the token digest.
     */
    protected $algorithm = 'sha256';

    /**
     * @var string  String to use for token digest calculation.
     * @link https://xkcd.com/936/  xkcd: Password Strength
     */
    protected $secret = 'correct horse battery staple';

    /**
     * @var int  Expiraton time as Unix timestamp.
     */
    protected $expires_at = 0;

    /**
     * @var string  Cached token string.
     */
    protected $token;

    /**
     * @var array  Payload data to embed into the token string.
     */
    protected $payload = array();

    /**
     * Constructs token object: sets secret string and expiration time.
     *
     * @param string $secret  String to use for token digest calculation. If
     * omitted the hardcoded default is used (**not recommended**).
     * @param int $expires_at  Expiraton time as Unix timestamp. If omitted 0
     * is used. See {@see setExpirationTime()} for details.
     */
    public function __construct($secret = NULL, $expires_at = NULL) {
        $this->setSecret($secret);
        $this->setExpirationTime($expires_at);
    }

    /**
     * Shorthand method for getting token string.
     *
     * @return string
     * @see getToken()
     */
    public function __toString() {
        return $this->getToken();
    }

    /**
     * Base64 decodes an URL-safely encoded string.
     *
     * @param string $string
     * @return string
     * @static
     */
    private static function base64Decode($string) {
        return base64_decode(strtr($string, '-_', '+/'));
    }

    /**
     * Base64 encodes a string and makes it URL-safe.
     *
     * @param string $string
     * @return string
     * @static
     */
    private static function base64Encode($string) {
        return strtr(trim(base64_encode($string), '='), '+/', '-_');
    }

    /**
     * Calculates token digest from payload.
     *
     * @param string $payload
     * @return string
     * @final
     */
    final protected function calculateDigest($payload) {
        return $this->saltDigest(
            hash_hmac($this->algorithm, $payload, $this->secret)
        );
    }

    /**
     * Returns algorithm used for creating the token digest.
     *
     * @return string
     */
    public function getAlgorithm() {
        return $this->algorithm;
    }

    /**
     * Returns calculated expiration time.
     *
     * @return int  If set value is a time-to-live value &mdash; meaning it's
     * smaller than or equals to 30 days (in seconds) &mdash; it is offsetted
     * with current time.
     */
    public function getCalculatedExpirationTime() {
        if ($this->expires_at > 0 && $this->expires_at <= 2592000) {
            return time() + $this->expires_at;
        }
        return $this->expires_at;
    }

    /**
     * Returns token expiration time as Unix Timestamp.
     *
     * @return int
     */
    public function getExpirationTime() {
        return $this->expires_at;
    }

    /**
     * Returns payload data extracted from token string.
     *
     * @return array
     */
    public function getPayload() {
        return $this->payload;
    }

    /**
     * Returns secret string used to create the token digest.
     *
     * @return string
     */
    public function getSecret() {
        return $this->secret;
    }

    /**
     * Returns token string. If a setter call occurred since last calculation,
     * it regenerates the string.
     *
     * @return string
     * @final
     */
    final public function getToken() {
        if (!isset($this->token)) {
            $payload = json_encode($this->preparePayload($this->payload));
            $this->token = $this->base64Encode($this->calculateDigest($payload)) .
                '.' . $this->base64Encode($payload);
        }

        return $this->token;
    }

    /**
     * Tells if token expiration time had passed already.
     *
     * @return bool
     */
    public function isExpired() {
        return ($expires_at = $this->getCalculatedExpirationTime()) > 0
        && $expires_at < time();
    }

    /**
     * Returns data extended with expiration time.
     *
     * Override this method for customization.
     *
     * @param array $payload
     * @return array
     */
    protected function preparePayload(array $payload) {
        if (
            !array_key_exists('expires_at', $payload)
            && ($expires_at = $this->getCalculatedExpirationTime()) > 0
        ) {
            $payload['expires_at'] = $expires_at;
        }
        return $payload;
    }

    /**
     * By overriding this method you can provide a custom mechanism to
     * manipulate token digest before use.
     *
     * @param string $digest  Digest calculated from payload.
     * @return string  Salted digest that will be used for token.
     */
    public function saltDigest($digest) {
        return $digest;
    }

    /**
     * Sets algorithm used for creating the token digest.
     *
     * @param string $algorithm
     * @return self  Object instance for method chaining.
     * @link http://php.net/function.hash_algos  How to get a list of
     * registered hashing algorithms?
     */
    public function setAlgorithm($algorithm) {
        if (in_array($algorithm, hash_algos())) {
            $this->algorithm = $algorithm;
            $this->token = NULL;
        }
        return $this;
    }

    /**
     * Sets expiration time for the token.
     *
     * @param int $expires_at  Unix Timestamp of expiration time. If given
     * value is smaller than or equals to 30 days (in seconds) then it is
     * handled as a relative time-to-live value. 0 means it will never expire.
     * Float values are casted to integer, while non-numeric values are
     * discarded.
     * @return self
     */
    public function setExpirationTime($expires_at) {
        if (is_numeric($expires_at)) {
            $this->expires_at = (int) $expires_at;
            $this->token = NULL;
        }
        return $this;
    }

    /**
     * Sets payload data to embed into the token string.
     *
     * @param array $payload
     * @return self  Object instance for method chaining.
     */
    public function setPayload(array $payload) {
        $this->payload = $payload;
        $this->token = NULL;
        return $this;
    }

    /**
     * Sets string to use for token digest calculation.
     *
     * @param string $secret  Non-scalar values are discarded.
     * @return self  Object instance for method chaining.
     */
    public function setSecret($secret) {
        if (is_scalar($secret)) {
            $this->secret = (string) $secret;
            $this->token = NULL;
        }
        return $this;
    }

    /**
     * Restores an object state from token string.
     *
     * @param string $token  Token string to process. Non-string values are
     * discarded.
     * @return self  Object instance for method chaining.
     * @throws \Antavo\SignedToken\Exceptions\ExpiredException
     * @throws \Antavo\SignedToken\Exceptions\InvalidPayloadException
     * @throws \Antavo\SignedToken\Exceptions\InvalidDigestException
     * @final
     */
    final public function setToken($token) {
        if (is_string($token)) {
            $this->token = $token;

            // Splitting token into parts and processing them.
            $digest = $this->base64Decode(strtok($token, '.'));
            $payload = $this->base64Decode(strtok('.'));

            // Checking integrity.
            if ($this->calculateDigest($payload) != $digest) {
                throw new Exceptions\InvalidDigestException;
            }

            // Extracting payload.
            $payload = json_decode($payload, true);
            if (!is_array($payload)) {
                throw new Exceptions\InvalidPayloadException;
            }
            if (isset($payload['expires_at'])) {
                $this->expires_at = $payload['expires_at'];
            }
            $this->payload = $payload;

            // Checking expiration.
            if ($this->isExpired()) {
                throw new Exceptions\ExpiredException;
            }
        }

        return $this;
    }
}
API documentation generated by ApiGen