diff options
-rw-r--r-- | Zotlabs/Module/Owa.php | 184 | ||||
-rw-r--r-- | tests/unit/Module/OwaTest.php | 64 |
2 files changed, 170 insertions, 78 deletions
diff --git a/Zotlabs/Module/Owa.php b/Zotlabs/Module/Owa.php index 85467d4f4..21082e166 100644 --- a/Zotlabs/Module/Owa.php +++ b/Zotlabs/Module/Owa.php @@ -18,96 +18,95 @@ use Zotlabs\Web\Controller; class Owa extends Controller { - function init() { + public function init(): void + { $ret = [ 'success' => false ]; - if (array_key_exists('REDIRECT_REMOTE_USER',$_SERVER) && (! array_key_exists('HTTP_AUTHORIZATION',$_SERVER))) { - $_SERVER['HTTP_AUTHORIZATION'] = $_SERVER['REDIRECT_REMOTE_USER']; + if (!$this->validateAuthorizationHeader()) { + $this->error('Missing or invalid authorization header.'); } - if (array_key_exists('HTTP_AUTHORIZATION',$_SERVER) && substr(trim($_SERVER['HTTP_AUTHORIZATION']),0,9) === 'Signature') { - $sigblock = HTTPSig::parse_sigheader($_SERVER['HTTP_AUTHORIZATION']); - if ($sigblock) { - $keyId = $sigblock['keyId']; - $parsed = parse_url($keyId); - if (str_starts_with($parsed['scheme'],'http')) { - unset($parsed['fragment']); - unset($parsed['query']); - $keyId = unparse_url($parsed); - } - else { - $keyId = str_replace('acct:', '', $keyId); + $sigblock = HTTPSig::parse_sigheader($_SERVER['HTTP_AUTHORIZATION']); + if ($sigblock) { + $keyId = $sigblock['keyId']; + $parsed = parse_url($keyId); + if (str_starts_with($parsed['scheme'],'http')) { + unset($parsed['fragment']); + unset($parsed['query']); + $keyId = unparse_url($parsed); + } + else { + $keyId = str_replace('acct:', '', $keyId); + } + if ($keyId) { + $r = q("SELECT * FROM hubloc LEFT JOIN xchan ON hubloc_hash = xchan_hash + WHERE (hubloc_addr = '%s' OR hubloc_id_url = '%s' OR xchan_hash = '%s') + AND hubloc_deleted = 0 AND xchan_pubkey != '' + ORDER BY hubloc_id DESC", + dbesc($keyId), + dbesc($keyId), + dbesc($keyId) + ); + if (! $r) { + $found = discover_by_webbie($keyId); + logger('found = ' . print_r($found, true)); + if ($found) { + $r = q("SELECT * FROM hubloc LEFT JOIN xchan ON hubloc_hash = xchan_hash + WHERE (hubloc_addr = '%s' OR hubloc_id_url = '%s' OR xchan_hash = '%s') AND hubloc_deleted = 0 AND xchan_pubkey != '' ORDER BY hubloc_id DESC ", + dbesc($keyId), + dbesc($keyId), + dbesc($keyId) + ); + } } - if ($keyId) { - $r = q("SELECT * FROM hubloc LEFT JOIN xchan ON hubloc_hash = xchan_hash - WHERE (hubloc_addr = '%s' OR hubloc_id_url = '%s' OR xchan_hash = '%s') - AND hubloc_deleted = 0 AND xchan_pubkey != '' - ORDER BY hubloc_id DESC", - dbesc($keyId), - dbesc($keyId), - dbesc($keyId) - ); - if (! $r) { + + if ($r) { + foreach ($r as $hubloc) { + $verified = HTTPSig::verify(file_get_contents('php://input'), $hubloc['xchan_pubkey']); + if ($verified && $verified['header_signed'] && $verified['header_valid'] && ($verified['content_valid'] || (! $verified['content_signed']))) { + logger('OWA header: ' . print_r($verified,true),LOGGER_DATA); + logger('OWA success: ' . $hubloc['hubloc_id_url'],LOGGER_DATA); + $ret['success'] = true; + $token = random_string(32); + Verify::create('owt',0,$token,$hubloc['hubloc_id_url']); + $result = ''; + openssl_public_encrypt($token,$result,$hubloc['xchan_pubkey']); + $ret['encrypted_token'] = base64url_encode($result); + break; + } else { + logger('OWA fail: ' . $hubloc['hubloc_id'] . ' ' . $hubloc['hubloc_id_url']); + } + } + + if (!$ret['success']) { + + // Possible a reinstall? + // In this case we probably already have an old hubloc + // but not the new one yet. + $found = discover_by_webbie($keyId); - logger('found = ' . print_r($found, true)); + if ($found) { $r = q("SELECT * FROM hubloc LEFT JOIN xchan ON hubloc_hash = xchan_hash - WHERE (hubloc_addr = '%s' OR hubloc_id_url = '%s' OR xchan_hash = '%s') AND hubloc_deleted = 0 AND xchan_pubkey != '' ORDER BY hubloc_id DESC ", - dbesc($keyId), - dbesc($keyId), + WHERE (hubloc_addr = '%s' OR hubloc_id_url = '%s') AND hubloc_deleted = 0 ORDER BY hubloc_id DESC LIMIT 1", + dbesc(str_replace('acct:', '', $keyId)), dbesc($keyId) ); - } - } - - if ($r) { - foreach ($r as $hubloc) { - $verified = HTTPSig::verify(file_get_contents('php://input'), $hubloc['xchan_pubkey']); - if ($verified && $verified['header_signed'] && $verified['header_valid'] && ($verified['content_valid'] || (! $verified['content_signed']))) { - logger('OWA header: ' . print_r($verified,true),LOGGER_DATA); - logger('OWA success: ' . $hubloc['hubloc_id_url'],LOGGER_DATA); - $ret['success'] = true; - $token = random_string(32); - Verify::create('owt',0,$token,$hubloc['hubloc_id_url']); - $result = ''; - openssl_public_encrypt($token,$result,$hubloc['xchan_pubkey']); - $ret['encrypted_token'] = base64url_encode($result); - break; - } else { - logger('OWA fail: ' . $hubloc['hubloc_id'] . ' ' . $hubloc['hubloc_id_url']); - } - } - if (!$ret['success']) { - - // Possible a reinstall? - // In this case we probably already have an old hubloc - // but not the new one yet. - - $found = discover_by_webbie($keyId); - - if ($found) { - $r = q("SELECT * FROM hubloc LEFT JOIN xchan ON hubloc_hash = xchan_hash - WHERE (hubloc_addr = '%s' OR hubloc_id_url = '%s') AND hubloc_deleted = 0 ORDER BY hubloc_id DESC LIMIT 1", - dbesc(str_replace('acct:', '', $keyId)), - dbesc($keyId) - ); - - if ($r) { - $verified = HTTPSig::verify(file_get_contents('php://input'), $r[0]['xchan_pubkey']); - if ($verified && $verified['header_signed'] && $verified['header_valid'] && ($verified['content_valid'] || (! $verified['content_signed']))) { - logger('OWA header: ' . print_r($verified,true), LOGGER_DATA); - logger('OWA success: ' . $r[0]['hubloc_id_url'], LOGGER_DATA); - $ret['success'] = true; - $token = random_string(32); - Verify::create('owt', 0, $token, $r[0]['hubloc_id_url']); - $result = ''; - openssl_public_encrypt($token, $result, $r[0]['xchan_pubkey']); - $ret['encrypted_token'] = base64url_encode($result); - } else { - logger('OWA fail: ' . $hubloc['hubloc_id'] . ' ' . $hubloc['hubloc_id_url']); - } + if ($r) { + $verified = HTTPSig::verify(file_get_contents('php://input'), $r[0]['xchan_pubkey']); + if ($verified && $verified['header_signed'] && $verified['header_valid'] && ($verified['content_valid'] || (! $verified['content_signed']))) { + logger('OWA header: ' . print_r($verified,true), LOGGER_DATA); + logger('OWA success: ' . $r[0]['hubloc_id_url'], LOGGER_DATA); + $ret['success'] = true; + $token = random_string(32); + Verify::create('owt', 0, $token, $r[0]['hubloc_id_url']); + $result = ''; + openssl_public_encrypt($token, $result, $r[0]['xchan_pubkey']); + $ret['encrypted_token'] = base64url_encode($result); + } else { + logger('OWA fail: ' . $hubloc['hubloc_id'] . ' ' . $hubloc['hubloc_id_url']); } } } @@ -118,4 +117,33 @@ class Owa extends Controller { json_return_and_die($ret,'application/x-zot+json'); } + + private function validateAuthorizationHeader(): bool + { + if (!empty($_SERVER['HTTP_AUTHORIZATION'])) { + $auth = trim($_SERVER['HTTP_AUTHORIZATION']); + } else if (!empty($_SERVER['REDIRECT_REMOTE_USER'])) { + $auth = trim($_SERVER['REDIRECT_REMOTE_USER']); + } else { + return false; + } + + return strncmp($auth, 'Signature', 9) === 0; + } + + /** + * Terminates the request, and return a json error response. + * + * @Note This function does not return! + * + * @param string $msg The error message for the response. + */ + private function error(string $msg): void { + $ret = [ + 'success' => false, + 'message' => $msg + ]; + + json_return_and_die($ret,'application/x-zot+json'); + } } diff --git a/tests/unit/Module/OwaTest.php b/tests/unit/Module/OwaTest.php new file mode 100644 index 000000000..dbb25c0b5 --- /dev/null +++ b/tests/unit/Module/OwaTest.php @@ -0,0 +1,64 @@ +<?php +/* + * SPDX-FileCopyrightText: 2025 Hubzilla Community + * SPDX-FileContributor: Harald Eilertsen + * + * SPDX-License-Identifier: MIT + */ + +namespace Zotlabs\Tests\Unit\Module; + +class OwaTest extends TestCase +{ + public function testShouldReturnErrorIfNoAuthorizationHeader(): void + { + // Expect the call to return error + $this->expectJsonResponse([ + 'success' => false, + 'message' => 'Missing or invalid authorization header.' + ]); + + $this->get('owa'); + } + + public function testShouldReturnErrorIfWrongAuthorizationHeader(): void + { + // Expect the call to return error + $this->expectJsonResponse([ + 'success' => false, + 'message' => 'Missing or invalid authorization header.' + ]); + + $_SERVER['HTTP_AUTHORIZATION'] = 'Bearer kjkjhkjhkjh'; + $this->get('owa'); + } + + public function testShouldReturnErrorIfInvalidAuthorizationHeader(): void + { + // Expect the call to return error + $this->expectJsonResponse(['success' => false]); + + $_SERVER['HTTP_AUTHORIZATION'] = 'Signature kjkjhkjhkjh'; + $this->get('owa'); + } + + /** + * Expect the request to be terminated and return a json response. + */ + private function expectJsonResponse(array $data): void + { + $this->getFunctionMock('Zotlabs\Module', 'json_return_and_die') + ->expects($this->once()) + ->with( + $this->identicalTo($data), + $this->identicalTo('application/x-zot+json') + ) + ->willReturnCallback( + function() { + throw new KillmeException(); + } + ); + + $this->expectException(KillmeException::class); + } +} |