Skip to content

Commit

Permalink
refactor: use URL from configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
thorsten committed Sep 14, 2024
1 parent 6fb0fa2 commit e8c860b
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 25 deletions.
85 changes: 60 additions & 25 deletions phpmyfaq/src/phpMyFAQ/Auth/AuthWebAuthn.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@
use phpMyFAQ\Auth\WebAuthn\WebAuthnUser;
use phpMyFAQ\Configuration;
use phpMyFAQ\Core\Exception;
use phpMyFAQ\Utils;
use phpseclib3\Crypt\PublicKeyLoader;
use phpseclib3\Math\BigInteger;
use Random\RandomException;
use stdClass;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;

class AuthWebAuthn extends Auth
Expand All @@ -43,67 +43,102 @@ public function __construct(Configuration $configuration)
{
parent::__construct($configuration);

$this->appId = Request::createFromGlobals()->getHost();
$this->appId = Utils::getHostFromUrl($configuration->getDefaultUrl());
}

/**
* Generate a challenge ready for registering a hardware key, fingerprint or whatever
*
* @param string $username
* @param string $userId
* @return array
* @return array<string, string>
* @throws RandomException
*/
public function prepareChallengeForRegistration(string $username, string $userId): array
{
$result = new stdClass();
$challenge = random_bytes(16);
$result->challenge = self::stringToArray($challenge);
$result->user = new stdClass();
$result->user->name = $result->user->displayName = $username;
$result->user->id = self::stringToArray($userId);

$result->rp = new stdClass();
$result->rp->name = $this->appId;
// Convert the challenge to an array of bytes
$challengeArray = self::stringToArray($challenge);

// Prepare user information
$user = [
'name' => $username,
'displayName' => $username,
'id' => self::stringToArray($userId),
];

// We need to set the ID only if we are not on localhost
// Prepare relying party (rp) information
$rp = [
'name' => $this->appId,
];

// Set the 'id' field if not running on localhost
if (!str_contains($this->appId, 'localhost')) {
$result->rp->id = $this->appId;
$rp['id'] = $this->appId;
}

$result->pubKeyCredParams = [
// Prepare public key credential parameters
$pubKeyCredParams = [
[
'alg' => self::ES256,
'alg' => self::ES256,
'type' => 'public-key',
],
[
'alg' => self::RS256,
'alg' => self::RS256,
'type' => 'public-key',
],
];

$result->authenticatorSelection = new stdClass();
$result->authenticatorSelection->requireResidentKey = false;
$result->authenticatorSelection->userVerification = 'discouraged';
// Prepare authenticator selection criteria
$authenticatorSelection = [
'requireResidentKey' => false,
'userVerification' => 'discouraged',
];

// Prepare extensions
$extensions = [
'exts' => true,
];

// Build the publicKey object
$publicKey = [
'challenge' => $challengeArray,
'user' => $user,
'rp' => $rp,
'pubKeyCredParams' => $pubKeyCredParams,
'authenticatorSelection' => $authenticatorSelection,
'attestation' => null,
'timeout' => 60000,
'excludeCredentials' => [],
'extensions' => $extensions,
];

$result->attestation = null;
$result->timeout = 60000;
$result->excludeCredentials = []; // No excludeList
$result->extensions = new stdClass();
$result->extensions->exts = true;
// Base64 URL-encode the challenge for later verification
$b64challenge = rtrim(strtr(base64_encode($challenge), '+/', '-_'), '=');

// Return the prepared data
return [
'publicKey' => $result,
'b64challenge' => rtrim(strtr(base64_encode($challenge), '+/', '-_'), '='),
'publicKey' => $publicKey,
'b64challenge' => $b64challenge,
];
}

/**
* Store the WebAuth user information in the session
* @param WebAuthnUser $user
* @return void
*/
public function storeUserInSession(WebAuthnUser $user): void
{
$session = new Session();
$session->set('webauthn', $user);
}

/**
* Get the WebAuth user information from the session
* @return WebAuthnUser|null
*/
public function getUserFromSession(): ?WebAuthnUser
{
$session = new Session();
Expand Down
17 changes: 17 additions & 0 deletions phpmyfaq/src/phpMyFAQ/Utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,23 @@ public static function parseUrl(string $string): string
return preg_replace($pattern, $replacement, $string);
}

/**
* Extracts the hostname from a given URL.
*
* @param string $url The URL from which to extract the hostname.
* @return string|null The hostname or null if the URL is invalid.
*/
public static function getHostFromUrl(string $url): ?string
{
$parsedUrl = parse_url($url);

if ($parsedUrl && isset($parsedUrl['host'])) {
return $parsedUrl['host'];
}

return null;
}

/**
* Moves given key of an array to the top
*
Expand Down
38 changes: 38 additions & 0 deletions tests/phpMyFAQ/UtilsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,44 @@ public function testParseUrl(): void
}
}

public function testGetHostFromValidUrl(): void
{
$url = 'https://example.com/path?query=param';
$host = Utils::getHostFromUrl($url);
$this->assertEquals('example.com', $host);
}

public function testGetHostFromUrlWithSubdomain(): void
{
$url = 'https://sub.example.com/path';
$host = Utils::getHostFromUrl($url);
$this->assertEquals('sub.example.com', $host);
}

public function testGetHostFromUrlWithoutScheme(): void
{
$url = 'example.com/path';
$host = Utils::getHostFromUrl($url);
$this->assertNull($host); // parse_url won't recognize 'example.com' without scheme
}

public function testGetHostFromInvalidUrl(): void
{
$url = 'not_a_url';
$host = Utils::getHostFromUrl($url);
$this->assertNull($host);
}

/**
* Test URL without a host part.
*/
public function testGetHostFromUrlWithoutHost(): void
{
$url = 'ftp://user:password@';
$host = Utils::getHostFromUrl($url);
$this->assertNull($host);
}

public function testChopString(): void
{
// Test case 1: String has more words than desired length
Expand Down

0 comments on commit e8c860b

Please sign in to comment.