Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions src/wp-includes/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -828,8 +828,8 @@ function wp_using_ext_object_cache( $using = null ) {
* Loads the secrets.php drop-in, if one exists, and records whether it left the
* Secrets API's provider, store, and keyring overrides in a usable state.
*
* Runs once, before must-use plugins load, so that every interface a drop-in's
* classes implement already exists and every plugin sees the drop-in's provider.
* Runs once, before must-use plugins load, so that every class a drop-in's classes
* extend already exists and every plugin sees the drop-in's provider.
*
* The drop-in sets its overrides in the `$wp_secrets_provider`, `$wp_secrets_store`
* and `$wp_secrets_keyring` globals. They are declared global before the require, so
Expand All @@ -841,14 +841,14 @@ function wp_using_ext_object_cache( $using = null ) {
*
* Sets `$GLOBALS['wp_secrets_dropin_broken']` when the require throws, or when any of
* `$GLOBALS['wp_secrets_provider']`, `$GLOBALS['wp_secrets_store']`, or
* `$GLOBALS['wp_secrets_keyring']` is set to something that does not implement its
* interface. A global left unset is fine: a drop-in that overrides only the keyring
* `$GLOBALS['wp_secrets_keyring']` is set to something that does not extend its
* class. A global left unset is fine: a drop-in that overrides only the keyring
* leaves the other two alone. Once the flag is set, every Secrets API operation
* returns WP_Error for the rest of the request rather than falling back to the
* default provider, so a broken drop-in reads as unreachable, never as absent.
*
* PHP treats a class that implements an interface but omits a required method as an
* uncatchable fatal error, even inside the try/catch around the require. That case
* PHP treats a class that extends an abstract class but omits one of its abstract
* methods as an uncatchable fatal error, even inside the try/catch around the require. That case
* cannot be intercepted from PHP code.
*
* @since 7.2.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
*
* @since 7.2.0
*/
final class WP_Secrets_Broken_Keyring implements WP_Secrets_Keyring {
final class WP_Secrets_Broken_Keyring extends WP_Secrets_Keyring {

/**
* Always fails closed.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
*
* @since 7.2.0
*/
final class WP_Secrets_Broken_Provider implements WP_Secrets_Provider {
final class WP_Secrets_Broken_Provider extends WP_Secrets_Provider {

/**
* The single error every operation reports.
Expand Down
2 changes: 1 addition & 1 deletion src/wp-includes/secrets/class-wp-secrets-broken-store.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
*
* @since 7.2.0
*/
final class WP_Secrets_Broken_Store implements WP_Secrets_Store {
final class WP_Secrets_Broken_Store extends WP_Secrets_Store {

/**
* Always fails closed.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
*
* @since 7.2.0
*/
final class WP_Secrets_Config_Key_Provider implements WP_Secrets_Keyring {
final class WP_Secrets_Config_Key_Provider extends WP_Secrets_Keyring {

/**
* AAD binding the root key's wrapped form to this purpose.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php
/**
* Secrets API: WP_Secrets_Keyring interface
* Secrets API: WP_Secrets_Keyring class
*
* @package WordPress
* @subpackage Secrets
Expand All @@ -10,17 +10,21 @@
/**
* Wraps and unwraps the root key that everything else in this API derives from.
*
* A KMS or HSM lives behind this interface in a production deployment. An
* A KMS or HSM lives behind this class in a production deployment. An
* implementation is never handed a plaintext secret, only 32 bytes of key material,
* and cannot turn encryption off. There is no method here that
* accepts a plaintext secret value at all.
*
* An implementation should run WP_Secrets_Keyring_Conformance against itself before
* shipping.
*
* Implementations extend this class. It is abstract rather than an interface so
* that a later release can add a method with a default implementation, and every
* existing subclass keeps working.
*
* @since 7.2.0
*/
interface WP_Secrets_Keyring {
abstract class WP_Secrets_Keyring {

/**
* Wraps (encrypts) raw key material for storage.
Expand All @@ -38,7 +42,7 @@ interface WP_Secrets_Keyring {
* @param string $key_material Raw key material to protect.
* @return string|WP_Error Opaque wrapped value on success, WP_Error on failure.
*/
public function wrap(
abstract public function wrap(
#[\SensitiveParameter]
string $key_material
);
Expand All @@ -51,7 +55,7 @@ public function wrap(
* @param string $wrapped An opaque value previously returned by wrap().
* @return string|WP_Error Raw key material on success, WP_Error on failure.
*/
public function unwrap( string $wrapped );
abstract public function unwrap( string $wrapped );

/**
* A human-readable description of the key source, for Site Health.
Expand All @@ -60,5 +64,5 @@ public function unwrap( string $wrapped );
*
* @return string
*/
public function get_key_source(): string;
abstract public function get_key_source(): string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* Uses libsodium envelope encryption and stores ciphertext in the options tables.
*
* This is the default rather than a privileged case. Everything it does is expressed
* through WP_Secrets_Provider, the same interface a platform implements, so a KMS-
* through WP_Secrets_Provider, the same class a platform extends, so a KMS-
* or HSM-backed provider is a peer rather than an exception carved into the side of
* this one.
*
Expand All @@ -32,7 +32,7 @@
*
* @since 7.2.0
*/
final class WP_Secrets_Libsodium_Provider implements WP_Secrets_Provider {
final class WP_Secrets_Libsodium_Provider extends WP_Secrets_Provider {

/**
* Where ciphertext records live.
Expand Down
2 changes: 1 addition & 1 deletion src/wp-includes/secrets/class-wp-secrets-option-store.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
*
* @since 7.2.0
*/
final class WP_Secrets_Option_Store implements WP_Secrets_Store {
final class WP_Secrets_Option_Store extends WP_Secrets_Store {

/**
* Option name prefix for site-scope secrets.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
<?php
/**
* Secrets API: WP_Secrets_Provider interface
* Secrets API: WP_Secrets_Provider class
*
* @package WordPress
* @subpackage Secrets
* @since 7.2.0
*/

/**
* Interface for a secrets provider.
* Base class for a secrets provider.
*
* A provider is responsible for a secret: storing it, protecting it, and returning
* it to a caller. It is the outermost extension point in the Secrets API, and the
* one a hosting platform implements.
* one a hosting platform extends.
*
* WordPress ships WP_Secrets_Libsodium_Provider, which encrypts with libsodium and
* stores ciphertext in the options tables. That provider is the default rather than
* a privileged case: a platform that protects credentials in a key management
* service, a hardware security module, or its own control panel implements this
* same interface.
* service, a hardware security module, or its own control panel extends this
* same class.
*
* A provider must be stronger than the default, never weaker. Storing a plaintext
* where the default would have stored ciphertext is what this interface exists to
* where the default would have stored ciphertext is what this class exists to
* prevent. Receiving a value over an authenticated channel and holding it in a
* hardware security module satisfies that requirement.
*
Expand Down Expand Up @@ -49,9 +49,13 @@
* caching the raw value alongside it would undo that. Memoize within the request
* only.
*
* Implementations extend this class. It is abstract rather than an interface so
* that a later release can add a method with a default implementation, and every
* existing subclass keeps working.
*
* @since 7.2.0
*/
interface WP_Secrets_Provider {
abstract class WP_Secrets_Provider {

/**
* Protection happens inside WordPress.
Expand Down Expand Up @@ -94,7 +98,7 @@ interface WP_Secrets_Provider {
*
* @phpstan-param WP_Secret_Version::CURRENT|WP_Secret_Version::PREVIOUS $version
*/
public function get( string $name, string $version, bool $network = false );
abstract public function get( string $name, string $version, bool $network = false );

/**
* Stores a secret.
Expand Down Expand Up @@ -138,7 +142,7 @@ public function get( string $name, string $version, bool $network = false );
* 'updated'.
* @return true|WP_Error
*/
public function set(
abstract public function set(
string $name,
#[\SensitiveParameter]
$value,
Expand All @@ -162,7 +166,7 @@ public function set(
* @param bool $network Whether this is a network-scope secret.
* @return true|WP_Error
*/
public function delete( string $name, bool $network = false );
abstract public function delete( string $name, bool $network = false );

/**
* Clears a secret's previous version, if this provider keeps one.
Expand All @@ -180,7 +184,7 @@ public function delete( string $name, bool $network = false );
* @param bool $network Whether this is a network-scope secret.
* @return true|WP_Error
*/
public function retire_previous( string $name, bool $network = false );
abstract public function retire_previous( string $name, bool $network = false );

/**
* Lists secrets by name and metadata. Never values.
Expand All @@ -195,7 +199,7 @@ public function retire_previous( string $name, bool $network = false );
*
* @phpstan-return list<array{name: string, fingerprint: string, created: int, has_previous: bool, needs_rotation: bool}>|WP_Error
*/
public function list_secrets( string $name_prefix = '', bool $network = false );
abstract public function list_secrets( string $name_prefix = '', bool $network = false );

/**
* A short human-readable name for this provider, for Site Health.
Expand All @@ -208,7 +212,7 @@ public function list_secrets( string $name_prefix = '', bool $network = false );
*
* @return string
*/
public function get_label(): string;
abstract public function get_label(): string;

/**
* Where secrets this provider holds are actually protected.
Expand All @@ -225,7 +229,7 @@ public function get_label(): string;
*
* @phpstan-return self::BOUNDARY_*
*/
public function get_protection_boundary(): string;
abstract public function get_protection_boundary(): string;

/**
* Whether wp_set_secret() can succeed against this provider.
Expand All @@ -239,5 +243,5 @@ public function get_protection_boundary(): string;
*
* @return bool
*/
public function is_writable(): bool;
abstract public function is_writable(): bool;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php
/**
* Secrets API: WP_Secrets_Store interface
* Secrets API: WP_Secrets_Store class
*
* @package WordPress
* @subpackage Secrets
Expand All @@ -20,9 +20,13 @@
* the write succeeded. Providers declare writability up front through
* WP_Secrets_Provider::is_writable().
*
* Implementations extend this class. It is abstract rather than an interface so
* that a later release can add a method with a default implementation, and every
* existing subclass keeps working.
*
* @since 7.2.0
*/
interface WP_Secrets_Store {
abstract class WP_Secrets_Store {

/**
* Reads a secret's stored record.
Expand All @@ -34,7 +38,7 @@ interface WP_Secrets_Store {
* @return array<mixed>|null|WP_Error The record array if it exists. Null if it does not.
* WP_Error if it could not be determined which.
*/
public function get( string $name, bool $network = false );
abstract public function get( string $name, bool $network = false );

/**
* Writes a secret's record.
Expand All @@ -47,7 +51,7 @@ public function get( string $name, bool $network = false );
* @return bool|WP_Error True on success. WP_Error on failure, including when the
* store does not accept writes.
*/
public function set( string $name, array $record, bool $network = false );
abstract public function set( string $name, array $record, bool $network = false );

/**
* Deletes a secret's record.
Expand All @@ -59,7 +63,7 @@ public function set( string $name, array $record, bool $network = false );
* @return bool|WP_Error True on success (including if it did not exist).
* WP_Error on failure.
*/
public function delete( string $name, bool $network = false );
abstract public function delete( string $name, bool $network = false );

/**
* Lists the names of every secret in this store, for this scope. Never a value.
Expand All @@ -69,5 +73,5 @@ public function delete( string $name, bool $network = false );
* @param bool $network Whether to list network-scope secrets.
* @return string[]|WP_Error Array of secret names on success. WP_Error on failure.
*/
public function list_names( bool $network = false );
abstract public function list_names( bool $network = false );
}
6 changes: 3 additions & 3 deletions src/wp-settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -324,9 +324,9 @@
require ABSPATH . WPINC . '/secrets.php';
require ABSPATH . WPINC . '/secrets/class-wp-secret-version.php';
require ABSPATH . WPINC . '/secrets/class-wp-secret.php';
require ABSPATH . WPINC . '/secrets/interface-wp-secrets-provider.php';
require ABSPATH . WPINC . '/secrets/interface-wp-secrets-keyring.php';
require ABSPATH . WPINC . '/secrets/interface-wp-secrets-store.php';
require ABSPATH . WPINC . '/secrets/class-wp-secrets-provider.php';
require ABSPATH . WPINC . '/secrets/class-wp-secrets-keyring.php';
require ABSPATH . WPINC . '/secrets/class-wp-secrets-store.php';
require ABSPATH . WPINC . '/secrets/class-wp-secrets-config-key-provider.php';
require ABSPATH . WPINC . '/secrets/class-wp-secrets-broken-keyring.php';
require ABSPATH . WPINC . '/secrets/class-wp-secrets-cipher.php';
Expand Down
2 changes: 1 addition & 1 deletion tests/phpunit/includes/secrets/class-mock-keyring.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* a real keyring under the conformance suite without needing that code to also
* exercise libsodium.
*/
class Mock_Keyring implements WP_Secrets_Keyring {
class Mock_Keyring extends WP_Secrets_Keyring {

const MARKER = 'mock-wrapped:';

Expand Down
2 changes: 1 addition & 1 deletion tests/phpunit/includes/secrets/class-mock-store.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* Can be told to fail any operation on demand, and records every value it is ever
* handed so that a test can assert it was never given a plaintext.
*/
class Mock_Store implements WP_Secrets_Store {
class Mock_Store extends WP_Secrets_Store {

/**
* Stored records, keyed by scope and name.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Conformance suite for WP_Secrets_Keyring implementations.
*
* Extend this, return a keyring from keyring(), and the contract every keyring is
* held to gets checked against it. "implements WP_Secrets_Keyring" is a claim PHP
* held to gets checked against it. "extends WP_Secrets_Keyring" is a claim PHP
* can verify about method names and nothing else – it cannot tell you that
* wrap() is non-deterministic, that unwrap() rejects tampering instead of
* silently returning garbage bytes, or that failure is reported as WP_Error
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Conformance suite for WP_Secrets_Provider implementations.
*
* Extend this, return a provider from provider(), and the contract every provider
* is held to gets checked against it. The point is that "implements
* is held to gets checked against it. The point is that "extends
* WP_Secrets_Provider" is a claim PHP can verify about method names and nothing
* else – it cannot tell you that absence is reported as null rather than false, or
* that an unreachable backend fails closed instead of looking empty, and those are
Expand Down
Loading
Loading