diff --git a/pirate-parrot.php b/pirate-parrot.php index 5d8820a..a8d4899 100644 --- a/pirate-parrot.php +++ b/pirate-parrot.php @@ -433,7 +433,21 @@ function derive_secret( $context, $length ) { } function get_admin_password() { - return $this->derive_secret( 'admin', self::ADMIN_PASSWORD_LENGTH ); + $this->get_options(); + if ( empty( $this->_options['seed'] ) ) { + return ''; + } + // map the raw digest onto the full password alphabet — a hex-only + // password looks weak and some hosts enforce mixed character classes + $alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_=+'; + $digest = hash_hmac( 'sha256', 'admin|' . $this->_options['seed'], wp_salt( 'auth' ), true ); + $size = strlen( $alphabet ); + $password = ''; + for ( $i = 0; $i < self::ADMIN_PASSWORD_LENGTH; $i++ ) { + $password .= $alphabet[ ord( $digest[ $i ] ) % $size ]; + } + + return $password; } function get_agent_token() { diff --git a/tests/test-agent-api.php b/tests/test-agent-api.php index 71aca46..e9307c4 100644 --- a/tests/test-agent-api.php +++ b/tests/test-agent-api.php @@ -92,8 +92,10 @@ public function test_parrot_page_shows_details_after_generation() { $this->assertStringContainsString( 'Access active', $page ); $this->assertStringContainsString( 'Details to share with support', $page ); - $this->assertStringContainsString( $this->agent_token, $page ); - $this->assertStringContainsString( $this->parrot->get_admin_password(), $page ); + // the page escapes the row values, so assert the escaped forms — the + // password alphabet includes & and other HTML-special characters + $this->assertStringContainsString( esc_html( $this->agent_token ), $page ); + $this->assertStringContainsString( esc_html( $this->parrot->get_admin_password() ), $page ); } public function test_credentials_are_redisplayable_across_requests() { @@ -108,6 +110,9 @@ public function test_admin_password_derives_and_matches_the_account() { $user = get_user_by( 'login', 'ti_parrot' ); $this->assertSame( TI_Parrot::ADMIN_PASSWORD_LENGTH, strlen( $password ) ); + $this->assertMatchesRegularExpression( '/^[a-zA-Z0-9!@#$%^&*()\-_=+]+$/', $password ); + // drawn from the full alphabet, not the digest's hex form + $this->assertMatchesRegularExpression( '/[^a-f0-9]/', $password ); $this->assertInstanceOf( 'WP_User', $user ); $this->assertTrue( wp_check_password( $password, $user->user_pass, $user->ID ) ); $this->assertTrue( $this->parrot->is_admin_password_in_sync() );