client = $client; $this->store = $store; $this->logger = $logger; } /** * The rotator of this site. */ public static function for_site(): self { $store = new SecretStore( Encryptor::for_site() ); return new self( new CoreClient( $store, CoreClient::configured_base_url() ), $store, new Logger() ); } /** * Rotates the secret. * * @return CoreError|null Null when the new secret is saved. */ public function rotate(): ?CoreError { // The store id the request is signed with; the core's answer carries only the new secret. $current = $this->store->get_connection(); if ( null === $current ) { // Nothing is sent; not worth a log entry (the button is only shown while connected). return new CoreError( $this->store->has_unreadable_connection() ? CoreError::CONNECTION_UNREADABLE : CoreError::NOT_ACTIVATED ); } $response = $this->client->post( self::PATH ); if ( ! $response->is_success() ) { return $this->fail( $response->error ); } $connection = self::connection( $current->store_id, $response->data ); if ( null === $connection ) { return $this->fail( new CoreError( CoreError::INVALID_RESPONSE, $response->status, $response->request_id ) ); } if ( ! $this->store->save_connection( $connection ) ) { return $this->fail( new CoreError( CoreError::SECRET_NOT_SAVED, $response->status, $response->request_id ) ); } StatusService::clear(); $this->logger->info( 'Shared secret rotated.', array( 'store_id' => $connection->store_id, 'request_id' => $response->request_id, ) ); return null; } /** * The connection with the new secret, or null when the answer has none in the core's format. * * @param string $store_id The store id (unchanged by a rotation). * @param array $data Decoded answer. */ private static function connection( string $store_id, array $data ): ?Connection { $shared_secret = $data['shared_secret'] ?? null; if ( ! is_string( $shared_secret ) ) { return null; } try { return new Connection( $store_id, $shared_secret ); } catch ( \InvalidArgumentException $e ) { return null; } } /** * Logs a failure and returns it. * * @param CoreError $error The failure. */ private function fail( CoreError $error ): CoreError { $this->logger->core_error( self::OPERATION, $error ); return $error; } }