secret = $secret; $this->clock = $clock ?? 'time'; } /** * Expiry for a token issued now. */ public function expires_at(): int { return ( $this->clock )() + self::LIFETIME; } /** * URL-safe token (43 characters). * * @param string $request_id Request UUID. * @param string $kind File kind, e.g. 'result'. * @param string $visitor_hash Owner of the request. * @param int $expires Unix time the token stops working. */ public function token( string $request_id, string $kind, string $visitor_hash, int $expires ): string { $mac = hash_hmac( 'sha256', implode( '|', array( 'v1', $request_id, $kind, $visitor_hash, (string) $expires ) ), $this->secret, true ); return rtrim( strtr( base64_encode( $mac ), '+/', '-_' ), '=' ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- URL-safe encoding of a binary MAC. } /** * Whether a token is valid and not expired. Constant-time comparison. * * @param string $request_id Request UUID. * @param string $kind File kind. * @param string $visitor_hash Visitor asking for the file. * @param int $expires Expiry from the URL. * @param string $token Token from the URL. */ public function verify( string $request_id, string $kind, string $visitor_hash, int $expires, string $token ): bool { if ( $expires < ( $this->clock )() ) { return false; } return hash_equals( $this->token( $request_id, $kind, $visitor_hash, $expires ), $token ); } }