mirror of
https://gitlab.linphone.org/BC/public/flexisip-account-manager.git
synced 2026-05-02 00:46:24 +00:00
Fix FLEXIAPI-464 Add per Space custom RFC8898 flow
This commit is contained in:
parent
8585095d99
commit
0669b0d965
16 changed files with 723 additions and 105 deletions
16
CHANGELOG.md
16
CHANGELOG.md
|
|
@ -8,12 +8,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- **Add CardDav servers** They can be configured in the administration panels and the API.
|
- **CardDav servers** They can be configured in the administration panels and the API.
|
||||||
- **Rockylinux 10 support** Packages are now available in the official repository
|
- **Rockylinux 10 support** Packages are now available in the official repository
|
||||||
- **Artisan cleanup script for statistics** Add an artisan console script to clear statistics after n days `app:clear-statistics {days} {--apply}`
|
- **Artisan cleanup script for statistics** Add an artisan console script to clear statistics after n days `app:clear-statistics {days} {--apply}`
|
||||||
- **Add Voicemail features and related API endpoints** to integrate with `flexisip-voicemail`
|
- **Voicemail features and related API endpoints** to integrate with `flexisip-voicemail`
|
||||||
- **Add Call Forwarding features and related API endpoints**
|
- **Call Forwarding features and related API endpoints**
|
||||||
- **Add generated licenses.md file in FlexiAPI**
|
- **Generated licenses.md file in FlexiAPI**
|
||||||
|
- **Per Space Custom SSO authentication flow**
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|
@ -21,6 +22,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
|
||||||
- **PHP 8.2 minimum** Laravel and its dependencies were upgraded to version 11 as well.
|
- **PHP 8.2 minimum** Laravel and its dependencies were upgraded to version 11 as well.
|
||||||
- **Logout the user when the password is correctly changed**
|
- **Logout the user when the password is correctly changed**
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- **Space SSO authentication:** Custom SSO authentication support has now be moved into Spaces configuraton, the following environnement keys are removed.
|
||||||
|
- `JWT_RSA_PUBLIC_KEY_PEM`
|
||||||
|
- `JWT_SIP_IDENTIFIER`
|
||||||
|
- `ACCOUNT_AUTHENTICATION_BEARER`, it is now generated directly from the Space configuration
|
||||||
|
|
||||||
## [2.0]
|
## [2.0]
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@ ACCOUNT_EMAIL_UNIQUE=false # Emails are unique between all the accounts
|
||||||
ACCOUNT_BLACKLISTED_USERNAMES=
|
ACCOUNT_BLACKLISTED_USERNAMES=
|
||||||
ACCOUNT_USERNAME_REGEX="^[a-z0-9+_.-]*$"
|
ACCOUNT_USERNAME_REGEX="^[a-z0-9+_.-]*$"
|
||||||
ACCOUNT_DEFAULT_PASSWORD_ALGORITHM=SHA-256 # Can ONLY be MD5 or SHA-256 in capital, default to SHA-256
|
ACCOUNT_DEFAULT_PASSWORD_ALGORITHM=SHA-256 # Can ONLY be MD5 or SHA-256 in capital, default to SHA-256
|
||||||
ACCOUNT_AUTHENTICATION_BEARER= # Bearer value (WWW-Authenticate: Bearer <value>) of the external service that can provide a trusted (eg. JWT token) for the authentication, takes priority and disable the DIGEST auth if set, see https://www.rfc-editor.org/rfc/rfc8898
|
|
||||||
|
|
||||||
# Blocking service
|
# Blocking service
|
||||||
BLOCKING_TIME_PERIOD_CHECK=30 # Time span on which the blocking service will proceed, in minutes
|
BLOCKING_TIME_PERIOD_CHECK=30 # Time span on which the blocking service will proceed, in minutes
|
||||||
|
|
@ -93,9 +92,5 @@ OVH_APP_SENDER=
|
||||||
HCAPTCHA_SECRET=secret-key
|
HCAPTCHA_SECRET=secret-key
|
||||||
HCAPTCHA_SITEKEY=site-key
|
HCAPTCHA_SITEKEY=site-key
|
||||||
|
|
||||||
# JWT
|
|
||||||
JWT_RSA_PUBLIC_KEY_PEM=
|
|
||||||
JWT_SIP_IDENTIFIER=
|
|
||||||
|
|
||||||
# Temporary toggles
|
# Temporary toggles
|
||||||
APP_SHOW_LOGIN_COUNTER_TEMP= # default true
|
APP_SHOW_LOGIN_COUNTER_TEMP= # default true
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Admin\Space;
|
||||||
|
|
||||||
|
use App\Space;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class SSOServerController extends Controller
|
||||||
|
{
|
||||||
|
public function show(int $spaceId)
|
||||||
|
{
|
||||||
|
return view('admin.space.sso_server.show', [
|
||||||
|
'space' => Space::findOrFail($spaceId)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function refreshPublicKey(int $spaceId)
|
||||||
|
{
|
||||||
|
$space = Space::findOrFail($spaceId);
|
||||||
|
|
||||||
|
if (!$space->refreshSSOCertificate()) {
|
||||||
|
return redirect()->back()->withErrors([
|
||||||
|
'sso_public_key' => __('The public key cannot be refreshed')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$space->save();
|
||||||
|
|
||||||
|
return redirect()->back();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(Request $request, int $spaceId)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'sso_server_url' => 'required|url|ends_with:/',
|
||||||
|
'sso_realm' => 'required',
|
||||||
|
'sso_sip_identifier' => 'required'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$space = Space::findOrFail($spaceId);
|
||||||
|
$space->sso_server_url = $request->get('sso_server_url');
|
||||||
|
$space->sso_realm = $request->get('sso_realm');
|
||||||
|
$space->sso_sip_identifier = $request->get('sso_sip_identifier');
|
||||||
|
|
||||||
|
if ($space->refreshSSOCertificate()) {
|
||||||
|
$space->save();
|
||||||
|
} else {
|
||||||
|
return redirect()->back()->withErrors([
|
||||||
|
'sso_public_key' => __('The public key cannot be refreshed')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('admin.spaces.integration', $spaceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -20,6 +20,7 @@
|
||||||
namespace App\Http\Middleware;
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
use App\Account;
|
use App\Account;
|
||||||
|
use App\Space;
|
||||||
use Closure;
|
use Closure;
|
||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
@ -39,13 +40,18 @@ class AuthenticateJWT
|
||||||
{
|
{
|
||||||
public function handle(Request $request, Closure $next)
|
public function handle(Request $request, Closure $next)
|
||||||
{
|
{
|
||||||
if ($request->bearerToken() && config('services.jwt.rsa_public_key_pem')) {
|
if ($request->bearerToken() && $request->space?->sso_public_key) {
|
||||||
if (!extension_loaded('sodium')) {
|
if (!extension_loaded('sodium')) {
|
||||||
abort(403, "PHP Sodium extension isn't loaded");
|
abort(403, "PHP Sodium extension isn't loaded");
|
||||||
}
|
}
|
||||||
|
|
||||||
$publicKey = InMemory::plainText(config('services.jwt.rsa_public_key_pem'));
|
$publicKey = InMemory::plainText($request->space->sso_public_key);
|
||||||
$token = (new Parser(new JoseEncoder()))->parse($request->bearerToken());
|
|
||||||
|
try {
|
||||||
|
$token = (new Parser(new JoseEncoder()))->parse($request->bearerToken());
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
return $this->generateUnauthorizedBearerResponse($request->space, 'invalid_token', 'Invalid bearer ' . $th->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
$signer = null;
|
$signer = null;
|
||||||
|
|
||||||
|
|
@ -64,19 +70,19 @@ class AuthenticateJWT
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($signer == null) {
|
if ($signer == null) {
|
||||||
return $this->generateUnauthorizedBearerResponse('invalid_token', 'Unsupported RSA signature');
|
return $this->generateUnauthorizedBearerResponse($request->space, 'invalid_token', 'Unsupported RSA signature');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(new Validator())->validate($token, new SignedWith($signer, $publicKey))) {
|
if (!(new Validator())->validate($token, new SignedWith($signer, $publicKey))) {
|
||||||
return $this->generateUnauthorizedBearerResponse('invalid_token', 'Invalid JWT token signature');
|
return $this->generateUnauthorizedBearerResponse($request->space, 'invalid_token', 'Invalid JWT token signature');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($token->isExpired(new DateTimeImmutable())) {
|
if ($token->isExpired(new DateTimeImmutable())) {
|
||||||
return $this->generateUnauthorizedBearerResponse('invalid_token', 'Expired JWT token');
|
return $this->generateUnauthorizedBearerResponse($request->space, 'invalid_token', 'Expired JWT token');
|
||||||
}
|
}
|
||||||
|
|
||||||
$account = null;
|
$account = null;
|
||||||
$identifierKey = config('services.jwt.sip_identifier');
|
$identifierKey = $request->space->sso_sip_identifier;
|
||||||
if ($identifierKey == '') $identifierKey = 'sip_identity';
|
if ($identifierKey == '') $identifierKey = 'sip_identity';
|
||||||
|
|
||||||
if ($token->claims()->has($identifierKey)) {
|
if ($token->claims()->has($identifierKey)) {
|
||||||
|
|
@ -101,7 +107,7 @@ class AuthenticateJWT
|
||||||
return $next($request);
|
return $next($request);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!empty(config('app.account_authentication_bearer'))
|
if ($request->space?->sso_authentication_bearer
|
||||||
// Bypass the JWT auth if we have an API Key
|
// Bypass the JWT auth if we have an API Key
|
||||||
&& !$request->header('x-api-key')
|
&& !$request->header('x-api-key')
|
||||||
&& !$request->cookie('x-api-key')
|
&& !$request->cookie('x-api-key')
|
||||||
|
|
@ -110,7 +116,7 @@ class AuthenticateJWT
|
||||||
|
|
||||||
$response->header(
|
$response->header(
|
||||||
'WWW-Authenticate',
|
'WWW-Authenticate',
|
||||||
'Bearer ' . config('app.account_authentication_bearer')
|
'Bearer ' . $request->space?->sso_authentication_bearer
|
||||||
);
|
);
|
||||||
|
|
||||||
$response->setStatusCode(401);
|
$response->setStatusCode(401);
|
||||||
|
|
@ -121,10 +127,10 @@ class AuthenticateJWT
|
||||||
return $next($request);
|
return $next($request);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function generateUnauthorizedBearerResponse(string $error, string $description): Response
|
private function generateUnauthorizedBearerResponse(Space $space, string $error, string $description): Response
|
||||||
{
|
{
|
||||||
$bearer = 'Bearer ' . config('app.account_authentication_bearer');
|
$bearer = 'Bearer ' . $space->sso_authentication_bearer;
|
||||||
$bearer .= !empty(config('app.account_authentication_bearer'))
|
$bearer .= $space->sso_authentication_bearer != null
|
||||||
? ', '
|
? ', '
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,13 +22,16 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
|
use CoderCat\JWKToPEM\JWKConverter;
|
||||||
|
|
||||||
class Space extends Model
|
class Space extends Model
|
||||||
{
|
{
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
protected $with = ['emailServer', 'carddavServers'];
|
protected $with = ['emailServer', 'carddavServers'];
|
||||||
|
protected $fillable = ['host', 'sso_public_key', 'sso_server_url', 'sso_realm'];
|
||||||
|
|
||||||
public const FORBIDDEN_KEYS = [
|
public const FORBIDDEN_KEYS = [
|
||||||
'account_proxy_registrar_address',
|
'account_proxy_registrar_address',
|
||||||
|
|
@ -66,13 +69,18 @@ class Space extends Model
|
||||||
'super' => 'boolean',
|
'super' => 'boolean',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
protected $attributes = [
|
||||||
|
'sso_sip_identifier' => 'sip_identity'
|
||||||
|
];
|
||||||
|
|
||||||
public const HOST_REGEX = '[\w\-]+';
|
public const HOST_REGEX = '[\w\-]+';
|
||||||
public const DOMAIN_REGEX = '(?=^.{4,253}$)(^((?!-)[a-z0-9-]{1,63}(?<!-)\.)+[a-z]{2,63}$)';
|
public const DOMAIN_REGEX = '(?=^.{4,253}$)(^((?!-)[a-z0-9-]{1,63}(?<!-)\.)+[a-z]{2,63}$)';
|
||||||
|
|
||||||
protected static function booted()
|
protected static function booted()
|
||||||
{
|
{
|
||||||
static::addGlobalScope('domain', function (Builder $builder) {
|
static::addGlobalScope('domain', function (Builder $builder) {
|
||||||
if (!Auth::hasUser()) return;
|
if (!Auth::hasUser())
|
||||||
|
return;
|
||||||
|
|
||||||
if (Auth::hasUser() || Auth::user()->superAdmin) {
|
if (Auth::hasUser() || Auth::user()->superAdmin) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -110,13 +118,13 @@ class Space extends Model
|
||||||
public function scopeNotFull(Builder $query)
|
public function scopeNotFull(Builder $query)
|
||||||
{
|
{
|
||||||
return $query->where('max_accounts', 0)
|
return $query->where('max_accounts', 0)
|
||||||
->orWhereRaw('max_accounts > (select count(*) from accounts where domain = spaces.domain)');
|
->orWhereRaw('max_accounts > (select count(*) from accounts where domain = spaces.domain)');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAccountsPercentageAttribute(): int
|
public function getAccountsPercentageAttribute(): int
|
||||||
{
|
{
|
||||||
if ($this->max_accounts != null) {
|
if ($this->max_accounts != null) {
|
||||||
return (int)($this->accounts()->count() / $this->max_accounts * 100);
|
return (int) ($this->accounts()->count() / $this->max_accounts * 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Command::SUCCESS;
|
return Command::SUCCESS;
|
||||||
|
|
@ -137,6 +145,37 @@ class Space extends Model
|
||||||
return $this->host == config('app.root_host');
|
return $this->host == config('app.root_host');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function refreshSSOCertificate(): bool
|
||||||
|
{
|
||||||
|
if (isset($this->attributes['sso_server_url']) && isset($this->attributes['sso_realm'])) {
|
||||||
|
$response = Http::get($this->attributes['sso_server_url'] . '/realms/' . $this->attributes['sso_realm'] . '/protocol/openid-connect/certs');
|
||||||
|
$jwkConverter = new JWKConverter();
|
||||||
|
|
||||||
|
if ($response->status() == '200' && $publicKey = $response->json('keys')[0]) {
|
||||||
|
$this->attributes['sso_public_key'] = $jwkConverter->toPEM($publicKey);
|
||||||
|
$this->attributes['updated_at'] = Carbon::now();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Non standard authentication flow based on RFC 8898
|
||||||
|
*/
|
||||||
|
public function getSSOAuthenticationBearerAttribute(): ?string
|
||||||
|
{
|
||||||
|
if (isset($this->attributes['sso_server_url']) && isset($this->attributes['sso_realm'])) {
|
||||||
|
return
|
||||||
|
'authz_server="' . $this->attributes['sso_server_url'] . 'realms/' . $this->attributes['sso_realm'] . '"' .
|
||||||
|
',realm="' . $this->attributes['sso_realm'] . '"';
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public function getAccountsPercentageClassAttribute(): string
|
public function getAccountsPercentageClassAttribute(): string
|
||||||
{
|
{
|
||||||
if ($this->getAccountsPercentageAttribute() >= 80) {
|
if ($this->getAccountsPercentageAttribute() >= 80) {
|
||||||
|
|
@ -153,7 +192,7 @@ class Space extends Model
|
||||||
public function getDaysLeftAttribute(): ?int
|
public function getDaysLeftAttribute(): ?int
|
||||||
{
|
{
|
||||||
if ($this->expire_at != null) {
|
if ($this->expire_at != null) {
|
||||||
return (int)$this->expire_at->diffInDays(Carbon::now()) + 1;
|
return (int) $this->expire_at->diffInDays(Carbon::now()) + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=8.2",
|
"php": ">=8.2",
|
||||||
"awobaz/compoships": "^2.4.1",
|
"awobaz/compoships": "^2.4.1",
|
||||||
|
"codercat/jwk-to-pem": "^1.1",
|
||||||
"comcast/php-legal-licenses": "^2.2",
|
"comcast/php-legal-licenses": "^2.2",
|
||||||
"doctrine/dbal": "^3.10.1",
|
"doctrine/dbal": "^3.10.1",
|
||||||
"endroid/qr-code": "^5.1",
|
"endroid/qr-code": "^5.1",
|
||||||
|
|
|
||||||
275
flexiapi/composer.lock
generated
275
flexiapi/composer.lock
generated
|
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "3e094d7231e491736d1b2cb70db958de",
|
"content-hash": "57ecd41850aee08a3926f70211252257",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "awobaz/compoships",
|
"name": "awobaz/compoships",
|
||||||
|
|
@ -252,6 +252,50 @@
|
||||||
],
|
],
|
||||||
"time": "2023-12-11T17:09:12+00:00"
|
"time": "2023-12-11T17:09:12+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "codercat/jwk-to-pem",
|
||||||
|
"version": "1.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/acodercat/php-jwk-to-pem.git",
|
||||||
|
"reference": "4b3cdcf5f87b9b074f132f763a6b7b82c7d3ff1d"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/acodercat/php-jwk-to-pem/zipball/4b3cdcf5f87b9b074f132f763a6b7b82c7d3ff1d",
|
||||||
|
"reference": "4b3cdcf5f87b9b074f132f763a6b7b82c7d3ff1d",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.1",
|
||||||
|
"phpseclib/phpseclib": "^3.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^7.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"CoderCat\\JWKToPEM\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "codercat",
|
||||||
|
"email": "1067302838@qq.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Convert JWK to PEM format.",
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/acodercat/php-jwk-to-pem/issues",
|
||||||
|
"source": "https://github.com/acodercat/php-jwk-to-pem/tree/1.1"
|
||||||
|
},
|
||||||
|
"time": "2021-04-28T07:37:03+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "comcast/php-legal-licenses",
|
"name": "comcast/php-legal-licenses",
|
||||||
"version": "v2.2.0",
|
"version": "v2.2.0",
|
||||||
|
|
@ -4128,6 +4172,125 @@
|
||||||
},
|
},
|
||||||
"time": "2025-01-02T16:09:40+00:00"
|
"time": "2025-01-02T16:09:40+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "paragonie/constant_time_encoding",
|
||||||
|
"version": "v3.1.3",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/paragonie/constant_time_encoding.git",
|
||||||
|
"reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77",
|
||||||
|
"reference": "d5b01a39b3415c2cd581d3bd3a3575c1ebbd8e77",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^8"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"infection/infection": "^0",
|
||||||
|
"nikic/php-fuzzer": "^0",
|
||||||
|
"phpunit/phpunit": "^9|^10|^11",
|
||||||
|
"vimeo/psalm": "^4|^5|^6"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"ParagonIE\\ConstantTime\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Paragon Initiative Enterprises",
|
||||||
|
"email": "security@paragonie.com",
|
||||||
|
"homepage": "https://paragonie.com",
|
||||||
|
"role": "Maintainer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Steve 'Sc00bz' Thomas",
|
||||||
|
"email": "steve@tobtu.com",
|
||||||
|
"homepage": "https://www.tobtu.com",
|
||||||
|
"role": "Original Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)",
|
||||||
|
"keywords": [
|
||||||
|
"base16",
|
||||||
|
"base32",
|
||||||
|
"base32_decode",
|
||||||
|
"base32_encode",
|
||||||
|
"base64",
|
||||||
|
"base64_decode",
|
||||||
|
"base64_encode",
|
||||||
|
"bin2hex",
|
||||||
|
"encoding",
|
||||||
|
"hex",
|
||||||
|
"hex2bin",
|
||||||
|
"rfc4648"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"email": "info@paragonie.com",
|
||||||
|
"issues": "https://github.com/paragonie/constant_time_encoding/issues",
|
||||||
|
"source": "https://github.com/paragonie/constant_time_encoding"
|
||||||
|
},
|
||||||
|
"time": "2025-09-24T15:06:41+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "paragonie/random_compat",
|
||||||
|
"version": "v9.99.100",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/paragonie/random_compat.git",
|
||||||
|
"reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a",
|
||||||
|
"reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">= 7"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "4.*|5.*",
|
||||||
|
"vimeo/psalm": "^1"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Paragon Initiative Enterprises",
|
||||||
|
"email": "security@paragonie.com",
|
||||||
|
"homepage": "https://paragonie.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
|
||||||
|
"keywords": [
|
||||||
|
"csprng",
|
||||||
|
"polyfill",
|
||||||
|
"pseudorandom",
|
||||||
|
"random"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"email": "info@paragonie.com",
|
||||||
|
"issues": "https://github.com/paragonie/random_compat/issues",
|
||||||
|
"source": "https://github.com/paragonie/random_compat"
|
||||||
|
},
|
||||||
|
"time": "2020-10-15T08:29:30+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "parsedown/laravel",
|
"name": "parsedown/laravel",
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
|
|
@ -4383,6 +4546,116 @@
|
||||||
],
|
],
|
||||||
"time": "2025-12-27T19:41:33+00:00"
|
"time": "2025-12-27T19:41:33+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "phpseclib/phpseclib",
|
||||||
|
"version": "3.0.52",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/phpseclib/phpseclib.git",
|
||||||
|
"reference": "2adaefc83df2ec548558307690f376dd7d4f4fce"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/2adaefc83df2ec548558307690f376dd7d4f4fce",
|
||||||
|
"reference": "2adaefc83df2ec548558307690f376dd7d4f4fce",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"paragonie/constant_time_encoding": "^1|^2|^3",
|
||||||
|
"paragonie/random_compat": "^1.4|^2.0|^9.99.99",
|
||||||
|
"php": ">=5.6.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "*"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-dom": "Install the DOM extension to load XML formatted public keys.",
|
||||||
|
"ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.",
|
||||||
|
"ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.",
|
||||||
|
"ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.",
|
||||||
|
"ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations."
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"phpseclib/bootstrap.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"phpseclib3\\": "phpseclib/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Jim Wigginton",
|
||||||
|
"email": "terrafrost@php.net",
|
||||||
|
"role": "Lead Developer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Patrick Monnerat",
|
||||||
|
"email": "pm@datasphere.ch",
|
||||||
|
"role": "Developer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Andreas Fischer",
|
||||||
|
"email": "bantu@phpbb.com",
|
||||||
|
"role": "Developer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Hans-Jürgen Petrich",
|
||||||
|
"email": "petrich@tronic-media.com",
|
||||||
|
"role": "Developer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Graham Campbell",
|
||||||
|
"email": "graham@alt-three.com",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.",
|
||||||
|
"homepage": "http://phpseclib.sourceforge.net",
|
||||||
|
"keywords": [
|
||||||
|
"BigInteger",
|
||||||
|
"aes",
|
||||||
|
"asn.1",
|
||||||
|
"asn1",
|
||||||
|
"blowfish",
|
||||||
|
"crypto",
|
||||||
|
"cryptography",
|
||||||
|
"encryption",
|
||||||
|
"rsa",
|
||||||
|
"security",
|
||||||
|
"sftp",
|
||||||
|
"signature",
|
||||||
|
"signing",
|
||||||
|
"ssh",
|
||||||
|
"twofish",
|
||||||
|
"x.509",
|
||||||
|
"x509"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/phpseclib/phpseclib/issues",
|
||||||
|
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.52"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/terrafrost",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://www.patreon.com/phpseclib",
|
||||||
|
"type": "patreon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2026-04-27T07:02:15+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "phpunit/php-code-coverage",
|
"name": "phpunit/php-code-coverage",
|
||||||
"version": "10.1.16",
|
"version": "10.1.16",
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ return [
|
||||||
'account_email_unique' => env('ACCOUNT_EMAIL_UNIQUE', false),
|
'account_email_unique' => env('ACCOUNT_EMAIL_UNIQUE', false),
|
||||||
'account_username_regex' => env('ACCOUNT_USERNAME_REGEX', '^[a-z0-9+_.-]*$'),
|
'account_username_regex' => env('ACCOUNT_USERNAME_REGEX', '^[a-z0-9+_.-]*$'),
|
||||||
'account_default_password_algorithm' => env('ACCOUNT_DEFAULT_PASSWORD_ALGORITHM', 'SHA-256'),
|
'account_default_password_algorithm' => env('ACCOUNT_DEFAULT_PASSWORD_ALGORITHM', 'SHA-256'),
|
||||||
'account_authentication_bearer' => env('ACCOUNT_AUTHENTICATION_BEARER', null),
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Time limit before the API Key and related cookie are expired
|
* Time limit before the API Key and related cookie are expired
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,4 @@ return [
|
||||||
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
'secret' => env('AWS_SECRET_ACCESS_KEY'),
|
||||||
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
|
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
|
||||||
],
|
],
|
||||||
|
|
||||||
'jwt' => [
|
|
||||||
'rsa_public_key_pem' => env('JWT_RSA_PUBLIC_KEY_PEM'),
|
|
||||||
'sip_identifier' => env('JWT_SIP_IDENTIFIER', 'sip_identity'),
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('spaces', function (Blueprint $table) {
|
||||||
|
$table->string('sso_server_url')->nullable();
|
||||||
|
$table->string('sso_realm')->nullable();
|
||||||
|
$table->string('sso_sip_identifier')->default('sip_identity');
|
||||||
|
$table->text('sso_public_key')->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('spaces', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('sso_server_url');
|
||||||
|
$table->dropColumn('sso_realm');
|
||||||
|
$table->dropColumn('sso_sip_identifier');
|
||||||
|
$table->dropColumn('sso_public_key');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -140,8 +140,11 @@
|
||||||
"Integration": "Intégration",
|
"Integration": "Intégration",
|
||||||
"Intercom features": "Fonctionnalités d'interphonie",
|
"Intercom features": "Fonctionnalités d'interphonie",
|
||||||
"It might actually disable this page, be careful": "Cette page pourrait être désactivée, faites attention",
|
"It might actually disable this page, be careful": "Cette page pourrait être désactivée, faites attention",
|
||||||
|
"JWT key containing the user's SIP identity. sip_identity by default.": "Clef du JWT contenant l'identité SIP de l'utilisateur. sip_identity par défaut.",
|
||||||
"Key": "Clef",
|
"Key": "Clef",
|
||||||
|
"SSO Server": "Serveur SSO",
|
||||||
"Last used": "Dernière utilisation",
|
"Last used": "Dernière utilisation",
|
||||||
|
"Last update": "Dernière mise à jour",
|
||||||
"Leave empty to create a root Space.": "Laisser vide si vous souhaitez créer un Espace à la racine",
|
"Leave empty to create a root Space.": "Laisser vide si vous souhaitez créer un Espace à la racine",
|
||||||
"Limit the number of results": "Limiter le nomber de résultats",
|
"Limit the number of results": "Limiter le nomber de résultats",
|
||||||
"List of vcard fields to match for SIP domain": "Liste des champs vcard à matcher pour le domaine SIP",
|
"List of vcard fields to match for SIP domain": "Liste des champs vcard à matcher pour le domaine SIP",
|
||||||
|
|
@ -192,10 +195,12 @@
|
||||||
"Provisioning": "Déploiement",
|
"Provisioning": "Déploiement",
|
||||||
"Proxy/registrar address":"Adresse Proxy/registrar",
|
"Proxy/registrar address":"Adresse Proxy/registrar",
|
||||||
"Public registration": "Inscription publiques",
|
"Public registration": "Inscription publiques",
|
||||||
|
"Public key": "Clef publique",
|
||||||
"QR Code scanning": "Scan de QR Code",
|
"QR Code scanning": "Scan de QR Code",
|
||||||
"Realm": "Royaume",
|
"Realm": "Royaume",
|
||||||
"Recover your account using your email": "Récupérer votre compte avec votre email",
|
"Recover your account using your email": "Récupérer votre compte avec votre email",
|
||||||
"Recorded at": "Enregistré le",
|
"Recorded at": "Enregistré le",
|
||||||
|
"Refresh": "Rafraichir",
|
||||||
"Register": "Inscription",
|
"Register": "Inscription",
|
||||||
"Registrar": "Registrar",
|
"Registrar": "Registrar",
|
||||||
"Registration confirmed": "Confirmation de l'inscription",
|
"Registration confirmed": "Confirmation de l'inscription",
|
||||||
|
|
@ -222,10 +227,12 @@
|
||||||
"Send an email to the user with provisioning information": "Envoyer un email à l'utilisateur avec les informations de déploiement",
|
"Send an email to the user with provisioning information": "Envoyer un email à l'utilisateur avec les informations de déploiement",
|
||||||
"Send": "Envoyer",
|
"Send": "Envoyer",
|
||||||
"Separated by commas": "Séparé par des virgules",
|
"Separated by commas": "Séparé par des virgules",
|
||||||
|
"Server URL": "URL du serveur",
|
||||||
"Settings": "Paramètres",
|
"Settings": "Paramètres",
|
||||||
"Show usernames only": "Afficher uniquement les noms d'utilisateur",
|
"Show usernames only": "Afficher uniquement les noms d'utilisateur",
|
||||||
"SIP Adress": "Adresse SIP",
|
"SIP Adress": "Adresse SIP",
|
||||||
"SIP Domain": "Domaine SIP",
|
"SIP Domain": "Domaine SIP",
|
||||||
|
"SIP Identifier": "Identifiant SIP",
|
||||||
"Space": "Espace",
|
"Space": "Espace",
|
||||||
"Spaces": "Espaces",
|
"Spaces": "Espaces",
|
||||||
"Statistics": "Statistiques",
|
"Statistics": "Statistiques",
|
||||||
|
|
@ -244,6 +251,7 @@
|
||||||
"The first line contains the labels": "La premières ligne contient les étiquettes",
|
"The first line contains the labels": "La premières ligne contient les étiquettes",
|
||||||
"The following email address wants to register to the mailing list:":"L’adresse e-mail suivante souhaite s’inscrire à la liste de diffusion :",
|
"The following email address wants to register to the mailing list:":"L’adresse e-mail suivante souhaite s’inscrire à la liste de diffusion :",
|
||||||
"The link can only be visited once": "Le lien ne peut être utilisé qu'une fois",
|
"The link can only be visited once": "Le lien ne peut être utilisé qu'une fois",
|
||||||
|
"The public key cannot be refreshed": "La clef publique ne peut être rafraichie",
|
||||||
"Third-party SIP account": "Compte SIP tiers",
|
"Third-party SIP account": "Compte SIP tiers",
|
||||||
"This code is valid for :minutes minutes.": "Ce code est valable pendant :minutes minutes.",
|
"This code is valid for :minutes minutes.": "Ce code est valable pendant :minutes minutes.",
|
||||||
"This link is not available anymore.": "Ce lien n'est plus disponible.",
|
"This link is not available anymore.": "Ce lien n'est plus disponible.",
|
||||||
|
|
|
||||||
|
|
@ -112,6 +112,33 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
|
### codercat/jwk-to-pem (Version 1.1 | 4b3cdcf)
|
||||||
|
Convert JWK to PEM format.
|
||||||
|
Homepage: Not configured.
|
||||||
|
Licenses Used: MIT
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2018 codercat
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
### comcast/php-legal-licenses (Version v2.2.0 | 2b01ea1)
|
### comcast/php-legal-licenses (Version v2.2.0 | 2b01ea1)
|
||||||
A utility to generate a Licenses file containing the full license text for every dependency in your project for legal purposes.
|
A utility to generate a Licenses file containing the full license text for every dependency in your project for legal purposes.
|
||||||
Homepage: https://github.com/Comcast/php-legal-licenses
|
Homepage: https://github.com/Comcast/php-legal-licenses
|
||||||
|
|
@ -2028,6 +2055,88 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### paragonie/constant_time_encoding (Version v3.1.3 | d5b01a3)
|
||||||
|
Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)
|
||||||
|
Homepage: Not configured.
|
||||||
|
Licenses Used: MIT
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2016 - 2022 Paragon Initiative Enterprises
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
This library was based on the work of Steve "Sc00bz" Thomas.
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014 Steve Thomas
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### paragonie/random_compat (Version v9.99.100 | 996434e)
|
||||||
|
PHP 5.x polyfill for random_bytes() and random_int() from PHP 7
|
||||||
|
Homepage: Not configured.
|
||||||
|
Licenses Used: MIT
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Paragon Initiative Enterprises
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### parsedown/laravel (Version 1.2.1 | c713ffe)
|
### parsedown/laravel (Version 1.2.1 | c713ffe)
|
||||||
Official Parsedown's Laravel Wrapper.
|
Official Parsedown's Laravel Wrapper.
|
||||||
Homepage: http://parsedown.org
|
Homepage: http://parsedown.org
|
||||||
|
|
@ -2333,6 +2442,31 @@ Licenses Used: Apache-2.0
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
|
|
||||||
|
### phpseclib/phpseclib (Version 3.0.52 | 2adaefc)
|
||||||
|
PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.
|
||||||
|
Homepage: http://phpseclib.sourceforge.net
|
||||||
|
Licenses Used: MIT
|
||||||
|
Copyright (c) 2011-2019 TerraFrost and other contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
### phpunit/php-code-coverage (Version 10.1.16 | 7e30826)
|
### phpunit/php-code-coverage (Version 10.1.16 | 7e30826)
|
||||||
Library that provides collection, processing, and rendering functionality for PHP code coverage information.
|
Library that provides collection, processing, and rendering functionality for PHP code coverage information.
|
||||||
Homepage: https://github.com/sebastianbergmann/php-code-coverage
|
Homepage: https://github.com/sebastianbergmann/php-code-coverage
|
||||||
|
|
@ -2679,14 +2813,14 @@ Homepage: https://github.com/php-fig/http-client
|
||||||
Licenses Used: MIT
|
Licenses Used: MIT
|
||||||
Copyright (c) 2017 PHP Framework Interoperability Group
|
Copyright (c) 2017 PHP Framework Interoperability Group
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
in the Software without restriction, including without limitation the rights
|
in the Software without restriction, including without limitation the rights
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
furnished to do so, subject to the following conditions:
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
The above copyright notice and this permission notice shall be included in
|
||||||
all copies or substantial portions of the Software.
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
|
@ -2731,14 +2865,14 @@ Homepage: https://github.com/php-fig/http-message
|
||||||
Licenses Used: MIT
|
Licenses Used: MIT
|
||||||
Copyright (c) 2014 PHP Framework Interoperability Group
|
Copyright (c) 2014 PHP Framework Interoperability Group
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
in the Software without restriction, including without limitation the rights
|
in the Software without restriction, including without limitation the rights
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
furnished to do so, subject to the following conditions:
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
The above copyright notice and this permission notice shall be included in
|
||||||
all copies or substantial portions of the Software.
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
|
@ -2756,14 +2890,14 @@ Homepage: https://github.com/php-fig/log
|
||||||
Licenses Used: MIT
|
Licenses Used: MIT
|
||||||
Copyright (c) 2012 PHP Framework Interoperability Group
|
Copyright (c) 2012 PHP Framework Interoperability Group
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
in the Software without restriction, including without limitation the rights
|
in the Software without restriction, including without limitation the rights
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
furnished to do so, subject to the following conditions:
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
The above copyright notice and this permission notice shall be included in
|
||||||
all copies or substantial portions of the Software.
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
|
|
||||||
|
|
@ -10,17 +10,28 @@
|
||||||
|
|
||||||
<div class="grid third">
|
<div class="grid third">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
|
@if ($space->emailServer)
|
||||||
|
<a class="btn small oppose" href="{{ route('admin.spaces.email.show', $space) }}">{{ __('Edit') }}</a>
|
||||||
|
<a class="btn small oppose tertiary" href="{{ route('admin.spaces.email.delete', $space) }}">{{ __('Delete') }}</a>
|
||||||
|
@else
|
||||||
|
<a class="btn small oppose secondary" href="{{ route('admin.spaces.email.show', $space) }}">{{ __('Configure') }}</a>
|
||||||
|
@endif
|
||||||
<span class="icon"><i class="ph ph-envelope"></i></span>
|
<span class="icon"><i class="ph ph-envelope"></i></span>
|
||||||
<h3>{{ __('Email Server') }}</h3>
|
<h3>{{ __('Email Server') }}</h3>
|
||||||
<p>
|
<p>
|
||||||
@if ($space->emailServer)
|
@if ($space->emailServer)
|
||||||
{{ $space->emailServer->host}}<br /><br />
|
{{ $space->emailServer->host}}<br /><br />
|
||||||
@endif
|
@endif
|
||||||
@if ($space->emailServer)
|
</p>
|
||||||
<a class="btn oppose" href="{{ route('admin.spaces.email.show', $space) }}">{{ __('Edit') }}</a>
|
</div>
|
||||||
<a class="btn oppose tertiary" href="{{ route('admin.spaces.email.delete', $space) }}">{{ __('Delete') }}</a>
|
|
||||||
@else
|
<div class="card">
|
||||||
<a class="btn oppose secondary" href="{{ route('admin.spaces.email.show', $space) }}">{{ __('Configure') }}</a>
|
<a class="btn small oppose secondary" href="{{ route('admin.spaces.keycloak.show', $space) }}">{{ __('Configure') }}</a>
|
||||||
|
<span class="icon"><i class="ph ph-key"></i></span>
|
||||||
|
<h3>{{ __('SSO Server') }}</h3>
|
||||||
|
<p>
|
||||||
|
@if ($space->sso_server_url)
|
||||||
|
<code>{{ $space->sso_server_url}}</code><br /><br />
|
||||||
@endif
|
@endif
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -38,14 +49,13 @@
|
||||||
<div class="grid third">
|
<div class="grid third">
|
||||||
@foreach ($space->carddavServers as $carddavServer)
|
@foreach ($space->carddavServers as $carddavServer)
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<small class="oppose"><i class="ph ph-users"></i> {{ $carddavServer->accounts()->count() }}</small>
|
<a class="btn small oppose" href="{{ route('admin.spaces.carddavs.edit', [$space, $carddavServer]) }}">{{ __('Edit') }}</a>
|
||||||
|
<a class="btn small oppose tertiary" href="{{ route('admin.spaces.carddavs.delete', [$space, $carddavServer]) }}">{{ __('Delete') }}</a>
|
||||||
<span class="icon"><i class="ph ph-identification-card"></i></span>
|
<span class="icon"><i class="ph ph-identification-card"></i></span>
|
||||||
<h3>{{ $carddavServer->name }}</h3>
|
<h3>{{ $carddavServer->name }}</h3>
|
||||||
<p>
|
<p>
|
||||||
|
<small class="oppose"><i class="ph ph-users"></i> {{ $carddavServer->accounts()->count() }}</small>
|
||||||
{{ $carddavServer->uri}}<br />
|
{{ $carddavServer->uri}}<br />
|
||||||
<br />
|
|
||||||
<a class="btn oppose" href="{{ route('admin.spaces.carddavs.edit', [$space, $carddavServer]) }}">{{ __('Edit') }}</a>
|
|
||||||
<a class="btn oppose tertiary" href="{{ route('admin.spaces.carddavs.delete', [$space, $carddavServer]) }}">{{ __('Delete') }}</a>
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@endforeach
|
@endforeach
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
@extends('layouts.main')
|
||||||
|
|
||||||
|
@section('breadcrumb')
|
||||||
|
@include('admin.parts.breadcrumb.spaces.integration')
|
||||||
|
<li class="breadcrumb-item active" aria-current="page">{{ __('SSO Server') }}</li>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<header>
|
||||||
|
<h1><i class="ph ph-key"></i> {{ $space->name }}</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<form method="POST"
|
||||||
|
action="{{ route('admin.spaces.keycloak.store', $space->id) }}"
|
||||||
|
id="show" accept-charset="UTF-8">
|
||||||
|
@csrf
|
||||||
|
@method('post')
|
||||||
|
<div>
|
||||||
|
<input placeholder="https://keycloak.server.tld/" required="required" name="sso_server_url" type="url"
|
||||||
|
value="@if($space->id){{ $space->sso_server_url }}@else{{ old('sso_server_url') }}@endif">
|
||||||
|
<label for="sso_server_url">{{ __('Server URL') }}</label>
|
||||||
|
@include('parts.errors', ['name' => 'sso_server_url'])
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input placeholder="cogip" required="required" name="sso_realm" type="text"
|
||||||
|
value="@if($space->id){{ $space->sso_realm }}@else{{ old('sso_realm') }}@endif">
|
||||||
|
<label for="sso_realm">{{ __('Realm') }}</label>
|
||||||
|
@include('parts.errors', ['name' => 'sso_realm'])
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input placeholder="sip_identity" name="sso_sip_identifier" type="text" required="required"
|
||||||
|
value="@if($space->id && isset($space->sso_sip_identifier)){{ $space->sso_sip_identifier }}@else{{ old('sso_sip_identifier') }}@endif">
|
||||||
|
<label for="sso_sip_identifier">{{ __('SIP Identifier') }}</label>
|
||||||
|
@include('parts.errors', ['name' => 'sso_sip_identifier'])
|
||||||
|
<span class="supporting">{{ __("JWT key containing the user's SIP identity. sip_identity by default.")}}</span>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
@include('parts.errors', ['name' => 'sso_public_key'])
|
||||||
|
|
||||||
|
@if ($space->sso_public_key)
|
||||||
|
<h4>{{ __('Public key') }}</h4> <small>{{ __('Last update') }}: {{ $space->updated_at }}</small>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<pre style="display: inline-block;"><code>{{ $space->sso_public_key }}</code></pre>
|
||||||
|
<br />
|
||||||
|
<a class="btn small secondary" href="{{ route('admin.spaces.keycloak.refresh_public_key', $space) }}">{{ __('Refresh') }}</a>
|
||||||
|
<hr />
|
||||||
|
@endif
|
||||||
|
|
||||||
|
|
||||||
|
<input form="show" class="btn" type="submit" value="@if($space->id){{ __('Update') }}@else{{ __('Create') }}@endif">
|
||||||
|
@endsection
|
||||||
|
|
@ -57,6 +57,7 @@ use App\Http\Controllers\Admin\Space\CardDavServerController;
|
||||||
use App\Http\Controllers\Admin\Space\ContactsListContactController;
|
use App\Http\Controllers\Admin\Space\ContactsListContactController;
|
||||||
use App\Http\Controllers\Admin\Space\ContactsListController;
|
use App\Http\Controllers\Admin\Space\ContactsListController;
|
||||||
use App\Http\Controllers\Admin\Space\EmailServerController;
|
use App\Http\Controllers\Admin\Space\EmailServerController;
|
||||||
|
use App\Http\Controllers\Admin\Space\SSOServerController;
|
||||||
use App\Http\Controllers\Admin\SpaceController;
|
use App\Http\Controllers\Admin\SpaceController;
|
||||||
use App\Http\Controllers\Admin\StatisticsController;
|
use App\Http\Controllers\Admin\StatisticsController;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
@ -88,7 +89,6 @@ Route::name('file.')->prefix('f')->controller(FileController::class)->group(func
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::group(['middleware' => ['auth.jwt', 'auth.digest_or_key']], function () {
|
Route::group(['middleware' => ['auth.jwt', 'auth.digest_or_key']], function () {
|
||||||
|
|
||||||
Route::get('provisioning/me', [ProvisioningController::class, 'me'])->name('provisioning.me');
|
Route::get('provisioning/me', [ProvisioningController::class, 'me'])->name('provisioning.me');
|
||||||
|
|
||||||
// vCard 4.0
|
// vCard 4.0
|
||||||
|
|
@ -199,6 +199,11 @@ Route::middleware(['feature.web_panel_enabled'])->group(function () {
|
||||||
Route::get('delete', 'delete')->name('delete');
|
Route::get('delete', 'delete')->name('delete');
|
||||||
Route::delete('/', 'destroy')->name('destroy');
|
Route::delete('/', 'destroy')->name('destroy');
|
||||||
});
|
});
|
||||||
|
Route::name('keycloak.')->prefix('{space}/keycloak')->controller(SSOServerController::class)->group(function () {
|
||||||
|
Route::get('/', 'show')->name('show');
|
||||||
|
Route::get('refresh_public_key', 'refreshPublicKey')->name('refresh_public_key');
|
||||||
|
Route::post('/', 'store')->name('store');
|
||||||
|
});
|
||||||
Route::resource('{space}/carddavs', CardDavServerController::class, ['except' => ['index', 'show']]);
|
Route::resource('{space}/carddavs', CardDavServerController::class, ['except' => ['index', 'show']]);
|
||||||
Route::get('{space}/carddavs/{carddav}/delete', [CardDavServerController::class, 'delete'])->name('carddavs.delete');
|
Route::get('{space}/carddavs/{carddav}/delete', [CardDavServerController::class, 'delete'])->name('carddavs.delete');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ class AccountJWTAuthenticationTest extends TestCase
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
||||||
$keys = openssl_pkey_new(array("private_key_bits" => 4096,"private_key_type" => OPENSSL_KEYTYPE_RSA));
|
$keys = openssl_pkey_new(array("private_key_bits" => 4096, "private_key_type" => OPENSSL_KEYTYPE_RSA));
|
||||||
$this->serverPublicKeyPem = openssl_pkey_get_details($keys)['key'];
|
$this->serverPublicKeyPem = openssl_pkey_get_details($keys)['key'];
|
||||||
openssl_pkey_export($keys, $this->serverPrivateKeyPem);
|
openssl_pkey_export($keys, $this->serverPrivateKeyPem);
|
||||||
}
|
}
|
||||||
|
|
@ -52,15 +52,20 @@ class AccountJWTAuthenticationTest extends TestCase
|
||||||
public function testBaseProvisioning()
|
public function testBaseProvisioning()
|
||||||
{
|
{
|
||||||
# JWT is disabled if Sodium is not loaded
|
# JWT is disabled if Sodium is not loaded
|
||||||
if (!extension_loaded('sodium')) return;
|
if (!extension_loaded('sodium'))
|
||||||
|
return;
|
||||||
|
|
||||||
$password = Password::factory()->create();
|
$password = Password::factory()->create();
|
||||||
$domain = 'sip_provisioning.example.com';
|
$domain = 'sip_provisioning.example.com';
|
||||||
$bearer = 'authz_server="https://sso.test/", realm="sip.test.org"';
|
|
||||||
|
|
||||||
\App\Space::where('domain', $password->account->domain)->update(['host' => $domain]);
|
$space = \App\Space::where('domain', $password->account->domain)->first();
|
||||||
|
$space->update([
|
||||||
|
'host' => $domain,
|
||||||
|
'sso_public_key' => $this->serverPublicKeyPem,
|
||||||
|
'sso_sso_server_url' => 'https://sso.test/',
|
||||||
|
'sso_realm' => 'sip.test.org'
|
||||||
|
]);
|
||||||
config()->set('app.sip_domain', $domain);
|
config()->set('app.sip_domain', $domain);
|
||||||
config()->set('services.jwt.rsa_public_key_pem', $this->serverPublicKeyPem);
|
|
||||||
|
|
||||||
$this->get($this->route)->assertStatus(400);
|
$this->get($this->route)->assertStatus(400);
|
||||||
|
|
||||||
|
|
@ -69,7 +74,7 @@ class AccountJWTAuthenticationTest extends TestCase
|
||||||
$token = (new JwtFacade(null, $clock))->issue(
|
$token = (new JwtFacade(null, $clock))->issue(
|
||||||
new Sha256(),
|
new Sha256(),
|
||||||
InMemory::plainText($this->serverPrivateKeyPem),
|
InMemory::plainText($this->serverPrivateKeyPem),
|
||||||
static fn (
|
static fn(
|
||||||
Builder $builder,
|
Builder $builder,
|
||||||
DateTimeImmutable $issuedAt
|
DateTimeImmutable $issuedAt
|
||||||
): Builder => $builder->withClaim('email', $password->account->email)
|
): Builder => $builder->withClaim('email', $password->account->email)
|
||||||
|
|
@ -79,13 +84,10 @@ class AccountJWTAuthenticationTest extends TestCase
|
||||||
|
|
||||||
// SIP identifier
|
// 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(
|
$token = (new JwtFacade(null, $clock))->issue(
|
||||||
new Sha256(),
|
new Sha256(),
|
||||||
InMemory::plainText($this->serverPrivateKeyPem),
|
InMemory::plainText($this->serverPrivateKeyPem),
|
||||||
static fn (
|
static fn(
|
||||||
Builder $builder,
|
Builder $builder,
|
||||||
DateTimeImmutable $issuedAt
|
DateTimeImmutable $issuedAt
|
||||||
): Builder => $builder->withClaim('sip_identity', 'sip:' . $password->account->username . '@' . $password->account->domain)
|
): Builder => $builder->withClaim('sip_identity', 'sip:' . $password->account->username . '@' . $password->account->domain)
|
||||||
|
|
@ -93,13 +95,11 @@ class AccountJWTAuthenticationTest extends TestCase
|
||||||
|
|
||||||
$this->checkToken($token);
|
$this->checkToken($token);
|
||||||
|
|
||||||
// Handle JWT_SIP_IDENTIFIER=
|
// Handle empty sso_sip_identifier
|
||||||
config()->set('services.jwt.sip_identifier', '');
|
|
||||||
|
|
||||||
$token = (new JwtFacade(null, $clock))->issue(
|
$token = (new JwtFacade(null, $clock))->issue(
|
||||||
new Sha256(),
|
new Sha256(),
|
||||||
InMemory::plainText($this->serverPrivateKeyPem),
|
InMemory::plainText($this->serverPrivateKeyPem),
|
||||||
static fn (
|
static fn(
|
||||||
Builder $builder,
|
Builder $builder,
|
||||||
DateTimeImmutable $issuedAt
|
DateTimeImmutable $issuedAt
|
||||||
): Builder => $builder->withClaim('sip_identity', 'sip:' . $password->account->username . '@' . $password->account->domain)
|
): Builder => $builder->withClaim('sip_identity', 'sip:' . $password->account->username . '@' . $password->account->domain)
|
||||||
|
|
@ -109,12 +109,12 @@ class AccountJWTAuthenticationTest extends TestCase
|
||||||
|
|
||||||
// Custom SIP identifier
|
// Custom SIP identifier
|
||||||
$otherIdentifier = 'sip_other_identifier';
|
$otherIdentifier = 'sip_other_identifier';
|
||||||
config()->set('services.jwt.sip_identifier', $otherIdentifier);
|
\App\Space::where('domain', $password->account->domain)->update(['sso_sip_identifier' => 'sip_other_identifier']);
|
||||||
|
|
||||||
$token = (new JwtFacade(null, $clock))->issue(
|
$token = (new JwtFacade(null, $clock))->issue(
|
||||||
new Sha256(),
|
new Sha256(),
|
||||||
InMemory::plainText($this->serverPrivateKeyPem),
|
InMemory::plainText($this->serverPrivateKeyPem),
|
||||||
static fn (
|
static fn(
|
||||||
Builder $builder,
|
Builder $builder,
|
||||||
DateTimeImmutable $issuedAt
|
DateTimeImmutable $issuedAt
|
||||||
): Builder => $builder->withClaim($otherIdentifier, 'sip:' . $password->account->username . '@' . $password->account->domain)
|
): Builder => $builder->withClaim($otherIdentifier, 'sip:' . $password->account->username . '@' . $password->account->domain)
|
||||||
|
|
@ -126,7 +126,7 @@ class AccountJWTAuthenticationTest extends TestCase
|
||||||
$token = (new JwtFacade(null, $clock))->issue(
|
$token = (new JwtFacade(null, $clock))->issue(
|
||||||
new Sha512(),
|
new Sha512(),
|
||||||
InMemory::plainText($this->serverPrivateKeyPem),
|
InMemory::plainText($this->serverPrivateKeyPem),
|
||||||
static fn (
|
static fn(
|
||||||
Builder $builder,
|
Builder $builder,
|
||||||
DateTimeImmutable $issuedAt
|
DateTimeImmutable $issuedAt
|
||||||
): Builder => $builder->withClaim('email', $password->account->email)
|
): Builder => $builder->withClaim('email', $password->account->email)
|
||||||
|
|
@ -140,84 +140,89 @@ class AccountJWTAuthenticationTest extends TestCase
|
||||||
$token = (new JwtFacade(null, $oldClock))->issue(
|
$token = (new JwtFacade(null, $oldClock))->issue(
|
||||||
new Sha256(),
|
new Sha256(),
|
||||||
InMemory::plainText($this->serverPrivateKeyPem),
|
InMemory::plainText($this->serverPrivateKeyPem),
|
||||||
static fn (
|
static fn(
|
||||||
Builder $builder,
|
Builder $builder,
|
||||||
DateTimeImmutable $issuedAt
|
DateTimeImmutable $issuedAt
|
||||||
): Builder => $builder->withClaim('email', $password->account->email)
|
): Builder => $builder->withClaim('email', $password->account->email)
|
||||||
);
|
);
|
||||||
|
|
||||||
$response = $this->withHeaders([
|
$response = $this->withHeaders([
|
||||||
'Authorization' => 'Bearer ' . $token->toString(),
|
'Authorization' => 'Bearer ' . $token->toString(),
|
||||||
'x-linphone-provisioning' => true,
|
'x-linphone-provisioning' => true,
|
||||||
])
|
])
|
||||||
->get($this->accountRoute)
|
->get($this->accountRoute)
|
||||||
->assertStatus(401);
|
->assertStatus(401);
|
||||||
|
|
||||||
$this->assertStringContainsString('invalid_token', $response->headers->get('WWW-Authenticate'));
|
$this->assertStringContainsString('invalid_token', $response->headers->get('WWW-Authenticate'));
|
||||||
|
|
||||||
// ...with the bearer
|
// ...with the bearer
|
||||||
config()->set('app.account_authentication_bearer', $bearer);
|
|
||||||
|
|
||||||
$response = $this->withHeaders([
|
$response = $this->withHeaders([
|
||||||
'Authorization' => 'Bearer ' . $token->toString(),
|
'Authorization' => 'Bearer ' . $token->toString(),
|
||||||
'x-linphone-provisioning' => true,
|
'x-linphone-provisioning' => true,
|
||||||
])
|
])
|
||||||
->get($this->accountRoute)
|
->get($this->accountRoute)
|
||||||
->assertStatus(401);
|
->assertStatus(401);
|
||||||
|
|
||||||
$this->assertStringContainsString($bearer . ', ', $response->headers->get('WWW-Authenticate'));
|
$this->assertStringContainsString($space->sso_authentication_bearer . ', ', $response->headers->get('WWW-Authenticate'));
|
||||||
$this->assertStringContainsString('invalid_token', $response->headers->get('WWW-Authenticate'));
|
$this->assertStringContainsString('invalid_token', $response->headers->get('WWW-Authenticate'));
|
||||||
|
|
||||||
// Wrong email
|
// Wrong email
|
||||||
$token = (new JwtFacade(null, $clock))->issue(
|
$token = (new JwtFacade(null, $clock))->issue(
|
||||||
new Sha256(),
|
new Sha256(),
|
||||||
InMemory::plainText($this->serverPrivateKeyPem),
|
InMemory::plainText($this->serverPrivateKeyPem),
|
||||||
static fn (
|
static fn(
|
||||||
Builder $builder,
|
Builder $builder,
|
||||||
DateTimeImmutable $issuedAt
|
DateTimeImmutable $issuedAt
|
||||||
): Builder => $builder->withClaim('email', 'unknow@man.org')
|
): Builder => $builder->withClaim('email', 'unknow@man.org')
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->withHeaders([
|
$this->withHeaders([
|
||||||
'Authorization' => 'Bearer ' . $token->toString(),
|
'Authorization' => 'Bearer ' . $token->toString(),
|
||||||
'x-linphone-provisioning' => true,
|
'x-linphone-provisioning' => true,
|
||||||
])
|
])
|
||||||
->get($this->accountRoute)
|
->get($this->accountRoute)
|
||||||
->assertStatus(403);
|
->assertStatus(403);
|
||||||
|
|
||||||
// Wrong signature key
|
// Wrong signature key
|
||||||
$keys = openssl_pkey_new(array("private_key_bits" => 4096,"private_key_type" => OPENSSL_KEYTYPE_RSA));
|
$keys = openssl_pkey_new(array("private_key_bits" => 4096, "private_key_type" => OPENSSL_KEYTYPE_RSA));
|
||||||
openssl_pkey_export($keys, $wrongServerPrivateKeyPem);
|
openssl_pkey_export($keys, $wrongServerPrivateKeyPem);
|
||||||
|
|
||||||
$wrongToken = (new JwtFacade(null, $clock))->issue(
|
$wrongToken = (new JwtFacade(null, $clock))->issue(
|
||||||
new Sha256(),
|
new Sha256(),
|
||||||
InMemory::plainText($wrongServerPrivateKeyPem),
|
InMemory::plainText($wrongServerPrivateKeyPem),
|
||||||
static fn (
|
static fn(
|
||||||
Builder $builder,
|
Builder $builder,
|
||||||
DateTimeImmutable $issuedAt
|
DateTimeImmutable $issuedAt
|
||||||
): Builder => $builder->withClaim('email', $password->account->email)
|
): Builder => $builder->withClaim('email', $password->account->email)
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->withHeaders([
|
$this->withHeaders([
|
||||||
'Authorization' => 'Bearer ' . $wrongToken->toString(),
|
'Authorization' => 'Bearer ' . $wrongToken->toString(),
|
||||||
'x-linphone-provisioning' => true,
|
'x-linphone-provisioning' => true,
|
||||||
])
|
])
|
||||||
->get($this->accountRoute)
|
->get($this->accountRoute)
|
||||||
->assertStatus(401);
|
->assertStatus(401);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAuthBearerUrl()
|
public function testAuthBearerUrl()
|
||||||
{
|
{
|
||||||
$value = 'authz_server="https://auth_bearer.com/" realm="realm"';
|
# JWT is disabled if Sodium is not loaded
|
||||||
config()->set('app.account_authentication_bearer', $value);
|
if (!extension_loaded('sodium'))
|
||||||
|
return;
|
||||||
|
|
||||||
Password::factory()->create();
|
$password = Password::factory()->create();
|
||||||
|
$space = \App\Space::where('domain', $password->account->domain)->first();
|
||||||
|
$space->update([
|
||||||
|
'sso_public_key' => $this->serverPublicKeyPem,
|
||||||
|
'sso_server_url' => 'https://auth_bearer.com/',
|
||||||
|
'sso_realm' => 'realm'
|
||||||
|
]);
|
||||||
|
|
||||||
$response = $this->json($this->method, $this->routeAccountMe)
|
$response = $this->json($this->method, $this->routeAccountMe)
|
||||||
->assertStatus(401);
|
->assertStatus(401);
|
||||||
|
|
||||||
$this->assertStringContainsString(
|
$this->assertStringContainsString(
|
||||||
'Bearer ' . $value,
|
'Bearer ' . $space->sso_authentication_bearer,
|
||||||
$response->headers->all()['www-authenticate'][0]
|
$response->headers->all()['www-authenticate'][0]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -227,19 +232,19 @@ class AccountJWTAuthenticationTest extends TestCase
|
||||||
->assertStatus(401);
|
->assertStatus(401);
|
||||||
|
|
||||||
$this->assertStringContainsString(
|
$this->assertStringContainsString(
|
||||||
'Bearer ' . $value,
|
'Bearer ' . $space->sso_authentication_bearer,
|
||||||
$response->headers->all()['www-authenticate'][0]
|
$response->headers->all()['www-authenticate'][0]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Wrong bearer message
|
// Wrong bearer message
|
||||||
$this->withHeaders([
|
$this->withHeaders([
|
||||||
'Authorization' => 'Bearer 1234'
|
'Authorization' => 'Bearer 1234'
|
||||||
])
|
])
|
||||||
->json($this->method, $this->routeAccountMe)
|
->json($this->method, $this->routeAccountMe)
|
||||||
->assertStatus(401);
|
->assertStatus(401);
|
||||||
|
|
||||||
$this->assertStringContainsString(
|
$this->assertStringContainsString(
|
||||||
'Bearer ' . $value,
|
'Bearer ' . $space->sso_authentication_bearer,
|
||||||
$response->headers->all()['www-authenticate'][0]
|
$response->headers->all()['www-authenticate'][0]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -247,9 +252,9 @@ class AccountJWTAuthenticationTest extends TestCase
|
||||||
protected function checkToken(UnencryptedToken $token): void
|
protected function checkToken(UnencryptedToken $token): void
|
||||||
{
|
{
|
||||||
$this->withHeaders([
|
$this->withHeaders([
|
||||||
'Authorization' => 'Bearer ' . $token->toString(),
|
'Authorization' => 'Bearer ' . $token->toString(),
|
||||||
'x-linphone-provisioning' => true,
|
'x-linphone-provisioning' => true,
|
||||||
])
|
])
|
||||||
->get($this->accountRoute)
|
->get($this->accountRoute)
|
||||||
->assertStatus(200)
|
->assertStatus(200)
|
||||||
->assertHeader('Content-Type', 'application/xml')
|
->assertHeader('Content-Type', 'application/xml')
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue