PHP Classes

File: src/Builder.php

Recommend this page to a friend!
  Classes of Scott Arciszewski   PHP PASeTo   src/Builder.php   Download  
File: src/Builder.php
Role: Class source
Content type: text/plain
Description: Class source
Class: PHP PASeTo
Encrypt and decrypt data with PaSeTO protocol
Author: By
Last change:
Date: 4 years ago
Size: 17,392 bytes
 

Contents

Class file image Download
<?php declare(strict_types=1); namespace ParagonIE\Paseto; use ParagonIE\Paseto\Exception\{ EncodingException, InvalidKeyException, InvalidPurposeException, PasetoException }; use ParagonIE\Paseto\Keys\{ AsymmetricSecretKey, SymmetricKey }; use ParagonIE\Paseto\Protocol\Version2; use ParagonIE\Paseto\Traits\RegisteredClaims; /** * Class Builder * @package ParagonIE\Paseto */ class Builder { use RegisteredClaims; /** @var string $cached */ protected $cached = ''; /** @var array<string, string> */ protected $claims = []; /** @var \Closure|null $unitTestEncrypter -- Do not use this. It's for unit testing! */ protected $unitTestEncrypter; /** @var SendingKey|null $key */ protected $key = null; /** @var Purpose|null $purpose */ protected $purpose; /** @var ProtocolInterface $version */ protected $version; /** @var JsonToken $token */ protected $token; /** * Builder constructor. * * @param JsonToken|null $baseToken * @param ProtocolInterface|null $protocol * @param SendingKey|null $key * * @throws PasetoException */ public function __construct( JsonToken $baseToken = null, ProtocolInterface $protocol = null, SendingKey $key = null ) { if (!$baseToken) { $baseToken = new JsonToken(); } if (!$protocol) { $protocol = new Version2(); } $this->token = $baseToken; $this->version = $protocol; if ($key) { $this->setKey($key); } } /** * Get any arbitrary claim. * * @param string $claim * @return mixed * @throws PasetoException */ public function get(string $claim) { return $this->token->get($claim); } /** * @return array * @throws PasetoException */ public function getFooterArray(): array { return $this->token->getFooterArray(); } /** * Get a Builder instance configured for local usage. * (i.e. shared-key authenticated encryption) * * @param SymmetricKey $key * @param ProtocolInterface|null $version * @param JsonToken|null $baseToken * * @return Builder * @throws PasetoException */ public static function getLocal( SymmetricKey $key, ProtocolInterface $version = null, JsonToken $baseToken = null ): self { if (!$version) { $version = new Version2(); } $instance = new static($baseToken); $instance->key = $key; $instance->version = $version; $instance->purpose = Purpose::local(); return $instance; } /** * Get a Builder instance configured for remote usage. * (i.e. public-key digital signatures) * * @param AsymmetricSecretKey $key * @param ProtocolInterface|null $version * @param JsonToken|null $baseToken * * @return Builder * @throws PasetoException */ public static function getPublic( AsymmetricSecretKey $key, ProtocolInterface $version = null, JsonToken $baseToken = null ): self { if (!$version) { $version = new Version2(); } $instance = new static($baseToken); $instance->key = $key; $instance->version = $version; $instance->purpose = Purpose::public(); return $instance; } /** * Get the JsonToken object (not the string) * * @return JsonToken */ public function getJsonToken(): JsonToken { return $this->token; } /** * Set a claim to an arbitrary value. * * @param string $claim * @param string $value * * @return self */ public function set(string $claim, $value): self { $this->token->set($claim, $value); return $this; } /** * Set the 'aud' claim for the token we're building. (Mutable.) * * @param string $aud * @return self */ public function setAudience(string $aud): self { return $this->set('aud', $aud); } /** * Set the 'exp' claim for the token we're building. (Mutable.) * * @param \DateTimeInterface|null $time * @return self */ public function setExpiration(\DateTimeInterface $time = null): self { if (!$time) { $time = new \DateTime('NOW'); } return $this->set('exp', $time->format(\DateTime::ATOM)); } /** * Set the 'iat' claim for the token we're building. (Mutable.) * * @param \DateTimeInterface|null $time * @return self */ public function setIssuedAt(\DateTimeInterface $time = null): self { if (!$time) { $time = new \DateTime('NOW'); } return $this->set('iat', $time->format(\DateTime::ATOM)); } /** * Set the 'iss' claim for the token we're building. (Mutable.) * * @param string $iss * @return self */ public function setIssuer(string $iss): self { return $this->set('iss', $iss); } /** * Set the 'jti' claim for the token we're building. (Mutable.) * * @param string $id * @return self */ public function setJti(string $id): self { return $this->set('jti', $id); } /** * Set the 'nbf' claim for the token we're building. (Mutable.) * * @param \DateTimeInterface|null $time * @return self */ public function setNotBefore(\DateTimeInterface $time = null): self { if (!$time) { $time = new \DateTime('NOW'); } return $this->set('nbf', $time->format(\DateTime::ATOM)); } /** * Set the 'sub' claim for the token we're building. (Mutable.) * * @param string $sub * @return self */ public function setSubject(string $sub): self { return $this->set('sub', $sub); } /** * Set an array of claims in one go. * * @param array<string, string> $claims * @return self */ public function setClaims(array $claims): self { $this->cached = ''; $this->token->setClaims($claims); return $this; } /** * Set the footer. * * @param string $footer * @return self */ public function setFooter(string $footer = ''): self { $this->cached = ''; $this->token->setFooter($footer); return $this; } /** * Set the footer, given an array of data. Converts to JSON. * * @param array $footer * @return self * @throws PasetoException */ public function setFooterArray(array $footer = []): self { $encoded = \json_encode($footer); if (!\is_string($encoded)) { throw new EncodingException('Could not encode array into JSON'); } return $this->setFooter($encoded); } /** * Set the cryptographic key used to authenticate (and possibly encrypt) * the serialized token. * * @param SendingKey $key * @param bool $checkPurpose * @return self * @throws PasetoException */ public function setKey(SendingKey $key, bool $checkPurpose = false): self { if ($checkPurpose) { if (!isset($this->purpose)) { throw new InvalidKeyException('Unknown purpose'); } elseif (!$this->purpose->isSendingKeyValid($key)) { throw new InvalidKeyException( 'Invalid key type. Expected ' . $this->purpose->expectedSendingKeyType() . ', got ' . \get_class($key) ); } switch ($this->purpose) { case Purpose::local(): break; case Purpose::public(): if (!($key->getProtocol() instanceof $this->version)) { throw new InvalidKeyException( 'Invalid key type. This key is for ' . $key->getProtocol()::header() . ', not ' . $this->version::header() ); } break; default: throw new InvalidKeyException('Unknown purpose'); } } $this->cached = ''; $this->key = $key; return $this; } /** * Set the purpose for this token. Allowed values: * Purpose::local(), Purpose::public(). * * @param Purpose $purpose * @param bool $checkKeyType * @return self * @throws InvalidKeyException * @throws InvalidPurposeException */ public function setPurpose(Purpose $purpose, bool $checkKeyType = false): self { if ($checkKeyType) { if (\is_null($this->key)) { throw new InvalidKeyException('Key cannot be null'); } $expectedPurpose = Purpose::fromSendingKey($this->key); if (!$purpose->equals($expectedPurpose)) { throw new InvalidPurposeException( 'Invalid purpose. Expected '.$expectedPurpose->rawString() .', got ' . $purpose->rawString() ); } } $this->cached = ''; $this->purpose = $purpose; return $this; } /** * Pass an existing JsonToken object. Useful for updating an existing token. * * @param JsonToken $token * * @return Builder */ public function setJsonToken(JsonToken $token): self { $this->token = $token; return $this; } /** * Specify the version of the protocol to be used. * * @param ProtocolInterface|null $version * * @return self */ public function setVersion(ProtocolInterface $version = null): self { if (!$version) { $version = new Version2(); } $this->version = $version; return $this; } /** * Get the token as a string. * * @return string * @throws PasetoException * @psalm-suppress MixedInferredReturnType */ public function toString(): string { if (!empty($this->cached)) { return $this->cached; } if (\is_null($this->key)) { throw new InvalidKeyException('Key cannot be null'); } if (\is_null($this->purpose)) { throw new InvalidPurposeException('Purpose cannot be null'); } // Mutual sanity checks $this->setKey($this->key, true); $this->setPurpose($this->purpose, true); $claims = \json_encode($this->token->getClaims()); $protocol = $this->version; ProtocolCollection::throwIfUnsupported($protocol); switch ($this->purpose) { case Purpose::local(): if ($this->key instanceof SymmetricKey) { /** * During unit tests, perform last-minute dependency * injection to swap $protocol for a conjured up version. * This new version can access a protected method on our * actual $protocol, giving unit tests the ability to * manually set a pre-decided nonce. */ if (isset($this->unitTestEncrypter)) { /** @var ProtocolInterface */ $protocol = ($this->unitTestEncrypter)($protocol); } $this->cached = (string) $protocol::encrypt( $claims, $this->key, $this->token->getFooter() ); return $this->cached; } break; case Purpose::public(): if ($this->key instanceof AsymmetricSecretKey) { try { $this->cached = (string) $protocol::sign( $claims, $this->key, $this->token->getFooter() ); return $this->cached; } catch (\Throwable $ex) { throw new PasetoException('Signing failed.', 0, $ex); } } break; } throw new PasetoException('Unsupported key/purpose pairing.'); } /** * Return a new Builder instance with a changed claim. * * @param string $claim * @param string $value * @return self */ public function with(string $claim, $value): self { $cloned = clone $this; $cloned->cached = ''; $cloned->token = $cloned->token->with($claim, $value); return $cloned; } /** * Return a new Builder instance with a changed 'aud' claim. * * @param string $aud * @return self */ public function withAudience(string $aud): self { return (clone $this)->setAudience($aud); } /** * Return a new Builder instance with an array of changed claims. * * @param array<string, string> $claims * @return self */ public function withClaims(array $claims): self { return (clone $this)->setClaims($claims); } /** * Return a new Builder instance with a changed 'exp' claim. * * @param \DateTimeInterface|null $time * @return self */ public function withExpiration(\DateTimeInterface $time = null): self { return (clone $this)->setExpiration($time); } /** * Return a new Builder instance with a changed footer. * * @param string $footer * @return self */ public function withFooter(string $footer = ''): self { return (clone $this)->setFooter($footer); } /** * Return a new Builder instance with a changed footer, * representing the JSON-encoded array provided. * * @param array $footer * @return self * @throws PasetoException */ public function withFooterArray(array $footer = []): self { return (clone $this)->setFooterArray($footer); } /** * Return a new Builder instance with a changed 'iat' claim. * * @param \DateTimeInterface|null $time * @return self */ public function withIssuedAt(\DateTimeInterface $time = null): self { return (clone $this)->setIssuedAt($time); } /** * Return a new Builder instance with a changed 'iss' claim. * * @param string $iss * @return self */ public function withIssuer(string $iss): self { return (clone $this)->setIssuer($iss); } /** * Return a new Builder instance with a changed 'jti' claim. * * @param string $id * @return self */ public function withJti(string $id): self { return (clone $this)->setJti($id); } /** * Return a new Builder instance with a changed 'nbf' claim. * * @param \DateTimeInterface|null $time * @return self */ public function withNotBefore(\DateTimeInterface $time = null): self { return (clone $this)->setNotBefore($time); } /** * Return a new Builder instance with a changed 'sub' claim. * * @param string $sub * @return self */ public function withSubject(string $sub): self { return (clone $this)->setSubject($sub); } /** * Return a new Builder instance, with the provided cryptographic key used * to authenticate (and possibly encrypt) the serialized token. * * @param SendingKey $key * @param bool $checkPurpose * @return self * @throws PasetoException */ public function withKey(SendingKey $key, bool $checkPurpose = false): self { return (clone $this)->setKey($key, $checkPurpose); } /** * Return a new Builder instance with a new purpose. * Allowed values: * Purpose::local(), Purpose::public(). * * @param Purpose $purpose * @param bool $checkKeyType * @return self * @throws InvalidKeyException * @throws InvalidPurposeException */ public function withPurpose(Purpose $purpose, bool $checkKeyType = false): self { return (clone $this)->setPurpose($purpose, $checkKeyType); } /** * Return a new Builder instance with the specified JsonToken object. * * @param JsonToken $token * * @return Builder */ public function withJsonToken(JsonToken $token): self { return (clone $this)->setJsonToken($token); } /** * Make a copy of the JsonToken object. * * @return void */ public function __clone() { $this->token = clone $this->token; } /** * @return string */ public function __toString() { try { return $this->toString(); } catch (\Throwable $ex) { return ''; } } }