Compare commits

..

53 commits

Author SHA1 Message Date
Timothée Jaussoin
f035b720cd Fix FLEXIAPI-406 Add an artisan console script to clear statistics after n days 2025-11-24 10:47:35 +00:00
Timothée Jaussoin
da63835902 Fix FLEXIAPI-404 Prevent the account domain to be reset during creation on error 2025-10-15 11:19:35 +02:00
Timothée Jaussoin
80d10bed7a Fix FLEXIAPI-402 Handle empty emails cases when importing accounts, handle... 2025-10-14 09:30:36 +00:00
Timothée Jaussoin
992761c1d0 Fix FLEXIAPI-355 Add withoutGlobalScope() to the Account ContactVcardList resolver 2025-10-14 09:04:07 +00:00
Timothée Jaussoin
5082654d01 Fix FLEXIAPI-401 Use a Space scoped reset password URL 2025-10-13 14:41:44 +02:00
Timothée Jaussoin
92978bf256 Fix FLEXIAPI-398 List all the availables Spaces domains when importing, fix... 2025-10-08 09:32:11 +00:00
Timothée Jaussoin
84fdb380d7 Fix FLEXIAPI-396 Remove the CHANGELOG.md file (redundant with the Git history)... 2025-10-07 10:04:37 +00:00
Timothée Jaussoin
d9b0f83e5d Fix FLEXIAPI-394 Apply SpaceCheck on all the pages and URLs, backport from 2.1 2025-09-29 15:26:25 +02:00
Timothée Jaussoin
7ae237eb7c Fix FLEXIAPI-392 Fix the recover_by_code view and use the account space object 2025-09-29 07:00:11 +00:00
Timothée Jaussoin
e888d53d31 Fix FLEXIAPI-391 Add missing account view attribute in the actions.delete view 2025-09-24 15:05:18 +02:00
Timothée Jaussoin
18cdf04026 Fix FLEXIAPI-390 Use the properly resolved Space domain when applying the... 2025-09-24 09:19:11 +00:00
Timothée Jaussoin
3b0dd419bb Fix FLEXIAPI-387 Ensure that INSTANCE_CONFIRMED_REGISTRATION_TEXT et... 2025-09-18 13:55:46 +00:00
Timothée Jaussoin
ef110eb4ce Fix FLEXIAPI-385 Use domains and not hosts in the EmailServer endpoints as... 2025-09-11 15:18:58 +02:00
Timothée Jaussoin
2006fa0929 Fix FLEXIAPI-378 Return a valid JSON containing the vCard and not the raw... 2025-08-28 13:56:07 +02:00
Timothée Jaussoin
7e9d34cf0b Fix FLEXIAPI-375 Fix VcardsStorage table UUID size, recover the UUID from the stored vCard 2025-08-27 16:36:16 +02:00
Timothée Jaussoin
740f603bbe Fix FLEXIAPI-372 Remove SESSION_DRIVER and CACHE_DRIVER and enforce them to file 2025-08-26 14:05:05 +02:00
Timothée Jaussoin
6af68374d6 Fix FLEXIAPI-371 Add documentation for the Wizard page 2025-08-18 17:05:15 +02:00
Timothée Jaussoin
fe89060698 Fix FLEXIAPI-361 Prepare the 2.0 release 2025-08-04 15:26:39 +02:00
Timothée Jaussoin
80246a232e Fix FLEXIAPI-364 Fix a faulty redirection in the ExternalAccount controller 2025-07-23 16:53:11 +02:00
Timothée Jaussoin
0578125f70 Fix FLEXIAPI-363 Send the Redis publish event when the externalAccount is... 2025-07-23 13:25:32 +00:00
Timothée Jaussoin
8882cdab18 Fix FLEXIAPI-312 Add Redis publish event when updating the externalAccount to... 2025-07-23 08:44:31 +00:00
Timothée Jaussoin
058d253dbc Fix FLEXIAPI-362 Return an empty object and not an empty array in the... 2025-07-22 08:18:53 +00:00
Timothée Jaussoin
2d611a1e33 Fix FLEXIAPI-360 Add rules on some jobs to only run them in the Gitlab pipeline when needed 2025-07-16 16:24:45 +02:00
Timothée Jaussoin
695d36e279 Fix FLEXIAPI-354 Fix contact deletion 2025-07-15 11:53:47 +02:00
Timothée Jaussoin
f5d6abc836 Fix FLEXIAPI-355 Add withoutGlobalScope() to the Account ContactVcardList resolver
Update the dependencies
Add missing rockylinux versions in deploy.yml for some jobs
2025-07-10 16:52:36 +02:00
Timothée Jaussoin
f37bc45194 Fix FLEXIAPI-356 Cleanup and reorganize the pipeline to mutualize some things and save time 2025-07-10 15:54:28 +02:00
Timothée Jaussoin
40c8209cdc Fix FLEXIAPI-353 Validate UUIDs in the vcards-storage endpoints, complete the... 2025-07-09 14:57:48 +00:00
Timothée Jaussoin
d55cc59e19 Fix FLEXIAPI-352 Add missing errors box in the password change form 2025-07-08 17:09:36 +02:00
Timothée Jaussoin
5771e39a9e Fix/351 import accounts windows 2025-07-08 14:24:02 +00:00
Timothée Jaussoin
dcd1719a61 Fix FLEXIAPI-350 Fix wrongly assigned variables in some views 2025-07-08 15:14:31 +02:00
Timothée Jaussoin
aa2051d281 Fix FLEXIAPI-348 Add a fallback 404 page for URLs that are pointing to no configured Spaces 2025-07-03 15:42:15 +02:00
Timothée Jaussoin
80f32a9887 Feature/346 ini info 2025-07-03 10:05:17 +00:00
Timothée Jaussoin
336c037590 Fix FLEXIAPI-342 Enforce password change when the External Account domain is changed 2025-07-02 17:19:57 +02:00
Timothée Jaussoin
d2cac6d60f Fix FLEXIAPI-341 Allow realm to be empty when creating a Space 2025-07-02 10:26:47 +02:00
Timothée Jaussoin
c8e587db01 Fix FLEXIAPI-341 Allow realm to be empty when creating a Space 2025-07-01 12:04:49 +02:00
Timothée Jaussoin
f712755b79 Fix FLEXIAPI-340 Fix the space resolution when getting the realm on Accounts 2025-06-30 15:49:56 +02:00
Jonathan Bartet
2e11db0c83 Fix FLEXIAPI-326 Rework email templates and translations 2025-06-30 12:00:31 +02:00
Timothée Jaussoin
bd4dbfeb7a Fix FLEXIAPI-337 Generate the provisioning URLs based on the user space 2025-06-24 13:04:59 +00:00
Timothée Jaussoin
37baeb2fc2 Fix FLEXIAPI-333 Remove HTML buttons because they cannot be rendered in "old" Outlook versions 2025-06-23 14:16:48 +00:00
Timothée Jaussoin
3a7f4486bb Fix FLEXIAPI-336 Fix broken ph icons 2025-06-19 11:08:00 +02:00
Timothée Jaussoin
f561a55221 Fix FLEXIAPI-335 Safari rendering issues with font icons 2025-06-19 10:23:49 +02:00
Timothée Jaussoin
73c0132051 Fix FLEXIAPI-324 Add an app setup wizard page 2025-06-17 16:06:29 +02:00
Timothée Jaussoin
4f3e9c57c9 Fix FLEXIAPI-330 Remove the ConfirmedRegistration email and related code 2025-06-17 10:58:35 +02:00
Timothée Jaussoin
f20056ec73 Fix FLEXIAPI-329 Use correct routes for accounts devices 2025-06-16 17:19:39 +02:00
Timothée Jaussoin
6b589d1d0f Fix FLEXIAPI-332 Check if the first line was untouched and that the number of... 2025-06-16 14:38:00 +00:00
Timothée Jaussoin
0348eecf4c Fix FLEXIAPI-325 Add endpoints to send the password reset and provisioning emails 2025-06-11 17:19:55 +02:00
Timothée Jaussoin
52eadf91ce Fix FLEXIAPI-328 Set realm on Space creation, limit the update if some accounts are present 2025-06-11 11:11:23 +02:00
Timothée Jaussoin
39992c0bd5 Fix FLEXIAPI-322 Api Keys documentation 2025-06-09 14:13:18 +02:00
Timothée Jaussoin
6b22d2ec86 Fix FLEXIAPI-321 Disable the account creation button when the Space is full for admins 2025-06-09 12:00:28 +02:00
Timothée Jaussoin
fae800343f Fix FLEXIAPI-319 Fix the admin device deletion link, recover the missing method 2025-06-09 09:59:59 +00:00
Timothée Jaussoin
92825b0211 Fix FLEXIAPI-318 Fix email recovery validation 2025-06-05 15:08:51 +02:00
Timothée Jaussoin
d4a757a892 Fix FLEXIAPI-313 Fix the admin device deletion link, recover the missing... 2025-06-05 11:16:09 +02:00
Timothée Jaussoin
f1c0247960 Fix/311 316 317 320 2025-06-04 14:35:15 +00:00
208 changed files with 2963 additions and 5384 deletions

View file

@ -14,14 +14,6 @@ rocky9-deploy:
- rocky9-package
- rocky9-test
rocky10-deploy:
extends: .deploy
script:
- ./deploy_packages.sh rockylinux 10
needs:
- rocky10-package
- rocky10-test
debian12-deploy:
extends: .deploy
script:
@ -30,24 +22,26 @@ debian12-deploy:
- debian12-package
- debian12-test
debian13-deploy:
extends: .deploy
script:
- ./deploy_packages.sh debian trixie
needs:
- debian13-package
- debian13-test
remi-rocky8-deploy:
extends: .deploy
#rules:
#- changes:
# - .gitlab-ci.yml
rules:
- changes:
- .gitlab-ci.yml
script:
- ./deploy_packages.sh rockylinux 8
needs:
- remi-rocky8-package
remi-rocky9-deploy:
extends: .deploy
rules:
- changes:
- .gitlab-ci.yml
script:
- ./deploy_packages.sh rockylinux 9
needs:
- remi-rocky9-package
.deploy:
stage: deploy
tags: ["docker"]

View file

@ -4,6 +4,12 @@ rocky8-package:
extends: .package
image: gitlab.linphone.org:4567/bc/public/docker/rocky8-php:$ROCKY_8_IMAGE_VERSION
script:
# We install this dependency only for the pipeline
- dnf -y install https://rpms.remirepo.net/enterprise/remi-release-8.rpm
- dnf -y module reset php
- dnf -y module enable php:remi-8.2
- dnf -y update php\*
- dnf -y install php-sodium
- make package-el8
rocky9-package:
@ -12,28 +18,20 @@ rocky9-package:
extends: .package
image: gitlab.linphone.org:4567/bc/public/docker/rocky9-php:$ROCKY_9_IMAGE_VERSION
script:
# We install this dependency only for the pipeline
- dnf -y install https://rpms.remirepo.net/enterprise/remi-release-9.rpm
- dnf -y module reset php
- dnf -y module enable php:remi-8.2
- dnf -y update php\*
- dnf -y install php-sodium
- make package-el9
rocky10-package:
needs:
- prepare-package
extends: .package
image: gitlab.linphone.org:4567/bc/public/docker/rocky10-php:$ROCKY_10_IMAGE_VERSION
script:
- make package-el10
debian12-package:
needs:
- prepare-package
extends: .debian_package
image: gitlab.linphone.org:4567/bc/public/docker/debian12-php:$DEBIAN_12_IMAGE_VERSION
debian13-package:
needs:
- prepare-package
extends: .debian_package
image: gitlab.linphone.org:4567/bc/public/docker/debian13-php:$DEBIAN_13_IMAGE_VERSION
.debian_package:
extends: .package
script:
@ -48,12 +46,21 @@ remi-rocky8-package:
- dnf -y module reset redis
- dnf -y install @redis:6
remi-rocky9-package:
image: gitlab.linphone.org:4567/bc/public/docker/rocky9-php:$ROCKY_9_IMAGE_VERSION
extends: .remi-rocky-package
variables:
ROCKY_RELEASE: 9
before_script:
- dnf -y module reset redis
- dnf -y install @redis:7
.remi-rocky-package:
extends: .package
rules:
- if: $CI_COMMIT_REF_NAME =~ /^release/ || $CI_COMMIT_REF_NAME == "master"
#- changes:
# - .gitlab-ci.yml
- changes:
- .gitlab-ci.yml
script:
# Remi
- mkdir -p $CI_PROJECT_DIR/build

View file

@ -10,18 +10,12 @@ rocky9-test:
needs:
- rocky9-package
rocky10-test:
extends: .rocky-test
image: gitlab.linphone.org:4567/bc/public/docker/rocky10-php:$ROCKY_10_IMAGE_VERSION
needs:
- rocky10-package
.rocky-test:
extends: .test
script:
- yum -y localinstall build/*.rpm
- cd /opt/belledonne-communications/share/flexisip-account-manager/flexiapi
- composer install --ignore-platform-req=ext-sodium # Rocky 8 and 9 use the external library
- composer install --ignore-platform-req=ext-sodium # Rocky 8 use the external library
- vendor/bin/phpcs
- vendor/bin/phpmd . ansi phpmd.xml
- php artisan key:generate
@ -33,16 +27,10 @@ debian12-test:
needs:
- debian12-package
debian13-test:
extends: .debian-test
image: gitlab.linphone.org:4567/bc/public/docker/debian13-php:$DEBIAN_13_IMAGE_VERSION
needs:
- debian13-package
.debian-test:
extends: .test
script:
#- apt update
- apt update
- apt install -y ./build/*.deb
- cd /opt/belledonne-communications/share/flexisip-account-manager/flexiapi
- composer install

View file

@ -1,9 +1,7 @@
variables:
ROCKY_8_IMAGE_VERSION: 20250702_171834_update_rocky8_dockerhub
ROCKY_9_IMAGE_VERSION: 20250702_171314_update_rocky9_dockerhub
ROCKY_10_IMAGE_VERSION: 20250908_164454_rocky10_first
DEBIAN_12_IMAGE_VERSION: 20250908_154742_refresh_dependencies
DEBIAN_13_IMAGE_VERSION: 20251204_115628_update_packages
DEBIAN_12_IMAGE_VERSION: 20241204_162237_update_download_linphone_org
PHP_REDIS_REMI_VERSION: php-pecl-redis6-6.1.0-1
PHP_IGBINARY_REMI_VERSION: php-pecl-igbinary-3.2.16-2
PHP_MSGPACK_REMI_VERSION: php-pecl-msgpack-2.2.0-3

View file

@ -4,21 +4,6 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/).
## [2.1]
### Added
- **Add CardDav servers** They can be configured in the administration panels and the API.
- **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}`
- **Add Voicemail features and related API endpoints** to integrate with `flexisip-voicemail`
### Changed
- **Contacts Lists** The Contacts Lists are now handled per Space. During the migration, if there is only one Space present, existing Contacts Lists are automatically attached to it, otherwise the first Super Space available is used. If they are then attached to the wrong Space you'll have to change directly their `space_id` value in the `contacts_lists` database table.
- **PHP 8.2 minimum** Laravel and its dependencies were upgraded to version 11 as well.
- **Logout the user when the password is correctly changed**
## [2.0]
### Added

View file

@ -24,14 +24,6 @@ FlexiAPI is packaged for Debian and RedHat, you can setup those repositories usi
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/`.
⚠️ If you want to enable JWT authentication the php-sodium dependency is required, on Rockylinux it is only available in the Remi repository in some cases. You can install it with the following steps:
dnf -y install https://rpms.remirepo.net/enterprise/remi-release-{rockylinux-release}.rpm
dnf -y module reset php
dnf -y module enable php:remi-{php-version}
dnf -y update php\*
dnf -y install php-sodium
# 2. Web server configuration
The package will deploy a `flexisip-account-manager.conf` file in the apache2 configuration directory.
@ -39,8 +31,6 @@ This file can be loaded and configured in your specific VirtualHost configuratio
To know more about the web server configuration part, you can directly [visit the official Laravel installation documentation](https://laravel.com/docs/).
⚠️ The Account Manager is handling files upload, please ensure that you raised `upload_max_filesize` and `post_max_size` to a reasonable number in your `php.ini` file to prevent file upload errors.
# 3. .env file configuration
Complete all the variables in the `.env` file (from the `.env.example` one if you setup the instance manually) or by overwriting them in your Docker or web-server configuration.

View file

@ -50,7 +50,7 @@ package-common:
cp -R cron/ $(OUTPUT_DIR)/flexisip-account-manager/
cp flexisip-account-manager.spec.run $(OUTPUT_DIR)/rpmbuild/SPECS/flexisip-account-manager.spec
tar cf flexisip-account-manager.tar.gz -C $(OUTPUT_DIR) flexisip-account-manager
tar cvf flexisip-account-manager.tar.gz -C $(OUTPUT_DIR) flexisip-account-manager
mv flexisip-account-manager.tar.gz $(OUTPUT_DIR)/rpmbuild/SOURCES/flexisip-account-manager.tar.gz
package-end-common:
@ -59,20 +59,15 @@ package-end-common:
rpm-el8-only:
mkdir -p build
sed -i 's/Requires:.*/Requires: php >= 8.2, php-gd, php-pdo, php-redis, php-mysqlnd, php-mbstring/g' $(OUTPUT_DIR)/rpmbuild/SPECS/flexisip-account-manager.spec
rpmbuild --quiet -bb --define 'dist .el8' --define '_topdir $(OUTPUT_DIR)/rpmbuild' --define "_rpmdir $(OUTPUT_DIR)/rpmbuild" $(OUTPUT_DIR)/rpmbuild/SPECS/flexisip-account-manager.spec
sed -i 's/Requires:.*/Requires: php >= 8.1, php-gd, php-pdo, php-redis, php-mysqlnd, php-mbstring/g' $(OUTPUT_DIR)/rpmbuild/SPECS/flexisip-account-manager.spec
rpmbuild -v -bb --define 'dist .el8' --define '_topdir $(OUTPUT_DIR)/rpmbuild' --define "_rpmdir $(OUTPUT_DIR)/rpmbuild" $(OUTPUT_DIR)/rpmbuild/SPECS/flexisip-account-manager.spec
@echo "📦✅ RPM el8 Package Created"
rpm-el9-only:
mkdir -p build
rpmbuild --quiet -bb --define 'dist .el9' --define '_topdir $(OUTPUT_DIR)/rpmbuild' --define "_rpmdir $(OUTPUT_DIR)/rpmbuild" $(OUTPUT_DIR)/rpmbuild/SPECS/flexisip-account-manager.spec
rpmbuild -v -bb --define 'dist .el9' --define '_topdir $(OUTPUT_DIR)/rpmbuild' --define "_rpmdir $(OUTPUT_DIR)/rpmbuild" $(OUTPUT_DIR)/rpmbuild/SPECS/flexisip-account-manager.spec
@echo "📦✅ RPM el9 Package Created"
rpm-el10-only:
mkdir -p build
rpmbuild --quiet -bb --define 'dist .el10' --define '_topdir $(OUTPUT_DIR)/rpmbuild' --define "_rpmdir $(OUTPUT_DIR)/rpmbuild" $(OUTPUT_DIR)/rpmbuild/SPECS/flexisip-account-manager.spec
@echo "📦✅ RPM el10 Package Created"
rpm-cleanup:
@echo "🧹 Cleanup"
mv rpmbuild/*/*.rpm build/.
@ -81,11 +76,11 @@ rpm-cleanup:
deb-only:
mkdir -p build
sed -i 's/posttrans/post/g' $(OUTPUT_DIR)/rpmbuild/SPECS/flexisip-account-manager.spec
rpmbuild --quiet -bb --with deb --define '_topdir $(OUTPUT_DIR)/rpmbuild' --define "_rpmfilename tmp.rpm" --define "_rpmdir $(OUTPUT_DIR)/rpmbuild" $(OUTPUT_DIR)/rpmbuild/SPECS/flexisip-account-manager.spec
rpmbuild -v -bb --with deb --define '_topdir $(OUTPUT_DIR)/rpmbuild' --define "_rpmfilename tmp.rpm" --define "_rpmdir $(OUTPUT_DIR)/rpmbuild" $(OUTPUT_DIR)/rpmbuild/SPECS/flexisip-account-manager.spec
fakeroot alien -g -k --scripts $(OUTPUT_DIR)/rpmbuild/tmp.rpm
rm -r $(OUTPUT_DIR)/rpmbuild
rm -rf $(OUTPUT_DIR)/*.orig
sed -i 's/Depends:.*/Depends: $${shlibs:Depends}, php (>= 8.2), php-xml, php-pdo, php-gd, php-redis, php-mysql, php-mbstring, php-sqlite3/g' $(OUTPUT_DIR)/bc-flexisip-account-manager*/debian/control
sed -i 's/Depends:.*/Depends: $${shlibs:Depends}, php (>= 8.1), php-xml, php-pdo, php-gd, php-redis, php-mysql, php-mbstring, php-sqlite3/g' $(OUTPUT_DIR)/bc-flexisip-account-manager*/debian/control
cd `ls -rt $(OUTPUT_DIR) | tail -1` && dpkg-buildpackage --no-sign
@echo "📦✅ DEB Package Created"
@ -106,10 +101,6 @@ package-el9: rpm-el9-only rpm-cleanup cleanup-package-semvers package-end-common
rpm-el9: prepare-common package-el9
rpm-el9-dev: prepare-dev package-semvers package-common package-el9
package-el10: rpm-el10-only rpm-cleanup cleanup-package-semvers package-end-common
rpm-el10: prepare-common package-el10
rpm-el10-dev: prepare-dev package-semvers package-common package-el10
package-deb: deb-only cleanup-package-semvers package-end-common
deb: prepare-common package-deb
deb-dev: prepare-dev package-semvers package-common package-deb

View file

@ -1 +0,0 @@
* * * * * apache /opt/belledonne-communications/share/flexisip-account-manager/flexiapi/artisan schedule:run >> /dev/null 2>&1

View file

@ -1,10 +1,9 @@
#!/bin/sh
cd /opt/belledonne-communications/share/flexisip-account-manager/flexiapi/
sudo -su www-data && php artisan accounts:clear-accounts-tombstones 7 --apply
sudo -su www-data && php artisan accounts:clear-api-keys 60
sudo -su www-data && php artisan accounts:clear-files 30 --apply
sudo -su www-data && php artisan accounts:clear-unconfirmed 30 --apply
sudo -su www-data && php artisan app:clear-statistics 30 --apply
sudo -su www-data && php artisan digest:clear-nonces 60
sudo -su www-data && php artisan accounts:clear-api-keys 60
sudo -su www-data && php artisan accounts:clear-accounts-tombstones 7 --apply
sudo -su www-data && php artisan accounts:clear-unconfirmed 30 --apply
sudo -su www-data && php artisan spaces:expiration-emails
sudo -su www-data && php artisan app:clear-statistics 30 --apply

View file

@ -1,10 +1,9 @@
#!/bin/sh
cd /opt/belledonne-communications/share/flexisip-account-manager/flexiapi/
php artisan accounts:clear-accounts-tombstones 7 --apply
php artisan accounts:clear-api-keys 60
php artisan accounts:clear-files 30 --apply
php artisan accounts:clear-unconfirmed 30 --apply
php artisan app:clear-statistics 30 --apply
php artisan digest:clear-nonces 60
php artisan accounts:clear-api-keys 60
php artisan accounts:clear-accounts-tombstones 7 --apply
php artisan accounts:clear-unconfirmed 30 --apply
php artisan spaces:expiration-emails
php artisan app:clear-statistics 30 --apply

View file

@ -97,5 +97,3 @@ HCAPTCHA_SITEKEY=site-key
JWT_RSA_PUBLIC_KEY_PEM=
JWT_SIP_IDENTIFIER=
# Temporary toggles
APP_SHOW_LOGIN_COUNTER_TEMP= # default true

View file

@ -23,8 +23,8 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Auth;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Illuminate\Database\Eloquent\Collection;
use Carbon\Carbon;
use Awobaz\Compoships\Compoships;
@ -36,7 +36,7 @@ class Account extends Authenticatable
use HasFactory;
use Compoships;
protected $with = ['passwords', 'emailChangeCode', 'types', 'actions', 'dictionaryEntries', 'carddavServers'];
protected $with = ['passwords', 'emailChangeCode', 'types', 'actions', 'dictionaryEntries'];
protected $hidden = ['expire_time', 'pivot', 'currentProvisioningToken', 'currentRecoveryCode', 'dictionaryEntries'];
protected $appends = ['realm', 'provisioning_token', 'provisioning_token_expire_at', 'dictionary'];
protected $casts = [
@ -73,9 +73,6 @@ class Account extends Authenticatable
return;
}
/**
* config('app.sip_domain') is required for the Tests suit
*/
$builder->where('domain', config('app.sip_domain') ?? space()->domain);
});
}
@ -134,23 +131,6 @@ class Account extends Authenticatable
return $this->belongsToMany(Account::class, 'contacts', 'account_id', 'contact_id');
}
public function files()
{
return $this->hasMany(AccountFile::class)->latest();
}
public function voicemails()
{
return $this->hasMany(AccountFile::class)
->whereIn('content_type', AccountFile::VOICEMAIL_CONTENTTYPES)
->latest();
}
public function uploadedVoicemails()
{
return $this->voicemails()->whereNotNull('name');
}
public function vcardsStorage()
{
return $this->hasMany(VcardStorage::class);
@ -166,12 +146,6 @@ class Account extends Authenticatable
return $this->hasMany(AccountDictionaryEntry::class);
}
public function carddavServers()
{
return $this->belongsToMany(SpaceCardDavServer::class, 'account_carddav_credentials', 'account_id', 'space_carddav_server_id')
->withPivot('username', 'realm', 'algorithm', 'password');
}
public function getDictionaryAttribute()
{
if ($this->dictionaryEntries->isEmpty()) return new stdClass;
@ -344,15 +318,6 @@ class Account extends Authenticatable
return null;
}
public function getRemainingCardDavCredentialsCreatableAttribute(): Collection
{
return $this->space->carddavServers()->whereNotIn('id', function ($query) {
$query->select('space_carddav_server_id')
->from('account_carddav_credentials')
->where('account_id', $this->id);
})->get();
}
public function getIdentifierAttribute(): string
{
return $this->attributes['username'] . '@' . $this->attributes['domain'];

View file

@ -1,20 +0,0 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class AccountCardDavCredentials extends Model
{
protected $table = 'account_carddav_credentials';
public function cardDavServer()
{
return $this->hasOne(SpaceCardDavServer::class, 'id', 'space_carddav_server_id');
}
public function getIdentifierAttribute()
{
return $this->username . '@' . $this->domain;
}
}

View file

@ -1,74 +0,0 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Support\Facades\Storage;
class AccountFile extends Model
{
use HasUuids;
public const VOICEMAIL_CONTENTTYPES = ['audio/opus', 'audio/wav'];
public const FILES_PATH = 'files';
protected $hidden = ['account_id', 'updated_at', 'sending_by_mail_at', 'sent_by_mail_at', 'sending_by_mail_tryouts'];
protected $appends = ['download_url'];
protected $casts = [
'uploaded_at' => 'datetime',
];
protected static function booted()
{
static::deleting(function (AccountFile $accountFile) {
Storage::delete($accountFile->getPathAttribute());
});
}
public function account()
{
return $this->belongsTo(Account::class)->withoutGlobalScopes();
}
public function getMaxUploadSizeAttribute(): ?int
{
return maxUploadSize();
}
public function getUploadUrlAttribute(): ?string
{
return route('file.upload', $this->attributes['id']);
}
public function getPathAttribute(): string
{
return self::FILES_PATH . '/' . $this->attributes['name'];
}
public function getUrlAttribute(): ?string
{
return !empty($this->attributes['name'])
&& !empty($this->attributes['id'])
? replaceHost(
route('file.show', ['uuid' => $this->attributes['id'], 'name' => $this->attributes['name']]),
$this->account->space->host
)
: null;
}
public function getDownloadUrlAttribute(): ?string
{
return !empty($this->attributes['name'])
&& !empty($this->attributes['id'])
? replaceHost(route(
'file.download',
['uuid' => $this->attributes['id'], 'name' => $this->attributes['name']]
), $this->account->space->host)
: null;
}
public function isVoicemailAudio(): bool
{
return in_array($this->attributes['content_type'], self::VOICEMAIL_CONTENTTYPES);
}
}

View file

@ -29,6 +29,11 @@ class ClearAccountsTombstones extends Command
protected $signature = 'accounts:clear-accounts-tombstones {days} {--apply}';
protected $description = 'Clear deleted accounts tombstones after n days';
public function __construct()
{
parent::__construct();
}
public function handle()
{
$tombstones = AccountTombstone::where(

View file

@ -1,35 +0,0 @@
<?php
namespace App\Console\Commands\Accounts;
use App\AccountFile;
use Carbon\Carbon;
use Illuminate\Console\Command;
class ClearFiles extends Command
{
protected $signature = 'accounts:clear-files {days} {--apply}';
protected $description = 'Remove the uploaded files after n days';
public function handle(): int
{
$files = AccountFile::where(
'created_at',
'<',
Carbon::now()->subDays($this->argument('days'))->toDateTimeString()
);
$count = $files->count();
if ($this->option('apply')) {
$this->info($count . ' files in deletion…');
$files->delete();
$this->info($count . ' files deleted');
return Command::SUCCESS;
}
$this->info($count . ' files to delete');
return Command::SUCCESS;
}
}

View file

@ -29,6 +29,11 @@ class ClearUnconfirmed extends Command
protected $signature = 'accounts:clear-unconfirmed {days} {--apply} {--and-confirmed}';
protected $description = 'Clear unconfirmed accounts after n days';
public function __construct()
{
parent::__construct();
}
public function handle()
{
$accounts = Account::where(

View file

@ -30,15 +30,18 @@ class CreateAdminTest extends Command
protected $signature = 'accounts:create-admin-test';
protected $description = 'Create a test admin account, only for tests purpose';
public function __construct()
{
parent::__construct();
}
public function handle()
{
$username = 'admin_test';
$domain = 'sip.example.org';
$this->call('spaces:create-update', [
'sip_domain' => $domain,
'host' => $domain,
'name' => $domain,
'domain' => $domain,
'--super' => 'true'
]);

View file

@ -28,6 +28,11 @@ class Seed extends Command
protected $signature = 'accounts:seed {json-file-path}';
protected $description = 'Seed some accounts from a JSON file';
public function __construct()
{
parent::__construct();
}
public function handle()
{
$file = $this->argument('json-file-path');

View file

@ -1,40 +0,0 @@
<?php
namespace App\Console\Commands\Accounts;
use App\AccountFile;
use App\Mail\Voicemail;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Mail;
class SendVoicemailsEmails extends Command
{
protected $signature = 'accounts:send-voicemails-emails {--tryout}';
protected $description = 'Send the voicemail emails';
public function handle()
{
$voicemails = AccountFile::whereNotNull('uploaded_at')
->whereNull('sent_by_mail_at')
->where('sending_by_mail_tryouts', '<', is_int($this->option('tryout'))
? $this->option('tryout')
: 3)
->get();
foreach ($voicemails as $voicemail) {
$voicemail->sending_by_mail_at = Carbon::now();
$voicemail->save();
if (Mail::to(users: $voicemail->account)->send(new Voicemail($voicemail))) {
$voicemail->sent_by_mail_at = Carbon::now();
$this->info('Voicemail sent to ' . $voicemail->account->identifier);
} else {
$voicemail->sending_by_mail_tryouts++;
$this->info('Error sending voicemail to ' . $voicemail->account->identifier);
}
$voicemail->save();
}
}
}

View file

@ -28,6 +28,11 @@ class SetAdmin extends Command
protected $signature = 'accounts:set-admin {id}';
protected $description = 'Give the admin role to an account';
public function __construct()
{
parent::__construct();
}
public function handle()
{
$account = Account::withoutGlobalScopes()->where('id', $this->argument('id'))->first();

View file

@ -1,33 +0,0 @@
<?php
namespace App\Console\Commands;
use App\PhoneCountry;
use Illuminate\Console\Command;
use libphonenumber\PhoneNumberUtil;
class UpdatePhoneCountries extends Command
{
protected $signature = 'app:update-phone-countries';
protected $description = 'Update the phone_countries table from the getCountryCodes() function';
public function handle()
{
$phoneNumberUtils = PhoneNumberUtil::getInstance();
$countryCodes = getCountryCodes();
foreach (array_diff(
array_keys($countryCodes),
PhoneCountry::pluck('code')->toArray()
) as $code) {
if ($resolvedMetadata = $phoneNumberUtils->getMetadataForRegion($code)) {
$phoneCountry = new PhoneCountry();
$phoneCountry->code = $code;
$phoneCountry->country_code = $resolvedMetadata->getCountryCode();
$phoneCountry->save();
$this->info($code . ' - ' . $countryCodes[$code] . ' inserted');
}
}
}
}

View file

@ -15,9 +15,4 @@ class ContactsList extends Model
{
return $this->belongsToMany(Account::class, 'contacts_list_contact', 'contacts_list_id', 'contact_id');
}
public function space()
{
return $this->belongsTo(Space::class);
}
}

View file

@ -17,7 +17,6 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
use Illuminate\Support\Facades\App;
use Illuminate\Support\Str;
use App\Account;
@ -29,15 +28,24 @@ use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkExtension;
use League\CommonMark\Extension\TableOfContents\TableOfContentsExtension;
use Illuminate\Support\Facades\DB;
function space(): ?Space
$hostSpace = null;
function space($reload = false): ?Space
{
return is_object(request()->space) ? request()->space : null;
global $hostSpace;
if ($hostSpace != null && $reload == false) {
return $hostSpace;
}
$hostSpace = Space::where('host', request()->host())->first();
return $hostSpace;
}
function passwordAlgorithms(): array
{
return [
'MD5' => 'md5',
'MD5' => 'md5',
'SHA-256' => 'sha256',
];
}
@ -101,7 +109,7 @@ function markdownDocumentationView(string $view): string
$converter->getEnvironment()->addExtension(new TableOfContentsExtension());
return (string) $converter->convert(
(string) view($view, [
(string)view($view, [
'app_name' => space()->name
])->render()
);
@ -159,15 +167,7 @@ function resolveDomain(Request $request): string
&& $request->user()
&& $request->user()->superAdmin
? $request->get('domain')
: $request->space->domain;
}
function maxUploadSize(): int
{
return min(
ini_parse_quantity(ini_get('upload_max_filesize')),
ini_parse_quantity(ini_get('post_max_size'))
);
: config('app.sip_domain');
}
function captchaConfigured(): bool
@ -214,7 +214,7 @@ function validateIsoDate($attribute, $value, $parameters, $validator): bool
// Regex from https://www.myintervals.com/blog/2009/05/20/iso-8601-date-validation-that-doesnt-suck/
: '/^([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/';
return (bool) preg_match($regex, $value);
return (bool)preg_match($regex, $value);
}
/**
@ -226,253 +226,236 @@ function validateIsoDate($attribute, $value, $parameters, $validator): bool
function getCountryCodes()
{
return [
'AD' => 'Andorra',
'AE' => 'United Arab Emirates',
'AF' => 'Afghanistan',
'AG' => 'Antigua & Barbuda',
'AI' => 'Anguilla',
'AL' => 'Albania',
'AM' => 'Armenia',
'AO' => 'Angola',
'AQ' => 'Antarctica',
'AR' => 'Argentina',
'AS' => 'American Samoa',
'AT' => 'Austria',
'AU' => 'Australia',
'AW' => 'Aruba',
'AX' => 'Åland Islands',
'AL' => 'Albania',
'DZ' => 'Algeria',
'AS' => 'American Samoa',
'AD' => 'Andorra',
'AO' => 'Angola',
'AI' => 'Anguilla',
'AG' => 'Antigua & Barbuda',
'AR' => 'Argentina',
'AU' => 'Australia',
'AT' => 'Austria',
'AZ' => 'Azerbaijan',
'BA' => 'Bosnia & Herzegovina',
'BB' => 'Barbados',
'BD' => 'Bangladesh',
'BE' => 'Belgium',
'BF' => 'Burkina Faso',
'BG' => 'Bulgaria',
'BH' => 'Bahrain',
'BI' => 'Burundi',
'BJ' => 'Benin',
'BL' => 'St. Barthélemy',
'BM' => 'Bermuda',
'BN' => 'Brunei',
'BO' => 'Bolivia',
'BQ' => 'Bonaire, Sint Eustatius & Saba',
'BR' => 'Brazil',
'BS' => 'Bahamas',
'BT' => 'Bhutan',
'BV' => 'Bouvet Island',
'BW' => 'Botswana',
'BH' => 'Bahrain',
'BD' => 'Bangladesh',
'BB' => 'Barbados',
'BY' => 'Belarus',
'BE' => 'Belgium',
'BZ' => 'Belize',
'CA' => 'Canada',
'CC' => 'Cocos (Keeling) Islands',
'CD' => 'Congo - Kinshasa',
'CF' => 'Central African Republic',
'CG' => 'Congo - Brazzaville',
'CH' => 'Switzerland',
'CI' => "Côte d'Ivoire",
'CK' => 'Cook Islands',
'CL' => 'Chile',
'BJ' => 'Benin',
'BM' => 'Bermuda',
'BT' => 'Bhutan',
'BO' => 'Bolivia',
'BA' => 'Bosnia & Herzegovina',
'BW' => 'Botswana',
'BR' => 'Brazil',
'IO' => 'British Indian Ocean Territory',
'BN' => 'Brunei',
'BG' => 'Bulgaria',
'BF' => 'Burkina Faso',
'BI' => 'Burundi',
'KH' => 'Cambodia',
'CM' => 'Cameroon',
'CA' => 'Canada',
'CV' => 'Cape Verde',
'KY' => 'Cayman Islands',
'CF' => 'Central African Republic',
'TD' => 'Chad',
'CL' => 'Chile',
'CN' => 'China',
'CO' => 'Colombia',
'CR' => 'Costa Rica',
'CU' => 'Cuba',
'CV' => 'Cabo Verde',
'CW' => 'Curaçao',
'CX' => 'Christmas Island',
'CC' => 'Cocos (Keeling) Islands',
'CO' => 'Colombia',
'KM' => 'Comoros',
'CG' => 'Congo - Brazzaville',
'CD' => 'Congo - Kinshasa',
'CK' => 'Cook Islands',
'CR' => 'Costa Rica',
'CI' => 'Côte dIvoire',
'HR' => 'Croatia',
'CU' => 'Cuba',
'CY' => 'Cyprus',
'CZ' => 'Czechia',
'DE' => 'Germany',
'DJ' => 'Djibouti',
'DK' => 'Denmark',
'DJ' => 'Djibouti',
'DM' => 'Dominica',
'DO' => 'Dominican Republic',
'DZ' => 'Algeria',
'EC' => 'Ecuador',
'EE' => 'Estonia',
'EG' => 'Egypt',
'EH' => 'Western Sahara',
'SV' => 'El Salvador',
'GQ' => 'Equatorial Guinea',
'ER' => 'Eritrea',
'ES' => 'Spain',
'EE' => 'Estonia',
'ET' => 'Ethiopia',
'FI' => 'Finland',
'FJ' => 'Fiji',
'FK' => 'Falkland Islands',
'FM' => 'Micronesia',
'FO' => 'Faroe Islands',
'FJ' => 'Fiji',
'FI' => 'Finland',
'FR' => 'France',
'GA' => 'Gabon',
'GB' => 'United Kingdom',
'GD' => 'Grenada',
'GE' => 'Georgia',
'GF' => 'French Guiana',
'GG' => 'Guernsey',
'PF' => 'French Polynesia',
'GA' => 'Gabon',
'GM' => 'Gambia',
'GE' => 'Georgia',
'DE' => 'Germany',
'GH' => 'Ghana',
'GI' => 'Gibraltar',
'GL' => 'Greenland',
'GM' => 'Gambia',
'GN' => 'Guinea',
'GP' => 'Guadeloupe',
'GQ' => 'Equatorial Guinea',
'GR' => 'Greece',
'GS' => 'South Georgia & South Sandwich Islands',
'GT' => 'Guatemala',
'GL' => 'Greenland',
'GD' => 'Grenada',
'GP' => 'Guadeloupe',
'GU' => 'Guam',
'GT' => 'Guatemala',
'GG' => 'Guernsey',
'GN' => 'Guinea',
'GW' => 'Guinea-Bissau',
'GY' => 'Guyana',
'HK' => 'Hong Kong',
'HM' => 'Heard & McDonald Islands',
'HN' => 'Honduras',
'HR' => 'Croatia',
'HT' => 'Haiti',
'HN' => 'Honduras',
'HK' => 'Hong Kong SAR China',
'HU' => 'Hungary',
'ID' => 'Indonesia',
'IE' => 'Ireland',
'IL' => 'Israel',
'IM' => 'Isle of Man',
'IN' => 'India',
'IO' => 'British Indian Ocean Territory',
'IQ' => 'Iraq',
'IR' => 'Iran',
'IS' => 'Iceland',
'IN' => 'India',
'ID' => 'Indonesia',
'IR' => 'Iran',
'IQ' => 'Iraq',
'IE' => 'Ireland',
'IM' => 'Isle of Man',
'IL' => 'Israel',
'IT' => 'Italy',
'JE' => 'Jersey',
'JM' => 'Jamaica',
'JO' => 'Jordan',
'JP' => 'Japan',
'JE' => 'Jersey',
'JO' => 'Jordan',
'KZ' => 'Kazakhstan',
'KE' => 'Kenya',
'KG' => 'Kyrgyzstan',
'KH' => 'Cambodia',
'KI' => 'Kiribati',
'KM' => 'Comoros',
'KN' => 'St. Kitts & Nevis',
'KP' => 'North Korea',
'KR' => 'South Korea',
'KW' => 'Kuwait',
'KY' => 'Cayman Islands',
'KZ' => 'Kazakhstan',
'KG' => 'Kyrgyzstan',
'LA' => 'Laos',
'LV' => 'Latvia',
'LB' => 'Lebanon',
'LC' => 'St. Lucia',
'LI' => 'Liechtenstein',
'LK' => 'Sri Lanka',
'LR' => 'Liberia',
'LS' => 'Lesotho',
'LR' => 'Liberia',
'LY' => 'Libya',
'LI' => 'Liechtenstein',
'LT' => 'Lithuania',
'LU' => 'Luxembourg',
'LV' => 'Latvia',
'LY' => 'Libya',
'MA' => 'Morocco',
'MC' => 'Monaco',
'MD' => 'Moldova',
'ME' => 'Montenegro',
'MF' => 'St. Martin',
'MG' => 'Madagascar',
'MH' => 'Marshall Islands',
'MO' => 'Macao SAR China',
'MK' => 'North Macedonia',
'MG' => 'Madagascar',
'MW' => 'Malawi',
'MY' => 'Malaysia',
'MV' => 'Maldives',
'ML' => 'Mali',
'MM' => 'Myanmar',
'MN' => 'Mongolia',
'MO' => 'Macao',
'MP' => 'Northern Mariana Islands',
'MT' => 'Malta',
'MH' => 'Marshall Islands',
'MQ' => 'Martinique',
'MR' => 'Mauritania',
'MS' => 'Montserrat',
'MT' => 'Malta',
'MU' => 'Mauritius',
'MV' => 'Maldives',
'MW' => 'Malawi',
'YT' => 'Mayotte',
'MX' => 'Mexico',
'MY' => 'Malaysia',
'FM' => 'Micronesia',
'MD' => 'Moldova',
'MC' => 'Monaco',
'MN' => 'Mongolia',
'ME' => 'Montenegro',
'MS' => 'Montserrat',
'MA' => 'Morocco',
'MZ' => 'Mozambique',
'MM' => 'Myanmar (Burma)',
'NA' => 'Namibia',
'NC' => 'New Caledonia',
'NE' => 'Niger',
'NF' => 'Norfolk Island',
'NG' => 'Nigeria',
'NI' => 'Nicaragua',
'NL' => 'Netherlands',
'NO' => 'Norway',
'NP' => 'Nepal',
'NR' => 'Nauru',
'NU' => 'Niue',
'NP' => 'Nepal',
'NL' => 'Netherlands',
'NC' => 'New Caledonia',
'NZ' => 'New Zealand',
'NI' => 'Nicaragua',
'NE' => 'Niger',
'NG' => 'Nigeria',
'NU' => 'Niue',
'NF' => 'Norfolk Island',
'MP' => 'Northern Mariana Islands',
'NO' => 'Norway',
'OM' => 'Oman',
'PA' => 'Panama',
'PE' => 'Peru',
'PF' => 'French Polynesia',
'PG' => 'Papua New Guinea',
'PH' => 'Philippines',
'PK' => 'Pakistan',
'PL' => 'Poland',
'PM' => 'St. Pierre & Miquelon',
'PN' => 'Pitcairn Islands',
'PR' => 'Puerto Rico',
'PS' => 'Palestine',
'PT' => 'Portugal',
'PW' => 'Palau',
'PS' => 'Palestinian Territories',
'PA' => 'Panama',
'PG' => 'Papua New Guinea',
'PY' => 'Paraguay',
'PE' => 'Peru',
'PH' => 'Philippines',
'PL' => 'Poland',
'PT' => 'Portugal',
'PR' => 'Puerto Rico',
'QA' => 'Qatar',
'RE' => 'Réunion',
'RO' => 'Romania',
'RS' => 'Serbia',
'RU' => 'Russia',
'RW' => 'Rwanda',
'SA' => 'Saudi Arabia',
'SB' => 'Solomon Islands',
'SC' => 'Seychelles',
'SD' => 'Sudan',
'SE' => 'Sweden',
'SG' => 'Singapore',
'SH' => 'St. Helena',
'SI' => 'Slovenia',
'SJ' => 'Svalbard & Jan Mayen',
'SK' => 'Slovakia',
'SL' => 'Sierra Leone',
'KN' => 'St. Kitts & Nevis',
'LC' => 'St. Lucia',
'PM' => 'St. Pierre & Miquelon',
'VC' => 'St. Vincent & Grenadines',
'WS' => 'Samoa',
'SM' => 'San Marino',
'SN' => 'Senegal',
'SO' => 'Somalia',
'SR' => 'Suriname',
'SS' => 'South Sudan',
'ST' => 'São Tomé & Príncipe',
'SV' => 'El Salvador',
'SX' => 'Sint Maarten',
'SY' => 'Syria',
'SA' => 'Saudi Arabia',
'SN' => 'Senegal',
'RS' => 'Serbia',
'SC' => 'Seychelles',
'SL' => 'Sierra Leone',
'SG' => 'Singapore',
'SK' => 'Slovakia',
'SI' => 'Slovenia',
'SB' => 'Solomon Islands',
'SO' => 'Somalia',
'ZA' => 'South Africa',
'ES' => 'Spain',
'LK' => 'Sri Lanka',
'SD' => 'Sudan',
'SR' => 'Suriname',
'SJ' => 'Svalbard & Jan Mayen',
'SZ' => 'Eswatini',
'TC' => 'Turks & Caicos Islands',
'TD' => 'Chad',
'TF' => 'French Southern Territories',
'TG' => 'Togo',
'TH' => 'Thailand',
'TJ' => 'Tajikistan',
'TK' => 'Tokelau',
'TL' => 'Timor-Leste',
'TM' => 'Turkmenistan',
'TN' => 'Tunisia',
'TO' => 'Tonga',
'TR' => 'Türkiye',
'TT' => 'Trinidad & Tobago',
'TV' => 'Tuvalu',
'SE' => 'Sweden',
'CH' => 'Switzerland',
'SY' => 'Syria',
'TW' => 'Taiwan',
'TJ' => 'Tajikistan',
'TZ' => 'Tanzania',
'UA' => 'Ukraine',
'TH' => 'Thailand',
'TL' => 'Timor-Leste',
'TG' => 'Togo',
'TK' => 'Tokelau',
'TO' => 'Tonga',
'TT' => 'Trinidad & Tobago',
'TN' => 'Tunisia',
'TM' => 'Turkmenistan',
'TC' => 'Turks & Caicos Islands',
'TV' => 'Tuvalu',
'UG' => 'Uganda',
'UM' => 'U.S. Minor Outlying Islands',
'UA' => 'Ukraine',
'AE' => 'United Arab Emirates',
'GB' => 'United Kingdom',
'US' => 'United States',
'UY' => 'Uruguay',
'UZ' => 'Uzbekistan',
'VA' => 'Holy See (Vatican City)',
'VC' => 'St. Vincent & Grenadines',
'VU' => 'Vanuatu',
'VE' => 'Venezuela',
'VN' => 'Vietnam',
'VG' => 'British Virgin Islands',
'VI' => 'U.S. Virgin Islands',
'VN' => 'Vietnam',
'VU' => 'Vanuatu',
'WF' => 'Wallis & Futuna',
'WS' => 'Samoa',
'EH' => 'Western Sahara',
'YE' => 'Yemen',
'YT' => 'Mayotte',
'ZA' => 'South Africa',
'ZM' => 'Zambia',
'ZW' => 'Zimbabwe',
];

View file

@ -43,9 +43,9 @@ class AuthenticateController extends Controller
return redirect()->route('account.dashboard');
}
return view('account.login', config('app.show_login_counter_temp') ? [
return view('account.login', [
'count' => Account::where('activated', true)->count()
]: []);
]);
}
public function authenticate(Request $request)

View file

@ -1,32 +0,0 @@
<?php
namespace App\Http\Controllers\Account;
use App\AccountFile;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Storage;
class FileController extends Controller
{
public function show(string $uuid, string $name)
{
$file = AccountFile::findOrFail($uuid);
if ($file->name != $name) {
abort(404);
}
return Storage::get($file->path);
}
public function download(string $uuid, string $name)
{
$file = AccountFile::findOrFail($uuid);
if ($file->name != $name) {
abort(404);
}
return Storage::download($file->path);
}
}

View file

@ -21,7 +21,6 @@ namespace App\Http\Controllers\Account;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
class PasswordController extends Controller
@ -47,10 +46,12 @@ class PasswordController extends Controller
if ($account->passwords()->count() > 0) {
Log::channel('events')->info('Web: Password changed', ['id' => $account->identifier]);
return redirect()->route('account.logout');
return redirect()->route('account.dashboard');
}
Log::channel('events')->info('Web: Password set for the first time', ['id' => $account->identifier]);
return redirect()->route('account.logout');
return redirect()->route('account.dashboard');
}
}

View file

@ -151,7 +151,7 @@ class ProvisioningController extends Controller
private function checkProvisioningHeader(Request $request)
{
if (!$request->hasHeader('x-linphone-provisioning')
&& $request->space->provisioning_use_linphone_provisioning_header) {
&& space()?->provisioning_use_linphone_provisioning_header) {
abort(400, 'x-linphone-provisioning header is missing');
}
}
@ -172,8 +172,8 @@ class ProvisioningController extends Controller
$dom->appendChild($config);
if ($request->space?->custom_provisioning_entries) {
$rc = parse_ini_string($request->space->custom_provisioning_entries, true);
if (space()?->custom_provisioning_entries) {
$rc = parse_ini_string(space()->custom_provisioning_entries, true);
foreach ($rc as $sectionName => $values) {
$section = $dom->createElement('section');
@ -189,44 +189,6 @@ class ProvisioningController extends Controller
}
}
$remoteContactDirectoryCounter = 0;
$authInfoIndex = 0;
// CardDav servers
if ($request->space?->carddavServers) {
foreach ($request->space->carddavServers as $carddavServer) {
$carddavServer->getProvisioningSection($config, $remoteContactDirectoryCounter);
$remoteContactDirectoryCounter++;
}
}
if ($account) {
foreach ($account->carddavServers as $carddavServer) {
$section = $dom->createElement('section');
$section->setAttribute('name', 'auth_info_' . $authInfoIndex);
$config->appendChild($section);
$entry = $dom->createElement('entry', $carddavServer->pivot->username);
$entry->setAttribute('name', 'username');
$section->appendChild($entry);
$entry = $dom->createElement('entry', $carddavServer->pivot->realm);
$entry->setAttribute('name', 'realm');
$section->appendChild($entry);
$entry = $dom->createElement('entry', $carddavServer->pivot->password);
$entry->setAttribute('name', 'ha1');
$section->appendChild($entry);
$entry = $dom->createElement('entry', $carddavServer->pivot->algorithm);
$entry->setAttribute('name', 'algorithm');
$section->appendChild($entry);
$authInfoIndex++;
}
}
// Password reset
if ($account && $request->has('reset_password')) {
$account->updatePassword(Str::random(10));
@ -289,6 +251,7 @@ class ProvisioningController extends Controller
}
$passwords = $account->passwords()->get();
$authInfoIndex = 0;
foreach ($passwords as $password) {
$section = $xpath->query("//section[@name='auth_info_" . $authInfoIndex . "']")->item(0);
@ -334,7 +297,7 @@ class ProvisioningController extends Controller
}
// Overwrite all the entries
if ($request->space?->custom_provisioning_overwrite_all) {
if (space()?->custom_provisioning_overwrite_all) {
$xpath = new \DOMXpath($dom);
$entries = $xpath->query("//section/entry");
if (!is_null($entries)) {

View file

@ -5,6 +5,7 @@ namespace App\Http\Controllers\Account;
use App\ResetPasswordEmailToken;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class ResetPasswordEmailController extends Controller
{

View file

@ -1,104 +0,0 @@
<?php
/*
Flexisip Account Manager is a set of tools to manage SIP accounts.
Copyright (C) 2023 Belledonne Communications SARL, All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers\Admin\Account;
use App\Account;
use App\AccountCardDavCredentials;
use App\Http\Controllers\Controller;
use Illuminate\Database\Query\Builder;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use App\Http\Requests\Account\CardDavCredentials;
class CardDavCredentialsController extends Controller
{
public function create(int $accountId)
{
$account = Account::findOrFail($accountId);
$this->checkFeatureEnabled($account);
return view('admin.account.carddav.create', [
'account' => $account,
'carddavServers' => $account->remainingCardDavCredentialsCreatable
]);
}
public function store(CardDavCredentials $request, int $accountId)
{
$account = Account::findOrFail($accountId);
$this->checkFeatureEnabled($account);
$request->validate([
'carddav_id' => ['required', Rule::exists('space_carddav_servers', 'id')->where(function (Builder $query) use ($account) {
return $query->where('space_id', $account->space->id);
})]
]);
$accountCarddavCredentials = new AccountCardDavCredentials;
$accountCarddavCredentials->space_carddav_server_id = $request->get('carddav_id');
$accountCarddavCredentials->account_id = $account->id;
$accountCarddavCredentials->username = $request->get('username');
$accountCarddavCredentials->realm = $request->get('realm');
$accountCarddavCredentials->password = bchash(
$request->get('username'),
$request->get('realm'),
$request->get('password'),
$request->get('algorithm')
);
$accountCarddavCredentials->algorithm = $request->get('algorithm');
$accountCarddavCredentials->save();
return redirect()->route('admin.account.show', $account);
}
public function delete(int $accountId, int $cardDavId)
{
$account = Account::findOrFail($accountId);
$this->checkFeatureEnabled($account);
$accountCarddavCredentials = AccountCardDavCredentials::where('space_carddav_server_id', $cardDavId)
->where('account_id', $account->id)
->firstOrFail();
return view('admin.account.carddav.delete', [
'account' => $account,
'carddavCredentials' => $accountCarddavCredentials,
]);
}
public function destroy(Request $request, int $accountId)
{
$account = Account::findOrFail($accountId);
$this->checkFeatureEnabled($account);
$accountCarddavCredentials = AccountCardDavCredentials::where('space_carddav_server_id', $request->carddav_id)
->where('account_id', $account->id)
->delete();
return redirect()->route('admin.account.show', $account);
}
private function checkFeatureEnabled(Account $account)
{
if (!$account->space->carddav_user_credentials) {
abort(403, 'CardDav Credentials features disabled');
}
}
}

View file

@ -1,32 +0,0 @@
<?php
namespace App\Http\Controllers\Admin\Account;
use App\Account;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class FileController extends Controller
{
public function delete(int $accountId, string $fileId)
{
$account = Account::findOrFail($accountId);
$file = $account->files()->where('id', $fileId)->firstOrFail();
return view('admin.account.file.delete', [
'account' => $account,
'file' => $file
]);
}
public function destroy(Request $request, int $accountId, string $fileId)
{
$account = Account::findOrFail($accountId);
$accountFile = $account->files()
->where('id', $fileId)
->firstOrFail();
$accountFile->delete();
return redirect()->route('admin.account.show', $account)->withFragment('#files');
}
}

View file

@ -17,7 +17,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers\Admin\Account;
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
@ -26,7 +26,7 @@ use Illuminate\Support\Facades\Log;
use App\Account;
use App\AccountType;
class AccountTypeController extends Controller
class AccountAccountTypeController extends Controller
{
public function create(int $id)
{

View file

@ -17,7 +17,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers\Admin\Account;
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
@ -27,7 +27,7 @@ use App\Account;
use App\AccountAction;
use App\Rules\NoUppercase;
class ActionController extends Controller
class AccountActionController extends Controller
{
public function create(int $accountId)
{

View file

@ -17,13 +17,13 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers\Admin\Account;
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Account;
class ActivityController extends Controller
class AccountActivityController extends Controller
{
public function index(int $accountId)
{

View file

@ -17,7 +17,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers\Admin\Account;
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
@ -26,7 +26,7 @@ use Illuminate\Support\Facades\Log;
use App\Account;
use App\ContactsList;
class ContactController extends Controller
class AccountContactController extends Controller
{
public function index(int $accountId)
{

View file

@ -17,14 +17,14 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers\Admin\Account;
namespace App\Http\Controllers\Admin;
use App\Account;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Libraries\FlexisipRedisConnector;
class DeviceController extends Controller
class AccountDeviceController extends Controller
{
public function index(int $accountId)
{

View file

@ -17,7 +17,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers\Admin\Account;
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
@ -25,7 +25,7 @@ use Illuminate\Http\Request;
use App\Account;
use App\AccountDictionaryEntry;
class DictionaryController extends Controller
class AccountDictionaryController extends Controller
{
public function create(int $accountId)
{

View file

@ -17,7 +17,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers\Admin\Account;
namespace App\Http\Controllers\Admin;
use App\Account;
use App\ExternalAccount;
@ -32,7 +32,7 @@ use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\Rules\File;
use Propaganistas\LaravelPhone\PhoneNumber;
class ImportController extends Controller
class AccountImportController extends Controller
{
private Collection $errors;
private string $importDirectory = 'imported_csv';
@ -307,7 +307,7 @@ class ImportController extends Controller
'account_id' => $passwordAccount->id,
'password' => bchash(
$passwordAccount->username,
$request->space?->account_realm ?? $domain,
space()?->account_realm ?? $domain,
$passwords[$passwordAccount->username],
$algorithm
),

View file

@ -17,7 +17,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers\Admin\Account;
namespace App\Http\Controllers\Admin;
use App\Account;
use App\Http\Controllers\Controller;
@ -26,7 +26,7 @@ use App\StatisticsCall;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class StatisticsController extends Controller
class AccountStatisticsController extends Controller
{
public function edit(Request $request, int $accountId)
{

View file

@ -17,7 +17,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers\Admin\Account;
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
@ -27,7 +27,7 @@ use Illuminate\Validation\Rule;
use App\AccountType;
use App\Rules\NoUppercase;
class TypeController extends Controller
class AccountTypeController extends Controller
{
public function index()
{

View file

@ -17,19 +17,18 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers\Admin\Space;
namespace App\Http\Controllers\Admin;
use App\Account;
use App\ContactsList;
use App\Space;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class ContactsListContactController extends Controller
{
public function add(Request $request, Space $space, int $contactsListId)
public function add(Request $request, int $contactsListId)
{
$accounts = $space->accounts()->orderBy('updated_at', $request->get('updated_at_order', 'desc'));
$accounts = Account::orderBy('updated_at', $request->get('updated_at_order', 'desc'));
if ($request->has('search')) {
$accounts = $accounts->where('username', 'like', '%' . $request->get('search') . '%');
@ -39,9 +38,9 @@ class ContactsListContactController extends Controller
$accounts = $accounts->where('domain', $request->get('domain'));
}
return view('admin.space.contacts_list.contacts.add', [
'space' => $space,
'contacts_list' => $space->contactsLists()->findOrFail($contactsListId),
return view('admin.contacts_list.contacts.add', [
'domains' => Account::groupBy('domain')->pluck('domain'),
'contacts_list' => ContactsList::findOrFail($contactsListId),
'params' => [
'contacts_list_id' => $contactsListId
],
@ -53,33 +52,33 @@ class ContactsListContactController extends Controller
]);
}
public function search(Request $request, Space $space, int $contactsListId)
public function search(Request $request, int $contactsListId)
{
return redirect()->route('admin.spaces.contacts_lists.contacts.add', ['contacts_list_id' => $contactsListId] + $request->except('_token'));
return redirect()->route('admin.contacts_lists.contacts.add', ['contacts_list_id' => $contactsListId] + $request->except('_token'));
}
public function store(Request $request, Space $space, int $contactsListId)
public function store(Request $request, int $contactsListId)
{
$request->validate([
'contacts_ids' => 'required|exists:accounts,id'
]);
$contactsList = $space->contactsLists()->findOrFail($contactsListId);
$contactsList = ContactsList::findOrFail($contactsListId);
$contactsList->contacts()->detach($request->get('contacts_ids')); // Just in case
$contactsList->contacts()->attach($request->get('contacts_ids'));
return redirect()->route('admin.spaces.contacts_lists.edit', [$space, $contactsList->id]);
return redirect()->route('admin.contacts_lists.edit', $contactsList->id);
}
public function destroy(Request $request, Space $space, int $contactsListId)
public function destroy(Request $request, int $contactsListId)
{
$request->validate([
'contacts_ids' => 'required|exists:accounts,id'
]);
$contactsList = $space->contactsLists()->findOrFail($contactsListId);
$contactsList = ContactsList::findOrFail($contactsListId);
$contactsList->contacts()->detach($request->get('contacts_ids'));
return redirect()->route('admin.spaces.contacts_lists.edit', [$space, $contactsList->id]);
return redirect()->route('admin.contacts_lists.edit', $contactsList->id);
}
}

View file

@ -17,67 +17,61 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers\Admin\Space;
namespace App\Http\Controllers\Admin;
use App\Account;
use App\ContactsList;
use App\Space;
use App\Http\Controllers\Controller;
use Illuminate\Validation\Rule;
use Illuminate\Http\Request;
class ContactsListController extends Controller
{
public function index(Request $request, Space $space)
public function index(Request $request)
{
$request->validate([
'order_by' => 'in:title,updated_at,contacts_count',
'order_sort' => 'in:asc,desc',
]);
$contactsLists = $space->contactsLists()->orderBy($request->get('order_by', 'updated_at'), $request->get('order_sort', 'desc'));
$contactsLists = ContactsList::orderBy($request->get('order_by', 'updated_at'), $request->get('order_sort', 'desc'));
return view('admin.space.contacts_list.index', [
'space' => $space,
return view('admin.contacts_list.index', [
'contacts_lists' => $contactsLists
->paginate(20)
->appends($request->query()),
]);
}
public function create(Request $request, Space $space)
public function create(Request $request)
{
return view('admin.space.contacts_list.create_edit', [
'space' => $space,
return view('admin.contacts_list.create_edit', [
'contacts_list' => new ContactsList,
]);
}
public function store(Request $request, Space $space)
public function store(Request $request)
{
$request->validate([
'title' => 'required|unique:contacts_lists'
]);
$contactsList = new ContactsList;
$contactsList->space_id = $space->id;
$contactsList->title = $request->get('title');
$contactsList->description = $request->get('description');
$contactsList->save();
return redirect()->route('admin.spaces.contacts_lists.edit', [$space, $contactsList->id]);
return redirect()->route('admin.contacts_lists.edit', $contactsList->id);
}
public function search(Request $request, Space $space, int $contactsListId)
public function search(Request $request, int $contactsListId)
{
return redirect()->route('admin.spaces.contacts_lists.edit', [
'space' => $space,
'contacts_list_id' => $contactsListId] + $request->except('_token'));
return redirect()->route('admin.contacts_lists.edit', ['contacts_list_id' => $contactsListId] + $request->except('_token'));
}
public function edit(Request $request, Space $space, int $id)
public function edit(Request $request, int $id)
{
$contacts = $space->contactsLists()->findOrFail($id)->contacts();
$contacts = ContactsList::findOrFail($id)->contacts();
if ($request->has('search')) {
$contacts = $contacts->where('username', 'like', '%' . $request->get('search') . '%');
@ -89,15 +83,14 @@ class ContactsListController extends Controller
$contacts = $contacts->get();
return view('admin.space.contacts_list.create_edit', [
'space' => $space,
return view('admin.contacts_list.create_edit', [
'domains' => Account::groupBy('domain')->pluck('domain'),
'contacts_list' => $space->contactsLists()->findOrFail($id),
'contacts_list' => ContactsList::findOrFail($id),
'contacts' => $contacts
]);
}
public function update(Request $request, Space $space, int $id)
public function update(Request $request, int $id)
{
$request->validate([
'title' => [
@ -106,27 +99,26 @@ class ContactsListController extends Controller
],
]);
$contactsList = $space->contactsLists()->findOrFail($id);
$contactsList = ContactsList::findOrFail($id);
$contactsList->title = $request->get('title');
$contactsList->description = $request->get('description');
$contactsList->save();
return redirect()->route('admin.spaces.contacts_lists.index', $space);
return redirect()->route('admin.contacts_lists.index');
}
public function delete(Space $space, int $id)
public function delete(int $id)
{
return view('admin.space.contacts_list.delete', [
'space' => $space,
'contacts_list' => $space->contactsLists()->findOrFail($id),
return view('admin.contacts_list.delete', [
'contacts_list' => ContactsList::findOrFail($id),
]);
}
public function destroy(Request $request, Space $space)
public function destroy(Request $request)
{
$contactsList = $space->contactsLists()->findOrFail($request->get('contacts_lists_id'));
$contactsList = ContactsList::findOrFail($request->get('contacts_lists_id'));
$contactsList->delete();
return redirect()->route('admin.spaces.contacts_lists.index', $space);
return redirect()->route('admin.contacts_lists.index');
}
}

View file

@ -1,68 +0,0 @@
<?php
namespace App\Http\Controllers\Admin\Space;
use App\Http\Controllers\Controller;
use App\Http\Requests\Space\CardDavServer;
use Illuminate\Http\Request;
use App\Space;
use App\SpaceCardDavServer;
class CardDavServerController extends Controller
{
public function create(Space $space)
{
return view('admin.space.carddav_server.create_edit', [
'space' => $space,
'carddavServer' => new SpaceCardDavServer
]);
}
public function store(CardDavServer $request, Space $space)
{
$carddavServer = new SpaceCardDavServer;
$carddavServer->space_id = $space->id;
$carddavServer->fill($request->validated());
$carddavServer->enabled = getRequestBoolean($request, 'enabled');
$carddavServer->use_exact_match_policy = getRequestBoolean($request, 'use_exact_match_policy');
$carddavServer->save();
return redirect()->route('admin.spaces.integration', $space);
}
public function edit(Space $space, int $carddavServerId)
{
return view('admin.space.carddav_server.create_edit', [
'space' => $space,
'carddavServer' => $space->carddavServers()->findOrFail($carddavServerId)
]);
}
public function update(CardDavServer $request, Space $space, int $carddavServerId)
{
$carddavServer = $space->carddavServers()->findOrFail($carddavServerId);
$carddavServer->fill($request->validated());
$carddavServer->enabled = getRequestBoolean($request, 'enabled');
$carddavServer->use_exact_match_policy = getRequestBoolean($request, 'use_exact_match_policy');
$carddavServer->save();
return redirect()->route('admin.spaces.integration', $space);
}
public function delete(Space $space, int $carddavServerId)
{
return view('admin.space.carddav_server.delete', [
'space' => $space,
'carddavServer' => $space->carddavServers()->findOrFail($carddavServerId)
]);
}
public function destroy(Space $space, int $carddavServerId)
{
$carddavServer = $space->carddavServers()->findOrFail($carddavServerId);
$carddavServer->delete();
return redirect()->route('admin.spaces.integration', $space->id);
}
}

View file

@ -146,7 +146,6 @@ class SpaceController extends Controller
$space->expire_at = $request->get('expire_at');
$space->custom_theme = getRequestBoolean($request, 'custom_theme');
$space->web_panel = getRequestBoolean($request, 'web_panel');
$space->carddav_user_credentials = getRequestBoolean($request, 'carddav_user_credentials');
$space->save();
return redirect()->route('admin.spaces.show', $space);

View file

@ -106,13 +106,9 @@ class StatisticsController extends Controller
$fromQuery = StatisticsCall::query();
$toQuery = StatisticsCall::query();
$domain = $request->user()->superAdmin
? $request->get('domain')
: $request->space->domain;
if ($domain) {
$fromQuery->where('to_domain', $domain);
$toQuery->where('from_domain', $domain);
if ($request->get('domain')) {
$fromQuery->where('to_domain', $request->get('domain'));
$toQuery->where('from_domain', $request->get('domain'));
}
if ($request->get('to')) {

View file

@ -1,42 +0,0 @@
<?php
namespace App\Http\Controllers\Api\Account;
use App\AccountFile;
use App\Http\Controllers\Controller;
use App\Rules\AudioMime;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
class FileController extends Controller
{
public function upload(Request $request, string $uuid)
{
$file = AccountFile::findOrFail($uuid);
if (!empty($file->name)) {
abort(404);
}
$request->validate(['file' => 'required|file']);
if ($file->isVoicemailAudio()) {
$request->validate(['file' => [new AudioMime($file)]]);
}
$uploadedFile = $request->file('file');
$name = Str::random(8) . '_' . $uploadedFile->getClientOriginalName();
if ($uploadedFile->storeAs(AccountFile::FILES_PATH, $name)) {
$file->name = $name;
$file->size = $uploadedFile->getSize();
$file->uploaded_at = Carbon::now();
$file->save();
return $file;
}
abort(503);
}
}

View file

@ -10,12 +10,12 @@ class VcardsStorageController extends Controller
{
public function index(Request $request)
{
return (new AdminVcardsStorageController)->index($request, $request->user()->id);
return (new AdminVcardsStorageController)->index($request->user()->id);
}
public function show(Request $request, string $uuid)
{
return (new AdminVcardsStorageController)->show($request, $request->user()->id, $uuid);
return (new AdminVcardsStorageController)->show($request->user()->id, $uuid);
}
public function store(Request $request)

View file

@ -1,30 +0,0 @@
<?php
namespace App\Http\Controllers\Api\Account;
use App\Http\Controllers\Controller;
use App\Http\Controllers\Api\Admin\Account\VoicemailController as AdminVoicemailController;
use Illuminate\Http\Request;
class VoicemailController extends Controller
{
public function index(Request $request)
{
return (new AdminVoicemailController)->index($request, $request->user()->id);
}
public function store(Request $request)
{
return (new AdminVoicemailController)->store($request, $request->user()->id);
}
public function show(Request $request, string $uuid)
{
return (new AdminVoicemailController)->show($request, $request->user()->id, $uuid);
}
public function destroy(Request $request, string $uuid)
{
return (new AdminVoicemailController)->destroy($request, $request->user()->id, $uuid);
}
}

View file

@ -1,78 +0,0 @@
<?php
namespace App\Http\Controllers\Api\Admin\Account;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Account;
use App\Space;
use App\AccountCardDavCredentials;
use App\SpaceCardDavServer;
use App\Http\Requests\Account\CardDavCredentials;
class CardDavCredentialsController extends Controller
{
public function index(Request $request, int $accountId)
{
$account = $request->space->accounts()->findOrFail($accountId);
$cardDavServers = $account->carddavServers;
if ($cardDavServers->isEmpty()) return new \stdClass;
return $cardDavServers->map(function ($cardDavServer) {
return $this->extractCardDavServer($cardDavServer);
})->keyBy('carddav_id');
}
public function show(Request $request, int $accountId, int $cardDavServerId)
{
$account = $request->space->accounts()->findOrFail($accountId);
$cardDavServer = $account->cardDavServers()->findOrFail($cardDavServerId);
return $this->extractCardDavServer($cardDavServer);
}
public function update(CardDavCredentials $request, int $accountId, int $cardDavServerId)
{
$account = $request->space->accounts()->findOrFail($accountId);
$cardDavServer = $request->space->cardDavServers()->findOrFail($cardDavServerId);
$accountCarddavCredentials = AccountCardDavCredentials::where('account_id', $account->id)
->where('space_carddav_server_id', $cardDavServer->id)
->delete();
$accountCarddavCredentials = new AccountCardDavCredentials;
$accountCarddavCredentials->space_carddav_server_id = $cardDavServer->id;
$accountCarddavCredentials->account_id = $account->id;
$accountCarddavCredentials->username = $request->get('username');
$accountCarddavCredentials->realm = $request->get('realm');
$accountCarddavCredentials->password = bchash(
$request->get('username'),
$request->get('realm'),
$request->get('password'),
$request->get('algorithm')
);
$accountCarddavCredentials->algorithm = $request->get('algorithm');
return $accountCarddavCredentials->save();
}
public function destroy(Request $request, int $accountId, int $cardDavServerId)
{
$account = $request->space->accounts()->findOrFail($accountId);
$cardDavServer = $account->cardDavServers()->findOrFail($cardDavServerId);
return $cardDavServer->delete();
}
private function extractCardDavServer(SpaceCardDavServer $cardDavServer)
{
return [
'carddav_id' => $cardDavServer->id,
'username' => $cardDavServer->pivot->username,
'realm' => $cardDavServer->pivot->realm,
'algorithm' => $cardDavServer->pivot->algorithm,
'password' => $cardDavServer->pivot->password,
];
}
}

View file

@ -1,49 +0,0 @@
<?php
namespace App\Http\Controllers\Api\Admin\Account;
use App\Account;
use App\AccountFile;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
class VoicemailController extends Controller
{
public function index(Request $request, int $accountId)
{
return Account::findOrFail($accountId)->voicemails;
}
public function store(Request $request, int $accountId)
{
$account = Account::findOrFail($accountId);
$request->validate([
'sip_from' => 'nullable|starts_with:sip',
'content_type' => [
'required',
Rule::in(AccountFile::VOICEMAIL_CONTENTTYPES),
]
]);
$voicemail = new AccountFile;
$voicemail->account_id = $account->id;
$voicemail->sip_from = $request->get('sip_from');
$voicemail->content_type = $request->get('content_type');
$voicemail->save();
$voicemail->append(['upload_url', 'max_upload_size']);
return $voicemail;
}
public function show(Request $request, int $accountId, string $uuid)
{
return Account::findOrFail($accountId)->voicemails()->where('id', $uuid)->firstOrFail();
}
public function destroy(Request $request, int $accountId, string $uuid)
{
return Account::findOrFail($accountId)->voicemails()->where('id', $uuid)->delete();
}
}

View file

@ -17,32 +17,33 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers\Api\Admin\Account;
namespace App\Http\Controllers\Api\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Account;
use App\AccountAction;
use App\Rules\NoUppercase;
class ActionController extends Controller
class AccountActionController extends Controller
{
public function index(Request $request, int $accountId)
public function index(int $id)
{
return $this->resolveAccount($request, $accountId)->actions;
return $this->resolveAccount($id)->actions;
}
public function get(Request $request, int $accountId, int $actionId)
public function get(int $id, int $actionId)
{
return $this->resolveAccount($request, $accountId)
return $this->resolveAccount($id)
->actions()
->where('id', $actionId)
->firstOrFail();
}
public function store(Request $request, int $accountId)
public function store(Request $request, int $id)
{
$account = $this->resolveAccount($request, $accountId);
$account = $this->resolveAccount($id);
$request->validate([
'key' => ['required', 'alpha_dash', new NoUppercase],
@ -58,9 +59,9 @@ class ActionController extends Controller
return $accountAction;
}
public function update(Request $request, int $accountId, int $actionId)
public function update(Request $request, int $id, int $actionId)
{
$account = $this->resolveAccount($request, $accountId);
$account = $this->resolveAccount($id);
$request->validate([
'key' => ['alpha_dash', new NoUppercase],
@ -78,17 +79,17 @@ class ActionController extends Controller
return $accountAction;
}
public function destroy(Request $request, int $accountId, int $actionId)
public function destroy(int $id, int $actionId)
{
return $this->resolveAccount($request, $accountId)
return $this->resolveAccount($id)
->actions()
->where('id', $actionId)
->delete();
}
private function resolveAccount(Request $request, int $accountId)
private function resolveAccount(int $id)
{
$account = $request->space->accounts()->findOrFail($accountId);
$account = Account::findOrFail($id);
if ($account->dtmf_protocol == null) abort(403, 'DTMF Protocol must be configured');
return $account;

View file

@ -17,39 +17,40 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers\Api\Admin\Account;
namespace App\Http\Controllers\Api\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class ContactController extends Controller
use App\Account;
class AccountContactController extends Controller
{
public function index(Request $request, int $accountId)
public function index(int $id)
{
return $request->space->accounts()->findOrFail($accountId)->contacts;
return Account::findOrFail($id)->contacts;
}
public function show(Request $request, int $accountId, int $contactId)
public function show(int $id, int $contactId)
{
return $request->space->accounts()->findOrFail($accountId)
return Account::findOrFail($id)
->contacts()
->where('id', $contactId)
->firstOrFail();
}
public function add(Request $request, int $accountId, int $contactId)
public function add(int $id, int $contactId)
{
$account = $request->space->accounts()->findOrFail($accountId);
$account = Account::findOrFail($id);
$account->contacts()->detach($contactId);
if ($request->space->accounts()->findOrFail($contactId)) {
if (Account::findOrFail($contactId)) {
return $account->contacts()->attach($contactId);
}
}
public function remove(Request $request, int $accountId, int $contactId)
public function remove(int $id, int $contactId)
{
$account = $request->space->accounts()->findOrFail($accountId);
$account = Account::findOrFail($id);
if (!$account->contacts()->pluck('id')->contains($contactId)) {
abort(404);

View file

@ -25,6 +25,7 @@ use Illuminate\Support\Str;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use App\Account;
use App\AccountTombstone;
use App\AccountType;
use App\ContactsList;
@ -34,23 +35,18 @@ use App\Http\Requests\Account\Update\Api\AsAdminRequest as ApiAsAdminRequest;
use App\Mail\Provisioning;
use App\Mail\ResetPassword;
use App\Services\AccountService;
use App\Space;
class AccountController extends Controller
{
public function index(Request $request)
{
return $request->space->accounts()
->without(['passwords', 'admin'])
->with(['phoneChangeCode', 'emailChangeCode'])
->paginate(20);
return Account::without(['passwords', 'admin'])->with(['phoneChangeCode', 'emailChangeCode'])->paginate(20);
}
public function show(Request $request, $accountId)
{
$account = $request->space->accounts()
->without(['passwords', 'admin'])
->with(['phoneChangeCode', 'emailChangeCode'])
->findOrFail($accountId);
$account = Account::without(['passwords', 'admin'])->with(['phoneChangeCode', 'emailChangeCode'])->findOrFail($accountId);
if ($request->user()->admin) {
if ($account->phoneChangeCode) {
@ -65,29 +61,27 @@ class AccountController extends Controller
return $account;
}
public function search(Request $request, string $sip)
public function search(string $sip)
{
$account = $request->space->accounts()->sip($sip)->first();
$account = Account::sip($sip)->first();
if (!$account)
abort(404, 'SIP address not found');
if (!$account) abort(404, 'SIP address not found');
return $account;
}
public function searchByEmail(Request $request, string $email)
public function searchByEmail(string $email)
{
$account = $request->space->accounts()->where('email', $email)->first();
$account = Account::where('email', $email)->first();
if (!$account)
abort(404, 'Email address not found');
if (!$account) abort(404, 'Email address not found');
return $account;
}
public function destroy(Request $request, int $accountId)
{
$account = $request->space->accounts()->findOrFail($accountId);
$account = Account::findOrFail($accountId);
if (!$account->hasTombstone()) {
$tombstone = new AccountTombstone();
@ -101,9 +95,9 @@ class AccountController extends Controller
Log::channel('events')->info('API Admin: Account destroyed', ['id' => $account->identifier]);
}
public function activate(Request $request, int $accountId)
public function activate(int $accountId)
{
$account = $request->space->accounts()->findOrFail($accountId);
$account = Account::findOrFail($accountId);
$account->activated = true;
$account->save();
@ -112,9 +106,9 @@ class AccountController extends Controller
return $account;
}
public function deactivate(Request $request, int $accountId)
public function deactivate(int $accountId)
{
$account = $request->space->accounts()->findOrFail($accountId);
$account = Account::findOrFail($accountId);
$account->activated = false;
$account->save();
@ -123,9 +117,9 @@ class AccountController extends Controller
return $account;
}
public function block(Request $request, int $accountId)
public function block(int $accountId)
{
$account = $request->space->accounts()->findOrFail($accountId);
$account = Account::findOrFail($accountId);
$account->blocked = true;
$account->save();
@ -134,9 +128,9 @@ class AccountController extends Controller
return $account;
}
public function unblock(Request $request, int $accountId)
public function unblock(int $accountId)
{
$account = $request->space->accounts()->findOrFail($accountId);
$account = Account::findOrFail($accountId);
$account->blocked = false;
$account->save();
@ -145,9 +139,9 @@ class AccountController extends Controller
return $account;
}
public function provision(Request $request, int $accountId)
public function provision(int $accountId)
{
$account = $request->space->accounts()->findOrFail($accountId);
$account = Account::findOrFail($accountId);
$account->provision();
$account->save();
@ -170,56 +164,55 @@ class AccountController extends Controller
return $account->makeVisible(['provisioning_token']);
}
public function typeAdd(Request $request, int $accountId, int $typeId)
public function typeAdd(int $accountId, int $typeId)
{
if ($request->space->accounts()->findOrFail($accountId)->types()->pluck('id')->contains($typeId)) {
if (Account::findOrFail($accountId)->types()->pluck('id')->contains($typeId)) {
abort(403);
}
if (AccountType::findOrFail($typeId)) {
return $request->space->accounts()->findOrFail($accountId)->types()->attach($typeId);
return Account::findOrFail($accountId)->types()->attach($typeId);
}
}
public function typeRemove(Request $request, int $accountId, int $typeId)
public function typeRemove(int $accountId, int $typeId)
{
if (!$request->space->accounts()->findOrFail($accountId)->types()->pluck('id')->contains($typeId)) {
if (!Account::findOrFail($accountId)->types()->pluck('id')->contains($typeId)) {
abort(403);
}
return $request->space->accounts()->findOrFail($accountId)->types()->detach($typeId);
return Account::findOrFail($accountId)->types()->detach($typeId);
}
public function contactsListAdd(Request $request, int $accountId, int $contactsListId)
public function contactsListAdd(int $accountId, int $contactsListId)
{
if ($request->space->accounts()->findOrFail($accountId)->contactsLists()->pluck('id')->contains($contactsListId)) {
if (Account::findOrFail($accountId)->contactsLists()->pluck('id')->contains($contactsListId)) {
abort(403);
}
if (ContactsList::findOrFail($contactsListId)) {
return $request->space->accounts()->findOrFail($accountId)->contactsLists()->attach($contactsListId);
return Account::findOrFail($accountId)->contactsLists()->attach($contactsListId);
}
}
public function contactsListRemove(Request $request, int $accountId, int $contactsListId)
public function contactsListRemove(int $accountId, int $contactsListId)
{
if (!$request->space->accounts()->findOrFail($accountId)->contactsLists()->pluck('id')->contains($contactsListId)) {
if (!Account::findOrFail($accountId)->contactsLists()->pluck('id')->contains($contactsListId)) {
abort(403);
}
return $request->space->accounts()->findOrFail($accountId)->contactsLists()->detach($contactsListId);
return Account::findOrFail($accountId)->contactsLists()->detach($contactsListId);
}
/**
* Emails
*/
public function sendProvisioningEmail(Request $request, int $accountId)
public function sendProvisioningEmail(int $accountId)
{
$account = $request->space->accounts()->findOrFail($accountId);
$account = Account::findOrFail($accountId);
if (!$account->email)
abort(403, 'No email configured');
if (!$account->email) abort(403, 'No email configured');
$account->provision();
@ -228,12 +221,11 @@ class AccountController extends Controller
Log::channel('events')->info('API: Sending provisioning email', ['id' => $account->identifier]);
}
public function sendResetPasswordEmail(Request $request, int $accountId)
public function sendResetPasswordEmail(int $accountId)
{
$account = $request->space->accounts()->findOrFail($accountId);
$account = Account::findOrFail($accountId);
if (!$account->email)
abort(403, 'No email configured');
if (!$account->email) abort(403, 'No email configured');
$resetPasswordEmail = new ResetPasswordEmailToken;
$resetPasswordEmail->account_id = $account->id;

View file

@ -17,7 +17,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers\Api\Admin\Account;
namespace App\Http\Controllers\Api\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
@ -26,7 +26,7 @@ use Illuminate\Support\Str;
use App\AccountCreationToken;
use App\Http\Controllers\Account\AuthenticateController as WebAuthenticateController;
class CreationTokenController extends Controller
class AccountCreationTokenController extends Controller
{
public function create(Request $request)
{

View file

@ -17,23 +17,23 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers\Api\Admin\Account;
namespace App\Http\Controllers\Api\Admin;
use App\Http\Controllers\Controller;
use App\Account;
use Illuminate\Http\Request;
class DictionaryController extends Controller
class AccountDictionaryController extends Controller
{
public function index(Request $request, int $accountId)
public function index(int $accountId)
{
return $request->space->accounts()->findOrFail($accountId)->dictionary;
return Account::findOrFail($accountId)->dictionary;
}
public function show(Request $request, int $accountId, string $key)
public function show(int $accountId, string $key)
{
return $request->space->accounts()
->findOrFail($accountId)->dictionaryEntries()->where('key', $key)->first();
return Account::findOrFail($accountId)->dictionaryEntries()->where('key', $key)->first();
}
public function set(Request $request, int $accountId, string $key)
@ -42,7 +42,7 @@ class DictionaryController extends Controller
'value' => 'required'
]);
$account = $request->space->accounts()->findOrFail($accountId);
$account = Account::findOrFail($accountId);
$result = $account->setDictionaryEntry($key, $request->get('value'));
if (function_exists('accountServiceAccountEditedHook')) {
@ -53,15 +53,8 @@ class DictionaryController extends Controller
return $result;
}
public function destroy(Request $request, int $accountId, string $key)
public function destroy(int $accountId, string $key)
{
return $request->space->accounts()
->findOrFail($accountId)->dictionaryEntries()->where('key', $key)->delete();
}
public function clear(Request $request, int $accountId)
{
return $request->space->accounts()
->findOrFail($accountId)->dictionaryEntries()->delete();
return Account::findOrFail($accountId)->dictionaryEntries()->where('key', $key)->delete();
}
}

View file

@ -17,7 +17,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers\Api\Admin\Account;
namespace App\Http\Controllers\Api\Admin;
use App\Http\Controllers\Controller;
use App\Rules\NoUppercase;
@ -25,7 +25,7 @@ use Illuminate\Http\Request;
use App\AccountType;
class TypeController extends Controller
class AccountTypeController extends Controller
{
public function index()
{

View file

@ -17,8 +17,9 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace App\Http\Controllers\Api\Admin\Space;
namespace App\Http\Controllers\Api\Admin;
use App\Account;
use App\ContactsList;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
@ -27,12 +28,12 @@ class ContactsListController extends Controller
{
public function index(Request $request)
{
return $request->space->contactsLists;
return ContactsList::all();
}
public function get(Request $request, int $contactsListId)
public function get(int $contactsListId)
{
return $request->space->contactsLists()->findOrFail($contactsListId);
return ContactsList::findOrFail($contactsListId);
}
public function store(Request $request)
@ -43,7 +44,6 @@ class ContactsListController extends Controller
]);
$contactsList = new ContactsList;
$contactsList->space_id = $request->space->id;
$contactsList->title = $request->get('title');
$contactsList->description = $request->get('description');
$contactsList->save();
@ -59,7 +59,6 @@ class ContactsListController extends Controller
]);
$contactsList = ContactsList::findOrFail($contactsListId);
$contactsList->space_id = $request->space->id;
$contactsList->title = $request->get('title');
$contactsList->description = $request->get('description');
$contactsList->save();
@ -67,25 +66,25 @@ class ContactsListController extends Controller
return $contactsList;
}
public function destroy(Request $request, int $contactsListId)
public function destroy(int $contactsListId)
{
return $request->space->contactsLists()->where('id', $contactsListId)
return ContactsList::where('id', $contactsListId)
->delete();
}
public function contactAdd(Request $request, int $id, int $contactId)
public function contactAdd(int $id, int $contactId)
{
$contactsList = $request->space->contactsLists()->findOrFail($id);
$contactsList = ContactsList::findOrFail($id);
$contactsList->contacts()->detach($contactId);
if ($request->space->accounts()->findOrFail($contactId)) {
if (Account::findOrFail($contactId)) {
return $contactsList->contacts()->attach($contactId);
}
}
public function contactRemove(Request $request, int $id, int $contactId)
public function contactRemove(int $id, int $contactId)
{
$contactsList = $request->space->contactsLists()->findOrFail($id);
$contactsList = ContactsList::findOrFail($id);
if (!$contactsList->contacts()->pluck('id')->contains($contactId)) {
abort(404);

View file

@ -22,27 +22,21 @@ namespace App\Http\Controllers\Api\Admin;
use App\Http\Controllers\Controller;
use App\Libraries\FlexisipRedisConnector;
use App\Account;
use Illuminate\Http\Request;
use stdClass;
class DeviceController extends Controller
{
public function index(Request $request, int $accountId)
public function index(int $accountId)
{
$devices = (new FlexisipRedisConnector)->getDevices(
$request->space->accounts()->findOrFail($accountId)->identifier
);
$devices = (new FlexisipRedisConnector)->getDevices(Account::findOrFail($accountId)->identifier);
return ($devices->isEmpty()) ? new stdClass : $devices;
}
public function destroy(Request $request, int $accountId, string $uuid)
public function destroy(int $accountId, string $uuid)
{
$connector = new FlexisipRedisConnector;
return $connector->deleteDevice(
$request->space->accounts()->findOrFail($accountId)->identifier,
$uuid
);
return $connector->deleteDevice(Account::findOrFail($accountId)->identifier, $uuid);
}
}

View file

@ -1,6 +1,6 @@
<?php
namespace App\Http\Controllers\Api\Admin\Space;
namespace App\Http\Controllers\Api\Admin;
use App\Http\Controllers\Controller;
use App\Http\Requests\EmailServer\CreateUpdate;

View file

@ -23,12 +23,16 @@ use App\Http\Controllers\Controller;
use App\Http\Requests\ExternalAccount\CreateUpdate;
use App\Services\AccountService;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use App\ExternalAccount;
use App\Account;
class ExternalAccountController extends Controller
{
public function show(Request $request, int $accountId)
public function show(int $accountId)
{
return $request->space->accounts()->findOrFail($accountId)->external()->firstOrFail();
return Account::findOrFail($accountId)->external()->firstOrFail();
}
public function store(CreateUpdate $request, int $accountId)

View file

@ -1,21 +0,0 @@
<?php
namespace App\Http\Controllers\Api\Admin;
use App\Http\Controllers\Controller;
use App\PhoneCountry;
class PhoneCountryController extends Controller
{
public function activate(string $code)
{
$phoneCountry = PhoneCountry::where('code', $code)->firstOrFail();
return PhoneCountry::where('country_code', $phoneCountry->country_code)->update(['activated' => true]);
}
public function deactivate(string $code)
{
$phoneCountry = PhoneCountry::where('code', $code)->firstOrFail();
return PhoneCountry::where('country_code', $phoneCountry->country_code)->update(['activated' => false]);
}
}

View file

@ -1,56 +0,0 @@
<?php
namespace App\Http\Controllers\Api\Admin\Space;
use App\Http\Controllers\Controller;
use App\Http\Requests\Space\CardDavServer;
use Illuminate\Http\Request;
use App\Space;
use App\SpaceCardDavServer;
class CardDavServerController extends Controller
{
public function index(string $domain)
{
return Space::where('domain', $domain)->firstOrFail()->carddavServers;
}
public function show(string $domain, int $carddavServerId)
{
return Space::where('domain', $domain)->firstOrFail()->carddavServers()->findOrFail($carddavServerId);
}
public function store(CardDavServer $request, string $domain)
{
$space = Space::where('domain', $domain)->firstOrFail();
$carddavServer = new SpaceCardDavServer;
$carddavServer->space_id = $space->id;
$carddavServer->fill($request->validated());
$carddavServer->enabled = $request->has('enabled') && (bool)$request->get('enabled');
$carddavServer->use_exact_match_policy = $request->has('use_exact_match_policy') && (bool)$request->get('use_exact_match_policy');
return $carddavServer->save();
}
public function update(CardDavServer $request, string $domain, int $carddavServerId)
{
$space = Space::where('domain', $domain)->firstOrFail();
$carddavServer = $space->carddavServers()->findOrFail($carddavServerId);
$carddavServer->fill($request->validated());
$carddavServer->enabled = $request->has('enabled') && (bool)$request->get('enabled');
$carddavServer->use_exact_match_policy = $request->has('use_exact_match_policy') && (bool)$request->get('use_exact_match_policy');
return $carddavServer->save();
}
public function destroy(string $domain, int $carddavServerId)
{
$space = Space::where('domain', $domain)->firstOrFail();
$carddavServer = $space->carddavServers()->findOrFail($carddavServerId);
return $carddavServer->delete();
}
}

View file

@ -45,37 +45,38 @@ class SpaceController extends Controller
]);
$space = new Space;
$space->account_proxy_registrar_address = $request->get('account_proxy_registrar_address');
$space->account_realm = $request->get('account_realm');
$space->copyright_text = $request->get('copyright_text');
$space->custom_provisioning_entries = $request->get('custom_provisioning_entries');
$space->domain = $request->get('domain');
$space->expire_at = $request->get('expire_at');
$space->host = $request->get('host');
$space->intro_registration_text = $request->get('intro_registration_text');
$space->max_account = $request->get('max_account', 0);
$space->max_accounts = $request->get('max_accounts', 0);
$space->name = $request->get('name');
$space->newsletter_registration_address = $request->get('newsletter_registration_address');
$this->setRequestBoolean($request, $space, 'assistant_disable_qr_code');
$this->setRequestBoolean($request, $space, 'assistant_hide_create_account');
$this->setRequestBoolean($request, $space, 'assistant_hide_third_party_account');
$this->setRequestBoolean($request, $space, 'carddav_user_credentials');
$this->setRequestBoolean($request, $space, 'custom_provisioning_overwrite_all');
$this->setRequestBoolean($request, $space, 'custom_theme');
$this->setRequestBoolean($request, $space, 'disable_broadcast_feature');
$this->setRequestBoolean($request, $space, 'disable_call_recordings_feature');
$space->domain = $request->get('domain');
$space->host = $request->get('host');
$this->setRequestBoolean($request, $space, 'super');
$this->setRequestBoolean($request, $space, 'disable_chat_feature');
$this->setRequestBoolean($request, $space, 'disable_meetings_feature');
$this->setRequestBoolean($request, $space, 'hide_account_settings');
$this->setRequestBoolean($request, $space, 'disable_broadcast_feature');
$this->setRequestBoolean($request, $space, 'hide_settings');
$this->setRequestBoolean($request, $space, 'intercom_features');
$this->setRequestBoolean($request, $space, 'hide_account_settings');
$this->setRequestBoolean($request, $space, 'disable_call_recordings_feature');
$this->setRequestBoolean($request, $space, 'only_display_sip_uri_username');
$this->setRequestBoolean($request, $space, 'phone_registration');
$this->setRequestBoolean($request, $space, 'assistant_hide_create_account');
$this->setRequestBoolean($request, $space, 'assistant_disable_qr_code');
$this->setRequestBoolean($request, $space, 'assistant_hide_third_party_account');
$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->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, 'public_registration');
$this->setRequestBoolean($request, $space, 'super');
$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();
@ -89,30 +90,30 @@ class SpaceController extends Controller
public function update(Request $request, string $domain)
{
$request->validate([
'account_realm' => ['nullable', new Domain()],
'assistant_disable_qr_code' => 'required|boolean',
'assistant_hide_create_account' => 'required|boolean',
'assistant_hide_third_party_account' => 'required|boolean',
'carddav_user_credentials' => 'required|boolean',
'custom_provisioning_entries' => ['nullable', new Ini(Space::FORBIDDEN_KEYS)],
'custom_provisioning_overwrite_all' => 'required|boolean',
'custom_theme' => 'required|boolean',
'disable_broadcast_feature' => 'required|boolean',
'disable_call_recordings_feature' => 'required|boolean',
'super' => 'required|boolean',
'disable_chat_feature' => 'required|boolean',
'disable_meetings_feature' => 'required|boolean',
'expire_at' => 'nullable|date|after_or_equal:today',
'hide_account_settings' => 'required|boolean',
'disable_broadcast_feature' => 'required|boolean',
'hide_settings' => 'required|boolean',
'intercom_features' => 'required|boolean',
'hide_account_settings' => 'required|boolean',
'disable_call_recordings_feature' => 'required|boolean',
'only_display_sip_uri_username' => 'required|boolean',
'assistant_hide_create_account' => 'required|boolean',
'assistant_disable_qr_code' => 'required|boolean',
'assistant_hide_third_party_account' => 'required|boolean',
'max_account' => 'required|integer',
'max_accounts' => 'required|integer',
'only_display_sip_uri_username' => 'required|boolean',
'phone_registration' => 'required|boolean',
'expire_at' => 'nullable|date|after_or_equal:today',
'account_realm' => ['nullable', new Domain()],
'custom_provisioning_entries' => ['nullable', new Ini(Space::FORBIDDEN_KEYS)],
'custom_provisioning_overwrite_all' => 'required|boolean',
'provisioning_use_linphone_provisioning_header' => 'required|boolean',
'public_registration' => 'required|boolean',
'super' => '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();
@ -131,33 +132,33 @@ class SpaceController extends Controller
$space->name = $request->get('name');
$space->host = $request->get('host');
$space->super = $request->get('super');
$space->account_proxy_registrar_address = $request->get('account_proxy_registrar_address');
$space->account_realm = $request->get('account_realm');
$space->assistant_disable_qr_code = $request->get('assistant_disable_qr_code');
$space->assistant_hide_create_account = $request->get('assistant_hide_create_account');
$space->assistant_hide_third_party_account = $request->get('assistant_hide_third_party_account');
$space->copyright_text = $request->get('copyright_text');
$space->carddav_user_credentials = $request->get('carddav_user_credentials');
$space->custom_provisioning_entries = $request->get('custom_provisioning_entries');
$space->custom_provisioning_overwrite_all = $request->get('custom_provisioning_overwrite_all');
$space->custom_theme = $request->get('custom_theme');
$space->disable_broadcast_feature = $request->get('disable_broadcast_feature');
$space->disable_call_recordings_feature = $request->get('disable_call_recordings_feature');
$space->disable_chat_feature = $request->get('disable_chat_feature');
$space->disable_meetings_feature = $request->get('disable_meetings_feature');
$space->expire_at = $request->get('expire_at');
$space->hide_account_settings = $request->get('hide_account_settings');
$space->disable_broadcast_feature = $request->get('disable_broadcast_feature');
$space->hide_settings = $request->get('hide_settings');
$space->intercom_features = $request->get('intercom_features');
$space->intro_registration_text = $request->get('intro_registration_text');
$space->hide_account_settings = $request->get('hide_account_settings');
$space->disable_call_recordings_feature = $request->get('disable_call_recordings_feature');
$space->only_display_sip_uri_username = $request->get('only_display_sip_uri_username');
$space->assistant_hide_create_account = $request->get('assistant_hide_create_account');
$space->assistant_disable_qr_code = $request->get('assistant_disable_qr_code');
$space->assistant_hide_third_party_account = $request->get('assistant_hide_third_party_account');
$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->newsletter_registration_address = $request->get('newsletter_registration_address');
$space->only_display_sip_uri_username = $request->get('only_display_sip_uri_username');
$space->phone_registration = $request->get('phone_registration');
$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->public_registration = $request->get('public_registration');
$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();

View file

@ -19,6 +19,7 @@
namespace App\Http\Controllers\Api\Admin;
use App\Account;
use App\Http\Controllers\Controller;
use App\Rules\Vcard;
use App\VcardStorage;
@ -29,15 +30,15 @@ use stdClass;
class VcardsStorageController extends Controller
{
public function index(Request $request, int $accountId)
public function index(int $accountId)
{
$list = $request->space->accounts()->findOrFail($accountId)->vcardsStorage()->get()->keyBy('uuid');
$list = Account::findOrFail($accountId)->vcardsStorage()->get()->keyBy('uuid');
return $list->isEmpty() ? new stdClass : $list;
}
public function show(Request $request, int $accountId, string $uuid)
public function show(int $accountId, string $uuid)
{
return $request->space->accounts()->findOrFail($accountId)->vcardsStorage()->where('uuid', $uuid)->firstOrFail();
return Account::findOrFail($accountId)->vcardsStorage()->where('uuid', $uuid)->firstOrFail();
}
public function store(Request $request, int $accountId)
@ -47,14 +48,18 @@ class VcardsStorageController extends Controller
]);
$vcardo = VObject\Reader::read($request->get('vcard'));
$vcardoUID = substr($vcardo->UID, 9);
if ($request->space->accounts()->findOrFail($accountId)->vcardsStorage()->where('uuid', $vcardo->UID)->first()) {
$request->merge(['uuid' => $vcardoUID]);
$request->validate(['uuid' => 'uuid']);
if (Account::findOrFail($accountId)->vcardsStorage()->where('uuid', $vcardoUID)->first()) {
abort(409, 'Vcard already exists');
}
$vcard = new VcardStorage();
$vcard->account_id = $accountId;
$vcard->uuid = $vcardo->UID;
$vcard->uuid = $vcardoUID;
$vcard->vcard = preg_replace('/\r\n?/', "\n", $vcardo->serialize());
$vcard->save();
@ -63,17 +68,24 @@ class VcardsStorageController extends Controller
public function update(Request $request, int $accountId, string $uuid)
{
$request->merge(['uuid' => $uuid]);
$request->validate([
'uuid' => 'uuid',
'vcard' => ['required', new Vcard()]
]);
$vcardo = VObject\Reader::read($request->get('vcard'));
$vcardoUID = substr($vcardo->UID, 9);
if ($vcardo->UID != $uuid) {
$request->merge(['vuuid' => $vcardoUID]);
$request->validate(['vuuid' => 'uuid']);
if ($vcardoUID != $uuid) {
abort(422, 'UUID should be the same');
}
$vcard = $request->space->accounts()->findOrFail($accountId)->vcardsStorage()->where('uuid', $uuid)->firstOrFail();
$vcard = Account::findOrFail($accountId)->vcardsStorage()->where('uuid', $uuid)->firstOrFail();
$vcard->vcard = preg_replace('/\r\n?/', "\n", $vcardo->serialize());
$vcard->save();
@ -82,7 +94,10 @@ class VcardsStorageController extends Controller
public function destroy(Request $request, int $accountId, string $uuid)
{
$vcard = $request->space->accounts()->findOrFail($accountId)->vcardsStorage()->where('uuid', $uuid)->firstOrFail();
$request->merge(['uuid' => $uuid]);
$request->validate(['uuid' => 'uuid']);
$vcard = Account::findOrFail($accountId)->vcardsStorage()->where('uuid', $uuid)->firstOrFail();
return $vcard->delete();
}

View file

@ -0,0 +1,116 @@
<?php
/*
Flexisip Account Manager is a set of tools to manage SIP accounts.
Copyright (C) 2020 Belledonne Communications SARL, All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
/**
* The application's global HTTP middleware stack.
*
* These middleware are run during every request to your application.
*
* @var array
*/
protected $middleware = [
\App\Http\Middleware\TrustProxies::class,
\App\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class
];
/**
* The application's route middleware groups.
*
* @var array
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\Localization::class,
'space.check',
],
'api' => [
'throttle:600,1', // move to 600 instead of 60
'bindings',
'validate_json',
'localization',
'space.check',
],
];
/**
* The application's route middleware.
*
* These middleware may be assigned to groups or used individually.
*
* @var array
*/
protected $middlewareAliases = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.admin' => \App\Http\Middleware\AuthenticateAdmin::class,
'auth.super_admin' => \App\Http\Middleware\AuthenticateSuperAdmin::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'auth.digest_or_key' => \App\Http\Middleware\AuthenticateDigestOrKey::class,
'auth.jwt' => \App\Http\Middleware\AuthenticateJWT::class,
'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,
'cookie' => \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
'cookie.encrypt' => \App\Http\Middleware\EncryptCookies::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'space.check' => \App\Http\Middleware\SpaceCheck::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'localization' => \App\Http\Middleware\Localization::class,
];
/**
* The priority-sorted list of middleware.
*
* This forces non-global middleware to always be in the given order.
*
* @var array
*/
protected $middlewarePriority = [
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\Illuminate\Routing\Middleware\ThrottleRequests::class,
\Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Illuminate\Auth\Middleware\Authorize::class,
];
}

View file

@ -29,9 +29,7 @@ class AuthenticateAdmin
return redirect()->route('account.login');
}
if (!$request->user()->admin
|| (!$request->user()->superAdmin && $request->user()->domain != $request->space->domain)
) {
if (!$request->user()->admin) {
return abort(403, 'Unauthorized area');
}

View file

@ -76,20 +76,18 @@ class AuthenticateJWT
}
$account = null;
$identifierKey = config('services.jwt.sip_identifier');
if ($identifierKey == '') $identifierKey = 'sip_identity';
if ($token->claims()->has($identifierKey)) {
list($username, $domain) = parseSIP($token->claims()->get($identifierKey));
if ($token->claims()->has(config('services.jwt.sip_identifier'))) {
list($username, $domain) = parseSIP($token->claims()->get(config('services.jwt.sip_identifier')));
$account = Account::withoutGlobalScopes()
->where('username', $username)
->where('domain', $domain)
->first();
->where('username', $username)
->where('domain', $domain)
->first();
} elseif ($token->claims()->has('email')) {
$account = Account::withoutGlobalScopes()
->where('email', $token->claims()->get('email'))
->first();
->where('email', $token->claims()->get('email'))
->first();
}
if (!$account) {
@ -101,7 +99,8 @@ class AuthenticateJWT
return $next($request);
}
if (!empty(config('app.account_authentication_bearer'))
if (
!empty(config('app.account_authentication_bearer'))
// Bypass the JWT auth if we have an API Key
&& !$request->header('x-api-key')
&& !$request->cookie('x-api-key')
@ -131,7 +130,7 @@ class AuthenticateJWT
$response = new Response();
$response->header(
'WWW-Authenticate',
$bearer . 'error="' . $error . '", error_description="' . $description . '"'
$bearer . 'error="' . $error . '", error_description="'. $description . '"'
);
$response->setStatusCode(401);

View file

@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode as Middleware;
class CheckForMaintenanceMode extends Middleware
{
/**
* The URIs that should be reachable while maintenance mode is enabled.
*
* @var array
*/
protected $except = [
//
];
}

View file

@ -0,0 +1,17 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Cookie\Middleware\EncryptCookies as Middleware;
class EncryptCookies extends Middleware
{
/**
* The names of the cookies that should not be encrypted.
*
* @var array
*/
protected $except = [
//
];
}

View file

@ -1,19 +0,0 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class IsCardDavCredentialsEnabled
{
public function handle(Request $request, Closure $next): Response
{
if ($request->space->carddav_user_credentials) {
return $next($request);
}
abort(403, 'CardDav Credentials features disabled');
}
}

View file

@ -10,7 +10,7 @@ class IsIntercomFeatures
{
public function handle(Request $request, Closure $next): Response
{
if ($request->space->intercom_features) {
if (space()?->intercom_features) {
return $next($request);
}

View file

@ -27,7 +27,7 @@ class IsPhoneRegistration
{
public function handle(Request $request, Closure $next): Response
{
if ($request->space->phone_registration) {
if (space()?->phone_registration) {
return $next($request);
}

View file

@ -27,10 +27,10 @@ class IsPublicRegistration
{
public function handle(Request $request, Closure $next): Response
{
if ($request->space?->public_registration) {
if (space()?->public_registration) {
return $next($request);
}
abort(404, 'Public registration disabled');
return abort(404, 'Public registration disabled');
}
}

View file

@ -27,7 +27,7 @@ class IsWebPanelEnabled
{
public function handle(Request $request, Closure $next)
{
if (!$request->expectsJson() && $request->space->web_panel) {
if (!$request->expectsJson() && space()?->web_panel) {
return $next($request);
}

View file

@ -3,7 +3,6 @@
namespace App\Http\Middleware;
use Closure;
use App\Space;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Config;
use Symfony\Component\HttpFoundation\Response;
@ -13,19 +12,18 @@ class SpaceCheck
public function handle(Request $request, Closure $next): Response
{
if (empty(config('app.root_host'))) {
abort(503, 'APP_ROOT_HOST is not configured');
return abort(503, 'APP_ROOT_HOST is not configured');
}
$space = Space::where('host', config('app.sip_domain') ?? request()->host())->first();
$space = space(reload: true);
if ($space != null) {
if (!str_ends_with($space->host, config('app.root_host'))) {
abort(503, 'The APP_ROOT_HOST configured does not match with the current root domain');
return abort(503, 'The APP_ROOT_HOST configured does not match with the current root domain');
}
$request->merge(['space' => $space]);
Config::set('app.url', '://' . $space->host);
Config::set('app.sip_domain', $space->domain);
if ($space->isExpired()) {
abort($request->expectsJson() ? 403 : 490, 'The related Space has expired');
@ -53,6 +51,6 @@ class SpaceCheck
return $next($request);
}
abort(404, 'Host not configured');
return abort(404, 'Host not configured');
}
}

View file

@ -0,0 +1,18 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\TrimStrings as Middleware;
class TrimStrings extends Middleware
{
/**
* The names of the attributes that should not be trimmed.
*
* @var array
*/
protected $except = [
'password',
'password_confirmation',
];
}

View file

@ -0,0 +1,28 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Http\Middleware\TrustProxies as Middleware;
use Illuminate\Http\Request;
class TrustProxies extends Middleware
{
/**
* The trusted proxies for this application.
*
* @var array|string
*/
protected $proxies;
/**
* The headers that should be used to detect proxies.
*
* @var int
*/
protected $headers =
Request::HEADER_X_FORWARDED_FOR |
Request::HEADER_X_FORWARDED_HOST |
Request::HEADER_X_FORWARDED_PORT |
Request::HEADER_X_FORWARDED_PROTO |
Request::HEADER_X_FORWARDED_AWS_ELB;
}

View file

@ -0,0 +1,24 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
class VerifyCsrfToken extends Middleware
{
/**
* Indicates whether the XSRF-TOKEN cookie should be set on the response.
*
* @var bool
*/
protected $addHttpCookie = true;
/**
* The URIs that should be excluded from CSRF verification.
*
* @var array
*/
protected $except = [
//
];
}

View file

@ -1,42 +0,0 @@
<?php
/*
Flexisip Account Manager is a set of tools to manage SIP accounts.
Copyright (C) 2023 Belledonne Communications SARL, All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace App\Http\Requests\Space;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
use App\EmailServer;
use App\Rules\CommaList;
class CardDavServer extends FormRequest
{
public function rules()
{
return [
'uri' => 'required|url',
'min_characters' => 'integer|min:1',
'results_limit' => 'integer|min:0',
'timeout' => 'integer|min:1',
'delay' => 'integer|min:100',
'fields_for_user_input' => [new CommaList],
'fields_for_domain' => [new CommaList],
];
}
}

View file

@ -57,9 +57,9 @@ class StatisticsGraphFactory
$fromQuery = StatisticsMessage::query();
$toQuery = StatisticsMessage::query();
if (!Auth::user()?->superAdmin) {
$fromQuery->where('from_domain', $this->request->space->domain);
$toQuery->toDomain(space()->domain);
if (!Auth::user()?->admin) {
$fromQuery->where('from_domain', space()->domain);
$toQuery->toDomain($this->domain);
} elseif ($this->domain) {
$fromQuery->where('from_domain', $this->domain);
$toQuery->toDomain($this->domain);

View file

@ -5,6 +5,7 @@ namespace App\Mail;
use App\Space;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;

View file

@ -22,6 +22,7 @@ namespace App\Mail;
use App\Account;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;

View file

@ -22,6 +22,7 @@ namespace App\Mail;
use App\Account;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;

View file

@ -22,6 +22,7 @@ namespace App\Mail;
use App\Account;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;

View file

@ -20,8 +20,10 @@
namespace App\Mail;
use App\Account;
use App\ResetPasswordEmailToken;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;

View file

@ -1,44 +0,0 @@
<?php
namespace App\Mail;
use App\AccountFile;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
use Illuminate\Mail\Mailables\Attachment;
class Voicemail extends Mailable
{
use Queueable, SerializesModels;
public function __construct(public AccountFile $accountFile)
{
}
public function envelope(): Envelope
{
return new Envelope(
subject: $this->accountFile->account->space->name .
': ' .
__('New voice message from :sipfrom', ['sipfrom' => $this->accountFile->sip_from]),
);
}
public function content(): Content
{
return new Content(
markdown: 'mails.voicemail',
);
}
public function attachments(): array
{
return [
Attachment::fromStorage($this->accountFile->path)
->withMime($this->accountFile->content_type)
];
}
}

View file

@ -1,38 +0,0 @@
<?php
namespace App\Rules;
use App\AccountFile;
use Illuminate\Contracts\Validation\Rule;
class AudioMime implements Rule
{
public function __construct(private AccountFile $accountFile)
{
}
public function passes($attribute, $file): bool
{
$mimeType = null;
switch ($file->getMimeType()) {
case 'audio/opus':
$mimeType = 'audio/opus';
break;
case 'audio/vnd.wave':
case 'audio/wav':
case 'audio/wave':
case 'audio/x-wav':
case 'audio/x-pn-wav':
$mimeType = 'audio/wav';
break;
}
return $this->accountFile->content_type == $mimeType;
}
public function message()
{
return __('The file should have the declared mime-type');
}
}

View file

@ -1,21 +0,0 @@
<?php
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
use Respect\Validation\Validator;
class CommaList implements Rule
{
public function passes($attribute, $value)
{
preg_match_all('/[^, ]+/', $value, $matches);
return $value == null || (!empty($matches) && (implode(',', $matches[0]) == $value));
}
public function message()
{
return 'The :attribute should be null or contain a list of words separated by commas without spaces';
}
}

View file

@ -3,6 +3,7 @@
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
use Respect\Validation\Validator;
use Propaganistas\LaravelPhone\PhoneNumber;
class IsNotPhoneNumber implements Rule

View file

@ -23,6 +23,11 @@ use Illuminate\Contracts\Validation\Rule;
class WithoutSpaces implements Rule
{
public function __construct()
{
//
}
public function passes($attribute, $value)
{
return preg_match('/^\S*$/u', $value);

View file

@ -62,10 +62,10 @@ class AccountService
$account = new Account();
$account->username = $request->get('username');
$account->activated = false;
$account->domain = $request->space->domain;
$account->domain = space()->domain;
$account->ip_address = $request->ip();
$account->created_at = Carbon::now();
$account->user_agent = $request->space->name;
$account->user_agent = space()->name;
$account->dtmf_protocol = $request->get('dtmf_protocol');
if ($request->asAdmin) {
@ -73,7 +73,7 @@ class AccountService
$account->display_name = $request->get('display_name');
$account->activated = $request->has('activated') ? (bool)$request->get('activated') : false;
$account->domain = resolveDomain($request);
$account->user_agent = $request->header('User-Agent') ?? $request->space->name;
$account->user_agent = $request->header('User-Agent') ?? space()->name;
$account->admin = $request->has('admin') && (bool)$request->get('admin');
if (!$request->api && $request->has('role')) {
@ -113,8 +113,8 @@ class AccountService
);
if (!$request->api) {
if (!empty($request->space?->newsletter_registration_address) && $request->has('newsletter')) {
Mail::to($request->space->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));
}
}

View file

@ -21,67 +21,52 @@ namespace App;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;
use Carbon\Carbon;
class Space extends Model
{
use HasFactory;
protected $with = ['emailServer', 'carddavServers'];
protected $with = ['emailServer'];
public const FORBIDDEN_KEYS = [
'account_proxy_registrar_address',
'account_realm',
'assistant_disable_qr_code',
'assistant_hide_create_account',
'assistant_hide_third_party_account',
'copyright_text',
'disable_broadcast_feature',
'disable_call_recordings_feature',
'disable_chat_feature',
'disable_meetings_feature',
'hide_account_settings',
'hide_settings',
'intro_registration_text',
'disable_broadcast_feature',
'max_account',
'newsletter_registration_address',
'hide_settings',
'hide_account_settings',
'disable_call_recordings_feature',
'only_display_sip_uri_username',
'assistant_hide_create_account',
'assistant_disable_qr_code',
'assistant_hide_third_party_account',
'copyright_text',
'intro_registration_text',
'newsletter_registration_address',
'account_proxy_registrar_address',
'account_realm'
];
protected $hidden = ['id'];
protected $casts = [
'assistant_disable_qr_code' => 'boolean',
'assistant_hide_create_account' => 'boolean',
'assistant_hide_third_party_account' => 'boolean',
'carddav_user_credentials' => 'boolean',
'disable_broadcast_feature' => 'boolean',
'disable_call_recordings_feature' => 'boolean',
'super' => 'boolean',
'disable_chat_feature' => 'boolean',
'disable_meetings_feature' => 'boolean',
'expire_at' => 'date',
'hide_account_settings' => 'boolean',
'disable_broadcast_feature' => 'boolean',
'hide_settings' => 'boolean',
'hide_account_settings' => 'boolean',
'disable_call_recordings_feature' => 'boolean',
'only_display_sip_uri_username' => 'boolean',
'super' => 'boolean',
'assistant_hide_create_account' => 'boolean',
'assistant_disable_qr_code' => 'boolean',
'assistant_hide_third_party_account' => 'boolean',
'expire_at' => 'date',
];
public const HOST_REGEX = '[\w\-]+';
public const DOMAIN_REGEX = '(?=^.{4,253}$)(^((?!-)[a-z0-9-]{1,63}(?<!-)\.)+[a-z]{2,63}$)';
protected static function booted()
{
static::addGlobalScope('domain', function (Builder $builder) {
if (!Auth::hasUser()) return;
if (Auth::hasUser() || Auth::user()->superAdmin) {
return;
}
$builder->where('domain', Auth::user()->domain);
});
}
public function accounts()
{
return $this->hasMany(Account::class, 'domain', 'domain');
@ -97,16 +82,6 @@ class Space extends Model
return $this->hasOne(SpaceEmailServer::class);
}
public function carddavServers()
{
return $this->hasMany(SpaceCardDavServer::class);
}
public function contactsLists()
{
return $this->hasMany(ContactsList::class);
}
public function scopeNotFull(Builder $query)
{
return $query->where('max_accounts', 0)

View file

@ -1,82 +0,0 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class SpaceCardDavServer extends Model
{
protected $hidden = ['space_id'];
protected $table = 'space_carddav_servers';
protected $fillable = ['uri', 'enabled', 'min_characters', 'results_limist', 'use_exact_match_policy', 'timeout', 'delay', 'fields_for_user_input', 'fields_for_domain'];
protected $casts = [
'enabled' => 'boolean',
'use_exact_match_policy' => 'boolean',
];
public function space()
{
return $this->belongsTo(Space::class);
}
public function accounts()
{
return $this->belongsToMany(Account::class, 'account_carddav_credentials', 'space_carddav_server_id', 'account_id');
}
public function getNameAttribute()
{
return __('CardDav Server') . ' ' . $this->id;
}
public function getProvisioningSection($config, int $remoteContactDirectoryCounter)
{
$dom = $config->ownerDocument;
$section = $dom->createElement('section');
$section->setAttribute('name', 'remote_contact_directory_' . $remoteContactDirectoryCounter);
$entry = $dom->createElement('entry', $this->enabled ? '1': '0');
$entry->setAttribute('name', 'enabled');
$section->appendChild($entry);
$entry = $dom->createElement('entry', 'carddav');
$entry->setAttribute('name', 'type');
$section->appendChild($entry);
$entry = $dom->createElement('entry', $this->uri);
$entry->setAttribute('name', 'uri');
$section->appendChild($entry);
$entry = $dom->createElement('entry', $this->min_characters);
$entry->setAttribute('name', 'min_characters');
$section->appendChild($entry);
$entry = $dom->createElement('entry', $this->results_limit);
$entry->setAttribute('name', 'results_limit');
$section->appendChild($entry);
$entry = $dom->createElement('entry', $this->timeout);
$entry->setAttribute('name', 'timeout');
$section->appendChild($entry);
$entry = $dom->createElement('entry', $this->delay);
$entry->setAttribute('name', 'delay');
$section->appendChild($entry);
$entry = $dom->createElement('entry', $this->fields_for_user_input);
$entry->setAttribute('name', 'carddav_fields_for_user_input');
$section->appendChild($entry);
$entry = $dom->createElement('entry', $this->fields_for_domain);
$entry->setAttribute('name', 'carddav_fields_for_domain');
$section->appendChild($entry);
$entry = $dom->createElement('entry', $this->use_exact_match_policy ? '1': '0');
$entry->setAttribute('name', 'carddav_use_exact_match_policy');
$section->appendChild($entry);
$config->appendChild($section);
}
}

View file

@ -1,18 +1,53 @@
#!/usr/bin/env php
<?php
use Illuminate\Foundation\Application;
use Symfony\Component\Console\Input\ArgvInput;
define('LARAVEL_START', microtime(true));
// Register the Composer autoloader...
/*
|--------------------------------------------------------------------------
| Register The Auto Loader
|--------------------------------------------------------------------------
|
| Composer provides a convenient, automatically generated class loader
| for our application. We just need to utilize it! We'll require it
| into the script here so that we do not have to worry about the
| loading of any our classes "manually". Feels great to relax.
|
*/
require __DIR__.'/vendor/autoload.php';
// Bootstrap Laravel and handle the command...
/** @var Application $app */
$app = require_once __DIR__.'/bootstrap/app.php';
$status = $app->handleCommand(new ArgvInput);
/*
|--------------------------------------------------------------------------
| Run The Artisan Application
|--------------------------------------------------------------------------
|
| When we run the console application, the current CLI command will be
| executed in this console and the response sent back to a terminal
| or another output device for the developers. Here goes nothing!
|
*/
exit($status);
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
$status = $kernel->handle(
$input = new Symfony\Component\Console\Input\ArgvInput,
new Symfony\Component\Console\Output\ConsoleOutput
);
/*
|--------------------------------------------------------------------------
| Shutdown The Application
|--------------------------------------------------------------------------
|
| Once Artisan has finished running, we will fire off the shutdown events
| so that any final work may be done by the application before we shut
| down the process. This is the last thing to happen to the request.
|
*/
$kernel->terminate($input, $status);
exit($status);

View file

@ -1,50 +1,55 @@
<?php
use App\Http\Middleware\Authenticate;
use App\Http\Middleware\AuthenticateAdmin;
use App\Http\Middleware\AuthenticateDigestOrKey;
use App\Http\Middleware\AuthenticateJWT;
use App\Http\Middleware\AuthenticateSuperAdmin;
use App\Http\Middleware\CheckBlocked;
use App\Http\Middleware\IsCardDavCredentialsEnabled;
use App\Http\Middleware\IsIntercomFeatures;
use App\Http\Middleware\IsPhoneRegistration;
use App\Http\Middleware\IsPublicRegistration;
use App\Http\Middleware\IsWebPanelEnabled;
use App\Http\Middleware\Localization;
use App\Http\Middleware\SpaceCheck;
use App\Http\Middleware\ValidateJSON;
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
/*
|--------------------------------------------------------------------------
| Create The Application
|--------------------------------------------------------------------------
|
| The first thing we will do is create a new Laravel application instance
| which serves as the "glue" for all the components of Laravel, and is
| the IoC container for the system binding all of the various parts.
|
*/
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__ . '/../routes/web.php',
commands: __DIR__ . '/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
$middleware->append(SpaceCheck::class);
$middleware->append(Localization::class);
$middleware->api(append: [ValidateJSON::class]);
$app = new Illuminate\Foundation\Application(
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);
$middleware->alias([
'auth.admin' => AuthenticateAdmin::class,
'auth.check_blocked' => CheckBlocked::class,
'auth.digest_or_key' => AuthenticateDigestOrKey::class,
'auth.jwt' => AuthenticateJWT::class,
'auth.super_admin' => AuthenticateSuperAdmin::class,
'auth' => Authenticate::class,
'feature.carddav_user_credentials' => IsCardDavCredentialsEnabled::class,
'feature.intercom' => IsIntercomFeatures::class,
'feature.phone_registration' => IsPhoneRegistration::class,
'feature.public_registration' => IsPublicRegistration::class,
'feature.web_panel_enabled' => IsWebPanelEnabled::class,
]);
})
->withExceptions(function (Exceptions $exceptions) {
//
})->create();
/*
|--------------------------------------------------------------------------
| Bind Important Interfaces
|--------------------------------------------------------------------------
|
| Next, we need to bind some important interfaces into the container so
| we will be able to resolve them when needed. The kernels serve the
| incoming requests to this application from both the web and CLI.
|
*/
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Console\Kernel::class,
App\Console\Kernel::class
);
$app->singleton(
Illuminate\Contracts\Debug\ExceptionHandler::class,
App\Exceptions\Handler::class
);
/*
|--------------------------------------------------------------------------
| Return The Application
|--------------------------------------------------------------------------
|
| This script returns the application instance. The instance is given to
| the calling script so we can separate the building of the instances
| from the actual running of the application and sending responses.
|
*/
return $app;

View file

@ -8,33 +8,33 @@
],
"license": "MIT",
"require": {
"php": ">=8.2",
"laravel/framework": "^11.45.1",
"awobaz/compoships": "^2.4.1",
"doctrine/dbal": "^3.10.1",
"endroid/qr-code": "^5.1",
"fakerphp/faker": "^1.24.1",
"laravel/tinker": "^2.10.1",
"php": ">=8.1",
"awobaz/compoships": "^2.3",
"doctrine/dbal": "^3.0",
"endroid/qr-code": "^5.0",
"fakerphp/faker": "^1.23",
"laravel/framework": "^10.0",
"laravel/tinker": "^2.9",
"lcobucci/jwt": "^4.3",
"namoshek/laravel-redis-sentinel": "^0.5",
"ovh/ovh": "^3.5",
"parsedown/laravel": "^1.2.1",
"phpunit/phpunit": "^10.5.50",
"propaganistas/laravel-phone": "^5.3.6",
"ovh/ovh": "^3.3",
"parsedown/laravel": "^1.2",
"phpunit/phpunit": "^10.0",
"propaganistas/laravel-phone": "^5.3",
"react/socket": "^1.16",
"respect/validation": "^2.4.4",
"respect/validation": "^2.3",
"rvxlab/hcaptcha": "^5.2",
"sabre/vobject": "^4.5.7"
"sabre/vobject": "^4.5"
},
"require-dev": {
"mockery/mockery": "^1.6.12",
"nunomaduro/collision": "^8.5",
"mockery/mockery": "^1.6",
"nunomaduro/collision": "^7.0",
"phpmd/phpmd": "^2.15",
"squizlabs/php_codesniffer": "^3.13.2"
"squizlabs/php_codesniffer": "^3.7"
},
"config": {
"platform": {
"php": "8.2"
"php": "8.1"
},
"optimize-autoloader": true,
"preferred-install": "dist",

1445
flexiapi/composer.lock generated

File diff suppressed because it is too large Load diff

Binary file not shown.

View file

@ -37,12 +37,6 @@ return [
'recovery_code_expiration_minutes' => env('APP_RECOVERY_CODE_EXPIRATION_MINUTES', 10),
'provisioning_token_expiration_minutes' => env('APP_PROVISIONING_TOKEN_EXPIRATION_MINUTES', 0),
'reset_password_email_token_expiration_minutes' => env('APP_RESET_PASSWORD_EMAIL_TOKEN_EXPIRATION_MINUTES', 1440),
/**
* Temporary toggles
*/
'show_login_counter_temp' => env('APP_SHOW_LOGIN_COUNTER_TEMP', true),
/**
* Amount of minutes before re-authorizing the generation of a new account creation token
*/

View file

@ -19,11 +19,13 @@
namespace Database\Factories;
use Illuminate\Support\Str;
use Illuminate\Database\Eloquent\Factories\Factory;
use Awobaz\Compoships\Database\Eloquent\Factories\ComposhipsFactory;
use App\Account;
use App\AccountCreationToken;
use App\Http\Controllers\Account\AuthenticateController as WebAuthenticateController;
use App\Space;
class AccountFactory extends Factory
@ -52,14 +54,14 @@ class AccountFactory extends Factory
public function fromSpace(Space $space)
{
return $this->state(fn(array $attributes) => [
return $this->state(fn (array $attributes) => [
'domain' => $space->domain
]);
}
public function admin()
{
return $this->state(fn(array $attributes) => [
return $this->state(fn (array $attributes) => [
'admin' => true,
]);
}
@ -79,21 +81,21 @@ class AccountFactory extends Factory
public function deactivated()
{
return $this->state(fn(array $attributes) => [
return $this->state(fn (array $attributes) => [
'activated' => false,
]);
}
public function withEmail()
{
return $this->state(fn(array $attributes) => [
return $this->state(fn (array $attributes) => [
'email' => $this->faker->email,
]);
}
public function withConsumedAccountCreationToken()
{
return $this->state(fn(array $attributes) => [])->afterCreating(function (Account $account) {
return $this->state(fn (array $attributes) => [])->afterCreating(function (Account $account) {
$accountCreationToken = new AccountCreationToken;
$accountCreationToken->token = 'test_token';
$accountCreationToken->account_id = $account->id;

Some files were not shown because too many files have changed in this diff Show more