PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 27.8
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v27.8
28.5 28.4 28.3 28.2 28.1 28.0 27.9 27.8 27.7 27.6 27.5 trunk 18.0 18.1 18.2 18.3 18.4 18.4.1 18.5 18.5.1 18.6 18.7 18.8 18.9 19.0 All 129 releases
wordpress-seo / src / myyoast-client / infrastructure / token / token-storage.php

token-storage.php in Yoast SEO – Advanced SEO with real-time guidance and built-in AI 27.8, at src/myyoast-client/infrastructure/token/token-storage.php

128 lines 3.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure.
3
4 namespace Yoast\WP\SEO\MyYoast_Client\Infrastructure\Token;
5
6 use Exception;
7 use Yoast\WP\SEO\MyYoast_Client\Application\Exceptions\Token_Storage_Exception;
8 use Yoast\WP\SEO\MyYoast_Client\Application\Ports\Token_Storage_Interface;
9 use Yoast\WP\SEO\MyYoast_Client\Domain\Token_Set;
10 use Yoast\WP\SEO\MyYoast_Client\Infrastructure\Crypto\Encryption;
11 use Yoast\WP\SEO\MyYoast_Client\Infrastructure\Crypto\Encryption_Exception;
12 use Yoast\WP\SEO\MyYoast_Client\Infrastructure\OIDC\Issuer_Config;
13 use YoastSEO_Vendor\Psr\Log\LoggerAwareInterface;
14 use YoastSEO_Vendor\Psr\Log\LoggerAwareTrait;
15 use YoastSEO_Vendor\Psr\Log\NullLogger;
16
17 /**
18 * Stores and retrieves encrypted site-level tokens as a WordPress option.
19 *
20 * Used for client_credentials tokens (site-level, no user context).
21 * The option key is scoped by issuer so that switching issuers
22 * isolates all stored data.
23 */
24 class Token_Storage implements Token_Storage_Interface, LoggerAwareInterface {
25 use LoggerAwareTrait;
26
27 private const OPTION_KEY_PREFIX = 'wpseo_myyoast_site_tokens_';
28 private const ENCRYPTION_CONTEXT = 'yoast-myyoast-site-tokens';
29
30 /**
31 * The encryption service.
32 *
33 * @var Encryption
34 */
35 private $encryption;
36
37 /**
38 * The issuer configuration.
39 *
40 * @var Issuer_Config
41 */
42 private $issuer_config;
43
44 /**
45 * Token_Storage constructor.
46 *
47 * @param Encryption $encryption The encryption service.
48 * @param Issuer_Config $issuer_config The issuer configuration.
49 */
50 public function __construct( Encryption $encryption, Issuer_Config $issuer_config ) {
51 $this->encryption = $encryption;
52 $this->issuer_config = $issuer_config;
53 $this->logger = new NullLogger();
54 }
55
56 /**
57 * Stores a token set (encrypted).
58 *
59 * @param Token_Set $token_set The token set to store.
60 *
61 * @return void
62 *
63 * @throws Token_Storage_Exception If encryption fails.
64 */
65 public function store( Token_Set $token_set ): void {
66 try {
67 // phpcs:ignore Yoast.Yoast.JsonEncodeAlternative.Found -- Encoding for encrypted storage, not user-facing output.
68 $json = \wp_json_encode( $token_set->to_array() );
69 if ( $json === false ) {
70 throw new Token_Storage_Exception( 'Failed to JSON-encode token set for storage.' );
71 }
72
73 $encrypted = $this->encryption->encrypt( $json, self::ENCRYPTION_CONTEXT );
74 }
75 catch ( Encryption_Exception $e ) {
76 // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Internal exception message.
77 throw new Token_Storage_Exception( 'Failed to encrypt token set for storage: ' . $e->getMessage(), 0, $e );
78 }
79
80 \update_option( $this->get_option_key(), $encrypted, false );
81 }
82
83 /**
84 * Retrieves the stored token set.
85 *
86 * @return Token_Set|null The token set, or null if not stored or decryption fails.
87 */
88 public function get(): ?Token_Set {
89 $stored = \get_option( $this->get_option_key(), '' );
90 if ( ! \is_string( $stored ) || $stored === '' ) {
91 return null;
92 }
93
94 try {
95 $decrypted = $this->encryption->decrypt( $stored, self::ENCRYPTION_CONTEXT );
96 $data = \json_decode( $decrypted, true, 512, \JSON_THROW_ON_ERROR );
97
98 if ( ! \is_array( $data ) || empty( $data['access_token'] ) ) {
99 return null;
100 }
101
102 return Token_Set::from_array( $data );
103 }
104 catch ( Exception $e ) {
105 $this->logger->error( 'Failed to decrypt stored site token: {error}', [ 'error' => $e->getMessage() ] );
106 return null;
107 }
108 }
109
110 /**
111 * Deletes the stored token set.
112 *
113 * @return void
114 */
115 public function delete(): void {
116 \delete_option( $this->get_option_key() );
117 }
118
119 /**
120 * Returns the issuer-scoped option key.
121 *
122 * @return string The option key.
123 */
124 private function get_option_key(): string {
125 return self::OPTION_KEY_PREFIX . $this->issuer_config->get_issuer_key();
126 }
127 }
128