Fix FLEXIAPI-250 Allow Spaces to be declared without a subdomain

This commit is contained in:
Timothée Jaussoin 2024-12-16 13:56:14 +00:00
parent 9cb24cad77
commit 4add0d7daa
13 changed files with 80 additions and 18 deletions

View file

@ -7,6 +7,7 @@ v1.7
- Fix GH-15 Add password import from CSV
- Fix FLEXIAPI-242 Add stricter validation for the AccountCreationToken Push Notification endpoint
- Fix FLEXIAPI-241 Add a /push-notification endpoint to send custom push notifications to the Flexisip Pusher
- Fix FLEXIAPI-250 Allow Spaces to be declared without a subdomain
v1.6
----

View file

@ -5,19 +5,54 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/).
## [1.7]
### Added
- **Spaces:** A new way to manage your SIP domains and hosts. A Space is defined by a unique SIP Domain and Host pair.
- **New mandatory DotEnv variable** `APP_ROOT_HOST`, replaces `APP_URL` and `APP_SIP_DOMAIN` that are now configured using the new dedicated Artisan script. It defines the root hostname where all the Spaces will be configured. All the Spaces will be as subdomains of `APP_ROOT_HOST` except one that can be equal to `APP_ROOT_HOST`. Example: if `APP_ROOT_HOST=myhost.com` the Spaces hosts will be `myhost.com`, `alpha.myhost.com` , `beta.myhost.com`...
- **New Artisan script** `php artisan spaces:create-update {sip_domain} {host} {--super}`, replaces `php artisan sip_domains:create-update {sip_domain} {--super}`. Can create a Space or update a Space Host base on its Space SIP Domain.
#### Migrate from [1.6]
1. Deploy the new version and migrate the database.
```
php artisan migrate
```
2. Set `APP_ROOT_HOST` in `.env` or as an environnement variable. And remove `APP_URL` and `APP_SIP_DOMAIN`
```
APP_ROOT_HOST=myhost.com
```
3. The migration script will automatically copy the `sip_domain` into `host` in the `spaces` table. You then have to "fix" the hosts and set them to equal or be subdomains of `APP_ROOT_HOST`.
```
php artisan spaces:create-update my.sip myhost.com --super # You can set some Spaces as SuperSpaces, the admin will be able to manage the other spaces
php artisan spaces:create-update alpha.sip alpha.myhost.com
php artisan spaces:create-update beta.sip beta.myhost.com
...
```
4. Configure your web server to point the `APP_ROOT_HOST` and subdomains to the app.
5. Configure the upcoming Spaces
## [1.5] - 2024-08-29
### Added
- **Account activity view:** new panel, available behind the Activity tab, will allow any admin to follow the activity of the accounts they manage.
- **Detect and block abusive accounts:** This activity tracking is coming with a related tool that is measuring the accounts activity and automatically block them if it detects some unusual behaviors on the service. An account can also directly be blocked and unblocked from the setting panel. Two new setting variables will allow you to fine tune those behaviors triggers.
- **New DotEnv variable:** `BLOCKING_TIME_PERIOD_CHECK=30` # Time span on which the blocking service will proceed, in minutes
- **New DotEnv variable:** `BLOCKING_AMOUNT_EVENTS_AUTHORIZED_DURING_PERIOD=5` # Amount of account events authorized during this period
- **New DotEnv variable:** `BLOCKING_TIME_PERIOD_CHECK=30` Time span on which the blocking service will proceed, in minutes
- **New DotEnv variable:** `BLOCKING_AMOUNT_EVENTS_AUTHORIZED_DURING_PERIOD=5` Amount of account events authorized during this period
- **OAuth JWT Authentication:** OAuth support with the handling of JWE tokens issues by a third party service such as Keycloack.
- **New DotEnv variable:** `JWT_RSA_PUBLIC_KEY_PEM=`
- **New DotEnv variable:** `JWT_SIP_IDENTIFIER=sip_identifier`
- **Super-domains and super-admins support:** Introduce SIP domains management. The app accounts are now divided by their domains with their own respective administrators that can only see and manage their own domain accounts and settings. On top of that it is possible to configure a SIP domain as a "super-domain" and then allow its admins to become "super-admins". Those super-admins will then be able to manage all the accounts handled by the instance and create/edit/delete the other SIP domains. Add new endpoints and a new super-admin role in the API to manage the SIP domains. SIP domains can also be created and updated directly from the console using a new artisan script (documented in the README);
- **New Artisan script:** `php artisan spaces:create-update {domain} {--super}`
- **New Artisan script:** `php artisan sip_domains:create-update {domain} {--super}`
- **Account Dictionary:** Each account can now handle a specific dictionary, configurable by the API or directly the web panel. This dictionary allows developers to store arbitrary `key -> value pairs` on each accounts.
- **Vcard storage:** Attach custom vCards on a dedicated account using new endpoints in the API. The published vCard are validated before being stored.

View file

@ -2,7 +2,7 @@ APP_NAME=FlexiAPI
APP_ENV=local
APP_KEY=
APP_DEBUG=false
APP_ROOT_DOMAIN=
APP_ROOT_HOST=
APP_LINPHONE_DAEMON_UNIX_PATH=
APP_FLEXISIP_PUSHER_PATH=

View file

@ -31,6 +31,10 @@ class CreateUpdate extends Command
{
$this->info('Your will create or update a Space in the database');
if (empty(config('app.root_host'))) {
$this->error('The environnement variable APP_ROOT_HOST doesn\'t seems to be set');
}
$space = Space::where('domain', $this->argument('sip_domain'))->firstOrNew();
$space->host = $this->argument('host');
$space->domain = $this->argument('sip_domain');

View file

@ -29,7 +29,7 @@ class SpaceController extends Controller
{
public function index()
{
return view('admin.space.index', ['spaces' => Space::withCount('accounts')->get()]);
return view('admin.space.index', ['spaces' => Space::withCount('accounts')->orderBy('host')->get()]);
}
public function me(Request $request)
@ -55,10 +55,14 @@ class SpaceController extends Controller
public function store(Request $request)
{
$request->merge(['full_host' => $request->get('host') . '.' . config('app.root_domain')]);
$fullHost = empty($request->get('host'))
? config('app.root_host')
: $request->get('host') . '.' . config('app.root_host');
$request->merge(['full_host' => $fullHost]);
$request->validate([
'domain' => 'required|unique:spaces|regex:/'. Space::DOMAIN_REGEX . '/',
'host' => 'required|regex:/'. Space::HOST_REGEX . '/',
'host' => 'nullable|regex:/'. Space::HOST_REGEX . '/',
'full_host' => 'required|unique:spaces,host',
]);

View file

@ -11,15 +11,15 @@ class IsSpaceExpired
{
public function handle(Request $request, Closure $next): Response
{
if (empty(config('app.root_domain'))) {
return abort(503, 'APP_ROOT_DOMAIN is not configured');
if (empty(config('app.root_host'))) {
return abort(503, 'APP_ROOT_HOST is not configured');
}
$space = \App\Space::where('host', $request->header('host'))->first();
if ($space) {
if (!str_ends_with($space->host, config('app.root_domain'))) {
return abort(503, 'The APP_ROOT_DOMAIN configured does not match with the current root domain');
if (!str_ends_with($space->host, config('app.root_host'))) {
return abort(503, 'The APP_ROOT_HOST configured does not match with the current root domain');
}
Config::set('app.url', '://' . $space->host);

View file

@ -73,6 +73,10 @@ class AccountService
$account->domain = resolveDomain($request);
$account->user_agent = $request->header('User-Agent') ?? config('app.name');
$account->admin = $request->has('admin') && (bool)$request->get('admin');
if (!$request->api && $request->has('role')) {
$account->setRole($request->get('role'));
}
}
if ($account->activated == false) {

View file

@ -65,6 +65,11 @@ class Space extends Model
return $this->expire_at && $this->expire_at->isPast();
}
public function isRoot(): bool
{
return $this->host == config('app.root_host');
}
public function getAccountsPercentageClassAttribute(): string
{
if ($this->getAccountsPercentageAttribute() >= 80) {

View file

@ -14,7 +14,7 @@ return [
*/
'name' => env('APP_NAME', 'Account Manager'),
'sip_domain' => env('APP_SIP_DOMAIN', 'sip.domain.com'),
//'sip_domain' => 'sip.domain.com',
'project_url' => env('APP_PROJECT_URL', ''),
'terms_of_use_url' => env('TERMS_OF_USE_URL', ''),
@ -128,7 +128,7 @@ return [
*/
'url' => env('APP_URL', 'http://localhost'),
'root_domain' => env('APP_ROOT_DOMAIN', null),
'root_host' => env('APP_ROOT_HOST', null),
'asset_url' => env('ASSET_URL', null),
/*

View file

@ -20,13 +20,13 @@
@method('post')
<div class="large">
<input placeholder="subdomain" required="required" name="host" type="text" pattern="{{ $space::HOST_REGEX}}" style="width: 60%"
value="{{ $space->host ?? old('host') }}" onchange="copyValueTo(this, this.form.querySelector('input[name=domain]'), '.{{ config('app.root_domain') }}')">
<input placeholder=".{{ config('app.root_domain') }}" style="position: absolute; width: calc(40% - 1rem); margin-left: 1rem;" disabled>
<input placeholder="subdomain" name="host" type="text" pattern="{{ $space::HOST_REGEX}}" style="width: 60%"
value="{{ $space->host ?? old('host') }}" onchange="copyValueTo(this, this.form.querySelector('input[name=domain]'), '.{{ config('app.root_host') }}')">
<input placeholder=".{{ config('app.root_host') }}" style="position: absolute; width: calc(40% - 1rem); margin-left: 1rem;" disabled>
<label for="username">Subdomain</label>
@include('parts.errors', ['name' => 'host'])
@include('parts.errors', ['name' => 'full_host'])
<span class="supporting">Cannot be changed once created</span>
<span class="supporting">Cannot be changed once created, leave empty to create a root Space</span>
</div>
<div class="large">

View file

@ -42,7 +42,7 @@ class ApiSpaceWithMiddlewareTest extends TestCaseWithSpaceMiddleware
// Try to create a new user as an admin
$admin->generateApiKey();
config()->set('app.root_domain', $admin->domain);
config()->set('app.root_host', $admin->domain);
$this->keyAuthenticated($admin)
->json($this->method, 'http://' . $admin->domain . $this->accountRoute, [

View file

@ -39,6 +39,8 @@ abstract class TestCase extends BaseTestCase
$this->withoutMiddleware([IsSpaceExpired::class]);
config()->set('app.sip_domain', 'sip.example.com');
PhoneCountry::truncate();
PhoneCountry::factory()->france()->activated()->create();
PhoneCountry::factory()->netherlands()->create();

View file

@ -27,4 +27,11 @@ abstract class TestCaseWithSpaceMiddleware extends BaseTestCase
use CreatesApplication;
use RefreshDatabase;
use TestUtilsTrait;
public function setUp(): void
{
parent::setUp();
config()->set('app.sip_domain', 'sip.example.com');
}
}