| [ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Secrets API: WP_Secrets_Provider class 4 * 5 * @package WordPress 6 * @subpackage Secrets 7 * @since 7.2.0 8 */ 9 10 /** 11 * Base class for a secrets provider. 12 * 13 * A provider is responsible for a secret: storing it, protecting it, and returning 14 * it to a caller. It is the outermost extension point in the Secrets API, and the 15 * one a hosting platform extends. 16 * 17 * WordPress ships WP_Secrets_Libsodium_Provider, which encrypts with libsodium and 18 * stores ciphertext in the options tables. That provider is the default rather than 19 * a privileged case: a platform that protects credentials in a key management 20 * service, a hardware security module, or its own control panel extends this 21 * same class. 22 * 23 * A provider must be stronger than the default, never weaker. Storing a plaintext 24 * where the default would have stored ciphertext is what this class exists to 25 * prevent. Receiving a value over an authenticated channel and holding it in a 26 * hardware security module satisfies that requirement. 27 * 28 * WordPress cannot verify any of this. A provider is loaded from a drop-in, which is 29 * fully trusted code: it runs before plugins, and could already read every secret by 30 * implementing the keyring. get_protection_boundary() and is_writable() are 31 * declarations rather than enforcement. They exist so that a developer reviewing a 32 * drop-in can see what it claims, Site Health can report where credentials are 33 * protected, and a settings screen can determine that a write will be refused before 34 * an operator enters a credential. They are not a security boundary. 35 * 36 * Every implementation shares these contracts: 37 * 38 * - Three states, never collapsed. get() returns a WP_Secret, null when the secret 39 * does not exist, or WP_Error when it exists but cannot be produced. Reporting an 40 * unreachable backend as absent turns an outage into an apparently deleted 41 * credential. 42 * - Fail closed. A provider that cannot answer returns WP_Error. It never 43 * substitutes a weaker source, and never returns a partial or placeholder value. 44 * - No filter on the retrieval path. A provider replaces a component; it is not a 45 * hook. Nothing is given the opportunity to observe or alter a value in transit. 46 * - Never persist a value more weakly than the provider protects it. A provider that 47 * fetches over the network must not write the plaintext into the persistent object 48 * cache. WP_Secret cannot round-trip a plaintext through wp_cache_set(), and 49 * caching the raw value alongside it would undo that. Memoize within the request 50 * only. 51 * 52 * Implementations extend this class. It is abstract rather than an interface so 53 * that a later release can add a method with a default implementation, and every 54 * existing subclass keeps working. 55 * 56 * @since 7.2.0 57 */ 58 abstract class WP_Secrets_Provider { 59 60 /** 61 * Protection happens inside WordPress. 62 * 63 * The provider holds ciphertext that WordPress produced, and WordPress holds the 64 * key material. 65 * 66 * @since 7.2.0 67 * @var string 68 */ 69 const BOUNDARY_WORDPRESS = 'wordpress'; 70 71 /** 72 * Protection happens outside WordPress. 73 * 74 * The provider is itself the encryption boundary, and WordPress is a consumer of a 75 * credential it does not protect. 76 * 77 * @since 7.2.0 78 * @var string 79 */ 80 const BOUNDARY_PROVIDER = 'provider'; 81 82 /** 83 * Retrieves a secret. 84 * 85 * Returning null means "I am not responsible for this name" as well as "this 86 * name does not exist". The two are the same answer from a caller's point of 87 * view, and keeping them distinct would require a provider to enumerate names 88 * it has never heard of. 89 * 90 * @since 7.2.0 91 * 92 * @param string $name The secret's name. 93 * @param string $version A WP_Secret_Version constant. A provider with no 94 * version history returns null for PREVIOUS, which is 95 * absence rather than an error. 96 * @param bool $network Whether this is a network-scope secret. 97 * @return WP_Secret|null|WP_Error 98 * 99 * @phpstan-param WP_Secret_Version::CURRENT|WP_Secret_Version::PREVIOUS $version 100 */ 101 abstract public function get( string $name, string $version, bool $network = false ); 102 103 /** 104 * Stores a secret. 105 * 106 * A provider whose credentials are managed elsewhere, by a control panel, host 107 * tooling, or a KMS with its own access policy, returns WP_Error here with code 108 * WP_SECRETS_ERROR_PROVIDER_READ_ONLY, and reports false from is_writable() so 109 * that callers can find that out without attempting the write first. 110 * 111 * Implementations are responsible for firing the `wp_secret_changed` action once 112 * the write has succeeded, with `$network` as its last argument. The API does 113 * not fire it on the provider's behalf, because only the provider knows the 114 * prior fingerprint without paying for an additional read. An audit hook that 115 * stopped firing once a host installed a provider would be unreliable in exactly 116 * the situation it matters most, so WP_Secrets_Provider_Conformance checks it. 117 * A write that fails fires nothing. 118 * 119 * Writing the value a secret already holds is not a rotation. A settings form 120 * that saves every field on every submit does this routinely, so a provider 121 * that keeps a previous version must not discard it for an unchanged write; 122 * doing so would end a rotation's grace period early, with every consumer 123 * still on the old credential losing access. WP_Secrets_Provider_Conformance 124 * checks this. Treating the write as a no-op that fires nothing is preferred, 125 * but a provider that cannot tell the value is unchanged without an additional 126 * read may write it and report it. 127 * 128 * @since 7.2.0 129 * 130 * @param string $name The secret's name. 131 * @param string $value The plaintext value. 132 * @param bool $network Whether this is a network-scope secret. 133 * @param bool $needs_rotation Mark the stored secret as needing rotation. 134 * Set for values that arrived from somewhere 135 * less protected than this provider, such as 136 * an imported option. A provider with nowhere 137 * to record this may ignore it, but must not 138 * report it as honored. 139 * @param string|null $action Overrides the action reported to 140 * `wp_secret_changed`; null means the 141 * provider decides between 'created' and 142 * 'updated'. 143 * @return true|WP_Error 144 */ 145 abstract public function set( 146 string $name, 147 #[\SensitiveParameter] 148 $value, 149 bool $network = false, 150 bool $needs_rotation = false, 151 ?string $action = null 152 ); 153 154 /** 155 * Deletes a secret. 156 * 157 * Deleting a secret that does not exist is a success. 158 * 159 * Fires `wp_secret_changed` with the 'deleted' action once a secret has been 160 * deleted, for the reason given on set(). A provider that can tell nothing was 161 * there to delete fires nothing. 162 * 163 * @since 7.2.0 164 * 165 * @param string $name The secret's name. 166 * @param bool $network Whether this is a network-scope secret. 167 * @return true|WP_Error 168 */ 169 abstract public function delete( string $name, bool $network = false ); 170 171 /** 172 * Clears a secret's previous version, if this provider keeps one. 173 * 174 * A provider with no version history treats this as a successful no-op: there 175 * is nothing to retire, which is the state the caller asked for. 176 * 177 * Fires `wp_secret_changed` with the 'retired' action once a previous version 178 * has been cleared, for the reason given on set(). Nothing fires when there was 179 * no previous version to clear. 180 * 181 * @since 7.2.0 182 * 183 * @param string $name The secret's name. 184 * @param bool $network Whether this is a network-scope secret. 185 * @return true|WP_Error 186 */ 187 abstract public function retire_previous( string $name, bool $network = false ); 188 189 /** 190 * Lists secrets by name and metadata. Never values. 191 * 192 * @since 7.2.0 193 * 194 * @param string $name_prefix Restrict to names beginning with this prefix, or 195 * '' for all of them. 196 * @param bool $network Whether to list network-scope secrets. 197 * @return array|WP_Error List of arrays with keys 'name', 'fingerprint', 198 * 'created', 'has_previous', 'needs_rotation'. 199 * 200 * @phpstan-return list<array{name: string, fingerprint: string, created: int, has_previous: bool, needs_rotation: bool}>|WP_Error 201 */ 202 abstract public function list_secrets( string $name_prefix = '', bool $network = false ); 203 204 /** 205 * A short human-readable name for this provider, for Site Health. 206 * 207 * Describes the protection, not the vendor's marketing: "AWS KMS 208 * (alias/wp-secrets)" tells an operator something, "SuperSecure Pro" does not. 209 * Never key material, never a credential, never a value. 210 * 211 * @since 7.2.0 212 * 213 * @return string 214 */ 215 abstract public function get_label(): string; 216 217 /** 218 * Where secrets this provider holds are actually protected. 219 * 220 * One of the BOUNDARY_* constants. This is what lets Site Health and a future 221 * admin screen tell an operator whether their credentials are protected by 222 * WordPress's own libsodium envelope or by something outside it, a question 223 * they currently have no way to answer, and the one hosts asked to be able to 224 * answer honestly. 225 * 226 * @since 7.2.0 227 * 228 * @return string 229 * 230 * @phpstan-return self::BOUNDARY_* 231 */ 232 abstract public function get_protection_boundary(): string; 233 234 /** 235 * Whether wp_set_secret() can succeed against this provider. 236 * 237 * False for a provider whose credentials are managed by host tooling or a 238 * control panel. Declared rather than discovered so a settings screen can 239 * disable its save control before an operator types a credential into a field 240 * that will only reject it. 241 * 242 * @since 7.2.0 243 * 244 * @return bool 245 */ 246 abstract public function is_writable(): bool; 247 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Wed Nov 11 08:20:02 2026 | Cross-referenced by PHPXref |