mirror of
https://gitlab.linphone.org/BC/public/flexisip-account-manager.git
synced 2026-01-17 01:58:07 +00:00
Fix FLEXIAPI-167 Add the handling of a custom identifier for the JWT tokens on top of the email one
This commit is contained in:
parent
c13a78002a
commit
4f79ddca2b
9 changed files with 86 additions and 39 deletions
|
|
@ -3,6 +3,7 @@
|
|||
v1.5
|
||||
----
|
||||
- Fix FLEXIAPI-168 Add POST /accounts/me/email to confirm the email change
|
||||
- Fix FLEXIAPI-167 Add the handling of a custom identifier for the JWT tokens on top of the email one
|
||||
- Fix FLEXIAPI-166 Reimplement the deprecated email validation URL
|
||||
- Fix FLEXIAPI-165 Remove for now text/vcard header constraint
|
||||
- Fix FLEXIAPI-164 Add vcards-storage endpoints
|
||||
|
|
|
|||
|
|
@ -114,3 +114,4 @@ HCAPTCHA_SITEKEY=site-key
|
|||
|
||||
# JWT
|
||||
JWT_RSA_PUBLIC_KEY_PEM=
|
||||
JWT_SIP_IDENTIFIER=
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ use Illuminate\Support\Str;
|
|||
use App\Account;
|
||||
use App\DigestNonce;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use League\CommonMark\CommonMarkConverter;
|
||||
use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkExtension;
|
||||
use League\CommonMark\Extension\TableOfContents\TableOfContentsExtension;
|
||||
|
|
@ -51,23 +50,23 @@ function generateValidNonce(Account $account): string
|
|||
return $nonce->nonce;
|
||||
}
|
||||
|
||||
function bchash(string $username, string $domain, string $password, string $algorithm = 'MD5')
|
||||
function bchash(string $username, string $domain, string $password, string $algorithm = 'MD5'): string
|
||||
{
|
||||
return hash(passwordAlgorithms()[$algorithm], $username . ':' . $domain . ':' . $password);
|
||||
}
|
||||
|
||||
function generatePin()
|
||||
function generatePin(): int
|
||||
{
|
||||
return mt_rand(1000, 9999);
|
||||
}
|
||||
|
||||
function percent($value, $max)
|
||||
function percent($value, $max): float
|
||||
{
|
||||
if ($max == 0) $max = 1;
|
||||
return round(($value * 100) / $max, 2);
|
||||
}
|
||||
|
||||
function markdownDocumentationView($view): string
|
||||
function markdownDocumentationView(string $view): string
|
||||
{
|
||||
$converter = new CommonMarkConverter([
|
||||
'heading_permalink' => [
|
||||
|
|
@ -92,7 +91,12 @@ function markdownDocumentationView($view): string
|
|||
);
|
||||
}
|
||||
|
||||
function isRegularExpression($string): bool
|
||||
function parseSIP(string $sipAdress): array
|
||||
{
|
||||
return explode('@', \substr($sipAdress, 4));
|
||||
}
|
||||
|
||||
function isRegularExpression(string $string): bool
|
||||
{
|
||||
set_error_handler(function () {
|
||||
}, E_WARNING);
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ class AuthenticateDigestOrKey
|
|||
])->validate();
|
||||
|
||||
$from = $this->extractFromHeader($request->header('From'));
|
||||
list($username, $domain) = explode('@', $from);
|
||||
list($username, $domain) = parseSIP($from);
|
||||
|
||||
$account = Account::withoutGlobalScopes()
|
||||
->where('username', $username)
|
||||
|
|
@ -234,7 +234,7 @@ class AuthenticateDigestOrKey
|
|||
|
||||
private function extractFromHeader(string $string): string
|
||||
{
|
||||
list($from) = explode(';', \substr($string, 4));
|
||||
list($from) = explode(';', $string);
|
||||
return \rawurldecode($from);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,15 +33,15 @@ class AuthenticateJWT
|
|||
|
||||
switch ($token->headers()->get('alg')) {
|
||||
case 'RS256':
|
||||
$signer = new Sha256;
|
||||
$signer = new Sha256();
|
||||
break;
|
||||
|
||||
case 'RS384':
|
||||
$signer = new Sha384;
|
||||
$signer = new Sha384();
|
||||
break;
|
||||
|
||||
case 'RS512':
|
||||
$signer = new Sha512;
|
||||
$signer = new Sha512();
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -57,9 +57,20 @@ class AuthenticateJWT
|
|||
abort(403, 'Expired JWT token');
|
||||
}
|
||||
|
||||
$account = Account::withoutGlobalScopes()
|
||||
->where('email', $token->claims()->get('email'))
|
||||
->first();
|
||||
$account = null;
|
||||
|
||||
if ($token->claims()->has(config('services.jwt.sip_identifier'))) {
|
||||
list($username, $domain) = parseSIP($token->claims()->get(config('services.jwt.sip_identifier')));
|
||||
|
||||
$account = Account::withoutGlobalScopes()
|
||||
->where('username', $username)
|
||||
->where('domain', $domain)
|
||||
->first();
|
||||
} elseif ($token->claims()->has('email')) {
|
||||
$account = Account::withoutGlobalScopes()
|
||||
->where('email', $token->claims()->get('email'))
|
||||
->first();
|
||||
}
|
||||
|
||||
if (!$account) {
|
||||
abort(403, 'The JWT token is not related to someone in the system');
|
||||
|
|
|
|||
14
flexiapi/composer.lock
generated
14
flexiapi/composer.lock
generated
|
|
@ -4738,20 +4738,20 @@
|
|||
},
|
||||
{
|
||||
"name": "ramsey/uuid",
|
||||
"version": "4.7.5",
|
||||
"version": "4.7.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ramsey/uuid.git",
|
||||
"reference": "5f0df49ae5ad6efb7afa69e6bfab4e5b1e080d8e"
|
||||
"reference": "91039bc1faa45ba123c4328958e620d382ec7088"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/ramsey/uuid/zipball/5f0df49ae5ad6efb7afa69e6bfab4e5b1e080d8e",
|
||||
"reference": "5f0df49ae5ad6efb7afa69e6bfab4e5b1e080d8e",
|
||||
"url": "https://api.github.com/repos/ramsey/uuid/zipball/91039bc1faa45ba123c4328958e620d382ec7088",
|
||||
"reference": "91039bc1faa45ba123c4328958e620d382ec7088",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11",
|
||||
"brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12",
|
||||
"ext-json": "*",
|
||||
"php": "^8.0",
|
||||
"ramsey/collection": "^1.2 || ^2.0"
|
||||
|
|
@ -4814,7 +4814,7 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/ramsey/uuid/issues",
|
||||
"source": "https://github.com/ramsey/uuid/tree/4.7.5"
|
||||
"source": "https://github.com/ramsey/uuid/tree/4.7.6"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -4826,7 +4826,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-11-08T05:53:05+00:00"
|
||||
"time": "2024-04-27T21:32:50+00:00"
|
||||
},
|
||||
{
|
||||
"name": "react/cache",
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -32,6 +32,7 @@ return [
|
|||
|
||||
'jwt' => [
|
||||
'rsa_public_key_pem' => env('JWT_RSA_PUBLIC_KEY_PEM'),
|
||||
],
|
||||
'sip_identifier' => env('JWT_SIP_IDENTIFIER', 'sip_identity'),
|
||||
],
|
||||
|
||||
];
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ use DateTimeImmutable;
|
|||
use Lcobucci\Clock\FrozenClock;
|
||||
use Lcobucci\JWT\Builder;
|
||||
use Lcobucci\JWT\JwtFacade;
|
||||
use Lcobucci\JWT\UnencryptedToken;
|
||||
use Lcobucci\JWT\Signer\Key\InMemory;
|
||||
use Lcobucci\JWT\Signer\Rsa\Sha256;
|
||||
use Lcobucci\JWT\Signer\Rsa\Sha512;
|
||||
|
|
@ -68,14 +69,38 @@ class AccountJWTAuthenticationTest extends TestCase
|
|||
): Builder => $builder->withClaim('email', $password->account->email)
|
||||
);
|
||||
|
||||
$this->withHeaders([
|
||||
'Authorization' => 'Bearer ' . $token->toString(),
|
||||
'x-linphone-provisioning' => true,
|
||||
])
|
||||
->get($this->accountRoute)
|
||||
->assertStatus(200)
|
||||
->assertHeader('Content-Type', 'application/xml')
|
||||
->assertSee('ha1');
|
||||
$this->checkToken($token);
|
||||
|
||||
// SIP identifier
|
||||
|
||||
// This line shoudn't be required, but the pipeline doesn't get the default value somehow
|
||||
config()->set('services.jwt.sip_identifier', 'sip_identity');
|
||||
|
||||
$token = (new JwtFacade(null, $clock))->issue(
|
||||
new Sha256(),
|
||||
InMemory::plainText($this->serverPrivateKeyPem),
|
||||
static fn (
|
||||
Builder $builder,
|
||||
DateTimeImmutable $issuedAt
|
||||
): Builder => $builder->withClaim('sip_identity', 'sip:' . $password->account->username . '@' . $password->account->domain)
|
||||
);
|
||||
|
||||
$this->checkToken($token);
|
||||
|
||||
// Custom SIP identifier
|
||||
$otherIdentifier = 'sip_other_identifier';
|
||||
config()->set('services.jwt.sip_identifier', $otherIdentifier);
|
||||
|
||||
$token = (new JwtFacade(null, $clock))->issue(
|
||||
new Sha256(),
|
||||
InMemory::plainText($this->serverPrivateKeyPem),
|
||||
static fn (
|
||||
Builder $builder,
|
||||
DateTimeImmutable $issuedAt
|
||||
): Builder => $builder->withClaim($otherIdentifier, 'sip:' . $password->account->username . '@' . $password->account->domain)
|
||||
);
|
||||
|
||||
$this->checkToken($token);
|
||||
|
||||
// Sha512
|
||||
$token = (new JwtFacade(null, $clock))->issue(
|
||||
|
|
@ -87,17 +112,9 @@ class AccountJWTAuthenticationTest extends TestCase
|
|||
): Builder => $builder->withClaim('email', $password->account->email)
|
||||
);
|
||||
|
||||
$this->withHeaders([
|
||||
'Authorization' => 'Bearer ' . $token->toString(),
|
||||
'x-linphone-provisioning' => true,
|
||||
])
|
||||
->get($this->accountRoute)
|
||||
->assertStatus(200)
|
||||
->assertHeader('Content-Type', 'application/xml')
|
||||
->assertSee('ha1');
|
||||
$this->checkToken($token);
|
||||
|
||||
// Expired token
|
||||
|
||||
$oldClock = new FrozenClock(new DateTimeImmutable('2022-06-24 22:51:10'));
|
||||
|
||||
$token = (new JwtFacade(null, $oldClock))->issue(
|
||||
|
|
@ -155,4 +172,16 @@ class AccountJWTAuthenticationTest extends TestCase
|
|||
->get($this->accountRoute)
|
||||
->assertStatus(403);
|
||||
}
|
||||
|
||||
private function checkToken(UnencryptedToken $token): void
|
||||
{
|
||||
$this->withHeaders([
|
||||
'Authorization' => 'Bearer ' . $token->toString(),
|
||||
'x-linphone-provisioning' => true,
|
||||
])
|
||||
->get($this->accountRoute)
|
||||
->assertStatus(200)
|
||||
->assertHeader('Content-Type', 'application/xml')
|
||||
->assertSee('ha1');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue