diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e633f1..392ece3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ v1.7 - Fix FLEXIAPI-256 Publish an empty string while deleting a device on Redis to force the refresh on the other clients - Fix FLEXIAPI-268 Allow pn-param in Apple format for the push notifications endpoints - Fix FLEXIAPI-269 Update the IsNotPhoneNumber rule to use a better phone number validator +- Fix FLEXIAPI-258 Move DotEnv instance configurations in the Spaces table v1.6 ---- diff --git a/INSTALL.md b/INSTALL.md index 49c52da..616f97d 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -4,7 +4,9 @@ FlexiAPI relies on [DotEnv](https://github.com/vlucas/phpdotenv) to be configure Thoses variables can then be set using Docker-Compose, a bash script or a web-server. -If you're installing FlexiAPI from the RPM package you can find the configuration file at `/etc/flexisip-account-manager/flexiapi.env`. +If you're installing FlexiAPI from the RedHat or Debian package you can find the configuration file at `/etc/flexisip-account-manager/flexiapi.env`. + +Check also the [RELEASE.md](RELEASE.md) to check if you don't have specific migrations to do between releases. # 1.a Manual setup @@ -13,14 +15,6 @@ Clone the repository, install the dependencies and generate a key. composer install --no-dev php artisan key:generate -Then configure the database connection in the `.env` file (from the `.env.example` one). And migrate the tables. The first migration *MUST* be run on an empty database. - - php artisan migrate - -You can also run the test suit using `phpunit`. - -To know more about the web server configuration part, you can directly [visit the official Laravel installation documentation](https://laravel.com/docs/). - # 1.b Packages setup FlexiAPI is packaged for Debian and RedHat, you can setup those repositories using the Flexisip documentation https://wiki.linphone.org/xwiki/wiki/public/view/Flexisip/1.%20Installation/#HInstallationfromourrepositories @@ -28,14 +22,18 @@ FlexiAPI is packaged for Debian and RedHat, you can setup those repositories usi yum install bc-flexisip-account-manager # For RedHat distributions apt install bc-flexisip-account-manager # For Debian distributions +The `artisan` script is in the root directory of where the application is setup, with packages its often `/opt/belledonne-communications/share/flexisip-account-manager/flexiapi/`. + # 2. Web server configuration The package will deploy a `flexisip-account-manager.conf` file in the apache2 configuration directory. This file can be loaded and configured in your specific VirtualHost configuration. +To know more about the web server configuration part, you can directly [visit the official Laravel installation documentation](https://laravel.com/docs/). + # 3. .env file configuration -Complete all the other variables in the `.env` file or by overwritting them in your Docker or web-server configuration. +Complete all the variables in the `.env` file (from the `.env.example` one if you setup the instance manually) or by overwritting them in your Docker or web-server configuration. ## 3.1. Mandatory `APP_ROOT_HOST` variable @@ -48,9 +46,11 @@ This is the host that you'll define in the Apache or webserver VirtualHost: If you are planning to manage several SIP domains (see Spaces bellow) a wildcard `ServerAlias` as above is required. -## 3.2. For manual setups +## 3.2. Database migration -`APP_KEY` Can be set using the `php artisan key:generate` command. +Then configure the database connection parameters and migrate the tables. The first migration *MUST* be run on an empty database. + + php artisan migrate # 4. Spaces @@ -106,24 +106,9 @@ On nginx use `fastcgi_param` to pass the parameter directly to PHP. > **Warning** Do not create a cache of your configuration (using `artisan config:cache`) if you have a multi-environnement setup. > The cache is always having the priority on the variables set in the configuration files. -## Multiple .env option - -To do so, configure several web servers virtualhosts and set a specific `APP_ENV` environnement variable in each of them. - -Note that if `APP_ENV` is not set FlexiAPI will directly use the default `.env` file. - -FlexiAPI will then try to load a custom configuration file with the following name `.env.$APP_ENV`. So for the previous example `.env.foobar`. - -You can then configure your instances with specific values. - - INSTANCE_COPYRIGHT="FooBar - Since 1997" - INSTANCE_INTRO_REGISTRATION="Welcome on the FooBar Server" - INSTANCE_CUSTOM_THEME=true - … - ## Custom theme -If you set `INSTANCE_CUSTOM_THEME` to true, FlexiAPI will try to load a CSS file located in `public/css/$APP_ENV.style.css`. If the file doesn't exists it will fallback to `public/css/style.css`. +If you enable the Custom CSS Theme option to true in the Space administration panel, FlexiAPI will try to load a CSS file located in `public/css/$space_host.style.css`. If the file doesn't exists it will fallback to `public/css/style.css`. You can find an example CSS file at `public/css/custom.style.css`. @@ -140,7 +125,7 @@ This binary will be executed under "web user" privileges. Ensure that all the re ## SELinux restrictions -If you are running on a CentOS/RedHat machine, please ensure that SELinux is correctly configured. +If you are running on a RedHat machine, please ensure that SELinux is correctly configured. Allow the webserver user to write in the `storage/` directory: diff --git a/README.md b/README.md index 1c90cd2..09d854f 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Once deployed you can have access to the global and API documentation on the `/a # Setup -Check the `INSTALL.md` file. +Check the [INSTALL.md](INSTALL.md) and [CHANGELOG.md](CHANGELOG.md) files. ## Usage @@ -39,6 +39,12 @@ Create or update a Space, required to then create accounts afterward. The `super php artisan spaces:create-update {sip_domain} {host} {--super} +### Import the old DotEnv instance configuration into a Space + +Since 1.7 some environnement instance configuration variables were moved into the Space configuration, you can import them using this command. + + php artisan spaces:import-configuration-from-dot-env {sip_domain} + ### Create an admin account Create an admin account, an API Key will also be generated along the way, it might expire after a while (regarding the API Key expiration policy). An empty `api_key_ip` will remove the IP restriction on the key. diff --git a/RELEASE.md b/RELEASE.md index 6e693d8..ebd5fa2 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -13,32 +13,66 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/). - **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. +### Changed + +- **Removing and moving DotEnv instance environnement variables to the Spaces** The following DotEnv variables were removed. You can now configure them directly in the designated spaces. + - INSTANCE_COPYRIGHT + - INSTANCE_INTRO_REGISTRATION + - INSTANCE_CUSTOM_THEME + - INSTANCE_CONFIRMED_REGISTRATION_TEXT + - WEB_PANEL + - PUBLIC_REGISTRATION + - PHONE_AUTHENTICATION + - DEVICES_MANAGEMENT + - INTERCOM_FEATURES + - NEWSLETTER_REGISTRATION_ADDRESS + - ACCOUNT_PROXY_REGISTRAR_ADDRESS + - ACCOUNT_TRANSPORT_PROTOCOL_TEXT + - ACCOUNT_REALM + - ACCOUNT_PROVISIONING_RC_FILE + - ACCOUNT_PROVISIONING_OVERWRITE_ALL + - ACCOUNT_PROVISIONING_USE_X_LINPHONE_PROVISIONING_HEADER + #### Migrate from [1.6] 1. Deploy the new version and migrate the database. ``` - php artisan migrate +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 +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 - ... +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 +5. Configure the upcoming Spaces. + +6. Remove the instance based environnement variables and configure them directly in the spaces. + +7. (Optional) Import the old instance DotEnv environnement variables into a space. + +``` +php artisan spaces:import-configuration-from-dot-env {sip_domain} +``` + +You can find more details regarding those steps in the [`INSTALL.md`](INSTALL.md) file. + +### Deprecated + +- **Last major version supporting the deprecated endpoints of the API** ## [1.5] - 2024-08-29 @@ -63,8 +97,3 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/). - **New DotEnv variable:** HCAPTCHA_SECRET=secret-key - **New DotEnv variable:** HCAPTCHA_SITEKEY=site-key - **Localization support:** The API is now accepting the `Accept-Language` header and adapt its internal localization to the client/browser one. For the moment only French and English are supported but more languages could be added in the future. - -### Deprecated - -- **Last major version supporting the deprecated endpoints of the API** - diff --git a/flexiapi/.env.example b/flexiapi/.env.example index a8c6d9c..d4b64dc 100644 --- a/flexiapi/.env.example +++ b/flexiapi/.env.example @@ -10,19 +10,6 @@ APP_FLEXISIP_PUSHER_FIREBASE_KEYSMAP= # Each pair is separated using a space and APP_ALLOW_PHONE_NUMBER_USERNAME_ADMIN_API=false # Allow phone numbers to be set as username in admin account creation endpoints -# Instance specific parameters -INSTANCE_COPYRIGHT= # Simple text displayed in the page footer -INSTANCE_INTRO_REGISTRATION= # Markdown text displayed in the home page -INSTANCE_CUSTOM_THEME=false -INSTANCE_CONFIRMED_REGISTRATION_TEXT= # Markdown text displayed when an account is confirmed - -WEB_PANEL=true # Fully enable/disable the web panels -PUBLIC_REGISTRATION=true # Toggle to enable/disable the public registration forms -PHONE_AUTHENTICATION=true # Toggle to enable/disable the SMS support, requires public registration -DEVICES_MANAGEMENT=false # Toggle to enable/disable the devices management supporttrue -INTERCOM_FEATURES=false # Toggle to enable/disable the intercom related features - -NEWSLETTER_REGISTRATION_ADDRESS= # Address to contact when a user wants to register to the newsletter TERMS_OF_USE_URL= # A URL pointing to the Terms of Use PRIVACY_POLICY_URL= # A URL pointing to the Privacy Policy APP_PROJECT_URL= # A URL pointing to the project information page @@ -32,11 +19,6 @@ LOG_CHANNEL=stack # Risky toggles APP_DANGEROUS_ENDPOINTS=false # Enable some dangerous endpoints used for XMLRPC like fallback usage -# SIP server parameters -ACCOUNT_PROXY_REGISTRAR_ADDRESS=sip.example.com # Proxy registrar address, can be different than the SIP domain -ACCOUNT_TRANSPORT_PROTOCOL_TEXT="TLS (recommended), TCP or UDP" # Simple text, to explain how the SIP server can be reached -ACCOUNT_REALM=null # Default realm for the accounts, fallback to the domain if not set, enforce null by default - # Expiration time for tokens and code, in minutes, 0 means no expiration APP_API_ACCOUNT_CREATION_TOKEN_RETRY_MINUTES=60 # Number of minutes between two consecutive account_creation_token creation APP_ACCOUNT_CREATION_TOKEN_EXPIRATION_MINUTES=0 @@ -54,11 +36,6 @@ 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_AUTHENTICATION_BEARER= # Bearer value (WWW-Authenticate: Bearer ) 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 -# Account provisioning -ACCOUNT_PROVISIONING_RC_FILE= -ACCOUNT_PROVISIONING_OVERWRITE_ALL= -ACCOUNT_PROVISIONING_USE_X_LINPHONE_PROVISIONING_HEADER=true - # Blocking service BLOCKING_TIME_PERIOD_CHECK=30 # Time span on which the blocking service will proceed, in minutes BLOCKING_AMOUNT_EVENTS_AUTHORIZED_DURING_PERIOD=5 # Amount of account events authorized during this period diff --git a/flexiapi/app/Account.php b/flexiapi/app/Account.php index 825b2ed..ac3d379 100644 --- a/flexiapi/app/Account.php +++ b/flexiapi/app/Account.php @@ -307,12 +307,12 @@ class Account extends Authenticatable public function getRealmAttribute() { - return config('app.account_realm'); + return space()?->account_realm; } public function getResolvedRealmAttribute() { - return config('app.account_realm') ?? $this->domain; + return space()?->account_realm ?? $this->domain; } public function getConfirmationKeyExpiresAttribute() diff --git a/flexiapi/app/Console/Commands/Accounts/CreateAdminAccount.php b/flexiapi/app/Console/Commands/Accounts/CreateAdminAccount.php index 8af0853..5b1ebd8 100644 --- a/flexiapi/app/Console/Commands/Accounts/CreateAdminAccount.php +++ b/flexiapi/app/Console/Commands/Accounts/CreateAdminAccount.php @@ -39,7 +39,7 @@ class CreateAdminAccount extends Command { $spaces = Space::all('domain')->pluck('domain'); - $this->info('Your will create a new admin account in the database, existing accounts with the same credentials will be overwritten'); + $this->info('Your creating a new admin account in the database, existing accounts with the same credentials will be overwritten'); $username = $this->option('username'); $domain = $this->option('domain'); diff --git a/flexiapi/app/Console/Commands/Spaces/CreateUpdate.php b/flexiapi/app/Console/Commands/Spaces/CreateUpdate.php index a11af8f..d7bf3e2 100644 --- a/flexiapi/app/Console/Commands/Spaces/CreateUpdate.php +++ b/flexiapi/app/Console/Commands/Spaces/CreateUpdate.php @@ -29,12 +29,16 @@ class CreateUpdate extends Command public function handle() { - $this->info('Your will create or update a Space in the database'); + $this->info('Your are creating or updating 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'); } + if (!str_ends_with($this->argument('host'), config('app.root_host'))) { + $this->error('The provided host doesn\'t seems to ends with ' . config('app.root_host')); + } + $space = Space::where('domain', $this->argument('sip_domain'))->firstOrNew(); $space->host = $this->argument('host'); $space->domain = $this->argument('sip_domain'); diff --git a/flexiapi/app/Console/Commands/Spaces/ImportConfigurationFromDotEnv.php b/flexiapi/app/Console/Commands/Spaces/ImportConfigurationFromDotEnv.php new file mode 100644 index 0000000..6dd1c87 --- /dev/null +++ b/flexiapi/app/Console/Commands/Spaces/ImportConfigurationFromDotEnv.php @@ -0,0 +1,54 @@ +argument('sip_domain'))->first(); + + if (!$space) { + $this->error('The space cannot be found'); + + return 0; + } + + $this->info('The following configuration will be imported in the space ' . $space->domain); + $this->info('The existing settings will be overwritten:'); + + $space->custom_theme = env('INSTANCE_CUSTOM_THEME', false); + $space->web_panel = env('WEB_PANEL', true); + + $space->copyright_text = env('INSTANCE_COPYRIGHT', null); + $space->intro_registration_text = env('INSTANCE_INTRO_REGISTRATION', null); + $space->confirmed_registration_text = env('INSTANCE_CONFIRMED_REGISTRATION_TEXT', null); + $space->newsletter_registration_address = env('NEWSLETTER_REGISTRATION_ADDRESS', null); + $space->account_proxy_registrar_address = env('ACCOUNT_PROXY_REGISTRAR_ADDRESS', 'sip.domain.com'); + $space->account_realm = env('ACCOUNT_REALM', null); + $space->custom_provisioning_overwrite_all = env('ACCOUNT_PROVISIONING_OVERWRITE_ALL', false); + $space->provisioning_use_linphone_provisioning_header = env('ACCOUNT_PROVISIONING_USE_X_LINPHONE_PROVISIONING_HEADER', true); + + $space->public_registration = env('PUBLIC_REGISTRATION', true); + $space->phone_registration = env('PHONE_AUTHENTICATION', true); + $space->intercom_features = env('INTERCOM_FEATURES', false); + + foreach ($space->getDirty() as $key => $value) { + $show = ' - ' . $key . ' => '; + $show .= ($value == null) ? 'null' : $value; + + $this->info($show); + } + + if ($this->confirm('Do you want to update ' . $space->domain . '?', false)) { + $space->save(); + $this->info('Space updated'); + } + } +} diff --git a/flexiapi/app/Helpers/Utils.php b/flexiapi/app/Helpers/Utils.php index 784ca1b..2a2193d 100644 --- a/flexiapi/app/Helpers/Utils.php +++ b/flexiapi/app/Helpers/Utils.php @@ -20,6 +20,7 @@ use Illuminate\Support\Str; use App\Account; +use App\Space; use App\DigestNonce; use Illuminate\Http\Request; use League\CommonMark\CommonMarkConverter; @@ -27,6 +28,20 @@ use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkExtension; use League\CommonMark\Extension\TableOfContents\TableOfContentsExtension; use Illuminate\Support\Facades\DB; +$hostSpace = null; + +function space($reload = false): ?Space +{ + global $hostSpace; + + if ($hostSpace != null && $reload == false) { + return $hostSpace; + } + + $hostSpace = Space::where('host', request()->host())->first(); + return $hostSpace; +} + function passwordAlgorithms(): array { return [ diff --git a/flexiapi/app/Http/Controllers/Account/ProvisioningController.php b/flexiapi/app/Http/Controllers/Account/ProvisioningController.php index ed122e5..5628574 100644 --- a/flexiapi/app/Http/Controllers/Account/ProvisioningController.php +++ b/flexiapi/app/Http/Controllers/Account/ProvisioningController.php @@ -148,7 +148,7 @@ class ProvisioningController extends Controller private function checkProvisioningHeader(Request $request) { if (!$request->hasHeader('x-linphone-provisioning') - && config('app.provisioning_use_x_linphone_provisioning_header')) { + && space()?->provisioning_use_linphone_provisioning_header) { abort(400, 'x-linphone-provisioning header is missing'); } } @@ -169,11 +169,8 @@ class ProvisioningController extends Controller $dom->appendChild($config); - // Default RC file handling - $rcFile = config('app.provisioning_rc_file'); - - if (file_exists($rcFile)) { - $rc = parse_ini_file($rcFile, true); + if (space()?->custom_provisioning_entries) { + $rc = parse_ini_string(space()->custom_provisioning_entries, true); foreach ($rc as $sectionName => $values) { $section = $dom->createElement('section'); @@ -356,7 +353,7 @@ class ProvisioningController extends Controller } // Overwrite all the entries - if (config('app.provisioning_overwrite_all')) { + if (space()?->custom_provisioning_overwrite_all) { $xpath = new \DOMXpath($dom); $entries = $xpath->query("//section/entry"); if (!is_null($entries)) { diff --git a/flexiapi/app/Http/Controllers/Admin/AccountImportController.php b/flexiapi/app/Http/Controllers/Admin/AccountImportController.php index 01ce23a..898d3ef 100644 --- a/flexiapi/app/Http/Controllers/Admin/AccountImportController.php +++ b/flexiapi/app/Http/Controllers/Admin/AccountImportController.php @@ -219,7 +219,7 @@ class AccountImportController extends Controller 'account_id' => $passwordAccount->id, 'password' => bchash( $passwordAccount->username, - config('app.account_realm') ?? $request->get('domain'), + space()?->account_realm ?? $request->get('domain'), $passwords[$passwordAccount->username], $algorithm ), diff --git a/flexiapi/app/Http/Controllers/Admin/SpaceController.php b/flexiapi/app/Http/Controllers/Admin/SpaceController.php index 1102a38..de6bc34 100644 --- a/flexiapi/app/Http/Controllers/Admin/SpaceController.php +++ b/flexiapi/app/Http/Controllers/Admin/SpaceController.php @@ -23,6 +23,7 @@ use Illuminate\Http\Request; use App\Http\Controllers\Controller; use App\Space; +use App\Rules\Ini; use Illuminate\Validation\Rule; class SpaceController extends Controller @@ -87,20 +88,35 @@ class SpaceController extends Controller 'max_account' => 'required|integer', ]); - $space = $this->setConfig($request, $space); + $space = $this->setAppConfiguration($request, $space); $space->save(); return redirect()->back(); } - public function parameters(Space $space) + public function configuration(Space $space) { - return view('admin.space.parameters', [ + return view('admin.space.configuration', [ 'space' => $space ]); } - public function parametersUpdate(Request $request, Space $space) + public function configurationUpdate(Request $request, Space $space) + { + $space = $this->setConfiguration($request, $space); + $space->save(); + + return redirect()->route('admin.spaces.configuration', $space); + } + + public function administration(Space $space) + { + return view('admin.space.administration', [ + 'space' => $space + ]); + } + + public function administrationUpdate(Request $request, Space $space) { $request->validate([ 'max_accounts' => 'required|integer|min:0', @@ -116,12 +132,38 @@ class SpaceController extends Controller $space->super = getRequestBoolean($request, 'super'); $space->max_accounts = $request->get('max_accounts'); $space->expire_at = $request->get('expire_at'); + $space->custom_theme = getRequestBoolean($request, 'custom_theme'); + $space->web_panel = getRequestBoolean($request, 'web_panel'); $space->save(); return redirect()->route('admin.spaces.show', $space); } - private function setConfig(Request $request, Space $space) + private function setConfiguration(Request $request, Space $space) + { + $request->validate([ + 'newsletter_registration_address' => 'nullable|email', + 'custom_provisioning_entries' => ['nullable', new Ini] + ]); + + $space->copyright_text = $request->get('copyright_text'); + $space->intro_registration_text = $request->get('intro_registration_text'); + $space->confirmed_registration_text = $request->get('confirmed_registration_text'); + $space->newsletter_registration_address = $request->get('newsletter_registration_address'); + $space->account_proxy_registrar_address = $request->get('account_proxy_registrar_address'); + $space->account_realm = $request->get('account_realm'); + $space->custom_provisioning_entries = $request->get('custom_provisioning_entries'); + $space->custom_provisioning_overwrite_all = getRequestBoolean($request, 'custom_provisioning_overwrite_all'); + $space->provisioning_use_linphone_provisioning_header = getRequestBoolean($request, 'provisioning_use_linphone_provisioning_header'); + + $space->public_registration = getRequestBoolean($request, 'public_registration'); + $space->phone_registration = getRequestBoolean($request, 'phone_registration'); + $space->intercom_features = getRequestBoolean($request, 'intercom_features'); + + return $space; + } + + private function setAppConfiguration(Request $request, Space $space) { $request->validate([ 'max_account' => 'required|integer', diff --git a/flexiapi/app/Http/Controllers/Api/Admin/SpaceController.php b/flexiapi/app/Http/Controllers/Api/Admin/SpaceController.php index c5c0b5a..a1deb4e 100644 --- a/flexiapi/app/Http/Controllers/Api/Admin/SpaceController.php +++ b/flexiapi/app/Http/Controllers/Api/Admin/SpaceController.php @@ -59,6 +59,21 @@ class SpaceController extends Controller $space->max_accounts = $request->get('max_accounts', 0); $space->expire_at = $request->get('expire_at'); + $space->copyright_text = $request->get('copyright_text'); + $space->intro_registration_text = $request->get('intro_registration_text'); + $space->confirmed_registration_text = $request->get('confirmed_registration_text'); + $space->newsletter_registration_address = $request->get('newsletter_registration_address'); + $space->account_proxy_registrar_address = $request->get('account_proxy_registrar_address'); + $space->account_realm = $request->get('account_realm'); + $space->custom_provisioning_entries = $request->get('custom_provisioning_entries'); + $this->setRequestBoolean($request, $space, 'custom_provisioning_overwrite_all'); + $this->setRequestBoolean($request, $space, 'provisioning_use_linphone_provisioning_header'); + $this->setRequestBoolean($request, $space, 'custom_theme'); + $this->setRequestBoolean($request, $space, 'web_panel'); + $this->setRequestBoolean($request, $space, 'public_registration'); + $this->setRequestBoolean($request, $space, 'phone_registration'); + $this->setRequestBoolean($request, $space, 'intercom_features'); + $space->save(); return $space->refresh(); @@ -86,6 +101,14 @@ class SpaceController extends Controller 'max_account' => 'required|integer', 'max_accounts' => 'required|integer', 'expire_at' => 'nullable|date|after_or_equal:today', + + 'custom_provisioning_overwrite_all' => 'required|boolean', + 'provisioning_use_linphone_provisioning_header' => 'required|boolean', + 'custom_theme' => 'required|boolean', + 'web_panel' => 'required|boolean', + 'public_registration' => 'required|boolean', + 'phone_registration' => 'required|boolean', + 'intercom_features' => 'required|boolean', ]); $space = Space::where('domain', $domain)->firstOrFail(); @@ -115,6 +138,22 @@ class SpaceController extends Controller $space->max_account = $request->get('max_account', 0); $space->max_accounts = $request->get('max_accounts', 0); $space->expire_at = $request->get('expire_at'); + + $space->copyright_text = $request->get('copyright_text'); + $space->intro_registration_text = $request->get('intro_registration_text'); + $space->confirmed_registration_text = $request->get('confirmed_registration_text'); + $space->newsletter_registration_address = $request->get('newsletter_registration_address'); + $space->account_proxy_registrar_address = $request->get('account_proxy_registrar_address'); + $space->account_realm = $request->get('account_realm'); + $space->custom_provisioning_entries = $request->get('custom_provisioning_entries'); + $space->custom_provisioning_overwrite_all = $request->get('custom_provisioning_overwrite_all'); + $space->provisioning_use_linphone_provisioning_header = $request->get('provisioning_use_linphone_provisioning_header'); + $space->custom_theme = $request->get('custom_theme'); + $space->web_panel = $request->get('web_panel'); + $space->public_registration = $request->get('public_registration'); + $space->phone_registration = $request->get('phone_registration'); + $space->intercom_features = $request->get('intercom_features'); + $space->save(); return $space; diff --git a/flexiapi/app/Http/Kernel.php b/flexiapi/app/Http/Kernel.php index b9d0a87..422871c 100644 --- a/flexiapi/app/Http/Kernel.php +++ b/flexiapi/app/Http/Kernel.php @@ -78,6 +78,9 @@ class Kernel extends HttpKernel 'auth.check_blocked' => \App\Http\Middleware\CheckBlocked::class, 'validate_json' => \App\Http\Middleware\ValidateJSON::class, 'web_panel_enabled' => \App\Http\Middleware\IsWebPanelEnabled::class, + 'public_registration' => \App\Http\Middleware\IsPublicRegistration::class, + 'phone_registration' => \App\Http\Middleware\IsPhoneRegistration::class, + 'intercom_features' => \App\Http\Middleware\IsIntercomFeatures::class, 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class, 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, 'can' => \Illuminate\Auth\Middleware\Authorize::class, diff --git a/flexiapi/app/Http/Middleware/AuthenticateDigestOrKey.php b/flexiapi/app/Http/Middleware/AuthenticateDigestOrKey.php index 8cae6ff..dea0f93 100644 --- a/flexiapi/app/Http/Middleware/AuthenticateDigestOrKey.php +++ b/flexiapi/app/Http/Middleware/AuthenticateDigestOrKey.php @@ -70,7 +70,7 @@ class AuthenticateDigestOrKey ->where('domain', $domain) ->firstOrFail(); - $resolvedRealm = config('app.account_realm') ?? $domain; + $resolvedRealm = space()?->account_realm ?? $domain; // DIGEST authentication @@ -199,7 +199,7 @@ class AuthenticateDigestOrKey private function generateAuthHeaders(Account $account, string $nonce): array { $headers = []; - $resolvedRealm = config('app.account_realm') ?? $account->domain; + $resolvedRealm = space()?->account_realm ?? $account->domain; foreach ($account->passwords as $password) { if ($password->algorithm == 'CLRTXT') { diff --git a/flexiapi/app/Http/Middleware/IsIntercomFeatures.php b/flexiapi/app/Http/Middleware/IsIntercomFeatures.php new file mode 100644 index 0000000..ed9c833 --- /dev/null +++ b/flexiapi/app/Http/Middleware/IsIntercomFeatures.php @@ -0,0 +1,19 @@ +intercom_features) { + return $next($request); + } + + return abort(404, 'Intercom features disabled'); + } +} diff --git a/flexiapi/app/Http/Middleware/IsPhoneRegistration.php b/flexiapi/app/Http/Middleware/IsPhoneRegistration.php new file mode 100644 index 0000000..2e6d2dd --- /dev/null +++ b/flexiapi/app/Http/Middleware/IsPhoneRegistration.php @@ -0,0 +1,36 @@ +. +*/ + +namespace App\Http\Middleware; + +use Closure; +use Illuminate\Http\Request; +use Symfony\Component\HttpFoundation\Response; + +class IsPhoneRegistration +{ + public function handle(Request $request, Closure $next): Response + { + if (space()?->phone_registration) { + return $next($request); + } + + return abort(404, 'Phone registration disabled'); + } +} diff --git a/flexiapi/app/Http/Middleware/IsPublicRegistration.php b/flexiapi/app/Http/Middleware/IsPublicRegistration.php new file mode 100644 index 0000000..57af56d --- /dev/null +++ b/flexiapi/app/Http/Middleware/IsPublicRegistration.php @@ -0,0 +1,36 @@ +. +*/ + +namespace App\Http\Middleware; + +use Closure; +use Illuminate\Http\Request; +use Symfony\Component\HttpFoundation\Response; + +class IsPublicRegistration +{ + public function handle(Request $request, Closure $next): Response + { + if (space()?->public_registration) { + return $next($request); + } + + return abort(404, 'Public registration disabled'); + } +} diff --git a/flexiapi/app/Http/Middleware/IsSpaceExpired.php b/flexiapi/app/Http/Middleware/IsSpaceExpired.php index aef07a4..dbe0936 100644 --- a/flexiapi/app/Http/Middleware/IsSpaceExpired.php +++ b/flexiapi/app/Http/Middleware/IsSpaceExpired.php @@ -15,7 +15,7 @@ class IsSpaceExpired return abort(503, 'APP_ROOT_HOST is not configured'); } - $space = \App\Space::where('host', $request->header('host'))->first(); + $space = space(); if ($space) { if (!str_ends_with($space->host, config('app.root_host'))) { diff --git a/flexiapi/app/Http/Middleware/IsWebPanelEnabled.php b/flexiapi/app/Http/Middleware/IsWebPanelEnabled.php index 156b191..31e0a30 100644 --- a/flexiapi/app/Http/Middleware/IsWebPanelEnabled.php +++ b/flexiapi/app/Http/Middleware/IsWebPanelEnabled.php @@ -21,19 +21,13 @@ namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; +use App\Space; class IsWebPanelEnabled { - /** - * Handle an incoming request. - * - * @param \Illuminate\Http\Request $request - * @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next - * @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse - */ public function handle(Request $request, Closure $next) { - if (!$request->expectsJson() && config('app.web_panel')) { + if (!$request->expectsJson() && space()?->web_panel) { return $next($request); } diff --git a/flexiapi/app/Rules/Ini.php b/flexiapi/app/Rules/Ini.php new file mode 100644 index 0000000..3fbd93b --- /dev/null +++ b/flexiapi/app/Rules/Ini.php @@ -0,0 +1,35 @@ +. +*/ + +namespace App\Rules; + +use Illuminate\Contracts\Validation\Rule; + +class Ini implements Rule +{ + public function passes($attribute, $value) + { + return parse_ini_string($value) != false; + } + + public function message() + { + return 'Invalid ini format'; + } +} diff --git a/flexiapi/app/Services/AccountService.php b/flexiapi/app/Services/AccountService.php index 8b88f19..37fa6d0 100644 --- a/flexiapi/app/Services/AccountService.php +++ b/flexiapi/app/Services/AccountService.php @@ -123,8 +123,8 @@ class AccountService ); if (!$request->api) { - if (!empty(config('app.newsletter_registration_address')) && $request->has('newsletter')) { - Mail::to(config('app.newsletter_registration_address'))->send(new NewsletterRegistration($account)); + if (!empty(space()?->newsletter_registration_address) && $request->has('newsletter')) { + Mail::to(space()->newsletter_registration_address)->send(new NewsletterRegistration($account)); } } diff --git a/flexiapi/config/app.php b/flexiapi/config/app.php index f067a35..157af35 100644 --- a/flexiapi/config/app.php +++ b/flexiapi/config/app.php @@ -20,16 +20,6 @@ return [ 'terms_of_use_url' => env('TERMS_OF_USE_URL', ''), 'privacy_policy_url' => env('PRIVACY_POLICY_URL', ''), - 'newsletter_registration_address' => env('NEWSLETTER_REGISTRATION_ADDRESS', ''), - 'phone_authentication' => env('PHONE_AUTHENTICATION', true), - 'public_registration' => env('PUBLIC_REGISTRATION', true), - 'intercom_features' => env('INTERCOM_FEATURES', false), - 'devices_management' => env('DEVICES_MANAGEMENT', false), - 'web_panel' => env('WEB_PANEL', true), - - 'proxy_registrar_address' => env('ACCOUNT_PROXY_REGISTRAR_ADDRESS', 'sip.domain.com'), - 'transport_protocol_text' => env('ACCOUNT_TRANSPORT_PROTOCOL_TEXT', 'TLS (recommended), TCP or UDP'), - 'allow_phone_number_username_admin_api' => env('APP_ALLOW_PHONE_NUMBER_USERNAME_ADMIN_API', false), 'account_blacklisted_usernames' => env('ACCOUNT_BLACKLISTED_USERNAMES', ''), 'account_email_unique' => env('ACCOUNT_EMAIL_UNIQUE', false), @@ -37,12 +27,6 @@ return [ 'account_default_password_algorithm' => env('ACCOUNT_DEFAULT_PASSWORD_ALGORITHM', 'SHA-256'), 'account_authentication_bearer' => env('ACCOUNT_AUTHENTICATION_BEARER', null), - /** - * Set a global realm for all the accounts, if not set, the account domain - * will be used as a fallback - */ - 'account_realm' => env('ACCOUNT_REALM', null), - /** * Time limit before the API Key and related cookie are expired */ @@ -78,13 +62,6 @@ return [ 'blocking_time_period_check' => env('BLOCKING_TIME_PERIOD_CHECK', 30), 'blocking_amount_events_authorized_during_period' => env('BLOCKING_AMOUNT_EVENTS_AUTHORIZED_DURING_PERIOD', 5), - /** - * Account provisioning - */ - 'provisioning_rc_file' => env('ACCOUNT_PROVISIONING_RC_FILE', ''), - 'provisioning_overwrite_all' => env('ACCOUNT_PROVISIONING_OVERWRITE_ALL', false), - 'provisioning_use_x_linphone_provisioning_header' => env('ACCOUNT_PROVISIONING_USE_X_LINPHONE_PROVISIONING_HEADER', true), - /** * /!\ Enable dangerous endpoints required for fallback */ diff --git a/flexiapi/config/instance.php b/flexiapi/config/instance.php deleted file mode 100644 index 1a1e6e1..0000000 --- a/flexiapi/config/instance.php +++ /dev/null @@ -1,8 +0,0 @@ - env('INSTANCE_COPYRIGHT', null), - 'intro_registration' => env('INSTANCE_INTRO_REGISTRATION', null), // Can be Markdown formatted - 'confirmed_registration_text' => env('INSTANCE_CONFIRMED_REGISTRATION_TEXT', null), // Can be Markdown formatted - 'custom_theme' => env('INSTANCE_CUSTOM_THEME', false), -]; \ No newline at end of file diff --git a/flexiapi/database/factories/PasswordFactory.php b/flexiapi/database/factories/PasswordFactory.php index f607f67..0a5f7f4 100644 --- a/flexiapi/database/factories/PasswordFactory.php +++ b/flexiapi/database/factories/PasswordFactory.php @@ -30,7 +30,7 @@ class PasswordFactory extends Factory public function definition() { $account = Account::factory()->create(); - $realm = config('app.account_realm') ?? $account->domain; + $realm = space()?->account_realm ?? $account->domain; return [ 'account_id' => $account->id, @@ -54,7 +54,7 @@ class PasswordFactory extends Factory { return $this->state(function (array $attributes) { $account = Account::find($attributes['account_id']); - $realm = config('app.account_realm') ?? $account->domain; + $realm = space()?->account_realm ?? $account->domain; return [ 'password' => hash('sha256', $account->username.':'.$realm.':testtest'), diff --git a/flexiapi/database/factories/SpaceFactory.php b/flexiapi/database/factories/SpaceFactory.php index 418e167..da866ad 100644 --- a/flexiapi/database/factories/SpaceFactory.php +++ b/flexiapi/database/factories/SpaceFactory.php @@ -35,6 +35,20 @@ class SpaceFactory extends Factory ]; } + public function local() + { + return $this->state(fn (array $attributes) => [ + 'host' => 'localhost', + ]); + } + + public function withoutProvisioningHeader() + { + return $this->state(fn (array $attributes) => [ + 'provisioning_use_linphone_provisioning_header' => false, + ]); + } + public function secondDomain() { return $this->state(fn (array $attributes) => [ @@ -43,6 +57,13 @@ class SpaceFactory extends Factory ]); } + public function withRealm(string $realm) + { + return $this->state(fn (array $attributes) => [ + 'account_realm' => $realm, + ]); + } + public function expired() { return $this->state(fn (array $attributes) => [ diff --git a/flexiapi/database/migrations/2025_02_03_102848_complete_spaces_configuration.php b/flexiapi/database/migrations/2025_02_03_102848_complete_spaces_configuration.php new file mode 100644 index 0000000..b6bb25d --- /dev/null +++ b/flexiapi/database/migrations/2025_02_03_102848_complete_spaces_configuration.php @@ -0,0 +1,55 @@ +text('copyright_text')->nullable(); + $table->text('intro_registration_text')->nullable(); + $table->text('confirmed_registration_text')->nullable(); + + $table->string('newsletter_registration_address')->nullable(); + $table->string('account_proxy_registrar_address')->nullable(); + $table->string('account_realm')->nullable(); + + $table->text('custom_provisioning_entries')->nullable(); + $table->boolean('custom_provisioning_overwrite_all')->default(false); + $table->boolean('provisioning_use_linphone_provisioning_header')->default(true); + + $table->boolean('custom_theme')->default(false); + $table->boolean('web_panel')->default(true); + $table->boolean('public_registration')->default(true); + $table->boolean('phone_registration')->default(true); + $table->boolean('intercom_features')->default(false); + }); + + } + + public function down(): void + { + Schema::table('spaces', function (Blueprint $table) { + $table->dropColumn('copyright_text'); + $table->dropColumn('intro_registration_text'); + $table->dropColumn('confirmed_registration_text'); + + $table->dropColumn('newsletter_registration_address'); + $table->dropColumn('account_proxy_registrar_address'); + $table->dropColumn('account_realm'); + + $table->dropColumn('custom_provisioning_entries'); + $table->dropColumn('custom_provisioning_overwrite_all'); + $table->dropColumn('provisioning_use_linphone_provisioning_header'); + + $table->dropColumn('custom_theme'); + $table->dropColumn('web_panel'); + $table->dropColumn('public_registration'); + $table->dropColumn('phone_registration'); + $table->dropColumn('intercom_features'); + }); + } +}; diff --git a/flexiapi/resources/views/about.blade.php b/flexiapi/resources/views/about.blade.php index 2d22a50..e8fca16 100644 --- a/flexiapi/resources/views/about.blade.php +++ b/flexiapi/resources/views/about.blade.php @@ -17,6 +17,6 @@

GNU General Public Licence v3.0 (Licence)

-

{{ config('instance.copyright') }}

+

{{ space()->instance_copyright }}

@endsection diff --git a/flexiapi/resources/views/account/dashboard.blade.php b/flexiapi/resources/views/account/dashboard.blade.php index 4937f39..09a67d8 100644 --- a/flexiapi/resources/views/account/dashboard.blade.php +++ b/flexiapi/resources/views/account/dashboard.blade.php @@ -17,7 +17,7 @@ Change my current account email

- @if (config('app.phone_authentication')) + @if (space()->phone_registration)

phone @if (!empty($account->phone)) @@ -66,8 +66,8 @@

user Username: {{ $account->username }}

globe-hemisphere-west Domain: {{ $account->domain }}

- @if (!empty(config('app.proxy_registrar_address'))) -

lan Proxy/registrar address: sip:{{ config('app.proxy_registrar_address') }} + @if (!empty(space()?->account_proxy_registrar_address)) +

lan Proxy/registrar address: sip:{{ space()?->account_proxy_registrar_address }}

@endif @if (!empty(config('app.transport_protocol_text'))) diff --git a/flexiapi/resources/views/account/documentation_markdown.blade.php b/flexiapi/resources/views/account/documentation_markdown.blade.php index 50b2f0f..10cdaf9 100644 --- a/flexiapi/resources/views/account/documentation_markdown.blade.php +++ b/flexiapi/resources/views/account/documentation_markdown.blade.php @@ -10,11 +10,7 @@ Registration can be achieve using several methods if they are correctly configur ## Email Registration -@if (!config('app.web_panel') || !config('app.public_registration')) -*The feature is not enabled on this instance.* -@endif - -You can @if (config('app.web_panel') && config('app.public_registration')) [create an account using an email address]({{ route('account.register.email') }}) @else create an account using an email address @endif. The form requires you to provide an username and your email address. +You can create an account using an email address. The form requires you to provide an username and your email address. Once completed a confirmation email containing a unique link will be sent to the address. This link is used to activate your account, allowing you to finish the setup. @@ -24,11 +20,7 @@ Allow the creation of an account using a previously generated Account Creation T ## Phone Registration -@if (!config('app.phone_authentication')) -*The feature is not enabled on this instance.* -@endif - -If enabled you can also @if (config('app.web_panel') && config('app.phone_authentication')) [create an account using a phone number]({{ route('account.register.phone') }}) @else create an account using a phone number @endif. You can also add an optional nickname to personnalize your SIP address. If not, your phone number will be used as a username. +If enabled you can also create an account using a phone number. You can also add an optional nickname to personnalize your SIP address. If not, your phone number will be used as a username. Once submitted, you will be asked to provide a unique pin code received by SMS to the phone number used during the registration. @@ -40,7 +32,7 @@ Once activated {{ $app_name }} will ask your to provide a password to finish you To authenticate please fill in the username or phone number and password you provided during the registration phase. -If you forgot your password or didn't configured it, you can always recover your account using the recover password forms, using your @if (config('app.web_panel')) [email address]({{ route('account.recovery.show.email') }}) @else email address @endif or @if (config('app.web_panel') && config('app.phone_authentication')) [phone number]({{ route('account.recovery.show.phone') }}) @else phone number (not enabled) @endif. Once authenticated you will then be able to change your password. +If you forgot your password or didn't configured it, you can always recover your account using the recover password forms, using your email address or phone number. Once authenticated you will then be able to change your password. ## Code based authentication @@ -48,7 +40,7 @@ If you forgot your password or didn't configured it, you can always recover your # Account panel -Once authenticated you will get access to @if (config('app.web_panel')) [your account panel]({{ route('account.dashboard') }}) @else your account panel @endif. +Once authenticated you will get access to your account panel. ## Generate an API Key @@ -56,22 +48,18 @@ You will be able to generate an API Key allowing you to use the {{ $app_name }} ## Change your email address -You can @if (config('app.web_panel')) [change your email address]({{ route('account.email.change') }}) @else change your email address @endif from the panel. A confirmation email containing a unique link will be sent to validate the new one. +You can change your email address from the panel. A confirmation email containing a unique link will be sent to validate the new one. ## Change your password -Your password can also be changed from the @if (config('app.web_panel')) [password change form]({{ route('account.password.show') }}) @else password change form @endif. You can enable SHA-256 encrypted password when changing it (required for some clients). +Your password can also be changed from the password change form. You can enable SHA-256 encrypted password when changing it (required for some clients). ## Delete your account -Your account can be deleted from the panel using the @if (config('app.web_panel')) [account deletion form]({{ route('account.delete') }}) @else account deletion form @endif. You must re-enter your full SIP address to confirm the deletion. +Your account can be deleted from the panel using the account deletion form. You must re-enter your full SIP address to confirm the deletion. ## Devices management -@if (config('app.devices_management') == false) -*The feature is not enabled on this instance.* -@endif - From the devices management panel an admin will be able to list and delete the devices attached to a SIP account. # Admin panel @@ -102,10 +90,6 @@ The deletion of an account is definitive, all the database related data (passwor ### Create, edit and delete account types -@if (config('app.intercom_features') == false) -*The feature is not enabled on this instance.* -@endif - An adminisator can create, edit and delete account types. Those can be used to categorize accounts in clients, they are often used for Internet of Things related devices. ## Statistics diff --git a/flexiapi/resources/views/account/login.blade.php b/flexiapi/resources/views/account/login.blade.php index f689d42..d3ff66e 100644 --- a/flexiapi/resources/views/account/login.blade.php +++ b/flexiapi/resources/views/account/login.blade.php @@ -4,14 +4,14 @@

hand-waving Welcome on {{ config('app.name') }}

- @if (config('instance.intro_registration')) - @parsedown(config('instance.intro_registration')) + @if (space()->intro_registration_text) + @parsedown(space()->intro_registration_text) @endif
@csrf
- @if (config('app.phone_authentication')) + @if (space()->phone_registration) @@ -37,7 +37,7 @@ @include('parts.recovery') - @if (config('app.public_registration')) + @if (space()->public_registration)

diff --git a/flexiapi/resources/views/account/register/email.blade.php b/flexiapi/resources/views/account/register/email.blade.php index 321d3f1..d10f380 100644 --- a/flexiapi/resources/views/account/register/email.blade.php +++ b/flexiapi/resources/views/account/register/email.blade.php @@ -45,7 +45,7 @@ @include('parts.errors', ['name' => 'password_confirmation'])
- @if (!empty(config('app.newsletter_registration_address'))) + @if (!empty(config(space()?->newsletter_registration_address))
diff --git a/flexiapi/resources/views/admin/account/create_edit.blade.php b/flexiapi/resources/views/admin/account/create_edit.blade.php index 65527fd..88da88d 100644 --- a/flexiapi/resources/views/admin/account/create_edit.blade.php +++ b/flexiapi/resources/views/admin/account/create_edit.blade.php @@ -120,7 +120,7 @@ @include('parts.form.toggle', ['object' => $account, 'key' => 'activated', 'label' => 'Status', 'supporting' => 'Is the account enabled?']) - @if (config('app.intercom_features')) + @if (space()?->intercom_features)
diff --git a/flexiapi/resources/views/admin/space/configuration.blade.php b/flexiapi/resources/views/admin/space/configuration.blade.php new file mode 100644 index 0000000..1fbf9d2 --- /dev/null +++ b/flexiapi/resources/views/admin/space/configuration.blade.php @@ -0,0 +1,102 @@ +@extends('layouts.main') + +@section('breadcrumb') + @if (auth()->user()->superAdmin) + + + @else + + @endif + +@endsection + +@section('content') +
+

globe-hemisphere-west {{ $space->host }}

+
+ + @include('admin.space.tabs') + + + @csrf + @method('put') + +
+ + + @include('parts.errors', ['name' => 'copyright_text']) +
+ +
+ + + Markdown text + @include('parts.errors', ['name' => 'intro_registration_text']) +
+ +
+ + + Markdown text + @include('parts.errors', ['name' => 'confirmed_registration_text']) +
+ +
+ + + An email will be sent to this address when someone register and join the newsletter + @include('parts.errors', ['name' => 'newsletter_registration_address']) +
+ +
+ + + Will be used for informational purpose in the user panel and communication emails + @include('parts.errors', ['name' => 'account_proxy_registrar_address']) +
+ +

Provisioning

+ +
+ + + In ini format, will complete the other settings + @include('parts.errors', ['name' => 'custom_provisioning_entries']) +
+ +
+ @include('parts.form.toggle', ['object' => $space, 'key' => 'custom_provisioning_overwrite_all', 'label' => 'Allow client settings to be overwritten by the provisioning ones']) +
+ +
+ @include('parts.form.toggle', ['object' => $space, 'key' => 'provisioning_use_linphone_provisioning_header', 'label' => 'Enforce X-Linphone-Provisioning header']) +
+ +

Space features

+
+ @include('parts.form.toggle', ['object' => $space, 'key' => 'public_registration', 'label' => 'Allow public registration']) +
+
$space, 'key' => 'phone_registration', 'label' => 'Allow registration using phones']) +
+
+ @include('parts.form.toggle', ['object' => $space, 'key' => 'intercom_features', 'label' => 'Enable intercom features']) +
+ +
+ +
+ +@endsection diff --git a/flexiapi/resources/views/admin/space/edit.blade.php b/flexiapi/resources/views/admin/space/edit.blade.php index 6608b2c..04d5fb0 100644 --- a/flexiapi/resources/views/admin/space/edit.blade.php +++ b/flexiapi/resources/views/admin/space/edit.blade.php @@ -4,8 +4,12 @@ - - + + @endsection @section('content') diff --git a/flexiapi/resources/views/admin/space/tabs.blade.php b/flexiapi/resources/views/admin/space/tabs.blade.php index d602db9..c9f9cd6 100644 --- a/flexiapi/resources/views/admin/space/tabs.blade.php +++ b/flexiapi/resources/views/admin/space/tabs.blade.php @@ -3,9 +3,13 @@ if (auth()->user()->superAdmin) { $items[route('admin.spaces.show', $space->id)] = 'Information'; - $items[route('admin.spaces.edit', $space->id)] = 'Configuration'; - $items[route('admin.spaces.parameters', $space->id)] = 'Parameters'; + $items[route('admin.spaces.administration', $space->id)] = 'Space Administration'; + $items[route('admin.spaces.edit', $space->id)] = 'App Configuration'; + } else if (auth()->user()->admin) { + $items[route('admin.spaces.me')] = 'Information'; } + + $items[route('admin.spaces.configuration', $space->id)] = 'Space Configuration'; @endphp @include('parts.tabs', [ diff --git a/flexiapi/resources/views/api/documentation_markdown.blade.php b/flexiapi/resources/views/api/documentation_markdown.blade.php index 69cd216..0098349 100644 --- a/flexiapi/resources/views/api/documentation_markdown.blade.php +++ b/flexiapi/resources/views/api/documentation_markdown.blade.php @@ -57,7 +57,7 @@ Currently supported languages: @php ### Using the API Key -You can retrieve an API Key from @if (config('app.web_panel')) [your account panel]({{ route('account.login') }}) @else your account panel @endif or using the dedicated API endpoint. +You can retrieve an API Key from your account panel or using the dedicated API endpoint. **The generated API Key will be restricted to the IP that generates it and will be destroyed if not used after some times.** @@ -168,6 +168,20 @@ JSON parameters: * `max_account` integer, the maximum number of accounts configurable in the app, default to `0` (infinite) * `max_accounts` integer, the maximum number of accounts that can be created in the space, default to `0` (infinite), cannot be less than the actual amount of accounts * `expire_at` date, the moment the space is expiring, default to `null` (never expire) +* `copyright_text` text, the copyright text +* `intro_registration_text` Markdown text, the main registration page text +* `confirmed_registration_text` Markdown text, the text displayed in the registration email +* `newsletter_registration_address`, the newsletter registration email address +* `account_proxy_registrar_address`, the account proxy registrar address +* `account_realm`, the default realm for the accounts, fallback to the domain if not set +* `custom_provisioning_entries` text, the custom configuration used for the provisioning +* `custom_provisioning_overwrite_all` boolean, allow the custom configuration to overwrite the default one +* `provisioning_use_linphone_provisioning_header` boolean +* `custom_theme` boolean, allow a custom CSS file to be loaded +* `web_panel` boolean, the web panel switch +* `public_registration` boolean, the public registration switch +* `phone_registration` boolean, the phone registration switch +* `intercom_features` boolean, the intercom features switch ### `PUT /spaces/{domain}` Super Admin @@ -190,6 +204,20 @@ JSON parameters: * `max_account` **required**, integer * `max_accounts` **required**,integer, the maximum number of accounts that can be created in the space, default to `0` (infinite), cannot be less than the actual amount of accounts * `expire_at` **required**, date, the moment the space is expiring, set to `null` to never expire +* `copyright_text` **required**, text, the copyright text +* `intro_registration_text` **required**, Markdown text, the main registration page text +* `confirmed_registration_text` **required**, Markdown text, the text displayed in the registration email +* `newsletter_registration_address`, **required**, the newsletter registration email address +* `account_proxy_registrar_address`, **required**, the account proxy registrar address +* `account_realm`, **required**, the default realm for the accounts, fallback to the domain if not set +* `custom_provisioning_entries` **required**, text, the custom configuration used for the provisioning +* `custom_provisioning_overwrite_all` **required**, boolean, allow the custom configuration to overwrite the default one +* `provisioning_use_linphone_provisioning_header` **required**, boolean +* `custom_theme` **required**, boolean, allow a custom CSS file to be loaded +* `web_panel` **required**, boolean, the web panel switch +* `public_registration` **required**, boolean, the public registration switch +* `phone_registration` **required**, boolean, the phone registration switch +* `intercom_features` **required**, boolean, the intercom features switch ### `DELETE /spaces/{domain}` Super Admin diff --git a/flexiapi/resources/views/layouts/main.blade.php b/flexiapi/resources/views/layouts/main.blade.php index 11b23fb..e6d4cf5 100644 --- a/flexiapi/resources/views/layouts/main.blade.php +++ b/flexiapi/resources/views/layouts/main.blade.php @@ -9,8 +9,13 @@ {{ config('app.name') }} - @if (config('instance.custom_theme') & file_exists(public_path('css/' . config('app.env') . '.style.css'))) - + + @php + $space = space(); + @endphp + + @if (space()?->custom_theme && file_exists(public_path('css/' . space()?->host . '.style.css'))) + @endif diff --git a/flexiapi/resources/views/mails/confirmed_registration.blade.php b/flexiapi/resources/views/mails/confirmed_registration.blade.php index 7dab2e9..92103ec 100644 --- a/flexiapi/resources/views/mails/confirmed_registration.blade.php +++ b/flexiapi/resources/views/mails/confirmed_registration.blade.php @@ -5,8 +5,8 @@

Hello,

- @if (config('instance.confirmed_registration_text')) - @parsedown(config('instance.confirmed_registration_text')) + @if (space()->confirmed_registration_text) + {{ strip_tags(parsedown(space()->confirmed_registration_text)) }} @else Your SIP account has been successfully created.
You can now configure this account on any SIP-compatible application using the following parameters:
@@ -17,8 +17,8 @@ Username: {{ $account->username }}
Domain: {{ $account->domain }}

- @if (!empty(config('app.proxy_registrar_address'))) - Proxy/registrar address: sip:{{ config('app.proxy_registrar_address') }}
+ @if (!empty(space()?->account_proxy_registrar_address)) + Proxy/registrar address: sip:{{ space()?->account_proxy_registrar_address }}
@endif @if (!empty(config('app.transport_protocol_text'))) Transport: {{ config('app.transport_protocol_text') }}
diff --git a/flexiapi/resources/views/mails/confirmed_registration_text.blade.php b/flexiapi/resources/views/mails/confirmed_registration_text.blade.php index 0a74ab3..63d9ca5 100644 --- a/flexiapi/resources/views/mails/confirmed_registration_text.blade.php +++ b/flexiapi/resources/views/mails/confirmed_registration_text.blade.php @@ -2,8 +2,8 @@ Registration confirmed {{ config('app.name') }} Hello, -@if (config('instance.confirmed_registration_text')) -{{ strip_tags(parsedown(config('instance.confirmed_registration_text'))) }} +@if (space()->confirmed_registration_text) +{{ strip_tags(parsedown(space()->confirmed_registration_text)) }} @else Your SIP account has been successfully created. You can now configure this account on any SIP-compatible application using the following parameters: @@ -13,8 +13,8 @@ You can now configure this account on any SIP-compatible application using the f Username: {{ $account->username }} Domain: {{ $account->domain }} -@if (!empty(config('app.proxy_registrar_address'))) - Proxy/registrar address: sip:{{ config('app.proxy_registrar_address') }} +@if (!empty(space()?->account_proxy_registrar_address)) + Proxy/registrar address: sip:{{ space()?->account_proxy_registrar_address }} @endif @if (!empty(config('app.transport_protocol_text'))) Transport: {{ config('app.transport_protocol_text') }} diff --git a/flexiapi/resources/views/parts/account_variables.blade.php b/flexiapi/resources/views/parts/account_variables.blade.php index fc9e929..dcd49dd 100644 --- a/flexiapi/resources/views/parts/account_variables.blade.php +++ b/flexiapi/resources/views/parts/account_variables.blade.php @@ -3,8 +3,8 @@ var account_infos = { sip: '{{ $account->identifier }}', username: '{{ $account->username }}', - @if (!empty(config('app.proxy_registrar_address'))) - registrar_address: '{{ config('app.proxy_registrar_address') }}', + @if (!empty(space()?->account_proxy_registrar_address)) + registrar_address: '{{ space()?->account_proxy_registrar_address }}', @endif domain: '{{ $account->domain }}' } diff --git a/flexiapi/resources/views/parts/recovery.blade.php b/flexiapi/resources/views/parts/recovery.blade.php index 8b63573..4d34749 100644 --- a/flexiapi/resources/views/parts/recovery.blade.php +++ b/flexiapi/resources/views/parts/recovery.blade.php @@ -5,7 +5,7 @@ Set or recover your password @endif using your Email address - @if (config('app.phone_authentication')) + @if (space()->phone_registration) or your Phone number @endif

diff --git a/flexiapi/resources/views/parts/tabs/register.blade.php b/flexiapi/resources/views/parts/tabs/register.blade.php index 21be028..16a92b6 100644 --- a/flexiapi/resources/views/parts/tabs/register.blade.php +++ b/flexiapi/resources/views/parts/tabs/register.blade.php @@ -1,4 +1,4 @@ -@if(config('app.phone_authentication')) +@if(space()->phone_registration) @include('parts.tabs', ['items' => [ route('account.register.phone') => 'Phone registration', route('account.register.email') => 'Email registration', diff --git a/flexiapi/routes/web.php b/flexiapi/routes/web.php index 94f40ae..a0a9a92 100644 --- a/flexiapi/routes/web.php +++ b/flexiapi/routes/web.php @@ -87,16 +87,16 @@ Route::name('provisioning.')->prefix('provisioning')->controller(ProvisioningCon }); Route::middleware(['web_panel_enabled', 'space.expired'])->group(function () { - if (config('app.public_registration')) { + Route::middleware(['public_registration'])->group(function () { Route::redirect('register', 'register/email')->name('account.register'); - if (config('app.phone_authentication')) { + Route::middleware(['phone_registration'])->group(function () { Route::get('register/phone', 'Account\RegisterController@registerPhone')->name('account.register.phone'); - } + }); Route::get('register/email', 'Account\RegisterController@registerEmail')->name('account.register.email'); Route::post('accounts', 'Account\AccountController@store')->name('account.store'); - } + }); Route::prefix('recovery')->controller(RecoveryController::class)->group(function () { Route::get('phone', 'showPhone')->name('account.recovery.show.phone'); @@ -115,14 +115,14 @@ Route::middleware(['web_panel_enabled', 'space.expired'])->group(function () { Route::post('/', 'store')->name('email.update'); }); - if (config('app.phone_authentication')) { + Route::middleware(['phone_registration'])->group(function () { Route::prefix('phone')->controller(PhoneController::class)->group(function () { Route::get('change', 'change')->name('phone.change'); Route::post('change', 'requestChange')->name('phone.request_change'); Route::get('validate', 'validateChange')->name('phone.validate'); Route::post('/', 'store')->name('phone.update'); }); - } + }); Route::name('device.')->prefix('devices')->controller(DeviceController::class)->group(function () { Route::get('/', 'index')->name('index'); @@ -156,13 +156,15 @@ Route::middleware(['web_panel_enabled', 'space.expired'])->group(function () { Route::name('admin.')->prefix('admin')->middleware(['auth.admin', 'auth.check_blocked'])->group(function () { Route::get('space', 'Admin\SpaceController@me')->name('spaces.me'); + Route::get('spaces/{space}/configuration', 'Admin\SpaceController@configuration')->name('spaces.configuration'); + Route::put('spaces/{space}/configuration', 'Admin\SpaceController@configurationUpdate')->name('spaces.configuration.update'); Route::middleware(['auth.super_admin'])->group(function () { Route::resource('spaces', SpaceController::class); Route::get('spaces/delete/{id}', 'Admin\SpaceController@delete')->name('spaces.delete'); - Route::get('spaces/{space}/parameters', 'Admin\SpaceController@parameters')->name('spaces.parameters'); - Route::put('spaces/{space}/parameters', 'Admin\SpaceController@parametersUpdate')->name('spaces.parameters.update'); + Route::get('spaces/{space}/administration', 'Admin\SpaceController@administration')->name('spaces.administration'); + Route::put('spaces/{space}/administration', 'Admin\SpaceController@administrationUpdate')->name('spaces.administration.update'); Route::name('phone_countries.')->controller(PhoneCountryController::class)->prefix('phone_countries')->group(function () { Route::get('/', 'index')->name('index'); @@ -213,7 +215,7 @@ Route::middleware(['web_panel_enabled', 'space.expired'])->group(function () { Route::post('handle', 'handle')->name('handle'); }); - if (config('app.intercom_features')) { + Route::middleware(['intercom_features'])->group(function () { Route::name('type.')->prefix('types')->controller(AccountTypeController::class)->group(function () { Route::get('/', 'index')->name('index'); Route::get('create', 'create')->name('create'); @@ -238,7 +240,7 @@ Route::middleware(['web_panel_enabled', 'space.expired'])->group(function () { Route::get('{action_id}/delete', 'delete')->name('delete'); Route::delete('{action_id}', 'destroy')->name('destroy'); }); - } + }); Route::name('contact.')->prefix('{account}/contacts')->controller(AccountContactController::class)->group(function () { Route::get('create', 'create')->name('create'); diff --git a/flexiapi/tests/Feature/AccountJWTAuthenticationTest.php b/flexiapi/tests/Feature/AccountJWTAuthenticationTest.php index 2a5051a..e6f4cda 100644 --- a/flexiapi/tests/Feature/AccountJWTAuthenticationTest.php +++ b/flexiapi/tests/Feature/AccountJWTAuthenticationTest.php @@ -57,6 +57,8 @@ class AccountJWTAuthenticationTest extends TestCase $password = Password::factory()->create(); + \App\Space::where('domain', $password->account->domain)->update(['host' => 'localhost']); + $bearer = 'authz_server="https://sso.test/", realm="sip.test.org"'; config()->set('services.jwt.rsa_public_key_pem', $this->serverPublicKeyPem); diff --git a/flexiapi/tests/Feature/AccountProvisioningTest.php b/flexiapi/tests/Feature/AccountProvisioningTest.php index 6d6c1be..d3d0d04 100644 --- a/flexiapi/tests/Feature/AccountProvisioningTest.php +++ b/flexiapi/tests/Feature/AccountProvisioningTest.php @@ -39,6 +39,10 @@ class AccountProvisioningTest extends TestCase public function testBaseProvisioning() { + Space::truncate(); + Space::factory()->local()->create(); + space(reload: true); + $this->get($this->route)->assertStatus(400); $this->withHeaders([ @@ -51,7 +55,9 @@ class AccountProvisioningTest extends TestCase public function testDisabledProvisioningHeader() { - config()->set('app.provisioning_use_x_linphone_provisioning_header', false); + Space::truncate(); + Space::factory()->local()->withoutProvisioningHeader()->create(); + space(reload: true); $this->get($this->route) ->assertStatus(200) @@ -61,6 +67,10 @@ class AccountProvisioningTest extends TestCase public function testDontProvisionHeaderDisabled() { + Space::truncate(); + Space::factory()->local()->create(); + space(reload: true); + $account = Account::factory()->deactivated()->create(); $account->generateApiKey(); diff --git a/flexiapi/tests/Feature/ApiAccountTest.php b/flexiapi/tests/Feature/ApiAccountTest.php index e7622ca..fb70372 100644 --- a/flexiapi/tests/Feature/ApiAccountTest.php +++ b/flexiapi/tests/Feature/ApiAccountTest.php @@ -581,14 +581,16 @@ class ApiAccountTest extends TestCase public function testSimpleAccount() { + $realm = 'realm.com'; + + Space::factory()->local()->withRealm($realm)->create(); + space(reload: true); + $password = Password::factory()->create(); $password->account->activated = false; $password->account->generateApiKey(); $password->account->save(); - $realm = 'realm.com'; - config()->set('app.account_realm', $realm); - /** * Public information */ diff --git a/flexiapi/tests/Feature/ApiAuthenticationTest.php b/flexiapi/tests/Feature/ApiAuthenticationTest.php index f18a63a..facff3d 100644 --- a/flexiapi/tests/Feature/ApiAuthenticationTest.php +++ b/flexiapi/tests/Feature/ApiAuthenticationTest.php @@ -20,6 +20,7 @@ namespace Tests\Feature; use App\Password; +use App\Space; use Tests\TestCase; @@ -176,6 +177,10 @@ class ApiAuthenticationTest extends TestCase public function testAuthenticationSHA265FromCLRTXT() { + Space::truncate(); + Space::factory()->local()->create(); + space(reload: true); + $password = Password::factory()->clrtxt()->create(); $response = $this->generateFirstResponse($password); @@ -204,7 +209,10 @@ class ApiAuthenticationTest extends TestCase public function testAuthenticationSHA265FromCLRTXTWithRealm() { $realm = 'realm.com'; - config()->set('app.account_realm', $realm); + + Space::truncate(); + Space::factory()->local()->withRealm($realm)->create(); + space(reload: true); $password = Password::factory()->clrtxt()->create(); $response = $this->generateFirstResponse($password); @@ -218,7 +226,7 @@ class ApiAuthenticationTest extends TestCase $response = $this->withHeaders([ 'From' => 'sip:'.$password->account->identifier, - 'Authorization' => $this->generateDigest($password, $response, $hash), + 'Authorization' => $this->generateDigest($password, $response, $hash) ])->json($this->method, $this->route); $this->assertStringContainsString('algorithm=MD5', $response->headers->all()['www-authenticate'][0]); diff --git a/flexiapi/tests/Feature/ApiSpaceWithMiddlewareTest.php b/flexiapi/tests/Feature/ApiSpaceWithMiddlewareTest.php index 7eeb550..36cd8d6 100644 --- a/flexiapi/tests/Feature/ApiSpaceWithMiddlewareTest.php +++ b/flexiapi/tests/Feature/ApiSpaceWithMiddlewareTest.php @@ -44,6 +44,8 @@ class ApiSpaceWithMiddlewareTest extends TestCaseWithSpaceMiddleware $admin->generateApiKey(); config()->set('app.root_host', $admin->domain); + space(reload: true); + $this->keyAuthenticated($admin) ->json($this->method, 'http://' . $admin->domain . $this->accountRoute, [ 'username' => 'new', @@ -62,6 +64,8 @@ class ApiSpaceWithMiddlewareTest extends TestCaseWithSpaceMiddleware ->json('PUT', $this->route . '/' . $admin->domain, $space) ->assertStatus(200); + space(reload: true); + $this->keyAuthenticated($admin) ->json($this->method, $this->accountRoute, [ 'username' => 'new',