mirror of
https://gitlab.linphone.org/BC/public/flexisip-account-manager.git
synced 2026-01-18 10:28:07 +00:00
Compare commits
275 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a1f3471d6 | ||
|
|
85c939b4da | ||
|
|
f883c3cee7 | ||
|
|
06dc357524 | ||
|
|
4d601c4a9c | ||
|
|
09d386a303 | ||
|
|
0740bd0425 | ||
|
|
d179d0f6df | ||
|
|
25ddd330c1 | ||
|
|
e0f33da4ac | ||
|
|
ae4a651f2a | ||
|
|
593d7ce5c0 | ||
|
|
6fcde1b467 | ||
|
|
789c27f654 | ||
|
|
a53910d364 | ||
|
|
abcc9c1c7b | ||
|
|
26aaab2f07 | ||
|
|
fe265a972d | ||
|
|
98d9d76225 | ||
|
|
9aeeb0fa73 | ||
|
|
57e09cc4de | ||
|
|
ed28e8fe55 | ||
|
|
689140a553 | ||
|
|
7221d55ff8 | ||
|
|
0e3b0d36d8 | ||
|
|
10cdbb4b6a | ||
|
|
b0de4841f6 | ||
|
|
a98c8764d5 | ||
|
|
fbf47fc9c9 | ||
|
|
6240f81f14 | ||
|
|
edbe49d404 | ||
|
|
d8f0c47d8f | ||
|
|
6770e198d9 | ||
|
|
5f22c8c862 | ||
|
|
a56de2e93a | ||
|
|
38f0120ecc | ||
|
|
abe67c9734 | ||
|
|
ee2c9fed8f | ||
|
|
e0a9b75923 | ||
|
|
2a3634d461 | ||
|
|
2ec4f488b6 | ||
|
|
60df61d508 | ||
|
|
9a9b8ab34e | ||
|
|
a876a8cf82 | ||
|
|
a5eeb06055 | ||
|
|
4d0c713174 | ||
|
|
507e913241 | ||
|
|
1a79ae7b16 | ||
|
|
dcb071b5bc | ||
|
|
005072e301 | ||
|
|
da7b401a67 | ||
|
|
9d98e466eb | ||
|
|
33373be186 | ||
|
|
41e754b424 | ||
|
|
327c017b8f | ||
|
|
c6170f5f07 | ||
|
|
a8a90e197b | ||
|
|
dd1345d1ba | ||
|
|
a1f73095fd | ||
|
|
e50aeefbfa | ||
|
|
cb5afe3343 | ||
|
|
5be36f5bbd | ||
|
|
1bc0ae9233 | ||
|
|
22797eb493 | ||
|
|
e4392951c7 | ||
|
|
532bb3e096 | ||
|
|
ab0755b2e9 | ||
|
|
8f4da0dc7b | ||
|
|
724e4c4e5b | ||
|
|
b590995b4e | ||
|
|
9b58c3b0b1 | ||
|
|
e6d2b8ee9a | ||
|
|
f130380809 | ||
|
|
ad8e520047 | ||
|
|
d8635a619c | ||
|
|
c3dcbab9cc | ||
|
|
cc45376d7d | ||
|
|
801b8bd047 | ||
|
|
477f23123d | ||
|
|
97667ada1c | ||
|
|
83b5e66644 | ||
|
|
cf0b835ae4 | ||
|
|
b0b6ab2c51 | ||
|
|
48961ea194 | ||
|
|
cd3b9b818b | ||
|
|
682b0ae67b | ||
|
|
0160779784 | ||
|
|
e2f40699fb | ||
|
|
a3861304cc | ||
|
|
672d6291b7 | ||
|
|
fd0fcd7045 | ||
|
|
b493e9006e | ||
|
|
deeea0ddb6 | ||
|
|
7cb63f3e51 | ||
|
|
a8e81908ee | ||
|
|
1d3c3b8c13 | ||
|
|
11a9f87f1d | ||
|
|
09f6e1fa6d | ||
|
|
f566bc0c7c | ||
|
|
cc9fb24db1 | ||
|
|
ac74ad31f4 | ||
|
|
9006bc1d0d | ||
|
|
b66cc28004 | ||
|
|
2bf8db6bd1 | ||
|
|
63c1c404a6 | ||
|
|
1ba3834f40 | ||
|
|
1b1df7eef8 | ||
|
|
0d48ff3964 | ||
|
|
07458db5c9 | ||
|
|
786258da1f | ||
|
|
0dcb74ef19 | ||
|
|
e761e03309 | ||
|
|
5f0595bbaa | ||
|
|
d86e297b81 | ||
|
|
4add0d7daa | ||
|
|
9cb24cad77 | ||
|
|
694265cc1c | ||
|
|
c310ee0566 | ||
|
|
3d715afc23 | ||
|
|
93c98ae73f | ||
|
|
0d6bc37207 | ||
|
|
0ec0986b9b | ||
|
|
9b3d3cd2f2 | ||
|
|
8fd273f4c0 | ||
|
|
dcdf364517 | ||
|
|
86715d6048 | ||
|
|
197705d872 | ||
|
|
61bc04da02 | ||
|
|
648936514f | ||
|
|
12ef6d472e | ||
|
|
23e61fdc38 | ||
|
|
1ff618d20a | ||
|
|
13715c73f5 | ||
|
|
b0cfa0f08d | ||
|
|
53f47045c5 | ||
|
|
a4e44d94a6 | ||
|
|
4d550d04ab | ||
|
|
08ff1b8675 | ||
|
|
f6c5562201 | ||
|
|
29fc1744a3 | ||
|
|
2ed4f02c11 | ||
|
|
e913b4a584 | ||
|
|
6031ea80d3 | ||
|
|
aea9de7923 | ||
|
|
747e308831 | ||
|
|
47acf6cb9d | ||
|
|
9f908c3f7d | ||
|
|
61a0339442 | ||
|
|
880f0cbc74 | ||
|
|
0d399503c4 | ||
|
|
0f3454fb68 | ||
|
|
afe29811ac | ||
|
|
3d1e313ca3 | ||
|
|
676760579d | ||
|
|
f90851b6f2 | ||
|
|
b44d6e1b25 | ||
|
|
c930859c6a | ||
|
|
1b5fc67985 | ||
|
|
a9a7fffd9b | ||
|
|
b9292fde97 | ||
|
|
d165857666 | ||
|
|
3a9e62394c | ||
|
|
905c440fde | ||
|
|
4f79ddca2b | ||
|
|
c13a78002a | ||
|
|
220ea6a6f6 | ||
|
|
fd57132d06 | ||
|
|
911862870c | ||
|
|
debf668e77 | ||
|
|
faf33f5ac3 | ||
|
|
7418d79b41 | ||
|
|
42e7ed83c0 | ||
|
|
3dc9f93216 | ||
|
|
c5f001e337 | ||
|
|
75599dd5ab | ||
|
|
00196e5957 | ||
|
|
d2316251d5 | ||
|
|
92e06df01f | ||
|
|
9d7618e9c4 | ||
|
|
f6ac67b8b1 | ||
|
|
668c79bc12 | ||
|
|
2e9455ef11 | ||
|
|
8fe5761859 | ||
|
|
ea9fa824ea | ||
|
|
8780050487 | ||
|
|
9e2fcf2c3d | ||
|
|
6da23a50a8 | ||
|
|
1df1ca4ddf | ||
|
|
7ce7c85184 | ||
|
|
07f8a6e7dd | ||
|
|
248fce60fe | ||
|
|
46ff32f6c4 | ||
|
|
4a5d7b6aee | ||
|
|
4035cbd0ab | ||
|
|
e4ccfe7a4a | ||
|
|
9fd4b56066 | ||
|
|
d6a6b6bce0 | ||
|
|
965d808932 | ||
|
|
a01cd8d922 | ||
|
|
146a5731e8 | ||
|
|
6226e867ad | ||
|
|
0597db0f8e | ||
|
|
3649dde4f3 | ||
|
|
63002e5c2c | ||
|
|
76430cc5af | ||
|
|
c2ebe29d77 | ||
|
|
245910374a | ||
|
|
697f9d148a | ||
|
|
5717994ab8 | ||
|
|
b409e37ab1 | ||
|
|
485d088219 | ||
|
|
1cb44ce318 | ||
|
|
cabf94273f | ||
|
|
b9591d4102 | ||
|
|
8bb2c514b0 | ||
|
|
06e92a6f93 | ||
|
|
b6b54802d2 | ||
|
|
459e8faf9d | ||
|
|
9abce3988b | ||
|
|
c0fda7cecd | ||
|
|
ff4e704bfa | ||
|
|
179c76251d | ||
|
|
95707c7749 | ||
|
|
3dece6158d | ||
|
|
c477973d84 | ||
|
|
ab6aa88b3c | ||
|
|
e996a9827c | ||
|
|
7feb7fd184 | ||
|
|
e516ae788c | ||
|
|
312c9e515f | ||
|
|
7d9e0bc95e | ||
|
|
a9fb3fd1c1 | ||
|
|
d2c5e9f48f | ||
|
|
a2e8d27b49 | ||
|
|
1debbc5f10 | ||
|
|
03bd8d8114 | ||
|
|
be429be82a | ||
|
|
8b17a9e6c1 | ||
|
|
3ae460de5a | ||
|
|
5fd6766a8e | ||
|
|
99e6505b9b | ||
|
|
3852218558 | ||
|
|
88f0d898fa | ||
|
|
f8ae6d93ff | ||
|
|
49d414c9ee | ||
|
|
4db2b591c5 | ||
|
|
77f334a730 | ||
|
|
adabb52f3f | ||
|
|
ec3f123c9a | ||
|
|
f8bde4345f | ||
|
|
0729718ccf | ||
|
|
16a26d1576 | ||
|
|
86983f3f9b | ||
|
|
c1e3f56e5d | ||
|
|
c1e355a829 | ||
|
|
806a77a756 | ||
|
|
f90304d768 | ||
|
|
79de0d5b65 | ||
|
|
a02a8ae745 | ||
|
|
fc96338bfb | ||
|
|
02983102c0 | ||
|
|
1d6eab83b9 | ||
|
|
171f55e42e | ||
|
|
04352d582a | ||
|
|
8570aaae15 | ||
|
|
716789592e | ||
|
|
63e13502dc | ||
|
|
f409f19ab4 | ||
|
|
23f35da223 | ||
|
|
2514de1754 | ||
|
|
b18b500af2 | ||
|
|
3162624fb5 | ||
|
|
13412119dc | ||
|
|
f05df1529d | ||
|
|
2ff5adfc4c |
586 changed files with 33825 additions and 18953 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -3,7 +3,6 @@ rpmbuild/
|
|||
|
||||
.env.*
|
||||
|
||||
xmlrpc/src/vendor
|
||||
flexiapi/node_modules
|
||||
flexiapi/public/hot
|
||||
flexiapi/public/storage
|
||||
|
|
|
|||
|
|
@ -1,35 +1,59 @@
|
|||
rocky8-deploy:
|
||||
extends: .deploy
|
||||
script:
|
||||
- ./deploy_packages.sh rockylinux
|
||||
- ./deploy_packages.sh rockylinux 8
|
||||
needs:
|
||||
- rocky8-package
|
||||
- rocky8-test
|
||||
|
||||
debian11-deploy:
|
||||
rocky9-deploy:
|
||||
extends: .deploy
|
||||
script:
|
||||
- ./deploy_packages.sh debian bullseye
|
||||
- ./deploy_packages.sh rockylinux 9
|
||||
needs:
|
||||
- debian11-package
|
||||
- debian11-test
|
||||
- rocky9-package
|
||||
- rocky9-test
|
||||
|
||||
remi-deploy:
|
||||
rocky10-deploy:
|
||||
extends: .deploy
|
||||
script:
|
||||
- ./deploy_packages.sh rockylinux
|
||||
- ./deploy_packages.sh rockylinux 10
|
||||
needs:
|
||||
- remi-phpredis-package
|
||||
- remi-igbinary-package
|
||||
- remi-msgpack-package
|
||||
- remi-phpredis-test
|
||||
- rocky10-package
|
||||
- rocky10-test
|
||||
|
||||
debian12-deploy:
|
||||
extends: .deploy
|
||||
script:
|
||||
- ./deploy_packages.sh debian bookworm
|
||||
needs:
|
||||
- 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
|
||||
script:
|
||||
- ./deploy_packages.sh rockylinux 8
|
||||
needs:
|
||||
- remi-rocky8-package
|
||||
|
||||
.deploy:
|
||||
stage: deploy
|
||||
tags: ["docker"]
|
||||
only:
|
||||
- master
|
||||
- /^release/.*$/
|
||||
rules:
|
||||
- if: $CI_COMMIT_REF_NAME == "master"
|
||||
- if: $CI_COMMIT_REF_NAME =~ /^release/
|
||||
|
||||
before_script:
|
||||
- rm -f $CI_PROJECT_DIR/build/*devel*.rpm # Remove devel packages
|
||||
|
|
|
|||
|
|
@ -1,53 +1,85 @@
|
|||
rocky8-package:
|
||||
extends: .package
|
||||
image: gitlab.linphone.org:4567/bc/public/docker/rocky8-php:$ROCKY_8_IMAGE_VERSION
|
||||
script:
|
||||
- make rpm
|
||||
|
||||
debian11-package:
|
||||
extends: .package
|
||||
image: gitlab.linphone.org:4567/bc/public/docker/debian11-php:$DEBIAN_11_IMAGE_VERSION
|
||||
script:
|
||||
- make deb
|
||||
|
||||
remi-phpredis-package:
|
||||
extends: .remi-package
|
||||
before_script:
|
||||
- yum -y localinstall build/*.rpm
|
||||
needs:
|
||||
- remi-igbinary-package
|
||||
- remi-msgpack-package
|
||||
- remi-xmlrpc-package
|
||||
variables:
|
||||
PACKAGE: $PHP_REDIS_REMI_VERSION
|
||||
|
||||
remi-igbinary-package:
|
||||
extends: .remi-package
|
||||
variables:
|
||||
PACKAGE: $PHP_IGBINARY_REMI_VERSION
|
||||
|
||||
remi-msgpack-package:
|
||||
extends: .remi-package
|
||||
variables:
|
||||
PACKAGE: $PHP_MSGPACK_REMI_VERSION
|
||||
|
||||
remi-xmlrpc-package:
|
||||
extends: .remi-package
|
||||
variables:
|
||||
PACKAGE: $PHP_XMLRPC_REMI_VERSION
|
||||
|
||||
.remi-package:
|
||||
- prepare-package
|
||||
extends: .package
|
||||
image: gitlab.linphone.org:4567/bc/public/docker/rocky8-php:$ROCKY_8_IMAGE_VERSION
|
||||
script:
|
||||
- make package-el8
|
||||
|
||||
rocky9-package:
|
||||
needs:
|
||||
- prepare-package
|
||||
extends: .package
|
||||
image: gitlab.linphone.org:4567/bc/public/docker/rocky9-php:$ROCKY_9_IMAGE_VERSION
|
||||
script:
|
||||
- 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:
|
||||
- make package-deb
|
||||
|
||||
remi-rocky8-package:
|
||||
image: gitlab.linphone.org:4567/bc/public/docker/rocky8-php:$ROCKY_8_IMAGE_VERSION
|
||||
extends: .remi-rocky-package
|
||||
variables:
|
||||
ROCKY_RELEASE: 8
|
||||
before_script:
|
||||
- dnf -y module reset redis
|
||||
- dnf -y install @redis:6
|
||||
|
||||
.remi-rocky-package:
|
||||
extends: .package
|
||||
rules:
|
||||
- if: $CI_COMMIT_REF_NAME =~ /^release/ || $CI_COMMIT_REF_NAME == "master"
|
||||
#- changes:
|
||||
# - .gitlab-ci.yml
|
||||
script:
|
||||
# Remi
|
||||
- mkdir -p $CI_PROJECT_DIR/build
|
||||
- dnf -y install https://rpms.remirepo.net/enterprise/remi-release-8.rpm
|
||||
- dnf -y install https://rpms.remirepo.net/enterprise/remi-release-$ROCKY_RELEASE.rpm
|
||||
- yum -y install wget php-devel gcc liblzf php-pear lz4-devel liblzf-devel libzstd-devel php-pecl-apcu-devel
|
||||
- wget https://rpms.remirepo.net/SRPMS/$PACKAGE.remi.src.rpm
|
||||
- rpmbuild --rebuild $PACKAGE.remi.src.rpm
|
||||
- rm -f /root/rpmbuild/RPMS/*/*debug*.rpm
|
||||
- mv /root/rpmbuild/RPMS/*/*devel*.rpm $CI_PROJECT_DIR/build/$PACKAGE-devel.el8.x86_64.rpm || true # Rename to fit our naming format
|
||||
- mv /root/rpmbuild/RPMS/*/*.rpm $CI_PROJECT_DIR/build/$PACKAGE.el8.x86_64.rpm # Rename to fit our naming format
|
||||
# igbinary
|
||||
- wget https://rpms.remirepo.net/SRPMS/$PHP_IGBINARY_REMI_VERSION.remi.src.rpm
|
||||
- rpmbuild --rebuild $PHP_IGBINARY_REMI_VERSION.remi.src.rpm
|
||||
- rm /root/rpmbuild/RPMS/*/*debug*.rpm
|
||||
- mv /root/rpmbuild/RPMS/*/*devel*.rpm $CI_PROJECT_DIR/build/$PHP_IGBINARY_REMI_VERSION-devel.el$ROCKY_RELEASE.x86_64.rpm || true # Rename to fit our naming format
|
||||
- mv /root/rpmbuild/RPMS/*/*.rpm $CI_PROJECT_DIR/build/$PHP_IGBINARY_REMI_VERSION.el$ROCKY_RELEASE.x86_64.rpm # Rename to fit our naming format
|
||||
# msgpack
|
||||
- wget https://rpms.remirepo.net/SRPMS/$PHP_MSGPACK_REMI_VERSION.remi.src.rpm
|
||||
- rpmbuild --rebuild $PHP_MSGPACK_REMI_VERSION.remi.src.rpm
|
||||
- rm /root/rpmbuild/RPMS/*/*debug*.rpm
|
||||
- mv /root/rpmbuild/RPMS/*/*devel*.rpm $CI_PROJECT_DIR/build/$PHP_MSGPACK_REMI_VERSION-devel.el$ROCKY_RELEASE.x86_64.rpm || true
|
||||
- mv /root/rpmbuild/RPMS/*/*.rpm $CI_PROJECT_DIR/build/$PHP_MSGPACK_REMI_VERSION.el$ROCKY_RELEASE.x86_64.rpm
|
||||
# install and cleanup the dependencies
|
||||
- yum -y localinstall build/*.rpm
|
||||
# phpredis
|
||||
- wget https://rpms.remirepo.net/SRPMS/$PHP_REDIS_REMI_VERSION.remi.src.rpm
|
||||
- rpmbuild --rebuild $PHP_REDIS_REMI_VERSION.remi.src.rpm
|
||||
- rm /root/rpmbuild/RPMS/*/*debug*.rpm
|
||||
- mv /root/rpmbuild/RPMS/*/*devel*.rpm $CI_PROJECT_DIR/build/$PHP_REDIS_REMI_VERSION-devel.el$ROCKY_RELEASE.x86_64.rpm || true
|
||||
- mv /root/rpmbuild/RPMS/*/*.rpm $CI_PROJECT_DIR/build/$PHP_REDIS_REMI_VERSION.el$ROCKY_RELEASE.x86_64.rpm
|
||||
|
||||
- rm -r /root/rpmbuild # Cleanup
|
||||
|
||||
.package:
|
||||
|
|
@ -59,7 +91,13 @@ remi-xmlrpc-package:
|
|||
- build/*
|
||||
when: always
|
||||
expire_in: 1 day
|
||||
|
||||
variables:
|
||||
COMPOSER_CACHE_DIR: $CI_PROJECT_DIR/.composer/cache
|
||||
COMPOSER_ALLOW_SUPERUSER: 1
|
||||
|
||||
cache:
|
||||
key: ${CI_COMMIT_REF_SLUG}
|
||||
paths:
|
||||
- flexiapi/vendor/
|
||||
- flexiapi/vendor/
|
||||
- .composer/cache
|
||||
|
|
|
|||
11
.gitlab-ci-files/prepare-package.yml
Normal file
11
.gitlab-ci-files/prepare-package.yml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
prepare-package:
|
||||
tags: ["docker"]
|
||||
stage: prepare-package
|
||||
image: gitlab.linphone.org:4567/bc/public/docker/debian12-php:$DEBIAN_12_IMAGE_VERSION
|
||||
script:
|
||||
- make prepare-common
|
||||
artifacts:
|
||||
paths:
|
||||
- rpmbuild/*
|
||||
when: always
|
||||
expire_in: 1 day
|
||||
|
|
@ -1,41 +1,78 @@
|
|||
rocky8-test:
|
||||
extends: .test
|
||||
extends: .rocky-test
|
||||
image: gitlab.linphone.org:4567/bc/public/docker/rocky8-php:$ROCKY_8_IMAGE_VERSION
|
||||
needs:
|
||||
- rocky8-package
|
||||
- remi-xmlrpc-package
|
||||
|
||||
rocky9-test:
|
||||
extends: .rocky-test
|
||||
image: gitlab.linphone.org:4567/bc/public/docker/rocky9-php:$ROCKY_9_IMAGE_VERSION
|
||||
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
|
||||
- vendor/bin/phpcs
|
||||
- vendor/bin/phpmd . ansi phpmd.xml
|
||||
- php artisan key:generate
|
||||
- vendor/bin/phpunit --log-junit $CI_PROJECT_DIR/flexiapi_phpunit.log
|
||||
|
||||
debian12-test:
|
||||
extends: .debian-test
|
||||
image: gitlab.linphone.org:4567/bc/public/docker/debian12-php:$DEBIAN_12_IMAGE_VERSION
|
||||
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 install -y ./build/*.deb
|
||||
- cd /opt/belledonne-communications/share/flexisip-account-manager/flexiapi
|
||||
- composer install
|
||||
- vendor/bin/phpcs
|
||||
- vendor/bin/phpmd . ansi phpmd.xml
|
||||
- php artisan key:generate
|
||||
- vendor/bin/phpunit --log-junit $CI_PROJECT_DIR/flexiapi_phpunit.log
|
||||
|
||||
debian11-test:
|
||||
mysql-latest-test:
|
||||
extends: .test
|
||||
image: gitlab.linphone.org:4567/bc/public/docker/debian11-php:$DEBIAN_11_IMAGE_VERSION
|
||||
image: gitlab.linphone.org:4567/bc/public/docker/debian12-php:$DEBIAN_12_IMAGE_VERSION
|
||||
needs:
|
||||
- debian11-package
|
||||
- debian12-package
|
||||
services:
|
||||
- mysql
|
||||
variables:
|
||||
MYSQL_ROOT_PASSWORD: flexiapi
|
||||
MYSQL_DATABASE: flexiapi
|
||||
DB_HOST: mysql
|
||||
DB_DATABASE: flexiapi
|
||||
DB_PASSWORD: flexiapi
|
||||
DB_USERNAME: root
|
||||
script:
|
||||
- pwd
|
||||
- apt update
|
||||
- apt install -y ./build/*.deb
|
||||
- cd /opt/belledonne-communications/share/flexisip-account-manager/flexiapi
|
||||
- composer install --dev
|
||||
- vendor/bin/phpcs
|
||||
- vendor/bin/phpmd . ansi phpmd.xml
|
||||
- php artisan key:generate
|
||||
- vendor/bin/phpunit --log-junit $CI_PROJECT_DIR/flexiapi_phpunit.log
|
||||
|
||||
remi-phpredis-test:
|
||||
extends: .test
|
||||
image: gitlab.linphone.org:4567/bc/public/docker/rocky8-php:$ROCKY_8_IMAGE_VERSION
|
||||
needs:
|
||||
- remi-phpredis-package
|
||||
script:
|
||||
- yum -y localinstall build/*.rpm
|
||||
- php artisan db:show
|
||||
- php artisan migrate
|
||||
- php artisan migrate:rollback
|
||||
|
||||
.test:
|
||||
tags: ["docker"]
|
||||
|
|
|
|||
|
|
@ -1,17 +1,21 @@
|
|||
variables:
|
||||
ROCKY_8_IMAGE_VERSION: 20230330_163028_remove_remi
|
||||
DEBIAN_11_IMAGE_VERSION: 20230322_172926_missing_tools
|
||||
PHP_REDIS_REMI_VERSION: php-pecl-redis5-5.3.6-1
|
||||
PHP_IGBINARY_REMI_VERSION: php-pecl-igbinary-3.2.14-1
|
||||
PHP_MSGPACK_REMI_VERSION: php-pecl-msgpack-2.2.0-1
|
||||
PHP_XMLRPC_REMI_VERSION: php-pecl-xmlrpc-1.0.0~rc3-2
|
||||
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
|
||||
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
|
||||
|
||||
include:
|
||||
- '.gitlab-ci-files/prepare-package.yml'
|
||||
- '.gitlab-ci-files/package.yml'
|
||||
- '.gitlab-ci-files/test.yml'
|
||||
- '.gitlab-ci-files/deploy.yml'
|
||||
|
||||
stages:
|
||||
- prepare-package
|
||||
- package
|
||||
- test
|
||||
- deploy
|
||||
- deploy
|
||||
|
|
|
|||
188
CHANGELOG.md
188
CHANGELOG.md
|
|
@ -1,39 +1,157 @@
|
|||
# Flexisip Account Manager Changelog
|
||||
# Releases
|
||||
|
||||
v1.3.1
|
||||
------
|
||||
- Fix #111 Disable phone authentication routes when the related toggle is set to false
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
v1.3
|
||||
----
|
||||
- Fix #90 Deploy packages from release branches as well
|
||||
- Fix #58 Fix the packaging process to use git describe as a reference
|
||||
- Fix #58 Move the generated packages in the build directory, and fix the release and version format in the .spec
|
||||
- Fix #58 Refactor and cleanup the .gitlab-ci file
|
||||
- Move the minimum PHP version to 8.0
|
||||
- Fix #47 Move the docker to an external repository
|
||||
- Fix #83 Add php-redis-remi package
|
||||
- Fix #85 Also package php-pecl-igbinary and php-pecl-msgpack from remi
|
||||
- Fix #84 Remove CentOS7 from the pipeline
|
||||
- Fix #80 Inject provisioning link and QRCode in the default email with a password_reset parameter
|
||||
- Fix #79 Add a refresh_password parameter to the provisioning URLs
|
||||
- Fix #78 Add a APP_ACCOUNTS_EMAIL_UNIQUE environnement setting
|
||||
- Fix #30 Remove APP_EVERYONE_IS_ADMIN
|
||||
- Fix #97 Validate usernames with a configurable regex
|
||||
- Fix #95 PUT /accounts admin endpoint implementation
|
||||
- Fix #102 Implement AccountCreationRequestToken
|
||||
- Fix #92 Add two new endpoints regarding email account reset and account search per email for admins
|
||||
- Fix #94 Implement the deprecated endpoint changes + tests + documentation
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/).
|
||||
|
||||
v1.2
|
||||
----
|
||||
## [2.1]
|
||||
|
||||
- Introduce FlexiAPI built on Laravel to replace XMLRPC
|
||||
- Deprecates XMLRPC (will be removed in the 2.0 release)
|
||||
- Create a REST API to manage the accounts, related features and provisioning
|
||||
- Create a user web panel for their account management, currently in testing phase (unstable)
|
||||
- Create an admin web panel to manage accounts and related features
|
||||
- Allow accounts to be exported as ExternalAccounts and imported in another Flexisip Account Manager instance
|
||||
- Add various artisan console commands to maintain the data (cleaning up, importing, exporting, seeding)
|
||||
- Add unit tests for the FlexiAIP REST API
|
||||
- Rebuild the existing database using the Laravel migration scripts
|
||||
### 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
|
||||
|
||||
- **Spaces:** A new way to manage your SIP domains and hosts. A Space is defined by a unique SIP Domain and Host pair.
|
||||
- **New mandatory DotEnv variable** `APP_ROOT_HOST`, replaces `APP_URL` and `APP_SIP_DOMAIN` that are now configured using the new dedicated Artisan script. It defines the root hostname where all the Spaces will be configured. All the Spaces will be as subdomains of `APP_ROOT_HOST` except one that can be equal to `APP_ROOT_HOST`. Example: if `APP_ROOT_HOST=myhost.com` the Spaces hosts will be `myhost.com`, `alpha.myhost.com` , `beta.myhost.com`...
|
||||
- **New DotEnv variable:** `APP_ACCOUNT_RECOVERY_TOKEN_EXPIRATION_MINUTES=0` Number of minutes before expiring the recovery tokens
|
||||
- **New Artisan script** `php artisan spaces:create-update {sip_domain} {host} {name} {--super}`, replaces `php artisan sip_domains:create-update {sip_domain} {--super}`. Can create a Space or update a Space Host base on its Space SIP Domain.
|
||||
- **Push Notification endpoint** Add a /push-notification endpoint to send custom push notifications to the Flexisip Pusher
|
||||
- **Add internationalisation support in the app** The web panels are now available in French and English
|
||||
- **Add External Accounts** In the API and web panels, allowing users to setup an external account that can be used in another service like the B2BUA
|
||||
- **Add configurable admin API Keys** Allowing admins to setup non-expiring services API Keys
|
||||
- **Add provisioning email** A user can now receive a custom generated email with all the provisioning related information
|
||||
- **Add API endpoints to send the password reset and provisioning emails**
|
||||
- **Add an app setup wizard page** Static web page inviting the users to download the app if it is not installed yet
|
||||
|
||||
### Changed
|
||||
|
||||
- **Complete and reorganize the Markdown documentation**
|
||||
- **Refactor the emails templates** All the emails were modernized and are now generated in HTML
|
||||
|
||||
### Removed
|
||||
|
||||
- **Remove the deprecated endpoints** The endpoints inherited from XMLRPC are now completely removed, the following variable can be removed:
|
||||
- APP_DANGEROUS_ENDPOINTS
|
||||
- APP_PROJECT_URL
|
||||
- **Removing and moving DotEnv instance environnement variables to the Spaces** The following DotEnv variables were removed. You can now configure them directly in the designated spaces after the migration.
|
||||
- INSTANCE_COPYRIGHT
|
||||
- INSTANCE_INTRO_REGISTRATION
|
||||
- INSTANCE_CONFIRMED_REGISTRATION_TEXT
|
||||
- INSTANCE_CUSTOM_THEME
|
||||
- WEB_PANEL
|
||||
- PUBLIC_REGISTRATION
|
||||
- PHONE_AUTHENTICATION
|
||||
- DEVICES_MANAGEMENT
|
||||
- INTERCOM_FEATURES
|
||||
- NEWSLETTER_REGISTRATION_ADDRESS
|
||||
- ACCOUNT_PROXY_REGISTRAR_ADDRESS
|
||||
- ACCOUNT_TRANSPORT_PROTOCOL_TEXT
|
||||
- ACCOUNT_REALM
|
||||
- ACCOUNT_PROVISIONING_RC_FILE
|
||||
- ACCOUNT_PROVISIONING_OVERWRITE_ALL
|
||||
- ACCOUNT_PROVISIONING_USE_X_LINPHONE_PROVISIONING_HEADER
|
||||
- **Enforce the session and cache in the configuration** The following variables can be removed from your DotEnv file as well:
|
||||
- SESSION_DRIVER
|
||||
- CACHE_DRIVER
|
||||
|
||||
### Migrate from [1.6]
|
||||
|
||||
1. Deploy the new version and migrate the database.
|
||||
|
||||
```
|
||||
php artisan migrate
|
||||
```
|
||||
|
||||
2. Set `APP_ROOT_HOST` in `.env` or as an environnement variable. And remove `APP_URL` and `APP_SIP_DOMAIN`
|
||||
|
||||
```
|
||||
APP_ROOT_HOST=myhost.com
|
||||
```
|
||||
|
||||
3. The migration script will automatically copy the `sip_domain` into `host` in the `spaces` table. You then have to "fix" the hosts and set them to equal or be subdomains of `APP_ROOT_HOST`.
|
||||
|
||||
```
|
||||
php artisan spaces:create-update my.sip myhost.com "My Super Space" --super # You can set some Spaces as SuperSpaces, the admin will be able to manage the other spaces
|
||||
php artisan spaces:create-update alpha.sip alpha.myhost.com "Alpha Space"
|
||||
php artisan spaces:create-update beta.sip beta.myhost.com "Beta Space"
|
||||
...
|
||||
```
|
||||
|
||||
4. Configure your web server to point the `APP_ROOT_HOST` and subdomains to the app. See the related documentation in [`INSTALL.md` file](INSTALL.md#31-mandatory-app_root_host-variable).
|
||||
|
||||
5. Configure your Spaces.
|
||||
|
||||
6. (Optional) Import the old instance DotEnv environnement variables into a space.
|
||||
|
||||
7. Remove the instance based environnement variables (see **Changed** above) and configure them directly in the spaces using the API or Web Panel.
|
||||
|
||||
⚠️ Be careful, during this import only the project DotEnv file variables will be imported, other environnement (eg. set in Apache, nginx or Docker) will be ignored.
|
||||
|
||||
⚠️ The content of the `ACCOUNT_PROVISIONING_RC_FILE` will not be imported. You will have to extract the sections and lines that you want to use manually using the dedicated form or the API.
|
||||
|
||||
```
|
||||
php artisan spaces:import-configuration-from-dot-env {sip_domain}
|
||||
```
|
||||
|
||||
You can find more details regarding those steps in the [`INSTALL.md`](INSTALL.md) and [`README.md`](README.md) files.
|
||||
|
||||
|
||||
## [1.6] - 2024-12-30
|
||||
|
||||
### Added
|
||||
|
||||
- **Allow the expiration of tokens and codes in the DotEnv configuration**
|
||||
- **New DotEnv variables:** check all the new `*_EXPIRATION_MINUTES` for each token and code in `.env.example`
|
||||
- **Phone validation system by country code:** all the provided phone numbers are now properly validated and some countries can be forbidden
|
||||
- **SIP Domain management:** the account domains are now managed in a set of panels and API endpoints, this is the base of the upcoming space administration system
|
||||
- **JSON validation in the API:** the provised JSON is now validated and returns an error if an issue is detected
|
||||
- **CoTURN credentials support:** TURN credentials can now be generated and return through the provisioning feature
|
||||
- **RFC 8898 Support**
|
||||
|
||||
## Changed
|
||||
|
||||
- **Replace Material Icons with Phosphor**
|
||||
|
||||
## Deprecated
|
||||
|
||||
- **Last major version supporting the deprecated endpoints of the API**
|
||||
|
||||
### Migrate from [1.5]
|
||||
|
||||
Nothing specific to do
|
||||
|
||||
## [1.5] - 2024-08-29
|
||||
|
||||
### Added
|
||||
|
||||
- **Account activity view:** new panel, available behind the Activity tab, will allow any admin to follow the activity of the accounts they manage.
|
||||
- **Detect and block abusive accounts:** This activity tracking is coming with a related tool that is measuring the accounts activity and automatically block them if it detects some unusual behaviors on the service. An account can also directly be blocked and unblocked from the setting panel. Two new setting variables will allow you to fine tune those behaviors triggers.
|
||||
- **New DotEnv variable:** `BLOCKING_TIME_PERIOD_CHECK=30` Time span on which the blocking service will proceed, in minutes
|
||||
- **New DotEnv variable:** `BLOCKING_AMOUNT_EVENTS_AUTHORIZED_DURING_PERIOD=5` Amount of account events authorized during this period
|
||||
- **OAuth JWT Authentication:** OAuth support with the handling of JWE tokens issues by a third party service such as Keycloack.
|
||||
- **New DotEnv variable:** `JWT_RSA_PUBLIC_KEY_PEM=`
|
||||
- **New DotEnv variable:** `JWT_SIP_IDENTIFIER=sip_identifier`
|
||||
- **Super-domains and super-admins support:** Introduce SIP domains management. The app accounts are now divided by their domains with their own respective administrators that can only see and manage their own domain accounts and settings. On top of that it is possible to configure a SIP domain as a "super-domain" and then allow its admins to become "super-admins". Those super-admins will then be able to manage all the accounts handled by the instance and create/edit/delete the other SIP domains. Add new endpoints and a new super-admin role in the API to manage the SIP domains. SIP domains can also be created and updated directly from the console using a new artisan script (documented in the README);
|
||||
- **New Artisan script:** `php artisan sip_domains:create-update {domain} {--super}`
|
||||
- **Account Dictionary:** Each account can now handle a specific dictionary, configurable by the API or directly the web panel. This dictionary allows developers to store arbitrary `key -> value pairs` on each accounts.
|
||||
- **Vcard storage:** Attach custom vCards on a dedicated account using new endpoints in the API. The published vCard are validated before being stored.
|
||||
|
||||
### Changed
|
||||
|
||||
- **User management of their own devices:** Allowing users will be able to manage its own devices. Specific API endpoints were also added to manage them directly from the clients.
|
||||
- **Migration to hCaptcha:** Migrate from Google Recaptcha to hCaptcha in this release.
|
||||
- **New DotEnv variable:** HCAPTCHA_SECRET=secret-key
|
||||
- **New DotEnv variable:** HCAPTCHA_SITEKEY=site-key
|
||||
- **Localization support:** The API is now accepting the `Accept-Language` header and adapt its internal localization to the client/browser one. For the moment only French and English are supported but more languages could be added in the future.
|
||||
|
|
|
|||
160
INSTALL.md
Normal file
160
INSTALL.md
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
# DotEnv configuration
|
||||
|
||||
FlexiAPI relies on [DotEnv](https://github.com/vlucas/phpdotenv) to be configured. This configuration can be accessed using the existing `.env` file that can be itself overwritten by an environnement variables.
|
||||
|
||||
Those variables can then be set using Docker-Compose, a bash script or a web-server.
|
||||
|
||||
If you're installing FlexiAPI from the RedHat or Debian package you can find the configuration file at `/etc/flexisip-account-manager/flexiapi.env`.
|
||||
|
||||
Check also the [RELEASE.md](RELEASE.md) to check if you don't have specific migrations to do between releases.
|
||||
|
||||
# 1.a Manual setup
|
||||
|
||||
Clone the repository, install the dependencies and generate a key.
|
||||
|
||||
composer install --no-dev
|
||||
php artisan key:generate
|
||||
|
||||
# 1.b Packages setup
|
||||
|
||||
FlexiAPI is packaged for Debian and RedHat, you can setup those repositories using the Flexisip documentation https://wiki.linphone.org/xwiki/wiki/public/view/Flexisip/1.%20Installation/#HInstallationfromourrepositories
|
||||
|
||||
yum install bc-flexisip-account-manager # For RedHat distributions
|
||||
apt install bc-flexisip-account-manager # For Debian distributions
|
||||
|
||||
The `artisan` script is in the root directory of where the application is setup, with packages its often `/opt/belledonne-communications/share/flexisip-account-manager/flexiapi/`.
|
||||
|
||||
⚠️ 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.
|
||||
This file can be loaded and configured in your specific VirtualHost configuration.
|
||||
|
||||
To know more about the web server configuration part, you can directly [visit the official Laravel installation documentation](https://laravel.com/docs/).
|
||||
|
||||
⚠️ 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.
|
||||
|
||||
## 3.1. Mandatory `APP_ROOT_HOST` variable
|
||||
|
||||
`APP_ROOT_HOST` contains the HTTP host where your FlexiAPI is hosted (eg. `flexiapi.domain.tld` or directly `flexiapi-domain.tld`).
|
||||
|
||||
This is the host that you'll define in the Apache or webserver VirtualHost:
|
||||
|
||||
ServerName flexiapi-domain.tld
|
||||
ServerAlias *.flexiapi-domain.tld
|
||||
|
||||
If you are planning to manage several Spaces (see Spaces bellow) a wildcard `ServerAlias` as above is required.
|
||||
|
||||
## 3.2. Database migration
|
||||
|
||||
Then configure the database connection parameters and migrate the tables. The first migration *MUST* be run on an empty database.
|
||||
|
||||
php artisan migrate
|
||||
|
||||
# 4. Spaces
|
||||
|
||||
Since the 1.6 FlexiAPI can manage different SIP Domains on separate HTTP subdomains.
|
||||
|
||||
A Space is defined as a specific HTTP subdomain of `APP_ROOT_HOST` and is linked to a specific SIP Domain. It is also possible to host one (and only one) specific Space directly under `APP_ROOT_HOST`.
|
||||
|
||||
By default administrator accounts in Spaces will only see the accounts of their own Space (that have the same SIP Domain).
|
||||
However it is possible to define a Space as a "SuperSpace" allowing the admins to see all the other Spaces and accounts and create/edit/delete the other Spaces.
|
||||
|
||||
## 4.1. Setup the first Space
|
||||
|
||||
You will need to create the first Space manually, generally as a SuperSpace, after that the other Spaces can directly be created in your browser through the Web Panels.
|
||||
|
||||
php artisan spaces:create-update {sip_domain} {host} {name} {--super}
|
||||
|
||||
For example:
|
||||
|
||||
php artisan spaces:create-update company-sip-domain.tld flexiapi-domain.tld "My Super Space" --super
|
||||
php artisan spaces:create-update other-sip-domain.tld other.flexiapi-domain.tld "My Other Space"
|
||||
|
||||
## 5. Create a first administrator and finish the setup
|
||||
|
||||
Create a first administrator account:
|
||||
|
||||
php artisan accounts:create-admin-account {-u|username=} {-p|password=} {-d|domain=}
|
||||
|
||||
For example:
|
||||
|
||||
php artisan accounts:create-admin-account -u admin -p strong_password -d company-sip-domain.tld
|
||||
|
||||
You can now try to authenticate on the web panel and continue the setup using your admin account.
|
||||
|
||||
# Other custom configurations
|
||||
|
||||
## Multiple virtualhosts option
|
||||
|
||||
In your web server configuration create several VirtualHosts that are pointing to the same FlexiAPI instance.
|
||||
Using the environnement variables you can then configure FlexiAPI per instance.
|
||||
|
||||
With Apache, use the [mod_env](https://httpd.apache.org/docs/2.4/mod/mod_env.html) module.
|
||||
|
||||
SetEnv APP_ENV "production"
|
||||
|
||||
On nginx use `fastcgi_param` to pass the parameter directly to PHP.
|
||||
|
||||
location ~ [^/]\.php(/|$) {
|
||||
…
|
||||
include /etc/nginx/fastcgi_params;
|
||||
fastcgi_param APP_ENV "staging";
|
||||
}
|
||||
|
||||
> **Warning** Do not create a cache of your configuration (using `artisan config:cache`) if you have a multi-environnement setup.
|
||||
> The cache is always having the priority on the variables set in the configuration files.
|
||||
|
||||
## Custom Theme
|
||||
|
||||
If you enable the Custom CSS Theme option to true in the Space administration panel, FlexiAPI will try to load a CSS file located in `public/css/$space_host.style.css`. If the file doesn't exists it will fallback to `public/css/style.css`.
|
||||
|
||||
You can find an example CSS file at `public/css/custom.style.css`.
|
||||
|
||||
## Flexisip Push notifications pusher
|
||||
|
||||
The API endpoint `POST /account_creation_tokens/send-by-push` uses the `flexisip_pusher` binary delivered by the [Flexisip](https://gitlab.linphone.org/BC/public/flexisip) project (and related package). You must configure the `APP_FLEXISIP_PUSHER_PATH` and `APP_FLEXISIP_PUSHER_FIREBASE_KEYSMAP` environnement variables to point to the correct binary.
|
||||
|
||||
APP_FLEXISIP_PUSHER_PATH=/opt/belledonne-communications/bin/flexisip_pusher
|
||||
|
||||
This binary will be executed under "web user" privileges. Ensure that all the related files required by `flexisip_pusher` can be accessed using this user account.
|
||||
|
||||
/var/opt/belledonne-communications/log/flexisip/flexisip-pusher.log // Write permissions
|
||||
/etc/flexisip/apn/*pem // Read permissions
|
||||
|
||||
## SELinux restrictions
|
||||
|
||||
If you are running on a RedHat machine, please ensure that SELinux is correctly configured.
|
||||
|
||||
Allow the webserver user to write in the `storage/` directory:
|
||||
|
||||
chcon -R -t httpd_sys_rw_content_t storage/
|
||||
|
||||
Don't forget to make this change persistent if the directory may be relabeled :
|
||||
|
||||
semanage fcontext -a -t httpd_sys_rw_content_t storage/
|
||||
|
||||
You can use the restorecon command to verify that this is working :
|
||||
|
||||
restorecon storage/
|
||||
|
||||
If your database is located on a remote machine, you should also allow your webserver user to connect to remote hosts:
|
||||
|
||||
semanage port -a -t http_port_t -p tcp 3306 // Open remote connections on the MySQL port for example
|
||||
setsebool -P httpd_can_network_connect 1 // Allow remote network connected
|
||||
setsebool -P httpd_can_network_connect_db 1 // Allow remote database connection
|
||||
|
||||
If you are planning to send emails using your account manager:
|
||||
|
||||
setsebool -P httpd_can_sendmail 1 // Allow email to be sent
|
||||
62
Makefile
62
Makefile
|
|
@ -20,15 +20,13 @@ else
|
|||
endif
|
||||
|
||||
cleanup-package-semvers:
|
||||
rm flexisip-account-manager.spec.run
|
||||
rm -f flexisip-account-manager.spec.run
|
||||
|
||||
prepare:
|
||||
cd flexiapi && php composer.phar install --ignore-platform-req=ext-redis --no-dev
|
||||
cd xmlrpc/src && cp ../../flexiapi/composer.phar . && php composer.phar install --ignore-platform-req=ext-redis --no-dev
|
||||
|
||||
prepare-dev:
|
||||
cd flexiapi && php composer.phar install --ignore-platform-req=ext-redis
|
||||
cd xmlrpc/src && cp ../../flexiapi/composer.phar . && php composer.phar install --ignore-platform-req=ext-redis
|
||||
|
||||
package-common:
|
||||
rm -rf $(OUTPUT_DIR)/flexisip-account-manager
|
||||
|
|
@ -36,16 +34,11 @@ package-common:
|
|||
mkdir -p $(OUTPUT_DIR)/rpmbuild/SPECS
|
||||
mkdir -p $(OUTPUT_DIR)/rpmbuild/SOURCES
|
||||
|
||||
# XMLRPC
|
||||
cp -R --parents xmlrpc/src/**/*.php $(OUTPUT_DIR)/flexisip-account-manager/
|
||||
cp -R --parents xmlrpc/src/vendor/**/* $(OUTPUT_DIR)/flexisip-account-manager/
|
||||
cp -R --parents xmlrpc/src/api/**/*.php $(OUTPUT_DIR)/flexisip-account-manager/
|
||||
cp -R --parents conf/*.conf $(OUTPUT_DIR)/flexisip-account-manager/
|
||||
|
||||
# FlexiAPI
|
||||
cp -R --parents flexiapi/**/* $(OUTPUT_DIR)/flexisip-account-manager/
|
||||
cp flexiapi/composer* $(OUTPUT_DIR)/flexisip-account-manager/flexiapi/
|
||||
cp README.md $(OUTPUT_DIR)/flexisip-account-manager/flexiapi/
|
||||
cp README.md $(OUTPUT_DIR)/flexisip-account-manager/
|
||||
cp INSTALL.md $(OUTPUT_DIR)/flexisip-account-manager/
|
||||
cp flexiapi/.env.example $(OUTPUT_DIR)/flexisip-account-manager/flexiapi/.env.example
|
||||
cp flexiapi/artisan $(OUTPUT_DIR)/flexisip-account-manager/flexiapi/
|
||||
cp flexiapi/phpunit.xml $(OUTPUT_DIR)/flexisip-account-manager/flexiapi/
|
||||
|
|
@ -53,32 +46,46 @@ package-common:
|
|||
cp flexiapi/phpmd.xml $(OUTPUT_DIR)/flexisip-account-manager/flexiapi/
|
||||
|
||||
# General
|
||||
cp xmlrpc/README.md $(OUTPUT_DIR)/flexisip-account-manager/
|
||||
cp -R httpd/ $(OUTPUT_DIR)/flexisip-account-manager/
|
||||
cp -R cron/ $(OUTPUT_DIR)/flexisip-account-manager/
|
||||
cp flexisip-account-manager.spec.run $(OUTPUT_DIR)/rpmbuild/SPECS/flexisip-account-manager.spec
|
||||
|
||||
tar cvf flexisip-account-manager.tar.gz -C $(OUTPUT_DIR) flexisip-account-manager
|
||||
tar cf 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:
|
||||
rm -rf $(OUTPUT_DIR)/flexisip-account-manager
|
||||
rm -rf $(OUTPUT_DIR)/rpmbuild/SPECS $(OUTPUT_DIR)/rpmbuild/SOURCES $(OUTPUT_DIR)/rpmbuild/SRPMS $(OUTPUT_DIR)/rpmbuild/BUILD $(OUTPUT_DIR)/rpmbuild/BUILDROOT
|
||||
|
||||
rpm-only:
|
||||
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 Package Created"
|
||||
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
|
||||
@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
|
||||
@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/.
|
||||
rm -r rpmbuild
|
||||
|
||||
deb-only:
|
||||
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
|
||||
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
|
||||
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.0), 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.2), 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"
|
||||
|
|
@ -89,9 +96,22 @@ deb-only:
|
|||
|
||||
mv *.deb build/.
|
||||
|
||||
rpm: prepare package-semvers package-common rpm-only cleanup-package-semvers package-end-common
|
||||
rpm-dev: prepare-dev package-semvers package-common rpm-only cleanup-package-semvers package-end-common
|
||||
deb: prepare package-semvers package-common deb-only cleanup-package-semvers package-end-common
|
||||
deb-dev: prepare-dev package-semvers package-common deb-only cleanup-package-semvers package-end-common
|
||||
prepare-common: prepare package-semvers package-common
|
||||
|
||||
package-el8: rpm-el8-only rpm-cleanup cleanup-package-semvers package-end-common
|
||||
rpm-el8: prepare-common package-el8
|
||||
rpm-el8-dev: prepare-dev package-semvers package-common package-el8
|
||||
|
||||
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
|
||||
|
||||
.PHONY: rpm
|
||||
|
|
|
|||
186
README.md
186
README.md
|
|
@ -6,8 +6,6 @@ Flexisip Account Manager brings several tools in one:
|
|||
- a web portal, powered by FlexiAPI
|
||||
- a remote provisioning server, able to generate configuration files compatible with Linphone's QrCode-based or URL-based remote provisioning feature
|
||||
|
||||
It replaces the historical XMLRPC tool that is still available in `xmlrpc/` for retrocompatibility purpose.
|
||||
|
||||
# License
|
||||
|
||||
Copyright © Belledonne Communications
|
||||
|
|
@ -19,148 +17,44 @@ Flexisip is dual licensed, and can be licensed and distributed:
|
|||
|
||||
# Documentation
|
||||
|
||||
Once deployed you can have access to the global and API documentation on the `/api` and `/documentation` pages.
|
||||
Once deployed you can have access to the global and API documentation on the `/api` and `/provisioning/documentation` pages.
|
||||
|
||||
# Setup
|
||||
|
||||
## DotEnv configuration
|
||||
|
||||
FlexiAPI relies on [DotEnv](https://github.com/vlucas/phpdotenv) to be configured. This configuration can be accessed using the existing `.env` file that can be itself overwritten by an environnement variables.
|
||||
|
||||
Thoses variables can then be set using Docker-Compose, a bash script or a web-server for example.
|
||||
|
||||
If you're installing FlexiAPI from the RPM package you can find the configuration file at `/etc/flexisip-account-manager/flexiapi.env`.
|
||||
|
||||
## Manual setup
|
||||
|
||||
Clone the repository, install the dependencies and generate a key.
|
||||
|
||||
composer install --no-dev
|
||||
php artisan key:generate
|
||||
|
||||
Then configure the database connection in the `.env` file (from the `.env.example` one). And migrate the tables. The migration *MUST* be run on an empty database. The `.env` file will be available at the root of the project or often located in `/etc/flexisip-account-manager` in packaged versions.
|
||||
|
||||
php artisan migrate
|
||||
|
||||
You can also run the test suit using `phpunit`.
|
||||
|
||||
To know more about the web server configuration part, you can directly [visit the official Laravel installation documentation](https://laravel.com/docs/8.x).
|
||||
|
||||
### Apache2 server configuration
|
||||
|
||||
The package will deploy a `flexisip-account-manager.conf` file in the apache2 configuration directory.
|
||||
This file can be loaded and configured in your specific VirtualHost configuration.
|
||||
|
||||
### Configure the .env file
|
||||
|
||||
Complete all the other variables in the `.env` file or by overwritting them in your Docker or web-server configuration:
|
||||
- The OVH SMS connector
|
||||
- SMTP configuration
|
||||
- App name, SIP domain…
|
||||
|
||||
### Multi instances environement
|
||||
|
||||
FlexiAPI can also handle multi domains setup.
|
||||
|
||||
#### Multiple virtualhosts option
|
||||
|
||||
In your web server configuration create several virtualhosts that are pointing to the same FlexiAPI instance.
|
||||
Using the environnement variables you can then configure FlexiAPI per instance.
|
||||
|
||||
With Apache, use the [mod_env](https://httpd.apache.org/docs/2.4/mod/mod_env.html) module.
|
||||
|
||||
SetEnv APP_NAME "VirtualHost One"
|
||||
|
||||
On nginx use `fastcgi_param` to pass the parameter directly to PHP.
|
||||
|
||||
location ~ [^/]\.php(/|$) {
|
||||
…
|
||||
include /etc/nginx/fastcgi_params;
|
||||
fastcgi_param APP_NAME "VirtualHost Two";
|
||||
}
|
||||
|
||||
|
||||
> **Warning** Do not create a cache of your configuration (using `artisan config:cache`) if you have a multi-environnement setup.
|
||||
> The cache is always having the priority on the variables set in the configuration files.
|
||||
|
||||
#### Multiple .env option
|
||||
|
||||
To do so, configure several web servers virtualhosts and set a specific `APP_ENV` environnement variable in each of them.
|
||||
|
||||
Note that if `APP_ENV` is not set FlexiAPI will directly use the default `.env` file.
|
||||
|
||||
FlexiAPI will then try to load a custom configuration file with the following name `.env.$APP_ENV`. So for the previous example `.env.foobar`.
|
||||
|
||||
You can then configure your instances with specific values.
|
||||
|
||||
INSTANCE_COPYRIGHT="FooBar - Since 1997"
|
||||
INSTANCE_INTRO_REGISTRATION="Welcome on the FooBar Server"
|
||||
INSTANCE_CUSTOM_THEME=true
|
||||
…
|
||||
|
||||
#### Custom theme
|
||||
|
||||
If you set `INSTANCE_CUSTOM_THEME` to true, FlexiAPI will try to load a CSS file located in `public/css/$APP_ENV.style.css`. If the file doesn't exists it will fallback to `public/css/style.css`.
|
||||
|
||||
We advise you to copy the `style.css` file and rename it to make your custom CSS configurations for your instance.
|
||||
|
||||
#### Flexisip Push notifications pusher
|
||||
|
||||
The API endpoint `POST /account_creation_tokens/send-by-push` uses the `flexisip_pusher` binary delivered by the [Flexisip](https://gitlab.linphone.org/BC/public/flexisip) project (and related package). You must configure the `APP_FLEXISIP_PUSHER_PATH` and `APP_FLEXISIP_PUSHER_FIREBASE_KEY` environnement variables to point to the correct binary.
|
||||
|
||||
APP_FLEXISIP_PUSHER_PATH=/opt/belledonne-communications/bin/flexisip_pusher
|
||||
|
||||
This binary will be executed under "web user" privileges. Ensure that all the related files required by `flexisip_pusher` can be accessed using this user account.
|
||||
|
||||
/var/opt/belledonne-communications/log/flexisip/flexisip-pusher.log // Write permissions
|
||||
/etc/flexisip/apn/*pem // Read permissions
|
||||
|
||||
### SELinux restrictions
|
||||
|
||||
If you are running on a CentOS/RedHat machine, please ensure that SELinux is correctly configured.
|
||||
|
||||
Allow the webserver user to write in the `storage/` directory:
|
||||
|
||||
chcon -R -t httpd_sys_rw_content_t storage/
|
||||
|
||||
Don't forget to make this change persistent if the directory may be relabeled :
|
||||
|
||||
semanage fcontext -a -t httpd_sys_rw_content_t storage/
|
||||
|
||||
You can use the restorecon command to verify that this is working :
|
||||
|
||||
restorecon storage/
|
||||
|
||||
If your database is located on a remote machine, you should also allow your webserver user to connect to remote hosts:
|
||||
|
||||
semanage port -a -t http_port_t -p tcp 3306 // Open remote connections on the MySQL port for example
|
||||
setsebool -P httpd_can_network_connect 1 // Allow remote network connected
|
||||
setsebool -P httpd_can_network_connect_db 1 // Allow remote database connection
|
||||
|
||||
If you are planning to send emails using your account manager:
|
||||
|
||||
setsebool -P httpd_can_sendmail 1 // Allow email to be sent
|
||||
Check the [INSTALL.md](INSTALL.md) and [CHANGELOG.md](CHANGELOG.md) files.
|
||||
|
||||
## Usage
|
||||
|
||||
For the web panel, a general documentation is available under the `/documentation` page.
|
||||
For the REST API, the `/api` page contains all the required documentation to authenticate and request the API.
|
||||
FlexiAPI is also providing endpoints to provision Liblinphone powered devices. You can find more documentation about it on the `/provisioning/documentation` documentation page.
|
||||
|
||||
## Console commands
|
||||
|
||||
FlexiAPI is shipped with several console commands that you can launch using the `artisan` executable available at the root of this project.
|
||||
|
||||
### Migrate an old database
|
||||
### Create or update a Space
|
||||
|
||||
FlexiAPI needs an empty database to run its migration. The following console command allow you to import simultanously an exisiting Flexisip database and the old FlexiAPI SQLite database file (if you have one) in the new one. To do so, please specify the new database configuration in the `.env` file and run the following command.
|
||||
Create or update a Space, required to then create accounts afterward. The `super` option enable/disable the domain as a super domain.
|
||||
|
||||
The migration script requires at least 300mb of memory to run. You should edit the `memory_limit` in the `php.ini` file to increase it.
|
||||
php artisan spaces:create-update {sip_domain} {host} {name} {--super}
|
||||
|
||||
php artisan db:import {old_dbname} {old_sqlite_file_path?} --username={old_username} --password={old_password}
|
||||
### Import the old DotEnv instance configuration into a Space
|
||||
|
||||
Several other parameters are also available to customize the migration process, you can list them all using the command documentation.
|
||||
Since 2.0 some environnement instance configuration variables were moved into the Space configuration, you can import them using this command.
|
||||
|
||||
php artisan -h db:import
|
||||
php artisan spaces:import-configuration-from-dot-env {sip_domain}
|
||||
|
||||
⚠️ Be careful, during this import only the project DotEnv file variables will be imported, other environnement (eg. set in Apache, nginx or Docker) will be ignored.
|
||||
|
||||
⚠️ The content of the `ACCOUNT_PROVISIONING_RC_FILE` will not be imported. You will have to extract the sections and lines that you want to use manually using the dedicated form or the API.
|
||||
|
||||
### Create an admin account
|
||||
|
||||
Create an admin account, an API Key will also be generated along the way, it might expire after a while (regarding the API Key expiration policy). An empty `api_key_ip` will remove the IP restriction on the key.
|
||||
|
||||
If no parameters are put, a default admin account will be created.
|
||||
|
||||
php artisan accounts:create-admin-account {-u|username=} {-p|password=} {-d|domain=} {-k|api_key_ip=}
|
||||
|
||||
### Clear the expired API Keys
|
||||
|
||||
|
|
@ -192,41 +86,33 @@ The base request will not delete the related tombstones by default. You need to
|
|||
|
||||
### Set an account admin
|
||||
|
||||
This command will set the admin role to any available Flexisip account (the external Flexisip database need to be configured beforehand). You need to use the account DB id as a parameter in this command.
|
||||
This command will set the admin role to any available Flexisip account. You need to use the account DB id as a parameter in this command.
|
||||
|
||||
php artisan accounts:set-admin {account_id}
|
||||
|
||||
Once one account is declared as administrator, you can directly configure the other ones using the web panel.
|
||||
|
||||
### Generate External Accounts
|
||||
### Seed liblinphone test accounts
|
||||
|
||||
Generate `amount` accounts defined by the `group` label.
|
||||
The generated accounts will have a random username suffixed by the group name.
|
||||
You can also seed the tables with test accounts for the liblinphone test suite with the following command (check LiblinphoneTesterAccountSeeder for the JSON syntax):
|
||||
|
||||
accounts:generate-external {amount} {group}
|
||||
php artisan accounts:seed /path/to/accounts.json
|
||||
|
||||
### Export External Accounts
|
||||
## SMS templates
|
||||
|
||||
Export all the accounts defined by the `group` label.
|
||||
The command generates a JSON file containing the accounts ready to by imported as External Accounts in the current directory. A specific path can be defined using the `--o|output` optional parameter.
|
||||
To send SMS to the USA some providers need to validate their templates before transferring them, see [Sending SMS messages to the USA - OVH](https://help.ovhcloud.com/csm/en-ie-sms-sending-sms-to-usa?id=kb_article_view&sysparm_article=KB0051359).
|
||||
|
||||
accounts:export-to-externals {group} {--o|output=}
|
||||
Here are the currently used SMS templates in the app to declare in your provider panel:
|
||||
- Validation code: `Your #APP_NAME# validation code is #CODE#`. Sent to validate the phone change by SMS.
|
||||
- Validation code with expiration: `Your #APP_NAME# validation code is #CODE#. The code is available for #CODE_MINUTES# minutes`. Sent to validate the phone change by SMS, include an expiration time.
|
||||
|
||||
### Import External Accounts
|
||||
|
||||
Import accounts previously exported as a JSON file. Accounts previously imported will be skipped in the process.
|
||||
|
||||
accounts:import-externals {file_path}
|
||||
|
||||
## Custom email templaces
|
||||
## Custom email templates
|
||||
|
||||
Some email templates can be customized.
|
||||
|
||||
To do so, copy and rename the existing `*_custom.blade.php.example` files into `*custom.blade.php` and adapt the content of the email (HTML and text versions), those files will then replace the default ones.
|
||||
|
||||
## Provisioning
|
||||
|
||||
FlexiAPI is providing endpoints to provision Liblinphone powered devices. You can find more documentation about it on the `/api#provisioning` documentation page.
|
||||
## Hooks
|
||||
|
||||
### Provisioning hooks
|
||||
|
||||
|
|
@ -235,11 +121,9 @@ The XML returned by the provisioning endpoint can be completed using hooks.
|
|||
To do so, copy and rename the `provisioning_hooks.php.example` file into `provisioning_hooks.php` in the configuration directory and complete the functions in the file.
|
||||
The functions already contains example codes to show you how the XML can be enhanced or completed.
|
||||
|
||||
### Seed liblinphone test accounts
|
||||
### Account Service hooks
|
||||
|
||||
You can also seed the tables with test accounts for the liblinphone test suite with the following command (check LiblinphoneTesterAccoutSeeder for the JSON syntax):
|
||||
|
||||
php artisan accounts:seed /path/to/accounts.json
|
||||
The internal Account Service is also providing hooks. Rename and complete the following file to enable and use them: `account_service_hooks.php.example`.
|
||||
|
||||
## Sending SIP messages from the API
|
||||
|
||||
|
|
@ -253,7 +137,7 @@ The `POST /api/messages` endpoint allows you to send messages on the SIP network
|
|||
|
||||
APP_LINPHONE_DAEMON_UNIX_PATH=/tmp/ld
|
||||
|
||||
If you have issues connecting to that socket check the [`systemd restrictions`](#systemd-restrictions) part of this document.
|
||||
If you have issues connecting to that socket check the [`systemd restrictions`](INSTALL.md#systemd-restrictions) part of this document.
|
||||
|
||||
The socket is located in the `/tmp` directory.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,94 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* The SIP domain to use to hash passwords.
|
||||
*
|
||||
* Default value: sip.example.org
|
||||
*/
|
||||
define("SIP_DOMAIN", "sip.example.org");
|
||||
|
||||
/*
|
||||
* If true, when account is created, the password will be generated automatically (see below).
|
||||
* Otherwise it has to be given as the last parameter of the create_account method call.
|
||||
*
|
||||
* Default value: False
|
||||
*/
|
||||
define("GENERATE_PASSWORD_ENABLED", False);
|
||||
|
||||
/*
|
||||
* A string with each character allowed in the password generation.
|
||||
*
|
||||
* Default value: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789``-=~!@#$%^&*()_+,./<>?;:[]{}\|
|
||||
*/
|
||||
define("GENERATED_PASSWORD_CHARACTERS", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789``-=~!@#$%^&*()_+,./<>?;:[]{}\|");
|
||||
|
||||
/*
|
||||
* The length of the passwords that will be generated.
|
||||
*
|
||||
* Default value: 8
|
||||
*/
|
||||
define("GENERATED_PASSWORD_LENGTH", 8);
|
||||
|
||||
/*
|
||||
* The default algorithm to use if not specified in the request
|
||||
*
|
||||
* Default value: MD5
|
||||
*/
|
||||
define("DEFAULT_ALGORITHM", "MD5");
|
||||
|
||||
/*
|
||||
* A string with each character allowed in the username generation.
|
||||
*
|
||||
* Default value: abcdefghijklmnopqrstuvwxyz0123456789.-_
|
||||
*/
|
||||
define("GENERATED_USERNAME_CHARACTERS", "abcdefghijklmnopqrstuvwxyz0123456789.-_");
|
||||
|
||||
/*
|
||||
* The length of the username that will be generated.
|
||||
*
|
||||
* Default value: 12
|
||||
*/
|
||||
define("GENERATED_USERNAME_LENGTH", 12);
|
||||
|
||||
/*
|
||||
* If set to True, a created account will automatically be activated and it's expiration date set to now + TRIAL_DURATION_DAYS,
|
||||
* otherwise expiration date for trial will be set when account is activated via a different xml rpc call.
|
||||
*/
|
||||
define('AUTO_ACTIVATE_ACCOUNT', False);
|
||||
|
||||
/*
|
||||
* Send an email to activate the account when it is created.
|
||||
*/
|
||||
define('SEND_ACTIVATION_EMAIL', True);
|
||||
|
||||
/*
|
||||
* Send a sms to activate the phone account when it is created.
|
||||
*/
|
||||
define('SEND_ACTIVATION_SMS', True);
|
||||
|
||||
/*
|
||||
* If false, creating an account with an email that is already used for another account will trigger an error
|
||||
*/
|
||||
define('ALLOW_SAME_EMAILS_ON_MULTILPLE_ACCOUNTS', True);
|
||||
|
||||
/*
|
||||
* If true, when an account creation request is received for an existing number, assumes recover procedure
|
||||
*/
|
||||
define('RECOVER_ACCOUNT_IF_EXISTS', False);
|
||||
|
||||
/*
|
||||
* Enabling geoloc of accounts in user_info table.
|
||||
* When this option is set, the fields coutry_name and country_code will be filled
|
||||
* with a call to api.ipapi.com
|
||||
*
|
||||
* Default value: False
|
||||
*/
|
||||
define("ENABLE_NEW_ACCOUNTS_GEOLOC", False);
|
||||
|
||||
/* API key for geoloc. If you need geoloc and don't have a key,
|
||||
* ask it on ipapi.com
|
||||
*/
|
||||
|
||||
define("GEOLOC_ACCESS_KEY", "");
|
||||
|
||||
?>
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
<?php
|
||||
|
||||
/* ### Authentication configuration ### */
|
||||
|
||||
/*
|
||||
* Attempt to authenticate sensitive xmlrpc requests using DIGEST.
|
||||
*
|
||||
* Default value: FALSE
|
||||
*/
|
||||
define("USE_DIGEST_AUTH", FALSE);
|
||||
|
||||
/*
|
||||
* The domain to use for digest auth.
|
||||
*
|
||||
* Default value: sip.example.org
|
||||
*/
|
||||
define("AUTH_REALM", "sip.example.org");
|
||||
|
||||
/* Authentication Nonce Key
|
||||
* This value must be a random string(12 characters minimum length) specific to each server and is PRIVATE
|
||||
*
|
||||
* Default value : The default is empty to force using a key different for each server
|
||||
*/
|
||||
define("AUTH_NONCE_KEY", "");
|
||||
|
||||
/* Authentication Nonce Validity
|
||||
* The authentication is aimed to provide a one time usage nonce, it is not strictly inforced by storing valid once, instead
|
||||
* we use a short living period, the maximum validity period will be twice the minimum one, value is in seconds
|
||||
*
|
||||
* Default value : 10 seconds
|
||||
*/
|
||||
define("MIN_NONCE_VALIDITY_PERIOD", 10);
|
||||
|
||||
?>
|
||||
126
conf/db.conf
126
conf/db.conf
|
|
@ -1,126 +0,0 @@
|
|||
<?php
|
||||
|
||||
/* ### Database configuration ### */
|
||||
|
||||
/*
|
||||
* The host on which the database is located.
|
||||
*
|
||||
* Default value: localhost
|
||||
*/
|
||||
define("DB_HOST", "localhost");
|
||||
|
||||
/*
|
||||
* Enable data transfert over ssl.
|
||||
*
|
||||
* Default value: False
|
||||
*/
|
||||
|
||||
define("DB_ENABLE_SSL", "False");
|
||||
|
||||
/*
|
||||
* rootca path. MANDATORY for DB SSL to work
|
||||
*
|
||||
* Default value: ""
|
||||
* Possible value : /opt/belledonne-communications/share/linphone/rootca.pem
|
||||
*/
|
||||
|
||||
define("ROOT_CA_PATH", "");
|
||||
|
||||
/*
|
||||
* The database username.
|
||||
*
|
||||
* Default value: flexisip_rw
|
||||
*/
|
||||
define("DB_USER", "flexisip_rw");
|
||||
|
||||
/*
|
||||
* The database user's password.
|
||||
*
|
||||
* Default value:
|
||||
*/
|
||||
define("DB_PASSWORD", "");
|
||||
|
||||
/*
|
||||
* The name of the database.
|
||||
*
|
||||
* Default value: flexisip
|
||||
*/
|
||||
define("DB_NAME", "flexisip");
|
||||
|
||||
/*
|
||||
* The name of the accounts table.
|
||||
*
|
||||
* Default value: accounts
|
||||
*/
|
||||
define("ACCOUNTS_DB_TABLE", "accounts");
|
||||
|
||||
/*
|
||||
* The name of the accounts_algo table.
|
||||
*
|
||||
* Default value: passwords
|
||||
*/
|
||||
define("ACCOUNTS_ALGO_DB_TABLE", "passwords");
|
||||
|
||||
/*
|
||||
* The name of the aliases table.
|
||||
* It is used to store links between an alias (phone number, facebook id, google email, ...) and a SIP address
|
||||
*
|
||||
* Default value: aliases
|
||||
*/
|
||||
define("ALIAS_DB_TABLE", "aliases");
|
||||
|
||||
/*
|
||||
* The name of the devices table.
|
||||
* It is used to store hardware information about devices running linphone
|
||||
*
|
||||
* Default value: devices
|
||||
*/
|
||||
define("DEVICES_DB_TABLE", "devices");
|
||||
|
||||
/*
|
||||
* The name of the sms table.
|
||||
* It is used to keep track of sent SMS
|
||||
*
|
||||
* Default value: sms
|
||||
*/
|
||||
define("SMS_DB_TABLE", "sms");
|
||||
|
||||
/*
|
||||
* The name of the inapp table.
|
||||
* It is used to store informations about in-app purchases, accounts expiration, etc...
|
||||
*
|
||||
* Default value: inapp_purchases
|
||||
*/
|
||||
define("INAPP_DB_TABLE", "inapp_purchases");
|
||||
|
||||
/*
|
||||
* The name of the user informations table.
|
||||
* It is used to store informations about user like firstname, lastname, gender, etc...
|
||||
*
|
||||
* Default value: user_info
|
||||
*/
|
||||
define("USER_INFO_DB_TABLE", "user_info");
|
||||
|
||||
/*
|
||||
* The delay in minutes before test account expiration.
|
||||
* It is used to delete old test accounts from database;
|
||||
*
|
||||
* Default value: 180
|
||||
*/
|
||||
define("EXPIRATION_DELAY", 180);
|
||||
|
||||
/*
|
||||
* The value to use in the database after a one time confirmation has been used
|
||||
*
|
||||
* Default value: ERROR
|
||||
*/
|
||||
define ("INVALID_CONFIRMATION_KEY", "ERROR");
|
||||
|
||||
/*
|
||||
* Update confirmation key to INVALID_CONFIRMATION_KEY after correct use
|
||||
*
|
||||
* Default value: TRUE
|
||||
*/
|
||||
define("REMOVE_CONFIRMATION_KEY_AFTER_USE", TRUE);
|
||||
|
||||
?>
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
<?php
|
||||
|
||||
/* ### Email configuration ### */
|
||||
|
||||
/*
|
||||
* Whever or not enable the send email feature.
|
||||
* Used to send link to generate random password if user forgot it, or the newly generated email once the link has been clicked.
|
||||
*
|
||||
* Default value: False
|
||||
*/
|
||||
define("EMAIL_ENABLED", False);
|
||||
|
||||
/*
|
||||
* The website address to display in the email header.
|
||||
*
|
||||
* Default value: https://linphone.org
|
||||
*/
|
||||
define("EMAIL_SITE", "https://linphone.org");
|
||||
|
||||
/*
|
||||
* The link to open when click on activation
|
||||
* It must have a %key% and %username% parameters and eventually a %domain% and/or %algo%
|
||||
*
|
||||
* Default value: www.linphone.org/flexisip-account-manager/activation.php?username=%username%&confirmation_key=%key%&domain=%domain%&algorithm=%algo%
|
||||
*/
|
||||
define("EMAIL_ACTIVATION_LINK", "www.linphone.org/flexisip-account-manager/activation.php?username=%username%&confirmation_key=%key%&domain=%domain%&algorithm=%algo%");
|
||||
|
||||
/*
|
||||
* The FROM address to set in the email header.
|
||||
*
|
||||
* Default value: no.reply@linphone.org
|
||||
*/
|
||||
define("EMAIL_FROM_ADDR", "no.reply@linphone.org");
|
||||
|
||||
/*
|
||||
* The FROM display name to set in the email header.
|
||||
*
|
||||
* Default value: No reply at Linphone.org
|
||||
*/
|
||||
define("EMAIL_FROM_NAME", "No reply at Linphone.org");
|
||||
|
||||
/*
|
||||
* The subject of the activation account email.
|
||||
*/
|
||||
define("EMAIL_ACTIVATION_SUBJECT", "Start your sip.linphone.org service");
|
||||
|
||||
/*
|
||||
* The body (as text) of the activation account email.
|
||||
* It must have a %link% parameter somewhere with the link to click to activate the account
|
||||
*/
|
||||
define("EMAIL_ACTIVATION_BODY", "Hello,\nActivation pending for using your Linphone account.\nPlease use the link bellow to activate your account :\n\n%link%\n\nRegards,\nThe Linphone team.\n");
|
||||
|
||||
/*
|
||||
* The body (as html) of the activation account email.
|
||||
* It must have a %link% parameter somewhere with the link to click to activate the account
|
||||
*/
|
||||
define("EMAIL_ACTIVATION_BODY_HTML", '<html><head><title>Start your sip.linphone.org service</title></head><body><p>Hello,</p><p>Activation pending for using your Linphone account.<br />Please use the link bellow to activate your account :</p><p><a href="%link%">%link%</a></p><p> </p><p>Regards,<br />The Linphone team.</p></body></html>');
|
||||
|
||||
/*
|
||||
* The subject of the account recovery email.
|
||||
*/
|
||||
define("EMAIL_RECOVERY_SUBJECT", "Recover your sip.linphone.org account");
|
||||
|
||||
/*
|
||||
* The body (as text) of the account recovery email.
|
||||
* It must have a %key% parameter that will be replaced with the recovery code
|
||||
*/
|
||||
define("EMAIL_RECOVERY_BODY", "Hello,\nHere is your recovery code: %key%\n\nRegards,\nThe Linphone team.\n");
|
||||
|
||||
/*
|
||||
* The body (as html) of the account recovery email.
|
||||
* It must have a %key% parameter that will be replaced with the recovery code
|
||||
*/
|
||||
define("EMAIL_RECOVERY_BODY_HTML", '<html><head><title>Recover your sip.linphone.org account</title></head><body><p>Hello,</p><p>Here is your recovery code: %key%</p><p>Regards,<br />The Linphone team.</p></body></html>');
|
||||
|
||||
?>
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
<?php
|
||||
|
||||
/* ### Hooks configuration ### */
|
||||
|
||||
/*
|
||||
* Set the following to TRUE to be called in the below functions
|
||||
*
|
||||
* Default value: FALSE
|
||||
*/
|
||||
define('CUSTOM_HOOKS', FALSE);
|
||||
|
||||
/** ### Hooks implementation ### */
|
||||
|
||||
function hook_on_account_created($account) {
|
||||
|
||||
}
|
||||
|
||||
function hook_on_account_activated($account) {
|
||||
|
||||
}
|
||||
|
||||
/** ### request_params array my contain username, domain, transport, ha1 and algo ### */
|
||||
|
||||
function provisioning_hook_on_proxy_config(&$xml, $request_params) {
|
||||
$xml .= '<entry name="conference_factory_uri" overwrite="true">sip:conference-factory@' . $request_params["domain"] . '</entry>';
|
||||
}
|
||||
function provisioning_hook_on_auth_info(&$xml, $request_params) {
|
||||
|
||||
}
|
||||
|
||||
function provisioning_hook_on_additional_section(&$xml, $request_params) {
|
||||
$xml .= '<section name="sip">';
|
||||
$xml .= '<entry name="rls_uri" overwrite="true">sips:rls@' . $request_params["domain"] . '</entry>';
|
||||
$xml .= '</section>';
|
||||
}
|
||||
|
||||
?>
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* If set to True, a created account will be flagged as trial with an expiration date set in TRIAL_DURATION_DAYS days in the future.
|
||||
*/
|
||||
define ('USE_IN_APP_PURCHASES', False);
|
||||
|
||||
/*
|
||||
* This value determines the number of days for trial starting when the account will be activated.
|
||||
*
|
||||
* Default value: 365
|
||||
*/
|
||||
define('TRIAL_DURATION_DAYS', 365);
|
||||
|
||||
/* ### Apple/ioS configuration ### */
|
||||
|
||||
/*
|
||||
* The URL to use to validate an Apple in app purchase receipts.
|
||||
*
|
||||
* Default value: https://buy.itunes.apple.com/verifyReceipt
|
||||
*/
|
||||
define("APPLE_URL", "https://buy.itunes.apple.com/verifyReceipt");
|
||||
|
||||
/*
|
||||
* The URL to use to validate an Apple in app purchase receipts while app is in development.
|
||||
*
|
||||
* Default value: https://buy.itunes.apple.com/verifyReceipt
|
||||
*/
|
||||
define("APPLE_SANDBOX_URL", "https://sandbox.itunes.apple.com/verifyReceipt");
|
||||
|
||||
/*
|
||||
* The shared secret for your application.
|
||||
* Used to validate in app purchase receipts.
|
||||
*
|
||||
* Default value:
|
||||
*/
|
||||
define("APPLE_SECRET", "");
|
||||
|
||||
/* ### Google/Android configuration ### */
|
||||
|
||||
/*
|
||||
* The package name of your Android application.
|
||||
* Used to validate in app purchase receipts.
|
||||
*
|
||||
* Default value: org.linphone
|
||||
*/
|
||||
define("ANDROID_PACKAGE", "org.linphone");
|
||||
|
||||
/*
|
||||
* The path to the public key generated by the Android Play Store.
|
||||
* See the documentation to know how to get it.
|
||||
*
|
||||
* Default value: google.pem
|
||||
*/
|
||||
define("ANDROID_PUB_KEY_PATH", "google.pem");
|
||||
|
||||
/*
|
||||
* The URL to use to get the authentication token to make calls to Google API server.
|
||||
* Used to validate in app purchase receipts.
|
||||
*
|
||||
* Default value: https://accounts.google.com/o/oauth2/token
|
||||
*/
|
||||
define("GOOGLE_API_OAUTH_URL", "https://accounts.google.com/o/oauth2/token");
|
||||
|
||||
/*
|
||||
* The project ID with the access to the Android Developer Console API
|
||||
* Used to validate in app purchase receipts.
|
||||
*
|
||||
* Default value:
|
||||
*/
|
||||
define("GOOGLE_PROJECT_ID", "");
|
||||
|
||||
/*
|
||||
* The previous project ID's password
|
||||
* Used to validate in app purchase receipts.
|
||||
*
|
||||
* Default value:
|
||||
*/
|
||||
define("GOOGLE_PROJECT_PASSWORD", "");
|
||||
|
||||
/*
|
||||
* The refresh token generated by the Google Developer server.
|
||||
* See documentation to know how to get it.
|
||||
* Used to validate in app purchase receipts.
|
||||
*
|
||||
* Default value:
|
||||
*/
|
||||
define("GOOGLE_PROJECT_REFRESH_TOKEN", "");
|
||||
|
||||
?>
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
<?php
|
||||
|
||||
/* ### Logs configuration ### */
|
||||
|
||||
/*
|
||||
* Whever or not to log each function called.
|
||||
* Passwords are never logged.
|
||||
*
|
||||
* Default value: True
|
||||
*/
|
||||
define("LOGS_ENABLED", True);
|
||||
|
||||
/*
|
||||
* Whever or not to log everything in the same file.
|
||||
* If false, a new log file will be created every day.
|
||||
*
|
||||
* Default value: True
|
||||
*/
|
||||
define("USE_ONE_LOG_FILE", True);
|
||||
|
||||
/*
|
||||
* The file in which to log.
|
||||
*
|
||||
* Default value: "/var/opt/belledonne-communications/log/account-manager.log"
|
||||
*/
|
||||
define("LOG_FILE", "/var/opt/belledonne-communications/log/account-manager.log");
|
||||
|
||||
/*
|
||||
* The dir in which to log.
|
||||
*
|
||||
* Default value: "/var/opt/belledonne-communications/log/account-manager.log"
|
||||
*/
|
||||
define("LOG_DIR", "/var/opt/belledonne-communications/log/");
|
||||
|
||||
?>
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
<?php
|
||||
|
||||
/* ### Overloads configuration ### */
|
||||
|
||||
/*
|
||||
* Set the following to TRUE to overload the xmlrpc_recover_phone_account function
|
||||
*
|
||||
* Default value: FALSE
|
||||
*/
|
||||
define('XMLRPC_RECOVER_PHONE_ACCOUNT_OVERLOAD', FALSE);
|
||||
|
||||
/** ### Overloads implementation */
|
||||
// We may need to access some of the functions provided by the server (database access at least)
|
||||
// so we need to know where to find them
|
||||
define("PATH_TO_INSTALLATION", "/opt/belledonne-communications/share/flexisip-account-manager");
|
||||
|
||||
if (XMLRPC_RECOVER_PHONE_ACCOUNT_OVERLOAD === True) {
|
||||
include_once PATH_TO_INSTALLATION . '/database/database.php';
|
||||
}
|
||||
|
||||
// args = [phone, [domain], [lang]]
|
||||
// is expected to return the recovered account username or ACCOUNT_NOT_FOUND
|
||||
function xmlrpc_recover_phone_account_overload($method, $args) {
|
||||
}
|
||||
|
||||
?>
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* If set to True, each section will be flagged as overwrite, otherwise none of them will be flagged.
|
||||
*
|
||||
* Default value: False
|
||||
*/
|
||||
define("REMOTE_PROVISIONING_OVERWRITE_ALL", False);
|
||||
|
||||
/*
|
||||
* The path to a default linphone rc file to add to the generated remote provisioning
|
||||
* If using the default value, the default.rc file should be created in /opt/belledonne-communications/share/flexisip-account-manager/xmlrpc/ directory
|
||||
* If the file does not exists it is ignored
|
||||
*
|
||||
* The file should follow the lpconfig format, for example:
|
||||
* [sip]
|
||||
* rls_uri=sips:rls@sip.linphone.org
|
||||
* # This is a commentary, it won't appear in the generated xml provisioning
|
||||
*
|
||||
* Default value: "default.rc"
|
||||
*/
|
||||
define("REMOTE_PROVISIONING_DEFAULT_CONFIG", "default.rc");
|
||||
|
||||
/*
|
||||
* The default transport to set in the proxy config if not specified
|
||||
* Can be "tls", "tcp" or "udp"
|
||||
*
|
||||
* Default value: "tls"
|
||||
*/
|
||||
define("REMOTE_PROVISIONING_DEFAULT_TRANSPORT", "tls");
|
||||
|
||||
/*
|
||||
* If set to True, provisioning.php will generate a new password if the account was not activated yet and activate it.
|
||||
*
|
||||
* Default value: False
|
||||
*/
|
||||
define("REMOTE_PROVISIONING_ONE_TIME_PASSWORD", False);
|
||||
|
||||
/*
|
||||
* If set to True, digest authentication will be asked for remote provisioning process (see auth.conf).
|
||||
*
|
||||
* Default value: False
|
||||
*/
|
||||
define("REMOTE_PROVISIONING_USE_DIGEST_AUTH", False);
|
||||
|
||||
?>
|
||||
124
conf/sms.conf
124
conf/sms.conf
|
|
@ -1,124 +0,0 @@
|
|||
<?php
|
||||
|
||||
/* ### SMS API configuration ### */
|
||||
|
||||
/*
|
||||
* Whever or not enable the send SMS feature.
|
||||
* Used to verify phone number when used as SIP username.
|
||||
*
|
||||
* Default value: False
|
||||
*/
|
||||
define("SMS_API_ENABLED", False);
|
||||
|
||||
/*
|
||||
* The application key for OVH SMS platform
|
||||
*
|
||||
* Default value:
|
||||
*/
|
||||
define("SMS_OVH_API_KEY", "");
|
||||
|
||||
/*
|
||||
* The application secret for OVH SMS platform
|
||||
*
|
||||
* Default value:
|
||||
*/
|
||||
define("SMS_OVH_API_SECRET", "");
|
||||
|
||||
/*
|
||||
* The consumer key for OVH SMS platform
|
||||
*
|
||||
* Default value:
|
||||
*/
|
||||
define("SMS_OVH_CONSUMER_KEY", "");
|
||||
|
||||
/*
|
||||
* The sender alias for OVH SMS
|
||||
*
|
||||
* Default value: "Linphone"
|
||||
*/
|
||||
define("SMS_OVH_SENDER", "Linphone");
|
||||
|
||||
/*
|
||||
* Whever or not to use a sender to send the SMS.
|
||||
* When using sender you can customize the name of the sender, otherwise it will be a phone number.
|
||||
* To disable for clients using our own OVH SMS account.
|
||||
*
|
||||
* Default value: True
|
||||
*/
|
||||
define("SMS_USE_SENDER", True);
|
||||
|
||||
/*
|
||||
* The sender reason for OVH SMS
|
||||
*
|
||||
* Default value: "created Linphone SMS sender"
|
||||
*/
|
||||
define("SMS_OVH_REASON", "created Linphone SMS sender");
|
||||
|
||||
/*
|
||||
* The sender description for OVH SMS
|
||||
*
|
||||
* Default value: "Linphone SMS sender"
|
||||
*/
|
||||
define("SMS_OVH_DESC", "Linphone SMS sender");
|
||||
|
||||
/*
|
||||
* The template to use to send SMS to the US
|
||||
* Remember to stay under 160 characters
|
||||
*
|
||||
* Default value: "Your Linphone validation code is #CODE#"
|
||||
*/
|
||||
define("SMS_OVH_US_TEMPLATE", "Your Linphone validation code is #CODE#");
|
||||
|
||||
/*
|
||||
* The OVH endpoint
|
||||
*
|
||||
* Default value: ovh-eu
|
||||
*/
|
||||
define("SMS_OVH_ENDPOINT", "ovh-eu");
|
||||
|
||||
/*
|
||||
* The URL at which the SMS API is available.
|
||||
*
|
||||
* Default value:
|
||||
*/
|
||||
define("SMS_API_URL", "");
|
||||
|
||||
/*
|
||||
* The username to authenticate to the SMS API if needed.
|
||||
*
|
||||
* Default value:
|
||||
*/
|
||||
define("SMS_API_USERNAME", "");
|
||||
|
||||
/*
|
||||
* The username's password to authenticate to the SMS API if needed.
|
||||
*
|
||||
* Default value:
|
||||
*/
|
||||
define("SMS_API_PASSWORD", "");
|
||||
|
||||
/*
|
||||
* The period of time (in milli seconds) over which we compute the number of sent sms
|
||||
*
|
||||
* Default value: 86400000 (24 hours)
|
||||
*/
|
||||
define("SMS_TIME_PERIOD", 86400000);
|
||||
|
||||
/*
|
||||
* The maximum number of allowed SMS to be sent over the period
|
||||
* MUST BE LESS THAN 255 !
|
||||
*
|
||||
* Default value: 3
|
||||
*/
|
||||
define("SMS_COUNT_LIMIT_IN_PERIOD", 3);
|
||||
|
||||
/**
|
||||
* Translation for OVH SMS template
|
||||
* Remember to stay under 160 characters
|
||||
*/
|
||||
$SMS_OVH_TEMPLATE = array (
|
||||
'US' => 'Your Linphone validation code is #CODE#', // This one isn't required but if present it MUST be equal to SMS_OVH_US_TEMPLATE
|
||||
'FR' => 'Votre code de validation Linphone est #CODE#',
|
||||
);
|
||||
|
||||
?>
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
<?php
|
||||
|
||||
/* ### Tests configuration ### */
|
||||
|
||||
/*
|
||||
* If true, more features are available for test purposes
|
||||
*
|
||||
* Default value: False
|
||||
*/
|
||||
define('ALLOW_TEST_ACCOUNTS', False);
|
||||
|
||||
/*
|
||||
* Prefix used only by tests account to enable/disable some features
|
||||
*
|
||||
* Default value: "+1000555"
|
||||
*/
|
||||
define("TESTS_PHONE_PREFIX", "+1000555");
|
||||
|
||||
/*
|
||||
* Prefix used only by tests account to enable/disable some features
|
||||
*
|
||||
* Default value: "XXXTEST"
|
||||
*/
|
||||
define("TESTS_LOGIN_PREFIX", "xxxtest");
|
||||
|
||||
?>
|
||||
1
cron/flexiapi.cron
Normal file
1
cron/flexiapi.cron
Normal file
|
|
@ -0,0 +1 @@
|
|||
* * * * * apache /opt/belledonne-communications/share/flexisip-account-manager/flexiapi/artisan schedule:run >> /dev/null 2>&1
|
||||
|
|
@ -1,7 +1,10 @@
|
|||
#!/bin/sh
|
||||
|
||||
cd /opt/belledonne-communications/share/flexisip-account-manager/flexiapi/
|
||||
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 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 spaces:expiration-emails
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
#!/bin/sh
|
||||
|
||||
cd /opt/belledonne-communications/share/flexisip-account-manager/flexiapi/
|
||||
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 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 spaces:expiration-emails
|
||||
|
|
|
|||
|
|
@ -1,52 +1,39 @@
|
|||
APP_NAME=FlexiAPI
|
||||
APP_ENV=local
|
||||
APP_KEY=
|
||||
APP_DEBUG=false
|
||||
APP_URL=http://localhost
|
||||
APP_SIP_DOMAIN=sip.example.com
|
||||
APP_ROOT_HOST=
|
||||
|
||||
APP_LINPHONE_DAEMON_UNIX_PATH=
|
||||
APP_FLEXISIP_PUSHER_PATH=
|
||||
APP_FLEXISIP_PUSHER_FIREBASE_KEY=
|
||||
APP_FLEXISIP_PUSHER_FIREBASE_KEYSMAP= # Each pair is separated using a space and defined as a key:value
|
||||
|
||||
APP_API_KEY_EXPIRATION_MINUTES=60 # Number of minutes the generated API Keys are valid
|
||||
APP_API_ACCOUNT_CREATION_TOKEN_RETRY_MINUTES=60 # Number of minutes between two consecutive account_creation_token creation
|
||||
|
||||
# Risky toggles
|
||||
APP_ADMINS_MANAGE_MULTI_DOMAINS=false # Allow admins to handle all the accounts in the database
|
||||
APP_DANGEROUS_ENDPOINTS=false # Enable some dangerous endpoints used for XMLRPC like fallback usage
|
||||
|
||||
# SIP server parameters
|
||||
ACCOUNT_PROXY_REGISTRAR_ADDRESS=sip.example.com # Proxy registrar address, can be different than the SIP domain
|
||||
ACCOUNT_TRANSPORT_PROTOCOL_TEXT="TLS (recommended), TCP or UDP" # Simple text, to explain how the SIP server can be reached
|
||||
ACCOUNT_REALM=null # Default realm for the accounts, fallback to the domain if not set, enforce null by default
|
||||
|
||||
# Account creation
|
||||
ACCOUNT_EMAIL_UNIQUE=false # Emails are unique between all the accounts
|
||||
ACCOUNT_CONSUME_EXTERNAL_ACCOUNT_ON_CREATE=false
|
||||
ACCOUNT_BLACKLISTED_USERNAMES=
|
||||
ACCOUNT_USERNAME_REGEX="^[a-z0-9+_.-]*$"
|
||||
|
||||
# Account provisioning
|
||||
ACCOUNT_PROVISIONING_RC_FILE=
|
||||
ACCOUNT_PROVISIONING_OVERWRITE_ALL=
|
||||
|
||||
# Instance specific parameters
|
||||
INSTANCE_COPYRIGHT= # Simple text displayed in the page footer
|
||||
INSTANCE_INTRO_REGISTRATION= # Markdown text displayed in the home page
|
||||
INSTANCE_CUSTOM_THEME=false
|
||||
INSTANCE_CONFIRMED_REGISTRATION_TEXT= # Markdown text displayed when an account is confirmed
|
||||
|
||||
WEB_PANEL=true # Fully enable/disable the web panels
|
||||
NEWSLETTER_REGISTRATION_ADDRESS= # Address to contact when a user wants to register to the newsletter
|
||||
PUBLIC_REGISTRATION=true # Toggle to enable/disable the public registration forms
|
||||
PHONE_AUTHENTICATION=true # Toggle to enable/disable the SMS support, requires public registration
|
||||
DEVICES_MANAGEMENT=false # Toggle to enable/disable the devices management support
|
||||
APP_ALLOW_PHONE_NUMBER_USERNAME_ADMIN_API=false # Allow phone numbers to be set as username in admin account creation endpoints
|
||||
|
||||
TERMS_OF_USE_URL= # A URL pointing to the Terms of Use
|
||||
PRIVACY_POLICY_URL= # A URL pointing to the Privacy Policy
|
||||
APP_PROJECT_URL= # A URL pointing to the project information page
|
||||
|
||||
LOG_CHANNEL=stack
|
||||
# Expiration time for tokens and code, in minutes, 0 means no expiration
|
||||
APP_API_ACCOUNT_CREATION_TOKEN_RETRY_MINUTES=60 # Number of minutes between two consecutive account_creation_token creation
|
||||
APP_ACCOUNT_CREATION_TOKEN_EXPIRATION_MINUTES=0
|
||||
APP_ACCOUNT_RECOVERY_TOKEN_EXPIRATION_MINUTES=0
|
||||
APP_EMAIL_CHANGE_CODE_EXPIRATION_MINUTES=10
|
||||
APP_PHONE_CHANGE_CODE_EXPIRATION_MINUTES=10
|
||||
APP_RECOVERY_CODE_EXPIRATION_MINUTES=10
|
||||
APP_PROVISIONING_TOKEN_EXPIRATION_MINUTES=0
|
||||
APP_API_KEY_EXPIRATION_MINUTES=60 # Number of minutes the unused API Keys are valid
|
||||
APP_RESET_PASSWORD_EMAIL_TOKEN_EXPIRATION_MINUTES=1440 # 24h
|
||||
|
||||
# Account creation and authentication
|
||||
ACCOUNT_EMAIL_UNIQUE=false # Emails are unique between all the accounts
|
||||
ACCOUNT_BLACKLISTED_USERNAMES=
|
||||
ACCOUNT_USERNAME_REGEX="^[a-z0-9+_.-]*$"
|
||||
ACCOUNT_DEFAULT_PASSWORD_ALGORITHM=SHA-256 # Can ONLY be MD5 or SHA-256 in capital, default to SHA-256
|
||||
ACCOUNT_AUTHENTICATION_BEARER= # Bearer value (WWW-Authenticate: Bearer <value>) of the external service that can provide a trusted (eg. JWT token) for the authentication, takes priority and disable the DIGEST auth if set, see https://www.rfc-editor.org/rfc/rfc8898
|
||||
|
||||
# Blocking service
|
||||
BLOCKING_TIME_PERIOD_CHECK=30 # Time span on which the blocking service will proceed, in minutes
|
||||
BLOCKING_AMOUNT_EVENTS_AUTHORIZED_DURING_PERIOD=5 # Amount of account events authorized during this period
|
||||
|
||||
# FlexiSIP database
|
||||
# Ensure that you have the proper SELinux configuration to allow database connections, see the README
|
||||
|
|
@ -71,10 +58,9 @@ REDIS_DB=
|
|||
|
||||
# Logs
|
||||
# Ensure that you have the proper SELinux configuration to write in the storage directory, see the README
|
||||
LOG_CHANNEL=stack
|
||||
BROADCAST_DRIVER=log
|
||||
CACHE_DRIVER=file
|
||||
QUEUE_CONNECTION=sync
|
||||
SESSION_DRIVER=cookie
|
||||
SESSION_LIFETIME=120
|
||||
|
||||
# SMTP and emails
|
||||
|
|
@ -84,13 +70,18 @@ MAIL_HOST=
|
|||
MAIL_PORT=2525
|
||||
MAIL_USERNAME=
|
||||
MAIL_PASSWORD=
|
||||
MAIL_FROM_ADDRESS=
|
||||
MAIL_FROM_ADDRESS=from@example.com
|
||||
MAIL_FROM_NAME=
|
||||
MAIL_ALLOW_SELF_SIGNED=false
|
||||
MAIL_VERIFY_PEER=true
|
||||
MAIL_VERIFY_PEER_NAME=true
|
||||
MAIL_SIGNATURE="The Example Team"
|
||||
|
||||
# CoTURN
|
||||
COTURN_SERVER_HOST= # IP or domain name
|
||||
COTURN_SESSION_TTL_MINUTES=1440 # 60 * 24
|
||||
COTURN_STATIC_AUTH_SECRET= # static-auth-secret in the coturn configuration
|
||||
|
||||
# OVH SMS API variables
|
||||
OVH_APP_KEY=
|
||||
OVH_APP_SECRET=
|
||||
|
|
@ -98,6 +89,13 @@ OVH_APP_ENDPOINT=ovh-eu
|
|||
OVH_APP_CONSUMER_KEY=
|
||||
OVH_APP_SENDER=
|
||||
|
||||
# Google reCaptcha v2 parameters
|
||||
NOCAPTCHA_SECRET=secret-key
|
||||
NOCAPTCHA_SITEKEY=site-key
|
||||
# HCaptcha
|
||||
HCAPTCHA_SECRET=secret-key
|
||||
HCAPTCHA_SITEKEY=site-key
|
||||
|
||||
# JWT
|
||||
JWT_RSA_PUBLIC_KEY_PEM=
|
||||
JWT_SIP_IDENTIFIER=
|
||||
|
||||
# Temporary toggles
|
||||
APP_SHOW_LOGIN_COUNTER_TEMP= # default true
|
||||
|
|
@ -21,80 +21,87 @@ namespace App;
|
|||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use App\Http\Controllers\Account\AuthenticateController as WebAuthenticateController;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
use App\ApiKey;
|
||||
use App\Password;
|
||||
use App\EmailChanged;
|
||||
use App\Mail\ChangingEmail;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
use Awobaz\Compoships\Compoships;
|
||||
use App\Http\Controllers\Account\AuthenticateController as WebAuthenticateController;
|
||||
use stdClass;
|
||||
|
||||
class Account extends Authenticatable
|
||||
{
|
||||
use HasFactory;
|
||||
use Compoships;
|
||||
|
||||
protected $with = ['passwords', 'admin', 'emailChanged', 'alias', 'activationExpiration', 'types', 'actions'];
|
||||
protected $hidden = ['alias', 'expire_time', 'confirmation_key', 'provisioning_token', 'pivot'];
|
||||
protected $dateTimes = ['creation_time'];
|
||||
protected $appends = ['realm', 'phone', 'confirmation_key_expires'];
|
||||
protected $with = ['passwords', 'emailChangeCode', 'types', 'actions', 'dictionaryEntries', 'carddavServers'];
|
||||
protected $hidden = ['expire_time', 'pivot', 'currentProvisioningToken', 'currentRecoveryCode', 'dictionaryEntries'];
|
||||
protected $appends = ['realm', 'provisioning_token', 'provisioning_token_expire_at', 'dictionary'];
|
||||
protected $casts = [
|
||||
'activated' => 'boolean',
|
||||
];
|
||||
public $timestamps = false;
|
||||
protected $fillable = ['username', 'domain', 'email'];
|
||||
|
||||
public static $dtmfProtocols = ['sipinfo' => 'SIPInfo', 'rfc2833' => 'RFC2833', 'sipmessage' => 'SIP Message'];
|
||||
|
||||
/**
|
||||
* Scopes
|
||||
*/
|
||||
public static function boot()
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
static::deleted(function (Account $account) {
|
||||
StatisticsMessage::where('from_username', $account->username)
|
||||
->where('from_domain', $account->domain)
|
||||
->delete();
|
||||
|
||||
StatisticsCall::where('from_username', $account->username)
|
||||
->where('from_domain', $account->domain)
|
||||
->delete();
|
||||
});
|
||||
|
||||
static::created(function (Account $account) {
|
||||
$account->provision();
|
||||
$account->refresh();
|
||||
});
|
||||
}
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
static::addGlobalScope('domain', function (Builder $builder) {
|
||||
if (Auth::hasUser()) {
|
||||
$user = Auth::user();
|
||||
if (!$user->admin || !config('app.admins_manage_multi_domains')) {
|
||||
$builder->where('domain', config('app.sip_domain'));
|
||||
}
|
||||
|
||||
if (Auth::hasUser() && Auth::user()->superAdmin) {
|
||||
return;
|
||||
}
|
||||
|
||||
$builder->where('domain', config('app.sip_domain'));
|
||||
});
|
||||
|
||||
/**
|
||||
* External account handling
|
||||
*/
|
||||
static::creating(function ($account) {
|
||||
if (config('app.consume_external_account_on_create') && !getAvailableExternalAccount()) {
|
||||
abort(403, 'Accounts cannot be created on the server');
|
||||
}
|
||||
});
|
||||
|
||||
static::created(function ($account) {
|
||||
if (config('app.consume_external_account_on_create')) {
|
||||
$account->attachExternalAccount();
|
||||
}
|
||||
/**
|
||||
* config('app.sip_domain') is required for the Tests suit
|
||||
*/
|
||||
$builder->where('domain', config('app.sip_domain') ?? space()->domain);
|
||||
});
|
||||
}
|
||||
|
||||
public function scopeSip($query, string $sip)
|
||||
{
|
||||
if (\str_contains($sip, '@')) {
|
||||
list($usernane, $domain) = explode('@', $sip);
|
||||
list($username, $domain) = explode('@', $sip);
|
||||
|
||||
return $query->where('username', $usernane)
|
||||
return $query->where('username', $username)
|
||||
->where('domain', $domain);
|
||||
};
|
||||
|
||||
return $query->where('id', '<', 0);
|
||||
}
|
||||
|
||||
public static function subByContactsList($query, int $contactsListId)
|
||||
{
|
||||
return $query->from('accounts')
|
||||
->whereIn('id', function ($query) use ($contactsListId) {
|
||||
$query->select('contact_id')
|
||||
->from('contacts_list_contact')
|
||||
->where('contacts_list_id', $contactsListId);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Relations
|
||||
*/
|
||||
|
|
@ -107,27 +114,17 @@ class Account extends Authenticatable
|
|||
});
|
||||
}
|
||||
|
||||
public function activationExpiration()
|
||||
{
|
||||
return $this->hasOne(ActivationExpiration::class);
|
||||
}
|
||||
|
||||
public function admin()
|
||||
{
|
||||
return $this->hasOne(Admin::class);
|
||||
}
|
||||
|
||||
public function alias()
|
||||
{
|
||||
return $this->hasOne(Alias::class);
|
||||
}
|
||||
|
||||
public function apiKey()
|
||||
{
|
||||
return $this->hasOne(ApiKey::class);
|
||||
return $this->hasOne(ApiKey::class)->whereNull('expires_after_last_used_minutes');
|
||||
}
|
||||
|
||||
public function externalAccount()
|
||||
public function adminApiKeys()
|
||||
{
|
||||
return $this->hasMany(ApiKey::class)->whereNotNull('expires_after_last_used_minutes');
|
||||
}
|
||||
|
||||
public function external()
|
||||
{
|
||||
return $this->hasOne(ExternalAccount::class);
|
||||
}
|
||||
|
|
@ -137,9 +134,64 @@ class Account extends Authenticatable
|
|||
return $this->belongsToMany(Account::class, 'contacts', 'account_id', 'contact_id');
|
||||
}
|
||||
|
||||
public function emailChanged()
|
||||
public function files()
|
||||
{
|
||||
return $this->hasOne(EmailChanged::class);
|
||||
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);
|
||||
}
|
||||
|
||||
public function contactsLists()
|
||||
{
|
||||
return $this->belongsToMany(ContactsList::class, 'account_contacts_list', 'account_id', 'contacts_list_id');
|
||||
}
|
||||
|
||||
public function dictionaryEntries()
|
||||
{
|
||||
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;
|
||||
|
||||
return $this->dictionaryEntries->keyBy('key')->map(function ($entry) {
|
||||
return $entry->value;
|
||||
});
|
||||
}
|
||||
|
||||
public function setDictionaryEntry(string $key, string $value): AccountDictionaryEntry
|
||||
{
|
||||
$this->dictionaryEntries()->where('key', $key)->delete();
|
||||
$entry = new AccountDictionaryEntry;
|
||||
|
||||
$entry->account_id = $this->id;
|
||||
$entry->key = $key;
|
||||
$entry->value = $value;
|
||||
$entry->save();
|
||||
|
||||
return $entry;
|
||||
}
|
||||
|
||||
public function nonces()
|
||||
|
|
@ -147,73 +199,182 @@ class Account extends Authenticatable
|
|||
return $this->hasMany(DigestNonce::class);
|
||||
}
|
||||
|
||||
public function authTokens()
|
||||
{
|
||||
return $this->hasMany(AuthToken::class);
|
||||
}
|
||||
|
||||
public function passwords()
|
||||
{
|
||||
return $this->hasMany(Password::class);
|
||||
}
|
||||
|
||||
public function phoneChangeCode()
|
||||
{
|
||||
return $this->hasOne(PhoneChangeCode::class);
|
||||
}
|
||||
|
||||
public function types()
|
||||
{
|
||||
return $this->belongsToMany(AccountType::class);
|
||||
}
|
||||
|
||||
public function space()
|
||||
{
|
||||
return $this->hasOne(Space::class, 'domain', 'domain');
|
||||
}
|
||||
|
||||
public function statisticsFromCalls()
|
||||
{
|
||||
return $this->hasMany(StatisticsCall::class, ['from_username', 'from_domain'], ['username', 'domain']);
|
||||
}
|
||||
|
||||
public function statisticsToCalls()
|
||||
{
|
||||
return $this->hasMany(StatisticsCall::class, ['to_username', 'to_domain'], ['username', 'domain']);
|
||||
}
|
||||
|
||||
public function statisticsFromMessages()
|
||||
{
|
||||
return $this->hasMany(StatisticsMessage::class, ['from_username', 'from_domain'], ['username', 'domain']);
|
||||
}
|
||||
|
||||
public function statisticsToMessageDevices()
|
||||
{
|
||||
return $this->hasMany(StatisticsMessageDevice::class, ['to_username', 'to_domain'], ['username', 'domain']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tokens and codes
|
||||
*/
|
||||
public function currentRecoveryCode()
|
||||
{
|
||||
return $this->hasOne(RecoveryCode::class)->whereNotNull('code')->latestOfMany();
|
||||
}
|
||||
|
||||
public function recoveryCodes()
|
||||
{
|
||||
return $this->hasMany(RecoveryCode::class)->latest();
|
||||
}
|
||||
|
||||
public function phoneChangeCode()
|
||||
{
|
||||
return $this->hasOne(PhoneChangeCode::class)->whereNotNull('code')->latestOfMany();
|
||||
}
|
||||
|
||||
public function phoneChangeCodes()
|
||||
{
|
||||
return $this->hasMany(PhoneChangeCode::class)->latest();
|
||||
}
|
||||
|
||||
public function emailChangeCode()
|
||||
{
|
||||
return $this->hasOne(EmailChangeCode::class)->whereNotNull('code')->latestOfMany();
|
||||
}
|
||||
|
||||
public function emailChangeCodes()
|
||||
{
|
||||
return $this->hasMany(EmailChangeCode::class)->latest();
|
||||
}
|
||||
|
||||
public function currentProvisioningToken()
|
||||
{
|
||||
return $this->hasOne(ProvisioningToken::class)->where('used', false)->latestOfMany();
|
||||
}
|
||||
|
||||
public function provisioningTokens()
|
||||
{
|
||||
return $this->hasMany(ProvisioningToken::class)->latest();
|
||||
}
|
||||
|
||||
public function accountCreationToken()
|
||||
{
|
||||
return $this->hasOne(AccountCreationToken::class);
|
||||
}
|
||||
|
||||
public function accountRecoveryTokens()
|
||||
{
|
||||
return $this->hasMany(AccountRecoveryToken::class);
|
||||
}
|
||||
|
||||
public function authTokens()
|
||||
{
|
||||
return $this->hasMany(AuthToken::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset password
|
||||
*/
|
||||
|
||||
public function getCurrentResetPasswordUrlAttribute(): string
|
||||
{
|
||||
return replaceHost(
|
||||
route('account.reset_password_email.change', $this->currentResetPasswordEmailToken->token),
|
||||
$this->space->host
|
||||
);
|
||||
}
|
||||
|
||||
public function currentResetPasswordEmailToken()
|
||||
{
|
||||
return $this->hasOne(ResetPasswordEmailToken::class)->where('used', false)->latestOfMany();
|
||||
}
|
||||
|
||||
public function resetPasswordEmailTokens()
|
||||
{
|
||||
return $this->hasMany(ResetPasswordEmailToken::class)->latest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attributes
|
||||
*/
|
||||
public function getIdentifierAttribute()
|
||||
public function getRecoveryCodeAttribute(): ?string
|
||||
{
|
||||
if ($this->currentRecoveryCode) {
|
||||
return $this->currentRecoveryCode->code;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getProvisioningTokenAttribute(): ?string
|
||||
{
|
||||
if ($this->currentProvisioningToken) {
|
||||
return $this->currentProvisioningToken->token;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getProvisioningTokenExpireAtAttribute(): ?string
|
||||
{
|
||||
if ($this->currentProvisioningToken) {
|
||||
return $this->currentProvisioningToken->expire_at;
|
||||
}
|
||||
|
||||
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'];
|
||||
}
|
||||
|
||||
public function getFullIdentifierAttribute()
|
||||
public function getFullIdentifierAttribute(): string
|
||||
{
|
||||
$displayName = $this->attributes['display_name']
|
||||
? '"' . $this->attributes['display_name'] . '" '
|
||||
: '';
|
||||
? '"' . $this->attributes['display_name'] . '" '
|
||||
: '';
|
||||
|
||||
return $displayName . '<sip:' . $this->getIdentifierAttribute() . '>';
|
||||
}
|
||||
|
||||
public function getRealmAttribute()
|
||||
{
|
||||
return config('app.realm');
|
||||
return $this->space->account_realm;
|
||||
}
|
||||
|
||||
public function getResolvedRealmAttribute()
|
||||
{
|
||||
return config('app.realm') ?? $this->domain;
|
||||
}
|
||||
|
||||
public function getPhoneAttribute()
|
||||
{
|
||||
if ($this->alias) {
|
||||
return $this->alias->alias;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function setPhoneAttribute(?string $phone)
|
||||
{
|
||||
$this->alias()->delete();
|
||||
|
||||
if (!empty($phone)) {
|
||||
$alias = new Alias;
|
||||
$alias->alias = $phone;
|
||||
$alias->domain = config('app.sip_domain');
|
||||
$alias->account_id = $this->id;
|
||||
$alias->save();
|
||||
}
|
||||
return $this->space->account_realm ?? $this->domain;
|
||||
}
|
||||
|
||||
public function getConfirmationKeyExpiresAttribute()
|
||||
|
|
@ -225,11 +386,6 @@ class Account extends Authenticatable
|
|||
return null;
|
||||
}
|
||||
|
||||
public function getSha256PasswordAttribute()
|
||||
{
|
||||
return $this->passwords()->where('algorithm', 'SHA-256')->exists();
|
||||
}
|
||||
|
||||
public static function dtmfProtocolsRule()
|
||||
{
|
||||
return implode(',', array_keys(self::$dtmfProtocols));
|
||||
|
|
@ -240,46 +396,44 @@ class Account extends Authenticatable
|
|||
return self::$dtmfProtocols[$this->attributes['dtmf_protocol']];
|
||||
}
|
||||
|
||||
public function getSuperAdminAttribute(): bool
|
||||
{
|
||||
return Space::where('domain', $this->domain)->where('super', true)->exists() && $this->admin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provisioning
|
||||
*/
|
||||
|
||||
public function getProvisioningUrlAttribute(): string
|
||||
{
|
||||
return replaceHost(
|
||||
route('provisioning.provision', $this->getProvisioningTokenAttribute()),
|
||||
$this->space->host
|
||||
);
|
||||
}
|
||||
|
||||
public function getProvisioningQrcodeUrlAttribute(): string
|
||||
{
|
||||
return replaceHost(
|
||||
route('provisioning.qrcode', $this->getProvisioningTokenAttribute()),
|
||||
$this->space->host
|
||||
);
|
||||
}
|
||||
|
||||
public function getProvisioningWizardUrlAttribute(): string
|
||||
{
|
||||
return replaceHost(
|
||||
route('provisioning.wizard', $this->getProvisioningTokenAttribute()),
|
||||
$this->space->host
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utils
|
||||
*/
|
||||
public function activationExpired(): bool
|
||||
{
|
||||
return ($this->activationExpiration && $this->activationExpiration->isExpired());
|
||||
}
|
||||
|
||||
public function attachExternalAccount(): bool
|
||||
{
|
||||
$externalAccount = getAvailableExternalAccount();
|
||||
|
||||
if (!$externalAccount) abort(403, 'No External Account left');
|
||||
|
||||
$externalAccount->account_id = $this->id;
|
||||
$externalAccount->used = true;
|
||||
return $externalAccount->save();
|
||||
}
|
||||
|
||||
public function requestEmailUpdate(string $newEmail)
|
||||
{
|
||||
// Remove all the old requests
|
||||
$this->emailChanged()->delete();
|
||||
|
||||
// Create a new one
|
||||
$emailChanged = new EmailChanged;
|
||||
$emailChanged->new_email = $newEmail;
|
||||
$emailChanged->hash = Str::random(16);
|
||||
$emailChanged->account_id = $this->id;
|
||||
$emailChanged->save();
|
||||
|
||||
$this->refresh();
|
||||
|
||||
// Set it temporary to try to send the validation email
|
||||
$this->email = $newEmail;
|
||||
|
||||
Mail::to($this)->send(new ChangingEmail($this));
|
||||
}
|
||||
|
||||
public function generateApiKey(): ApiKey
|
||||
public function generateUserApiKey(?string $ip = null): ApiKey
|
||||
{
|
||||
$this->apiKey()->delete();
|
||||
|
||||
|
|
@ -287,6 +441,7 @@ class Account extends Authenticatable
|
|||
$apiKey->account_id = $this->id;
|
||||
$apiKey->last_used_at = Carbon::now();
|
||||
$apiKey->key = Str::random(40);
|
||||
$apiKey->ip = $ip;
|
||||
$apiKey->save();
|
||||
|
||||
return $apiKey;
|
||||
|
|
@ -310,37 +465,82 @@ class Account extends Authenticatable
|
|||
return $authToken;
|
||||
}
|
||||
|
||||
public function provision(): string
|
||||
public function recover(?string $code = null, ?string $phone = null, ?string $email = null): string
|
||||
{
|
||||
$this->provisioning_token = Str::random(WebAuthenticateController::$emailCodeSize);
|
||||
return $this->provisioning_token;
|
||||
}
|
||||
$recoveryCode = new RecoveryCode;
|
||||
$recoveryCode->code = $code ?? generatePin();
|
||||
$recoveryCode->phone = $phone;
|
||||
$recoveryCode->email = $email;
|
||||
$recoveryCode->account_id = $this->id;
|
||||
|
||||
public function getAdminAttribute(): bool
|
||||
{
|
||||
return ($this->admin()->exists());
|
||||
}
|
||||
|
||||
public function setAdminAttribute(bool $isAdmin)
|
||||
{
|
||||
$this->admin()->delete();
|
||||
|
||||
if ($isAdmin) {
|
||||
$admin = new Admin;
|
||||
$admin->account_id = $this->id;
|
||||
$admin->save();
|
||||
if (request()) {
|
||||
$recoveryCode->fillRequestInfo(request());
|
||||
}
|
||||
|
||||
$recoveryCode->save();
|
||||
|
||||
return $recoveryCode->code;
|
||||
}
|
||||
|
||||
public function hasTombstone()
|
||||
public function provision(?string $token = null): string
|
||||
{
|
||||
$provisioningToken = new ProvisioningToken;
|
||||
$provisioningToken->token = $token ?? Str::random(WebAuthenticateController::$emailCodeSize);
|
||||
$provisioningToken->account_id = $this->id;
|
||||
|
||||
if (request()) {
|
||||
$provisioningToken->fillRequestInfo(request());
|
||||
}
|
||||
|
||||
$provisioningToken->save();
|
||||
|
||||
return $provisioningToken->token;
|
||||
}
|
||||
|
||||
public function setRole(string $role)
|
||||
{
|
||||
if ($role == 'end_user') {
|
||||
$this->admin = false;
|
||||
}
|
||||
|
||||
if ($role == 'admin') {
|
||||
$this->admin = true;
|
||||
}
|
||||
|
||||
$this->save();
|
||||
}
|
||||
|
||||
public function hasTombstone(): bool
|
||||
{
|
||||
return AccountTombstone::where('username', $this->attributes['username'])
|
||||
->where('domain', $this->attributes['domain'])
|
||||
->exists();
|
||||
}
|
||||
|
||||
public function updatePassword($newPassword, ?string $algorithm = 'SHA-256')
|
||||
public function createTombstone(): bool
|
||||
{
|
||||
if (!$this->hasTombstone()) {
|
||||
$tombstone = new AccountTombstone();
|
||||
$tombstone->username = $this->attributes['username'];
|
||||
$tombstone->domain = $this->attributes['domain'];
|
||||
$tombstone->save();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function failedRecentRecovery(): bool
|
||||
{
|
||||
$oneHourAgo = Carbon::now()->subHour();
|
||||
return !empty($this->recovery_code) && $this->updated_at->greaterThan($oneHourAgo);
|
||||
}
|
||||
|
||||
public function updatePassword(string $newPassword, ?string $algorithm = null)
|
||||
{
|
||||
$algorithm = $algorithm ?? config('app.account_default_password_algorithm');
|
||||
|
||||
$this->passwords()->delete();
|
||||
|
||||
$password = new Password;
|
||||
|
|
@ -350,14 +550,6 @@ class Account extends Authenticatable
|
|||
$password->save();
|
||||
}
|
||||
|
||||
public function fillPassword(Request $request)
|
||||
{
|
||||
if ($request->filled('password')) {
|
||||
$this->algorithm = $request->has('password_sha256') ? 'SHA-256' : 'MD5';
|
||||
$this->updatePassword($request->get('password'), $this->algorithm);
|
||||
}
|
||||
}
|
||||
|
||||
public function toVcard4()
|
||||
{
|
||||
$vcard = 'BEGIN:VCARD
|
||||
|
|
@ -367,8 +559,8 @@ IMPP:sip:' . $this->getIdentifierAttribute();
|
|||
|
||||
$vcard .= '
|
||||
FN:';
|
||||
$vcard .= !empty($this->attributes['display_name'])
|
||||
? $this->attributes['display_name']
|
||||
$vcard .= !empty($this->display_name)
|
||||
? $this->display_name
|
||||
: $this->getIdentifierAttribute();
|
||||
|
||||
if ($this->dtmf_protocol) {
|
||||
|
|
|
|||
20
flexiapi/app/AccountCardDavCredentials.php
Normal file
20
flexiapi/app/AccountCardDavCredentials.php
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
<?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;
|
||||
}
|
||||
}
|
||||
|
|
@ -20,9 +20,8 @@
|
|||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class AccountCreationRequestToken extends Model
|
||||
class AccountCreationRequestToken extends Consommable
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
|
|
@ -40,4 +39,10 @@ class AccountCreationRequestToken extends Model
|
|||
? route('account.creation_request_token.check', $this->token)
|
||||
: null;
|
||||
}
|
||||
|
||||
public function consume()
|
||||
{
|
||||
$this->used = true;
|
||||
$this->save();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,17 +19,47 @@
|
|||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class AccountCreationToken extends Model
|
||||
class AccountCreationToken extends Consommable
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $hidden = ['id', 'updated_at', 'created_at'];
|
||||
protected $appends = ['expire_at'];
|
||||
protected ?string $configExpirationMinutesKey = 'account_creation_token_expiration_minutes';
|
||||
|
||||
public function accountCreationRequestToken()
|
||||
{
|
||||
return $this->hasOne(AccountCreationRequestToken::class, 'acc_creation_token_id');
|
||||
}
|
||||
|
||||
public function account()
|
||||
{
|
||||
return $this->belongsTo(Account::class);
|
||||
}
|
||||
|
||||
public function consume()
|
||||
{
|
||||
$this->used = true;
|
||||
$this->save();
|
||||
}
|
||||
|
||||
public function consumed(): bool
|
||||
{
|
||||
return $this->used == true;
|
||||
}
|
||||
|
||||
public function toLog()
|
||||
{
|
||||
return [
|
||||
'token' => $this->token,
|
||||
'pn_param' => $this->pn_param,
|
||||
'used' => $this->used,
|
||||
'account_id' => $this->account_id,
|
||||
'ip' => $this->ip,
|
||||
'user_agent' => $this->user_agent,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
18
flexiapi/app/AccountDictionaryEntry.php
Normal file
18
flexiapi/app/AccountDictionaryEntry.php
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class AccountDictionaryEntry extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $visible = ['value'];
|
||||
|
||||
public function account()
|
||||
{
|
||||
return $this->belongsTo(Account::class);
|
||||
}
|
||||
}
|
||||
74
flexiapi/app/AccountFile.php
Normal file
74
flexiapi/app/AccountFile.php
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
<?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);
|
||||
}
|
||||
}
|
||||
42
flexiapi/app/AccountRecoveryToken.php
Normal file
42
flexiapi/app/AccountRecoveryToken.php
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
||||
class AccountRecoveryToken extends Consommable
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $hidden = ['id', 'updated_at', 'created_at'];
|
||||
protected $appends = ['expire_at'];
|
||||
protected ?string $configExpirationMinutesKey = 'account_recovery_token_expiration_minutes';
|
||||
|
||||
public function account()
|
||||
{
|
||||
return $this->belongsTo(Account::class);
|
||||
}
|
||||
|
||||
public function consume()
|
||||
{
|
||||
$this->used = true;
|
||||
$this->save();
|
||||
}
|
||||
|
||||
public function consumed(): bool
|
||||
{
|
||||
return $this->used == true;
|
||||
}
|
||||
|
||||
public function toLog()
|
||||
{
|
||||
return [
|
||||
'token' => $this->token,
|
||||
'pn_param' => $this->pn_param,
|
||||
'used' => $this->used,
|
||||
'account_id' => $this->account_id,
|
||||
'ip' => $this->ip,
|
||||
'user_agent' => $this->user_agent,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -28,8 +28,12 @@ class ApiKey extends Model
|
|||
|
||||
protected $table = 'api_keys';
|
||||
|
||||
protected $casts = [
|
||||
'last_used_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function account()
|
||||
{
|
||||
return $this->belongsTo('App\Account');
|
||||
return $this->belongsTo(Account::class)->withoutGlobalScopes();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,9 +4,8 @@ namespace App;
|
|||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class AuthToken extends Model
|
||||
class AuthToken extends Consommable
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
|
|
@ -14,7 +13,7 @@ class AuthToken extends Model
|
|||
|
||||
public function account()
|
||||
{
|
||||
return $this->belongsTo('App\Account');
|
||||
return $this->belongsTo(Account::class);
|
||||
}
|
||||
|
||||
public function scopeValid($query)
|
||||
|
|
|
|||
|
|
@ -17,22 +17,18 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Console\Commands;
|
||||
namespace App\Console\Commands\Accounts;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Carbon\Carbon;
|
||||
|
||||
use App\AccountTombstone;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class ClearOldAccountsTombstones extends Command
|
||||
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(
|
||||
|
|
@ -45,10 +41,10 @@ class ClearOldAccountsTombstones extends Command
|
|||
$this->info($tombstones->count() . ' tombstones deleted');
|
||||
$tombstones->delete();
|
||||
|
||||
return 0;
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
$this->info($tombstones->count() . ' tombstones to delete');
|
||||
return 0;
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
68
flexiapi/app/Console/Commands/Accounts/ClearApiKeys.php
Normal file
68
flexiapi/app/Console/Commands/Accounts/ClearApiKeys.php
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
<?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\Console\Commands\Accounts;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Carbon\Carbon;
|
||||
|
||||
use App\ApiKey;
|
||||
|
||||
class ClearApiKeys extends Command
|
||||
{
|
||||
protected $signature = 'accounts:clear-api-keys {minutes?}';
|
||||
protected $description = 'Clear the expired user API Keys after n minutes and clear the other expired admin keys';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
// User API Keys
|
||||
$minutes = $this->argument('minutes') ?? config('app.api_key_expiration_minutes');
|
||||
|
||||
if ($minutes == 0) {
|
||||
$this->info('Expiration time is set to 0, nothing to clear');
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
$this->info('Deleting user API Keys unused after ' . $minutes . ' minutes');
|
||||
|
||||
$count = ApiKey::whereNull('expires_after_last_used_minutes')
|
||||
->where('last_used_at', '<', Carbon::now()->subMinutes($minutes)->toDateTimeString())
|
||||
->delete();
|
||||
|
||||
$this->info($count . ' user API Keys deleted');
|
||||
|
||||
// Admin API Keys
|
||||
$keys = ApiKey::whereNotNull('expires_after_last_used_minutes')
|
||||
->where('expires_after_last_used_minutes', '>', 0)
|
||||
->with('account')
|
||||
->get();
|
||||
|
||||
$count = 0;
|
||||
|
||||
foreach ($keys as $key) {
|
||||
if ($key->last_used_at->addMinutes($key->expires_after_last_used_minutes)->isPast()) {
|
||||
$this->info('Deleting ' . $key->account->identifier . ' admin API Key expired after ' . $key->expires_after_last_used_minutes .'min');
|
||||
$key->delete();
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
|
||||
$this->info($count . ' admin API Keys deleted');
|
||||
}
|
||||
}
|
||||
35
flexiapi/app/Console/Commands/Accounts/ClearFiles.php
Normal file
35
flexiapi/app/Console/Commands/Accounts/ClearFiles.php
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
<?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;
|
||||
}
|
||||
}
|
||||
|
|
@ -17,27 +17,22 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Console\Commands;
|
||||
namespace App\Console\Commands\Accounts;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
use Carbon\Carbon;
|
||||
|
||||
use App\Account;
|
||||
|
||||
class RemoveUnconfirmedAccounts extends Command
|
||||
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(
|
||||
'creation_time',
|
||||
'created_at',
|
||||
'<',
|
||||
Carbon::now()->subDays($this->argument('days'))->toDateTimeString()
|
||||
);
|
||||
|
|
@ -53,10 +48,10 @@ class RemoveUnconfirmedAccounts extends Command
|
|||
$accounts->delete();
|
||||
$this->info($count . ' accounts deleted');
|
||||
|
||||
return 0;
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
$this->info($count . ' accounts to delete');
|
||||
return 0;
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
<?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\Console\Commands\Accounts;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Carbon\Carbon;
|
||||
|
||||
use App\Account;
|
||||
use App\Space;
|
||||
|
||||
class CreateAdminAccount extends Command
|
||||
{
|
||||
protected $signature = 'accounts:create-admin-account {--u|username=} {--p|password=} {--d|domain=} {--k|api_key_ip=}';
|
||||
protected $description = 'Create an admin account and generate an API Key';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$spaces = Space::all('domain')->pluck('domain');
|
||||
|
||||
$this->info('Your creating a new admin account in the database, existing accounts with the same credentials will be overwritten');
|
||||
|
||||
$username = $this->option('username');
|
||||
$domain = $this->option('domain');
|
||||
$password = $this->option('password');
|
||||
|
||||
if (!$this->option('username')) {
|
||||
$username = $this->ask('What will be the admin username? Default: admin');
|
||||
}
|
||||
|
||||
if (!$this->option('domain')) {
|
||||
$domain = $this->ask('What will be the admin domain? Default: ' . $spaces->first());
|
||||
}
|
||||
|
||||
if (!$this->option('password')) {
|
||||
$password = $this->ask('What will be the admin password? Default: changeme');
|
||||
}
|
||||
|
||||
$username = $username ?? 'admin';
|
||||
$domain = $domain ?? $spaces->first();
|
||||
$password = $password ?? 'change_me';
|
||||
|
||||
if (!$spaces->contains($domain)) {
|
||||
$this->error('The domain must be one of the following ones: ' . $spaces->implode(', '));
|
||||
$this->comment('You can create an extra domain using the dedicated console command');
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
// Delete the account if it already exists
|
||||
$account = Account::withoutGlobalScopes()
|
||||
->where('username', $username)
|
||||
->where('domain', $domain)
|
||||
->first();
|
||||
|
||||
if ($account) {
|
||||
$account->delete();
|
||||
}
|
||||
|
||||
$account = new Account;
|
||||
$account->username = $username;
|
||||
$account->domain = $domain;
|
||||
$account->activated = true;
|
||||
$account->user_agent = 'Test';
|
||||
$account->ip_address = '0.0.0.0';
|
||||
$account->admin = true;
|
||||
|
||||
// Create an "old" account to prevent unwanted deletion on the test server
|
||||
$account->created_at = Carbon::now()->subYears(3);
|
||||
$account->save();
|
||||
|
||||
$account->generateUserApiKey(ip: $this->option('api_key_ip') ?? null);
|
||||
$account->updatePassword($password);
|
||||
|
||||
$this->info('Admin test account created: "' . $username . '@' . $domain . '" | Password: "' . $password . '" | API Key: "' . $account->apiKey->key . '" (valid on ' . ($account->apiKey->ip ?? 'any') . ' ip)');
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
|
@ -17,61 +17,45 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Console\Commands;
|
||||
namespace App\Console\Commands\Accounts;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
use App\Account;
|
||||
use App\Admin;
|
||||
use App\ApiKey;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class CreateAdminAccountTest extends Command
|
||||
use App\Account;
|
||||
use App\ApiKey;
|
||||
|
||||
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,
|
||||
'--super' => 'true'
|
||||
]);
|
||||
|
||||
$this->call('accounts:create-admin-account', [
|
||||
'--username' => $username,
|
||||
'--domain' => $domain,
|
||||
'--password' => 'admin_test_password'
|
||||
]);
|
||||
|
||||
$secret = 'no_secret_at_all';
|
||||
|
||||
// Delete the existing keys
|
||||
ApiKey::where('key', $secret)->delete();
|
||||
|
||||
// Delete the account if it already exists
|
||||
$account = Account::withoutGlobalScopes()
|
||||
->where('username', $username)
|
||||
->where('domain', $domain)
|
||||
->first();
|
||||
->where('username', $username)
|
||||
->where('domain', $domain)
|
||||
->first();
|
||||
|
||||
if ($account) {
|
||||
// We don't have foreign keys yet…
|
||||
$account->admin()->delete();
|
||||
$account->delete();
|
||||
}
|
||||
|
||||
$account = new Account;
|
||||
$account->username = $username;
|
||||
$account->domain = $domain;
|
||||
$account->email = 'admin_test@sip.example.org';
|
||||
$account->activated = true;
|
||||
$account->user_agent = 'Test';
|
||||
$account->ip_address = '0.0.0.0';
|
||||
|
||||
// Create an "old" account to prevent unwanted deletion on the test server
|
||||
$account->creation_time = Carbon::now()->subYears(3);
|
||||
$account->save();
|
||||
|
||||
$admin = new Admin;
|
||||
$admin->account_id = $account->id;
|
||||
$admin->save();
|
||||
ApiKey::where('account_id', $account->id)->delete();
|
||||
|
||||
$apiKey = new ApiKey;
|
||||
$apiKey->account_id = $account->id;
|
||||
|
|
@ -79,8 +63,8 @@ class CreateAdminAccountTest extends Command
|
|||
$apiKey->key = $secret;
|
||||
$apiKey->save();
|
||||
|
||||
$this->info('Admin test account created: "sip:' . $username . '@' . $domain . '" | API Key: "' . $secret . '"');
|
||||
$this->info('API Key updated to: ' . $secret);
|
||||
|
||||
return 0;
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
|
@ -17,22 +17,17 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Console\Commands;
|
||||
namespace App\Console\Commands\Accounts;
|
||||
|
||||
use Database\Seeders\LiblinphoneTesterAccoutSeeder;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
class RunAccountSeeder extends Command
|
||||
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');
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
<?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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -17,38 +17,36 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Console\Commands;
|
||||
namespace App\Console\Commands\Accounts;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
use App\Account;
|
||||
use App\Admin;
|
||||
|
||||
class SetAccountAdmin extends Command
|
||||
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();
|
||||
|
||||
if (!$account) {
|
||||
$this->error('Account not found, please use an existing SIP address');
|
||||
return 1;
|
||||
$this->error('Account not found, please use an existing account id');
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$admin = new Admin;
|
||||
$admin->account_id = $account->id;
|
||||
$admin->save();
|
||||
if ($account->admin) {
|
||||
$this->error('The account is already having the admin role');
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$account->admin = true;
|
||||
$account->save();
|
||||
|
||||
$this->info('Account '.$account->identifier.' is now admin');
|
||||
|
||||
return 0;
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
<?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\Console\Commands;
|
||||
|
||||
use App\ApiKey;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
use Carbon\Carbon;
|
||||
|
||||
class ClearApiKeys extends Command
|
||||
{
|
||||
protected $signature = 'accounts:clear-api-keys {minutes?}';
|
||||
protected $description = 'Clear the expired API Keys after n minutes';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$minutes = $this->argument('minutes') ?? config('app.api_key_expiration_minutes');
|
||||
|
||||
$this->info('Deleting api keys unused after ' . $minutes . ' minutes');
|
||||
|
||||
$count = ApiKey::where(
|
||||
'last_used_at',
|
||||
'<',
|
||||
Carbon::now()->subMinutes($minutes)->toDateTimeString()
|
||||
)->delete();
|
||||
|
||||
$this->info($count . ' api keys deleted');
|
||||
}
|
||||
}
|
||||
47
flexiapi/app/Console/Commands/ClearStatistics.php
Normal file
47
flexiapi/app/Console/Commands/ClearStatistics.php
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\StatisticsCall;
|
||||
use App\StatisticsMessage;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class ClearStatistics extends Command
|
||||
{
|
||||
protected $signature = 'app:clear-statistics {days} {--apply}';
|
||||
protected $description = 'Command description';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$calls = StatisticsCall::where(
|
||||
'created_at',
|
||||
'<',
|
||||
Carbon::now()->subDays($this->argument('days'))->toDateTimeString()
|
||||
);
|
||||
$messages = StatisticsMessage::where(
|
||||
'created_at',
|
||||
'<',
|
||||
Carbon::now()->subDays($this->argument('days'))->toDateTimeString()
|
||||
);
|
||||
|
||||
$callsCount = $calls->count();
|
||||
$messagesCount = $messages->count();
|
||||
|
||||
if ($this->option('apply')) {
|
||||
$this->info($callsCount . ' calls statistics in deletion…');
|
||||
$calls->delete();
|
||||
$this->info($callsCount . ' calls statistics deleted');
|
||||
|
||||
$this->info($messagesCount . ' messages statistics in deletion…');
|
||||
$messages->delete();
|
||||
$this->info($messagesCount . ' messages statistics deleted');
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
$this->info($callsCount . ' calls statistics to delete');
|
||||
$this->info($messagesCount . ' messages statistics to delete');
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
|
@ -17,11 +17,11 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Console\Commands;
|
||||
namespace App\Console\Commands\Digest;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
use Carbon\Carbon;
|
||||
|
||||
use App\DigestNonce;
|
||||
|
||||
class ClearNonces extends Command
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Account;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class ExportToExternalAccounts extends Command
|
||||
{
|
||||
protected $signature = 'accounts:export-to-externals {group} {--o|output=}';
|
||||
|
||||
protected $description = 'Export accounts from a group as external ones';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$accounts = Account::where('group', $this->argument('group'))
|
||||
->with('passwords')
|
||||
->get();
|
||||
|
||||
if ($accounts->count() == 0) {
|
||||
$this->error('Nothing to export');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->info('Exporting '.$accounts->count().' accounts');
|
||||
|
||||
$data = [];
|
||||
|
||||
foreach ($accounts as $account) {
|
||||
array_push($data, [
|
||||
'username' => $account->username,
|
||||
'domain' => $account->domain,
|
||||
'group' => $account->group,
|
||||
'password' => $account->passwords->first()->password,
|
||||
'algorithm' => $account->passwords->first()->algorithm,
|
||||
]);
|
||||
}
|
||||
|
||||
file_put_contents(
|
||||
$this->option('output') ?? getcwd() . '/exported_accounts.json',
|
||||
json_encode($data)
|
||||
);
|
||||
|
||||
$this->info('Exported');
|
||||
}
|
||||
}
|
||||
|
|
@ -1,101 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Account;
|
||||
use App\Password;
|
||||
use App\Rules\NoUppercase;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class GenerateExternalAccounts extends Command
|
||||
{
|
||||
protected $signature = 'accounts:generate-external {amount} {group}';
|
||||
|
||||
protected $description = 'Generate external accounts in the designed group';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$validator = Validator::make([
|
||||
'amount' => $this->argument('amount'),
|
||||
'group' => $this->argument('group'),
|
||||
], [
|
||||
'amount' => ['required', 'integer'],
|
||||
'group' => ['required', 'alpha-dash', new NoUppercase]
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
$this->info('External accounts no created:');
|
||||
|
||||
foreach ($validator->errors()->all() as $error) {
|
||||
$this->error($error);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
$groups = Account::distinct('group')
|
||||
->whereNotNull('group')
|
||||
->get('group')
|
||||
->pluck('group')
|
||||
->toArray();
|
||||
|
||||
if (!in_array($this->argument('group'), $groups)) {
|
||||
$this->info('Existing groups: '.implode(',', $groups));
|
||||
|
||||
if (!$this->confirm('You are creating a new group of External Account, are you sure?', false)) {
|
||||
$this->info('Creation aborted');
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
$accounts = collect();
|
||||
$passwords = collect();
|
||||
$algorithm = 'SHA-256';
|
||||
|
||||
$i = 0;
|
||||
|
||||
while ($i < $this->argument('amount')) {
|
||||
$account = new Account;
|
||||
$account->username = Str::random(12);
|
||||
$account->domain = config('app.sip_domain');
|
||||
$account->activated = 1;
|
||||
$account->ip_address = '127.0.0.1';
|
||||
$account->user_agent = 'External Account Generator';
|
||||
$account->group = $this->argument('group');
|
||||
$account->creation_time = Carbon::now();
|
||||
$i++;
|
||||
|
||||
$account->push($account->toArray());
|
||||
}
|
||||
|
||||
Account::insert($accounts->toArray());
|
||||
|
||||
$insertedAccounts = Account::where('group', $this->argument('group'))
|
||||
->orderBy('creation_time', 'desc')
|
||||
->take($this->argument('amount'))
|
||||
->get();
|
||||
|
||||
foreach ($insertedAccounts as $account) {
|
||||
$password = new Password;
|
||||
$password->account_id = $account->id;
|
||||
$password->password = bchash($account->username, $account->resolvedRealm, Str::random(6), $algorithm);
|
||||
$password->algorithm = $algorithm;
|
||||
$passwords->push($password->only(['account_id', 'password', 'algorithm']));
|
||||
}
|
||||
|
||||
Password::insert($passwords->toArray());
|
||||
|
||||
$this->info($this->argument('amount') . ' accounts created under the "' . $this->argument('group') . '" group');
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,243 +0,0 @@
|
|||
<?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\Console\Commands;
|
||||
|
||||
use Illuminate\Database\Capsule\Manager as Capsule;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
use App\Account;
|
||||
use App\Admin;
|
||||
use App\Alias;
|
||||
use App\ApiKey;
|
||||
use App\DigestNonce;
|
||||
use App\EmailChanged;
|
||||
use App\Password;
|
||||
use App\PhoneChangeCode;
|
||||
|
||||
class ImportDatabase extends Command
|
||||
{
|
||||
protected $signature = 'db:import {dbname} {sqlite-file-path?} {--u|username=} {--p|password=} {--P|port=3306} {--t|type=mysql} {--host=localhost} {--accounts-table=accounts} {--aliases-table=aliases} {--passwords-table=passwords}';
|
||||
protected $description = 'Import an existing Flexisip database into FlexiAPI';
|
||||
private $pagination = 1000;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function enableForeignKeyCheck()
|
||||
{
|
||||
DB::statement('SET FOREIGN_KEY_CHECKS=1;');
|
||||
}
|
||||
|
||||
public function disableForeignKeyCheck()
|
||||
{
|
||||
DB::statement('SET FOREIGN_KEY_CHECKS=0;');
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$capsule = new Capsule;
|
||||
|
||||
$capsule->addConnection([
|
||||
'driver' => $this->option('type'),
|
||||
'host' => $this->option('host'),
|
||||
'database' => $this->argument('dbname'),
|
||||
'username' => $this->option('username'),
|
||||
'password' => $this->option('password'),
|
||||
'port' => $this->option('port'),
|
||||
'charset' => 'utf8',
|
||||
'collation' => 'utf8_unicode_ci',
|
||||
'prefix' => '',
|
||||
], 'default');
|
||||
|
||||
if ($this->argument('sqlite-file-path')) {
|
||||
$capsule->addConnection([
|
||||
'driver' => 'sqlite',
|
||||
'database' => $this->argument('sqlite-file-path'),
|
||||
], 'sqlite');
|
||||
}
|
||||
|
||||
$capsule->setAsGlobal();
|
||||
|
||||
if (!$this->argument('sqlite-file-path')) {
|
||||
$this->confirm('No SQLite database file was specified : Do you wish to continue?');
|
||||
}
|
||||
|
||||
// Ensure that the target database is empty
|
||||
if (Account::count() > 0) {
|
||||
$this->error('An empty database is required to run the migration');
|
||||
return 1;
|
||||
}
|
||||
|
||||
$accountsCount = Capsule::table($this->option('accounts-table'))->count();
|
||||
|
||||
if ($this->confirm($accountsCount . ' accounts will be migrated : Do you wish to continue?')) {
|
||||
// Accounts
|
||||
$this->info('Migrating the accounts');
|
||||
|
||||
$pages = $accountsCount / $this->pagination;
|
||||
$bar = $this->output->createProgressBar($pages);
|
||||
|
||||
for ($page = 0; $page <= $pages; $page++) {
|
||||
$originAccounts = Capsule::table($this->option('accounts-table'))
|
||||
->take($this->pagination)
|
||||
->skip($page*$this->pagination)
|
||||
->get()
|
||||
->map(function ($element) {
|
||||
// Fix bad creation_time
|
||||
$creationTime = strtotime($element->creation_time);
|
||||
if ($creationTime == false || $creationTime < 0) {
|
||||
$element->creation_time = gmdate('Y-m-d H:i:s', 1);
|
||||
}
|
||||
return (array)$element;
|
||||
})
|
||||
->toArray();
|
||||
|
||||
Account::insert($originAccounts);
|
||||
|
||||
$bar->advance();
|
||||
}
|
||||
|
||||
$bar->finish();
|
||||
|
||||
$this->newLine();
|
||||
|
||||
$this->disableForeignKeyCheck();
|
||||
|
||||
// Passwords
|
||||
$this->info('Migrating the passwords');
|
||||
|
||||
$pages = Capsule::table($this->option('passwords-table'))->count() / $this->pagination;
|
||||
$bar = $this->output->createProgressBar($pages);
|
||||
|
||||
for ($page = 0; $page <= $pages; $page++) {
|
||||
$originPasswords = Capsule::table($this->option('passwords-table'))
|
||||
->take($this->pagination)
|
||||
->skip($page*$this->pagination)
|
||||
->get()
|
||||
->map(function ($element) {
|
||||
return (array)$element;
|
||||
})
|
||||
->toArray();
|
||||
|
||||
Password::insert($originPasswords);
|
||||
|
||||
$bar->advance();
|
||||
}
|
||||
|
||||
$bar->finish();
|
||||
|
||||
$this->newLine();
|
||||
|
||||
// Aliases
|
||||
$this->info('Migrating the aliases');
|
||||
|
||||
$pages = Capsule::table($this->option('aliases-table'))->count() / $this->pagination;
|
||||
$bar = $this->output->createProgressBar($pages);
|
||||
|
||||
for ($page = 0; $page <= $pages; $page++) {
|
||||
$originAliases = Capsule::table($this->option('aliases-table'))
|
||||
->take($this->pagination)
|
||||
->skip($page*$this->pagination)
|
||||
->get()
|
||||
->map(function ($element) {
|
||||
return (array)$element;
|
||||
})
|
||||
->toArray();
|
||||
|
||||
Alias::insert($originAliases);
|
||||
|
||||
$bar->advance();
|
||||
}
|
||||
|
||||
$bar->finish();
|
||||
|
||||
// SQLite database migration
|
||||
|
||||
if ($this->argument('sqlite-file-path')) {
|
||||
$this->newLine();
|
||||
|
||||
$this->info('Migrating the admins');
|
||||
|
||||
$originAdmins = Capsule::connection('sqlite')
|
||||
->table('admins')
|
||||
->get()
|
||||
->map(function ($element) {
|
||||
return (array)$element;
|
||||
})
|
||||
->toArray();
|
||||
Admin::insert($originAdmins);
|
||||
|
||||
$this->info('Migrating the api keys');
|
||||
|
||||
$originApiKeys = Capsule::connection('sqlite')
|
||||
->table('api_keys')
|
||||
->get()
|
||||
->map(function ($element) {
|
||||
return (array)$element;
|
||||
})
|
||||
->toArray();
|
||||
ApiKey::insert($originApiKeys);
|
||||
|
||||
$this->info('Migrating the nonces');
|
||||
|
||||
$originNonces = Capsule::connection('sqlite')
|
||||
->table('nonces')
|
||||
->get()
|
||||
->map(function ($element) {
|
||||
return (array)$element;
|
||||
})
|
||||
->toArray();
|
||||
DigestNonce::insert($originNonces);
|
||||
|
||||
$this->info('Migrating the email changed');
|
||||
|
||||
$originEmailChanged = Capsule::connection('sqlite')
|
||||
->table('email_changed')
|
||||
->get()
|
||||
->map(function ($element) {
|
||||
return (array)$element;
|
||||
})
|
||||
->toArray();
|
||||
EmailChanged::insert($originEmailChanged);
|
||||
|
||||
$this->info('Migrating the phone change code');
|
||||
|
||||
$originPhoneChangeCodes = Capsule::connection('sqlite')
|
||||
->table('phone_change_codes')
|
||||
->get()
|
||||
->map(function ($element) {
|
||||
return (array)$element;
|
||||
})
|
||||
->toArray();
|
||||
PhoneChangeCode::insert($originPhoneChangeCodes);
|
||||
}
|
||||
|
||||
$this->enableForeignKeyCheck();
|
||||
|
||||
$this->newLine();
|
||||
$this->info('Databases migrated');
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\ExternalAccount;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class ImportExternalAccounts extends Command
|
||||
{
|
||||
protected $signature = 'accounts:import-externals {file_path}';
|
||||
|
||||
protected $description = 'Import external accounts from a file';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
if (!file_exists($this->argument('file_path'))) {
|
||||
$this->error('The file does not exists');
|
||||
return 1;
|
||||
}
|
||||
|
||||
$json = json_decode(file_get_contents($this->argument('file_path')));
|
||||
|
||||
if (empty($json)) {
|
||||
$this->error('Nothing to import or incorrect file');
|
||||
return 1;
|
||||
}
|
||||
|
||||
$existingUsernames = ExternalAccount::select('username')
|
||||
->from('external_accounts')
|
||||
->get()
|
||||
->pluck('username');
|
||||
$existingCounter = 0;
|
||||
$importedCounter = 0;
|
||||
|
||||
$externalAccounts = collect();
|
||||
foreach ($json as $account) {
|
||||
if ($existingUsernames->contains($account->username)) {
|
||||
$existingCounter++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$externalAccount = new ExternalAccount;
|
||||
$externalAccount->username = $account->username;
|
||||
$externalAccount->domain = $account->domain;
|
||||
$externalAccount->group = $account->group;
|
||||
$externalAccount->password = $account->password;
|
||||
$externalAccount->algorithm = $account->algorithm;
|
||||
|
||||
$externalAccounts->push($externalAccount->toArray());
|
||||
$importedCounter++;
|
||||
}
|
||||
|
||||
ExternalAccount::insert($externalAccounts->toArray());
|
||||
|
||||
$this->info($importedCounter . ' accounts imported');
|
||||
|
||||
if ($existingCounter > 0) {
|
||||
$this->info($existingCounter . ' accounts already in the database');
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
67
flexiapi/app/Console/Commands/Spaces/CreateUpdate.php
Normal file
67
flexiapi/app/Console/Commands/Spaces/CreateUpdate.php
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
<?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\Console\Commands\Spaces;
|
||||
|
||||
use App\Space;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class CreateUpdate extends Command
|
||||
{
|
||||
protected $signature = 'spaces:create-update {sip_domain} {host} {name} {--super}';
|
||||
protected $description = 'Create a Space';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$this->info('Your are creating or updating a Space in the database');
|
||||
|
||||
if (empty(config('app.root_host'))) {
|
||||
$this->error('The environnement variable APP_ROOT_HOST doesn\'t seems to be set');
|
||||
}
|
||||
|
||||
if (!str_ends_with($this->argument('host'), config('app.root_host'))) {
|
||||
$this->error('The provided host doesn\'t seems to ends with ' . config('app.root_host'));
|
||||
}
|
||||
|
||||
$space = Space::where('domain', $this->argument('sip_domain'))->firstOrNew();
|
||||
$space->host = $this->argument('host');
|
||||
$space->domain = $this->argument('sip_domain');
|
||||
$space->name = $this->argument('name');
|
||||
|
||||
if ($hostSpace = Space::where('host', $this->argument('host'))->first()) {
|
||||
if (!$space->exists && $hostSpace->domain != $space->domain) {
|
||||
$this->error('A Space with this host and a different sip_domain already exists in the database');
|
||||
return Command::FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
$space->exists
|
||||
? $this->info('The space already exists, updating it')
|
||||
: $this->info('A new Space will be created');
|
||||
|
||||
$space->super = (bool)$this->option('super');
|
||||
$space->super
|
||||
? $this->info('Set as a super Space')
|
||||
: $this->info('Set as a normal Space');
|
||||
|
||||
$space->save();
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
57
flexiapi/app/Console/Commands/Spaces/ExpirationEmails.php
Normal file
57
flexiapi/app/Console/Commands/Spaces/ExpirationEmails.php
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands\Spaces;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Carbon\Carbon;
|
||||
|
||||
use App\Mail\ExpiringSpace;
|
||||
use App\Space;
|
||||
|
||||
class ExpirationEmails extends Command
|
||||
{
|
||||
protected $signature = 'spaces:expiration-emails {days?}';
|
||||
protected $description = 'Send an expiration email on the designated configured days before expiration. Days must be ordered descending and comma separated (eg. 7,3,1)';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$days = ['7','3','1'];
|
||||
|
||||
if ($this->argument('days')) {
|
||||
preg_match_all('/\d++/', $this->argument('days'), $matches);
|
||||
|
||||
if (!empty($matches[0])) {
|
||||
$i = 0;
|
||||
|
||||
while ($i + 1 < count($matches[0]) && (int)$matches[0][$i] > (int)$matches[0][$i + 1]) {
|
||||
$i++;
|
||||
}
|
||||
|
||||
if ($i != count($matches[0]) - 1) {
|
||||
$this->error('The days must be integer, ordered descending and comma separated');
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$days = $matches[0];
|
||||
}
|
||||
}
|
||||
|
||||
$expiringSpaces = Space::whereNotNull('expire_at')->whereDate('expire_at', '>=', Carbon::now())->get();
|
||||
|
||||
foreach ($expiringSpaces as $expiringSpace) {
|
||||
if (in_array($expiringSpace->daysLeft, $days)) {
|
||||
$this->info($expiringSpace->name . ' (' . $expiringSpace->host . ') is expiring in ' . $expiringSpace->daysLeft . ' days');
|
||||
|
||||
$admins = $expiringSpace->admins()->withoutGlobalScopes()->whereNotNull('email')->get();
|
||||
|
||||
$this->info('Sending an email to the admins ' . $admins->implode('email', ','));
|
||||
|
||||
foreach ($admins as $admin) {
|
||||
Mail::to($admin->email)->send(new ExpiringSpace($expiringSpace));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands\Spaces;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Space;
|
||||
|
||||
class ImportConfigurationFromDotEnv extends Command
|
||||
{
|
||||
protected $signature = 'spaces:import-configuration-from-dot-env {sip_domain}';
|
||||
protected $description = 'Import the deprecated space DotEnv configuration in a Space';
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$space = Space::where('domain', $this->argument('sip_domain'))->first();
|
||||
|
||||
if (!$space) {
|
||||
$this->error('The space cannot be found');
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
$this->info('The following configuration will be imported in the space ' . $space->domain);
|
||||
$this->info('The existing settings will be overwritten:');
|
||||
|
||||
$space->name = env('APP_NAME', null);
|
||||
|
||||
$space->custom_theme = env('INSTANCE_CUSTOM_THEME', false);
|
||||
$space->web_panel = env('WEB_PANEL', true);
|
||||
|
||||
$space->copyright_text = env('INSTANCE_COPYRIGHT', null);
|
||||
$space->intro_registration_text = env('INSTANCE_INTRO_REGISTRATION', null);
|
||||
$space->newsletter_registration_address = env('NEWSLETTER_REGISTRATION_ADDRESS', null);
|
||||
$space->account_proxy_registrar_address = env('ACCOUNT_PROXY_REGISTRAR_ADDRESS', 'sip.domain.com');
|
||||
$space->account_realm = env('ACCOUNT_REALM', null);
|
||||
$space->custom_provisioning_overwrite_all = env('ACCOUNT_PROVISIONING_OVERWRITE_ALL', false);
|
||||
$space->provisioning_use_linphone_provisioning_header = env('ACCOUNT_PROVISIONING_USE_X_LINPHONE_PROVISIONING_HEADER', true);
|
||||
|
||||
$space->public_registration = env('PUBLIC_REGISTRATION', true);
|
||||
$space->phone_registration = env('PHONE_AUTHENTICATION', true);
|
||||
$space->intercom_features = env('INTERCOM_FEATURES', false);
|
||||
|
||||
foreach ($space->getDirty() as $key => $value) {
|
||||
$show = ' - ' . $key . ' => ';
|
||||
$show .= ($value == null) ? 'null' : $value;
|
||||
|
||||
$this->info($show);
|
||||
}
|
||||
|
||||
if ($this->confirm('Do you want to update ' . $space->domain . '?', false)) {
|
||||
$space->save();
|
||||
$this->info('Space updated');
|
||||
}
|
||||
}
|
||||
}
|
||||
33
flexiapi/app/Console/Commands/UpdatePhoneCountries.php
Normal file
33
flexiapi/app/Console/Commands/UpdatePhoneCountries.php
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<?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');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
61
flexiapi/app/Consommable.php
Normal file
61
flexiapi/app/Consommable.php
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use DateTime;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
abstract class Consommable extends Model
|
||||
{
|
||||
protected string $consommableAttribute = 'code';
|
||||
protected ?string $configExpirationMinutesKey = null;
|
||||
protected $casts = [
|
||||
'expire_at' => 'datetime'
|
||||
];
|
||||
|
||||
public function consume()
|
||||
{
|
||||
$this->{$this->consommableAttribute} = null;
|
||||
$this->save();
|
||||
}
|
||||
|
||||
public function fillRequestInfo(Request $request)
|
||||
{
|
||||
$this->ip = $request->ip();
|
||||
$this->user_agent = $request->userAgent();
|
||||
}
|
||||
|
||||
public function offed(): bool
|
||||
{
|
||||
return $this->consumed() || $this->expired();
|
||||
}
|
||||
|
||||
public function consumed(): bool
|
||||
{
|
||||
return $this->{$this->consommableAttribute} == null;
|
||||
}
|
||||
|
||||
public function getExpireAtAttribute(): ?string
|
||||
{
|
||||
if ($this->isExpirable()) {
|
||||
return $this->created_at->addMinutes((int)config('app.' . $this->configExpirationMinutesKey))->toJSON();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function expired(): bool
|
||||
{
|
||||
return ($this->isExpirable()
|
||||
&& Carbon::now()->subMinutes((int)config('app.' . $this->configExpirationMinutesKey))->isAfter($this->created_at));
|
||||
}
|
||||
|
||||
private function isExpirable(): bool
|
||||
{
|
||||
return $this->configExpirationMinutesKey != null
|
||||
&& config('app.' . $this->configExpirationMinutesKey) != null
|
||||
&& (int)config('app.' . $this->configExpirationMinutesKey) > 0;
|
||||
}
|
||||
}
|
||||
23
flexiapi/app/ContactsList.php
Normal file
23
flexiapi/app/ContactsList.php
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ContactsList extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $withCount = ['contacts'];
|
||||
|
||||
public function contacts()
|
||||
{
|
||||
return $this->belongsToMany(Account::class, 'contacts_list_contact', 'contacts_list_id', 'contact_id');
|
||||
}
|
||||
|
||||
public function space()
|
||||
{
|
||||
return $this->belongsTo(Space::class);
|
||||
}
|
||||
}
|
||||
|
|
@ -24,27 +24,19 @@ use Carbon\Carbon;
|
|||
|
||||
class Device extends Model
|
||||
{
|
||||
protected $fillable = ['user_agent'];
|
||||
public function fromRedisContact(string $contact)
|
||||
{
|
||||
// Ugly :'(
|
||||
$result = [];
|
||||
$exploded = explode(';', urldecode($contact));
|
||||
preg_match("/<(.*)>;(.*)/", $contact, $matches);
|
||||
|
||||
foreach ($exploded as $line) {
|
||||
$line = explode('=', $line);
|
||||
$parsed = parse_url($matches[1]);
|
||||
parse_str($parsed["query"], $query);
|
||||
|
||||
if (count($line) == 2) {
|
||||
$result[trim($line[0])] = $line[1];
|
||||
}
|
||||
parse_str(str_replace(";", "&", $parsed["path"]), $sipParams);
|
||||
parse_str(str_replace(";", "&", $matches[2]), $sipHeaders);
|
||||
|
||||
// User agent
|
||||
if (count($line) == 4) {
|
||||
$result['userAgent'] = substr($line[3], 0, -1);
|
||||
}
|
||||
}
|
||||
|
||||
$this->uuid = \substr($result['sip.instance'], 2, -2);
|
||||
$this->update_time = Carbon::createFromTimestamp($result['updatedAt']);
|
||||
$this->user_agent = $result['userAgent'];
|
||||
$this->uuid = substr($sipHeaders['sip_instance'], 2, -2);
|
||||
$this->update_time = Carbon::createFromTimestamp($sipParams['updatedAt']);
|
||||
$this->user_agent = $query["user-agent"];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,6 @@ class DigestNonce extends Model
|
|||
|
||||
public function account()
|
||||
{
|
||||
return $this->belongsTo('App\Account');
|
||||
return $this->belongsTo(Account::class);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
48
flexiapi/app/EmailChangeCode.php
Normal file
48
flexiapi/app/EmailChangeCode.php
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
<?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;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
||||
class EmailChangeCode extends Consommable
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected ?string $configExpirationMinutesKey = 'email_change_code_expiration_minutes';
|
||||
protected $hidden = ['id', 'account_id', 'code'];
|
||||
|
||||
public function account()
|
||||
{
|
||||
return $this->belongsTo(Account::class);
|
||||
}
|
||||
|
||||
public function validate(int $code): bool
|
||||
{
|
||||
return ($this->code == $code);
|
||||
}
|
||||
|
||||
public function getObfuscatedEmailAttribute()
|
||||
{
|
||||
$stars = 4; // Min Stars to use
|
||||
$at = strpos($this->attributes['email'], '@');
|
||||
if ($at - 2 > $stars) $stars = $at - 2;
|
||||
return substr($this->attributes['email'], 0, 1) . str_repeat('*', $stars) . substr($this->attributes['email'], $at - 1);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,21 @@
|
|||
<?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;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
|
@ -9,18 +25,15 @@ class ExternalAccount extends Model
|
|||
{
|
||||
use HasFactory;
|
||||
|
||||
public const PROTOCOLS = ['UDP', 'TCP','TLS'];
|
||||
|
||||
public function account()
|
||||
{
|
||||
return $this->belongsTo('App\Account');
|
||||
return $this->belongsTo(Account::class);
|
||||
}
|
||||
|
||||
public function getIdentifierAttribute()
|
||||
public function getIdentifierAttribute(): string
|
||||
{
|
||||
return $this->attributes['username'].'@'.$this->attributes['domain'];
|
||||
}
|
||||
|
||||
public function getResolvedRealmAttribute()
|
||||
{
|
||||
return $this->attributes['domain'];
|
||||
return $this->attributes['username'] . '@' . $this->attributes['domain'];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,25 +17,46 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
use App\Account;
|
||||
use App\Space;
|
||||
use App\DigestNonce;
|
||||
use App\ExternalAccount;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use League\CommonMark\CommonMarkConverter;
|
||||
use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkExtension;
|
||||
use League\CommonMark\Extension\TableOfContents\TableOfContentsExtension;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
function space(): ?Space
|
||||
{
|
||||
return is_object(request()->space) ? request()->space : null;
|
||||
}
|
||||
|
||||
function passwordAlgorithms(): array
|
||||
{
|
||||
return [
|
||||
'MD5' => 'md5',
|
||||
'SHA-256' => 'sha256',
|
||||
];
|
||||
}
|
||||
|
||||
function generateNonce(): string
|
||||
{
|
||||
return Str::random(32);
|
||||
}
|
||||
|
||||
function getRequestBoolean(Request $request, string $key, bool $reversed = false): bool
|
||||
{
|
||||
$bool = $request->has($key) ? $request->get($key) == "on" : false;
|
||||
|
||||
return $reversed ? !$bool : $bool;
|
||||
}
|
||||
|
||||
function generateValidNonce(Account $account): string
|
||||
{
|
||||
$nonce = new DigestNonce;
|
||||
$nonce = new DigestNonce();
|
||||
$nonce->account_id = $account->id;
|
||||
$nonce->nonce = generateNonce();
|
||||
$nonce->save();
|
||||
|
|
@ -43,25 +64,25 @@ function generateValidNonce(Account $account): string
|
|||
return $nonce->nonce;
|
||||
}
|
||||
|
||||
function bchash(string $username, string $domain, string $password, string $algorithm = 'MD5')
|
||||
function bchash(string $username, string $domain, string $password, string $algorithm = 'MD5'): string
|
||||
{
|
||||
$algos = ['MD5' => 'md5', 'SHA-256' => 'sha256'];
|
||||
|
||||
return hash($algos[$algorithm], $username . ':' . $domain . ':' . $password);
|
||||
return hash(passwordAlgorithms()[$algorithm], $username . ':' . $domain . ':' . $password);
|
||||
}
|
||||
|
||||
function generatePin()
|
||||
function generatePin(): int
|
||||
{
|
||||
return mt_rand(1000, 9999);
|
||||
}
|
||||
|
||||
function percent($value, $max)
|
||||
function percent($value, $max): float
|
||||
{
|
||||
if ($max == 0) $max = 1;
|
||||
if ($max == 0) {
|
||||
$max = 1;
|
||||
}
|
||||
return round(($value * 100) / $max, 2);
|
||||
}
|
||||
|
||||
function markdownDocumentationView($view): string
|
||||
function markdownDocumentationView(string $view): string
|
||||
{
|
||||
$converter = new CommonMarkConverter([
|
||||
'heading_permalink' => [
|
||||
|
|
@ -76,41 +97,46 @@ function markdownDocumentationView($view): string
|
|||
],
|
||||
]);
|
||||
|
||||
$converter->getEnvironment()->addExtension(new HeadingPermalinkExtension);
|
||||
$converter->getEnvironment()->addExtension(new TableOfContentsExtension);
|
||||
$converter->getEnvironment()->addExtension(new HeadingPermalinkExtension());
|
||||
$converter->getEnvironment()->addExtension(new TableOfContentsExtension());
|
||||
|
||||
return (string) $converter->convert(
|
||||
(string)view($view, [
|
||||
'app_name' => config('app.name')
|
||||
(string) view($view, [
|
||||
'app_name' => space()->name
|
||||
])->render()
|
||||
);
|
||||
}
|
||||
|
||||
function getAvailableExternalAccount(): ?ExternalAccount
|
||||
function hasCoTURNConfigured(): bool
|
||||
{
|
||||
if (Schema::hasTable('external_accounts')) {
|
||||
return ExternalAccount::where('used', false)
|
||||
->where('account_id', null)
|
||||
->first();
|
||||
}
|
||||
|
||||
return null;
|
||||
return config('app.coturn_session_ttl_minutes') > 0
|
||||
&& !empty(config('app.coturn_server_host'))
|
||||
&& !empty(config('app.coturn_static_auth_secret'));
|
||||
}
|
||||
|
||||
function publicRegistrationEnabled(): bool
|
||||
function getCoTURNCredentials(): array
|
||||
{
|
||||
if (config('app.public_registration')) {
|
||||
if (config('app.consume_external_account_on_create')) {
|
||||
return (bool)getAvailableExternalAccount();
|
||||
}
|
||||
$user = Str::random(8);
|
||||
$secret = config('app.coturn_static_auth_secret');
|
||||
|
||||
return true;
|
||||
}
|
||||
$ttl = config('app.coturn_session_ttl_minutes') * 60;
|
||||
$time = time() + $ttl;
|
||||
|
||||
return false;
|
||||
$username = $time . ':' . Str::random(16);
|
||||
$password = base64_encode(hash_hmac('sha1', $username, $secret, true));
|
||||
|
||||
return [
|
||||
'username' => $username,
|
||||
'password' => $password,
|
||||
];
|
||||
}
|
||||
|
||||
function isRegularExpression($string): bool
|
||||
function parseSIP(string $sipAdress): array
|
||||
{
|
||||
return explode('@', \substr($sipAdress, 4));
|
||||
}
|
||||
|
||||
function isRegularExpression(string $string): bool
|
||||
{
|
||||
set_error_handler(function () {
|
||||
}, E_WARNING);
|
||||
|
|
@ -121,12 +147,333 @@ function isRegularExpression($string): bool
|
|||
return $isRegularExpression;
|
||||
}
|
||||
|
||||
function replaceHost(string $url, string $host): string
|
||||
{
|
||||
$components = parse_url($url);
|
||||
return str_replace($components['host'], $host, $url);
|
||||
}
|
||||
|
||||
function resolveDomain(Request $request): string
|
||||
{
|
||||
return $request->has('domain')
|
||||
&& $request->user()
|
||||
&& $request->user()->admin
|
||||
&& config('app.admins_manage_multi_domains')
|
||||
? $request->get('domain')
|
||||
: config('app.sip_domain');
|
||||
&& $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'))
|
||||
);
|
||||
}
|
||||
|
||||
function captchaConfigured(): bool
|
||||
{
|
||||
return env('HCAPTCHA_SECRET', false) != false || env('HCAPTCHA_SITEKEY', false) != false;
|
||||
}
|
||||
|
||||
function resolveUserContacts(Request $request)
|
||||
{
|
||||
$selected = ['id', 'username', 'domain', 'activated', 'dtmf_protocol', 'display_name'];
|
||||
|
||||
return Account::withoutGlobalScopes()->whereIn('id', function ($query) use ($request) {
|
||||
$query->select('contact_id')
|
||||
->from('contacts')
|
||||
->where('account_id', $request->user()->id)
|
||||
->union(
|
||||
DB::table('contacts_list_contact')
|
||||
->select('contact_id')
|
||||
->whereIn('contacts_list_id', function ($query) use ($request) {
|
||||
$query->select('contacts_list_id')
|
||||
->from('account_contacts_list')
|
||||
->where('account_id', $request->user()->id);
|
||||
})
|
||||
);
|
||||
})->select($selected);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate date string to ISO8601
|
||||
* From: https://github.com/penance316/laravel-iso8601-validator/blob/master/src/IsoDateValidator.php
|
||||
*
|
||||
* @param $attribute
|
||||
* @param $value
|
||||
* @param $parameters
|
||||
* @param $validator
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function validateIsoDate($attribute, $value, $parameters, $validator): bool
|
||||
{
|
||||
$regex = (is_array($parameters) && in_array('utc', $parameters))
|
||||
? '/^(\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]((([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)?)))?$/'
|
||||
// 2012-04-23T18:25:43.511Z
|
||||
// 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* This list was got from the Internet
|
||||
*
|
||||
* @see https://gist.github.com/vxnick/380904
|
||||
* @return array
|
||||
*/
|
||||
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',
|
||||
'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',
|
||||
'BY' => 'Belarus',
|
||||
'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',
|
||||
'CM' => 'Cameroon',
|
||||
'CN' => 'China',
|
||||
'CO' => 'Colombia',
|
||||
'CR' => 'Costa Rica',
|
||||
'CU' => 'Cuba',
|
||||
'CV' => 'Cabo Verde',
|
||||
'CW' => 'Curaçao',
|
||||
'CX' => 'Christmas Island',
|
||||
'CY' => 'Cyprus',
|
||||
'CZ' => 'Czechia',
|
||||
'DE' => 'Germany',
|
||||
'DJ' => 'Djibouti',
|
||||
'DK' => 'Denmark',
|
||||
'DM' => 'Dominica',
|
||||
'DO' => 'Dominican Republic',
|
||||
'DZ' => 'Algeria',
|
||||
'EC' => 'Ecuador',
|
||||
'EE' => 'Estonia',
|
||||
'EG' => 'Egypt',
|
||||
'EH' => 'Western Sahara',
|
||||
'ER' => 'Eritrea',
|
||||
'ES' => 'Spain',
|
||||
'ET' => 'Ethiopia',
|
||||
'FI' => 'Finland',
|
||||
'FJ' => 'Fiji',
|
||||
'FK' => 'Falkland Islands',
|
||||
'FM' => 'Micronesia',
|
||||
'FO' => 'Faroe Islands',
|
||||
'FR' => 'France',
|
||||
'GA' => 'Gabon',
|
||||
'GB' => 'United Kingdom',
|
||||
'GD' => 'Grenada',
|
||||
'GE' => 'Georgia',
|
||||
'GF' => 'French Guiana',
|
||||
'GG' => 'Guernsey',
|
||||
'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',
|
||||
'GU' => 'Guam',
|
||||
'GW' => 'Guinea-Bissau',
|
||||
'GY' => 'Guyana',
|
||||
'HK' => 'Hong Kong',
|
||||
'HM' => 'Heard & McDonald Islands',
|
||||
'HN' => 'Honduras',
|
||||
'HR' => 'Croatia',
|
||||
'HT' => 'Haiti',
|
||||
'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',
|
||||
'IT' => 'Italy',
|
||||
'JE' => 'Jersey',
|
||||
'JM' => 'Jamaica',
|
||||
'JO' => 'Jordan',
|
||||
'JP' => 'Japan',
|
||||
'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',
|
||||
'LA' => 'Laos',
|
||||
'LB' => 'Lebanon',
|
||||
'LC' => 'St. Lucia',
|
||||
'LI' => 'Liechtenstein',
|
||||
'LK' => 'Sri Lanka',
|
||||
'LR' => 'Liberia',
|
||||
'LS' => 'Lesotho',
|
||||
'LT' => 'Lithuania',
|
||||
'LU' => 'Luxembourg',
|
||||
'LV' => 'Latvia',
|
||||
'LY' => 'Libya',
|
||||
'MA' => 'Morocco',
|
||||
'MC' => 'Monaco',
|
||||
'MD' => 'Moldova',
|
||||
'ME' => 'Montenegro',
|
||||
'MF' => 'St. Martin',
|
||||
'MG' => 'Madagascar',
|
||||
'MH' => 'Marshall Islands',
|
||||
'MK' => 'North Macedonia',
|
||||
'ML' => 'Mali',
|
||||
'MM' => 'Myanmar',
|
||||
'MN' => 'Mongolia',
|
||||
'MO' => 'Macao',
|
||||
'MP' => 'Northern Mariana Islands',
|
||||
'MQ' => 'Martinique',
|
||||
'MR' => 'Mauritania',
|
||||
'MS' => 'Montserrat',
|
||||
'MT' => 'Malta',
|
||||
'MU' => 'Mauritius',
|
||||
'MV' => 'Maldives',
|
||||
'MW' => 'Malawi',
|
||||
'MX' => 'Mexico',
|
||||
'MY' => 'Malaysia',
|
||||
'MZ' => 'Mozambique',
|
||||
'NA' => 'Namibia',
|
||||
'NC' => 'New Caledonia',
|
||||
'NE' => 'Niger',
|
||||
'NF' => 'Norfolk Island',
|
||||
'NG' => 'Nigeria',
|
||||
'NI' => 'Nicaragua',
|
||||
'NL' => 'Netherlands',
|
||||
'NO' => 'Norway',
|
||||
'NP' => 'Nepal',
|
||||
'NR' => 'Nauru',
|
||||
'NU' => 'Niue',
|
||||
'NZ' => 'New Zealand',
|
||||
'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',
|
||||
'PY' => 'Paraguay',
|
||||
'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',
|
||||
'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',
|
||||
'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',
|
||||
'TW' => 'Taiwan',
|
||||
'TZ' => 'Tanzania',
|
||||
'UA' => 'Ukraine',
|
||||
'UG' => 'Uganda',
|
||||
'UM' => 'U.S. Minor Outlying Islands',
|
||||
'US' => 'United States',
|
||||
'UY' => 'Uruguay',
|
||||
'UZ' => 'Uzbekistan',
|
||||
'VA' => 'Holy See (Vatican City)',
|
||||
'VC' => 'St. Vincent & Grenadines',
|
||||
'VE' => 'Venezuela',
|
||||
'VG' => 'British Virgin Islands',
|
||||
'VI' => 'U.S. Virgin Islands',
|
||||
'VN' => 'Vietnam',
|
||||
'VU' => 'Vanuatu',
|
||||
'WF' => 'Wallis & Futuna',
|
||||
'WS' => 'Samoa',
|
||||
'YE' => 'Yemen',
|
||||
'YT' => 'Mayotte',
|
||||
'ZA' => 'South Africa',
|
||||
'ZM' => 'Zambia',
|
||||
'ZW' => 'Zimbabwe',
|
||||
];
|
||||
}
|
||||
|
|
|
|||
13
flexiapi/app/Http/Controllers/AboutController.php
Normal file
13
flexiapi/app/Http/Controllers/AboutController.php
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class AboutController extends Controller
|
||||
{
|
||||
public function about(Request $request)
|
||||
{
|
||||
return view('about');
|
||||
}
|
||||
}
|
||||
|
|
@ -23,42 +23,38 @@ use Illuminate\Http\Request;
|
|||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Account;
|
||||
use App\AccountTombstone;
|
||||
use App\Http\Requests\Account\Create\Web\Request as WebRequest;
|
||||
use App\Services\AccountService;
|
||||
|
||||
class AccountController extends Controller
|
||||
{
|
||||
public function home(Request $request)
|
||||
public function blocked(Request $request)
|
||||
{
|
||||
if ($request->user()) {
|
||||
return redirect()->route('account.panel');
|
||||
}
|
||||
|
||||
return view('account.home', [
|
||||
'count' => Account::where('activated', true)->count()
|
||||
]);
|
||||
return view('account.blocked');
|
||||
}
|
||||
|
||||
public function documentation(Request $request)
|
||||
public function dashboard(Request $request)
|
||||
{
|
||||
return view('account.documentation', [
|
||||
'documentation' => markdownDocumentationView('account.documentation_markdown')
|
||||
]);
|
||||
}
|
||||
|
||||
public function panel(Request $request)
|
||||
{
|
||||
return view('account.panel', [
|
||||
return view('account.dashboard', [
|
||||
'account' => $request->user()
|
||||
]);
|
||||
}
|
||||
|
||||
public function generateApiKey(Request $request)
|
||||
public function store(WebRequest $request)
|
||||
{
|
||||
$account = $request->user();
|
||||
$account->generateApiKey();
|
||||
$account = (new AccountService())->store($request);
|
||||
|
||||
return redirect()->back();
|
||||
Auth::login($account);
|
||||
|
||||
if ($request->has('phone')) {
|
||||
(new AccountService(api: false))->requestPhoneChange($request);
|
||||
return redirect()->route('account.phone.validate');
|
||||
} elseif ($request->has('email')) {
|
||||
(new AccountService(api: false))->requestEmailChange($request);
|
||||
return redirect()->route('account.email.validate');
|
||||
}
|
||||
|
||||
return abort(404);
|
||||
}
|
||||
|
||||
public function delete(Request $request)
|
||||
|
|
@ -72,14 +68,10 @@ class AccountController extends Controller
|
|||
{
|
||||
$request->validate(['identifier' => 'required|same:identifier_confirm']);
|
||||
|
||||
if (!$request->user()->hasTombstone()) {
|
||||
$tombstone = new AccountTombstone;
|
||||
$tombstone->username = $request->user()->username;
|
||||
$tombstone->domain = $request->user()->domain;
|
||||
$tombstone->save();
|
||||
}
|
||||
$request->user()->createTombstone();
|
||||
|
||||
(new AccountService)->destroy($request, $request->user()->id);
|
||||
|
||||
$request->user()->delete();
|
||||
Auth::logout();
|
||||
|
||||
return redirect()->route('account.login');
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
/*
|
||||
Flexisip Account Manager is a set of tools to manage SIP accounts.
|
||||
Copyright (C) 2021 Belledonne Communications SARL, All rights reserved.
|
||||
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
|
||||
|
|
@ -17,27 +17,25 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
namespace App\Http\Controllers\Account;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
use App\Libraries\StatisticsCruncher;
|
||||
|
||||
class StatisticController extends Controller
|
||||
class ApiKeyController extends Controller
|
||||
{
|
||||
public function month(Request $request)
|
||||
public function show(Request $request)
|
||||
{
|
||||
return StatisticsCruncher::month();
|
||||
return view('account.api_key', [
|
||||
'account' => $request->user()
|
||||
]);
|
||||
}
|
||||
|
||||
public function week(Request $request)
|
||||
public function update(Request $request)
|
||||
{
|
||||
return StatisticsCruncher::week();
|
||||
}
|
||||
$account = $request->user();
|
||||
$account->generateUserApiKey($request->ip());
|
||||
|
||||
public function day(Request $request)
|
||||
{
|
||||
return StatisticsCruncher::day();
|
||||
return redirect()->back();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,21 @@
|
|||
<?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\Account;
|
||||
|
||||
|
|
@ -8,7 +25,7 @@ use Illuminate\Http\Request;
|
|||
|
||||
use Endroid\QrCode\Builder\Builder;
|
||||
use Endroid\QrCode\Encoding\Encoding;
|
||||
use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelHigh;
|
||||
use Endroid\QrCode\ErrorCorrectionLevel;
|
||||
use Endroid\QrCode\Writer\PngWriter;
|
||||
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
|
@ -26,10 +43,10 @@ class AuthTokenController extends Controller
|
|||
->data(
|
||||
$authToken->account_id
|
||||
? route('auth_tokens.auth', ['token' => $authToken->token])
|
||||
: route('auth_tokens.auth.external', ['token' => $authToken->token])
|
||||
: route('account.auth_tokens.auth.external', ['token' => $authToken->token])
|
||||
)
|
||||
->encoding(new Encoding('UTF-8'))
|
||||
->errorCorrectionLevel(new ErrorCorrectionLevelHigh())
|
||||
->errorCorrectionLevel(ErrorCorrectionLevel::High)
|
||||
->size(300)
|
||||
->margin(10)
|
||||
->build();
|
||||
|
|
@ -55,9 +72,7 @@ class AuthTokenController extends Controller
|
|||
|
||||
$authToken->delete();
|
||||
|
||||
$request->session()->flash('success', 'Successfully authenticated');
|
||||
|
||||
return redirect()->route('account.panel');
|
||||
return redirect()->route('account.home');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -70,10 +85,8 @@ class AuthTokenController extends Controller
|
|||
if (!$authToken->account_id) {
|
||||
$authToken->account_id = $request->user()->id;
|
||||
$authToken->save();
|
||||
|
||||
$request->session()->flash('success', 'External device successfully authenticated');
|
||||
}
|
||||
|
||||
return redirect()->route('account.panel');
|
||||
return redirect()->route('account.home');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,13 +23,9 @@ use App\Http\Controllers\Controller;
|
|||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
use App\Account;
|
||||
use App\Alias;
|
||||
use App\AuthToken;
|
||||
use App\Libraries\OvhSMS;
|
||||
use App\Mail\PasswordAuthentication;
|
||||
|
||||
class AuthenticateController extends Controller
|
||||
{
|
||||
|
|
@ -37,11 +33,19 @@ class AuthenticateController extends Controller
|
|||
|
||||
public function login(Request $request)
|
||||
{
|
||||
if (auth()->user()) {
|
||||
return redirect()->route('account.panel');
|
||||
if ($request->user()) {
|
||||
if ($request->user()->superAdmin) {
|
||||
return redirect()->route('admin.spaces.index');
|
||||
} elseif ($request->user()->admin) {
|
||||
return redirect()->route('admin.spaces.me');
|
||||
}
|
||||
|
||||
return redirect()->route('account.dashboard');
|
||||
}
|
||||
|
||||
return view('account.login');
|
||||
return view('account.login', config('app.show_login_counter_temp') ? [
|
||||
'count' => Account::where('activated', true)->count()
|
||||
]: []);
|
||||
}
|
||||
|
||||
public function authenticate(Request $request)
|
||||
|
|
@ -54,17 +58,12 @@ class AuthenticateController extends Controller
|
|||
$account = Account::where('username', $request->get('username'))
|
||||
->first();
|
||||
|
||||
// Try alias
|
||||
if (!$account) {
|
||||
$alias = Alias::where('alias', $request->get('username'))->first();
|
||||
|
||||
if ($alias) {
|
||||
$account = $alias->account;
|
||||
}
|
||||
$account = Account::where('phone', $request->get('username'))->first();
|
||||
}
|
||||
|
||||
if (!$account) {
|
||||
return redirect()->back()->withErrors(['authentication' => 'The account doesn\'t exists']);
|
||||
return redirect()->back()->withErrors(['authentication' => __('Incorrect username or password')]);
|
||||
}
|
||||
|
||||
// Try out the passwords
|
||||
|
|
@ -74,182 +73,11 @@ class AuthenticateController extends Controller
|
|||
bchash($account->username, $account->resolvedRealm, $request->get('password'), $password->algorithm)
|
||||
)) {
|
||||
Auth::login($account);
|
||||
return redirect()->route('account.panel');
|
||||
return redirect()->route('account.home');
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->back()->withErrors(['authentication' => 'Wrong username or password']);
|
||||
}
|
||||
|
||||
public function loginEmail(Request $request)
|
||||
{
|
||||
return view('account.login.email', [
|
||||
'domain' => '@' . config('app.sip_domain')
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the form
|
||||
*/
|
||||
public function authenticateEmail(Request $request)
|
||||
{
|
||||
$rules = [
|
||||
'email' => 'required|email|exists:accounts,email',
|
||||
'g-recaptcha-response' => 'required|captcha',
|
||||
];
|
||||
|
||||
if (config('app.account_email_unique') == false) {
|
||||
$rules['username'] = 'required';
|
||||
}
|
||||
|
||||
$request->validate($rules);
|
||||
|
||||
$account = Account::where('email', $request->get('email'));
|
||||
|
||||
/**
|
||||
* Because several accounts can have the same email
|
||||
*/
|
||||
if (config('app.account_email_unique') == false) {
|
||||
$account = $account->where('username', $request->get('username'));
|
||||
}
|
||||
|
||||
$account = $account->first();
|
||||
|
||||
// Try alias
|
||||
if (!$account) {
|
||||
$alias = Alias::where('alias', $request->get('username'))->first();
|
||||
|
||||
if ($alias && $alias->account->email == $request->get('email')) {
|
||||
$account = $alias->account;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$account) {
|
||||
return redirect()->back()->withErrors(['authentication' => 'The account doesn\'t exists']);
|
||||
}
|
||||
|
||||
$account->confirmation_key = Str::random(self::$emailCodeSize);
|
||||
$account->provision();
|
||||
$account->save();
|
||||
|
||||
Mail::to($account)->send(new PasswordAuthentication($account));
|
||||
|
||||
return redirect()->route('account.check.email', $account->identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* A page that check if the email was validated and reload if not
|
||||
*/
|
||||
public function checkEmail(Request $request, string $sip)
|
||||
{
|
||||
if (auth()->user()) {
|
||||
return redirect()->route('account.panel');
|
||||
}
|
||||
|
||||
$account = Account::sip($sip)->firstOrFail();
|
||||
|
||||
return view('account.authenticate.email', [
|
||||
'account' => $account
|
||||
]);
|
||||
}
|
||||
|
||||
public function validateEmail(Request $request, string $code)
|
||||
{
|
||||
$request->merge(['code' => $code]);
|
||||
$request->validate(['code' => 'required|size:' . self::$emailCodeSize]);
|
||||
|
||||
$account = Account::where('confirmation_key', $code)->first();
|
||||
|
||||
if (!$account) {
|
||||
return redirect()->route('account.login_email');
|
||||
}
|
||||
|
||||
$account->confirmation_key = null;
|
||||
|
||||
// If there is already a password set, we directly activate the account
|
||||
if ($account->passwords()->count() != 0) {
|
||||
$account->activated = true;
|
||||
}
|
||||
|
||||
$account->save();
|
||||
|
||||
Auth::login($account);
|
||||
|
||||
// Ask the user to set a password
|
||||
if (!$account->activated) {
|
||||
return redirect()->route('account.password');
|
||||
}
|
||||
|
||||
return redirect()->route('account.panel');
|
||||
}
|
||||
|
||||
public function loginPhone(Request $request)
|
||||
{
|
||||
return view('account.login.phone');
|
||||
}
|
||||
|
||||
public function authenticatePhone(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'phone' => 'required|starts_with:+',
|
||||
'g-recaptcha-response' => 'required|captcha',
|
||||
]);
|
||||
|
||||
$account = Account::where('username', $request->get('phone'))
|
||||
->first();
|
||||
|
||||
// Try alias
|
||||
if (!$account) {
|
||||
$alias = Alias::where('alias', $request->get('phone'))->first();
|
||||
|
||||
if ($alias) {
|
||||
$account = $alias->account;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$account) {
|
||||
return redirect()->back()->withErrors([
|
||||
'phone' => 'Invalid phone number'
|
||||
]);
|
||||
}
|
||||
|
||||
$account->confirmation_key = generatePin();
|
||||
$account->save();
|
||||
|
||||
$ovhSMS = new OvhSMS;
|
||||
$ovhSMS->send($request->get('phone'), 'Your ' . config('app.name') . ' validation code is ' . $account->confirmation_key);
|
||||
|
||||
// Ask the user to set a password
|
||||
if (!$account->activated) {
|
||||
return redirect()->route('account.password');
|
||||
}
|
||||
|
||||
return view('account.authenticate.phone', [
|
||||
'account' => $account
|
||||
]);
|
||||
}
|
||||
|
||||
public function validatePhone(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'account_id' => 'required',
|
||||
'code' => 'required|digits:4'
|
||||
]);
|
||||
|
||||
$account = Account::where('id', $request->get('account_id'))
|
||||
->firstOrFail();
|
||||
|
||||
if ($account->confirmation_key != $request->get('code')) {
|
||||
return redirect()->back()->withErrors([
|
||||
'code' => 'Wrong code'
|
||||
]);
|
||||
}
|
||||
|
||||
$account->confirmation_key = null;
|
||||
$account->save();
|
||||
|
||||
Auth::login($account);
|
||||
return redirect()->route('account.panel');
|
||||
return redirect()->back()->withErrors(['authentication' => __('Incorrect username or password')]);
|
||||
}
|
||||
|
||||
public function loginAuthToken(Request $request, ?string $token = null)
|
||||
|
|
@ -263,6 +91,7 @@ class AuthenticateController extends Controller
|
|||
if ($authToken == null) {
|
||||
$authToken = new AuthToken;
|
||||
$authToken->token = Str::random(32);
|
||||
$authToken->fillRequestInfo($request);
|
||||
$authToken->save();
|
||||
|
||||
return redirect()->route('account.authenticate.auth_token', ['token' => $authToken->token]);
|
||||
|
|
@ -274,9 +103,7 @@ class AuthenticateController extends Controller
|
|||
|
||||
$authToken->delete();
|
||||
|
||||
$request->session()->flash('success', 'Successfully authenticated');
|
||||
|
||||
return redirect()->route('account.panel');
|
||||
return redirect()->route('account.home');
|
||||
}
|
||||
|
||||
return view('account.authenticate.auth_token', [
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ class ContactVcardController extends Controller
|
|||
public function index(Request $request)
|
||||
{
|
||||
return response(
|
||||
$request->user()->contacts->map(function ($contact) {
|
||||
resolveUserContacts($request)->get()->map(function ($contact) {
|
||||
return $contact->toVcard4();
|
||||
})->implode("\n")
|
||||
);
|
||||
|
|
@ -36,8 +36,7 @@ class ContactVcardController extends Controller
|
|||
|
||||
public function show(Request $request, string $sip)
|
||||
{
|
||||
return $request->user()
|
||||
->contacts()
|
||||
return resolveUserContacts($request)
|
||||
->sip($sip)
|
||||
->firstOrFail()
|
||||
->toVcard4();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,21 @@
|
|||
<?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\Account;
|
||||
|
||||
|
|
@ -34,7 +51,7 @@ class CreationRequestTokenController extends Controller
|
|||
'required',
|
||||
new RulesAccountCreationRequestToken
|
||||
],
|
||||
'g-recaptcha-response' => 'required|captcha',
|
||||
'h-captcha-response' => captchaConfigured() ? 'required|HCaptcha' : '',
|
||||
]);
|
||||
|
||||
$accountCreationRequestToken = AccountCreationRequestToken::where('token', $request->get('account_creation_request_token'))->firstOrFail();
|
||||
|
|
|
|||
|
|
@ -21,17 +21,18 @@ namespace App\Http\Controllers\Account;
|
|||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Libraries\FlexisipConnector;
|
||||
use App\Libraries\FlexisipRedisConnector;
|
||||
|
||||
class DeviceController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$connector = new FlexisipConnector;
|
||||
$connector = new FlexisipRedisConnector;
|
||||
|
||||
return view(
|
||||
'account.devices.index',
|
||||
'account.device.index',
|
||||
[
|
||||
'account' => $request->user(),
|
||||
'devices' => $connector->getDevices($request->user()->identifier)
|
||||
]
|
||||
);
|
||||
|
|
@ -39,11 +40,12 @@ class DeviceController extends Controller
|
|||
|
||||
public function delete(Request $request, string $uuid)
|
||||
{
|
||||
$connector = new FlexisipConnector;
|
||||
$connector = new FlexisipRedisConnector;
|
||||
|
||||
return view(
|
||||
'account.devices.delete',
|
||||
'account.device.delete',
|
||||
[
|
||||
'account' => $request->user(),
|
||||
'device' => $connector->getDevices($request->user()->identifier)
|
||||
->where('uuid', $uuid)->first()
|
||||
]
|
||||
|
|
@ -52,7 +54,7 @@ class DeviceController extends Controller
|
|||
|
||||
public function destroy(Request $request)
|
||||
{
|
||||
$connector = new FlexisipConnector;
|
||||
$connector = new FlexisipRedisConnector;
|
||||
$connector->deleteDevice($request->user()->identifier, $request->get('uuid'));
|
||||
|
||||
return redirect()->route('account.device.index');
|
||||
|
|
|
|||
|
|
@ -20,65 +20,47 @@
|
|||
namespace App\Http\Controllers\Account;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Mail\ChangedEmail;
|
||||
use App\Services\AccountService;
|
||||
use App\Services\BlockingService;
|
||||
|
||||
class EmailController extends Controller
|
||||
{
|
||||
public function show(Request $request)
|
||||
public function change(Request $request)
|
||||
{
|
||||
return view('account.email', [
|
||||
return view('account.email.change', [
|
||||
'account' => $request->user()
|
||||
]);
|
||||
}
|
||||
|
||||
public function requestUpdate(Request $request)
|
||||
public function requestChange(Request $request)
|
||||
{
|
||||
$request->validate(
|
||||
$request->user()->email
|
||||
? [
|
||||
'email_current' => ['required', Rule::in([$request->user()->email])],
|
||||
'email' => config('app.account_email_unique')
|
||||
? 'required|different:email_current|confirmed|email|unique:accounts,email'
|
||||
: 'required|different:email_current|confirmed|email',
|
||||
]
|
||||
: [
|
||||
'email' => config('app.account_email_unique')
|
||||
? 'required|email|confirmed|unique:accounts,email'
|
||||
: 'required|confirmed|email',
|
||||
]
|
||||
);
|
||||
$request->validate(['h-captcha-response' => captchaConfigured() ? 'required|HCaptcha': '']);
|
||||
|
||||
$request->user()->requestEmailUpdate($request->get('email'));
|
||||
|
||||
Log::channel('events')->info('Web: Email change requested', ['id' => $request->user()->identifier]);
|
||||
|
||||
$request->session()->flash('success', 'An email was sent with a confirmation link. Please click it to update your email address.');
|
||||
return redirect()->route('account.panel');
|
||||
}
|
||||
|
||||
public function update(Request $request, string $hash)
|
||||
{
|
||||
$account = $request->user();
|
||||
|
||||
if ($account->emailChanged && $account->emailChanged->hash == $hash) {
|
||||
$account->email = $account->emailChanged->new_email;
|
||||
$account->save();
|
||||
|
||||
Mail::to($account)->send(new ChangedEmail());
|
||||
|
||||
$account->emailChanged->delete();
|
||||
|
||||
Log::channel('events')->info('Web: Email change updated', ['id' => $account->identifier]);
|
||||
|
||||
$request->session()->flash('success', 'Email successfully updated');
|
||||
return redirect()->route('account.panel');
|
||||
if ((new BlockingService($request->user()))->checkBlock()) {
|
||||
return redirect()->route('account.blocked');
|
||||
}
|
||||
|
||||
abort(404);
|
||||
(new AccountService(api: false))->requestEmailChange($request);
|
||||
|
||||
return redirect()->route('account.email.validate');
|
||||
}
|
||||
|
||||
public function validateChange(Request $request)
|
||||
{
|
||||
return view('account.email.validate', [
|
||||
'emailChangeCode' => $request->user()->emailChangeCode()->firstOrFail()
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
if ((new AccountService(api: false))->updateEmail($request)) {
|
||||
return redirect()->route('account.dashboard');
|
||||
}
|
||||
|
||||
return redirect()->route('account.email.change')->withErrors([
|
||||
'code' => __('The code is not valid')
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
32
flexiapi/app/Http/Controllers/Account/FileController.php
Normal file
32
flexiapi/app/Http/Controllers/Account/FileController.php
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<?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);
|
||||
}
|
||||
}
|
||||
|
|
@ -21,11 +21,9 @@ namespace App\Http\Controllers\Account;
|
|||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
use App\Mail\ConfirmedRegistration;
|
||||
|
||||
class PasswordController extends Controller
|
||||
{
|
||||
public function show(Request $request)
|
||||
|
|
@ -45,24 +43,14 @@ class PasswordController extends Controller
|
|||
$account->activated = true;
|
||||
$account->save();
|
||||
|
||||
$algorithm = $request->has('password_sha256') ? 'SHA-256' : 'MD5';
|
||||
|
||||
$account->updatePassword($request->get('password'), $algorithm);
|
||||
$account->updatePassword($request->get('password'));
|
||||
|
||||
if ($account->passwords()->count() > 0) {
|
||||
Log::channel('events')->info('Web: Password changed', ['id' => $account->identifier]);
|
||||
$request->session()->flash('success', 'Password successfully changed');
|
||||
|
||||
return redirect()->route('account.panel');
|
||||
return redirect()->route('account.logout');
|
||||
}
|
||||
|
||||
Log::channel('events')->info('Web: Password set for the first time', ['id' => $account->identifier]);
|
||||
$request->session()->flash('success', 'Password successfully set. Your SIP account creation process is now finished.');
|
||||
|
||||
if (!empty($account->email)) {
|
||||
Mail::to($account)->send(new ConfirmedRegistration($account));
|
||||
}
|
||||
|
||||
return redirect()->route('account.panel');
|
||||
return redirect()->route('account.logout');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
66
flexiapi/app/Http/Controllers/Account/PhoneController.php
Normal file
66
flexiapi/app/Http/Controllers/Account/PhoneController.php
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
<?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\Controllers\Account;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\AccountService;
|
||||
use App\Services\BlockingService;
|
||||
|
||||
class PhoneController extends Controller
|
||||
{
|
||||
public function change(Request $request)
|
||||
{
|
||||
return view('account.phone.change', [
|
||||
'account' => $request->user()
|
||||
]);
|
||||
}
|
||||
|
||||
public function requestChange(Request $request)
|
||||
{
|
||||
$request->validate(['h-captcha-response' => captchaConfigured() ? 'required|HCaptcha': '']);
|
||||
|
||||
if ((new BlockingService($request->user()))->checkBlock()) {
|
||||
return redirect()->route('account.blocked');
|
||||
}
|
||||
|
||||
(new AccountService(api: false))->requestPhoneChange($request);
|
||||
|
||||
return redirect()->route('account.phone.validate');
|
||||
}
|
||||
|
||||
public function validateChange(Request $request)
|
||||
{
|
||||
return view('account.phone.validate', [
|
||||
'phoneChangeCode' => $request->user()->phoneChangeCode()->firstOrFail()
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
if ((new AccountService(api: false))->updatePhone($request)) {
|
||||
return redirect()->route('account.dashboard');
|
||||
}
|
||||
|
||||
return redirect()->route('account.phone.change')->withErrors([
|
||||
'code' => __('The code is not valid')
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -27,32 +27,50 @@ use Illuminate\Support\Str;
|
|||
|
||||
use Endroid\QrCode\Builder\Builder;
|
||||
use Endroid\QrCode\Encoding\Encoding;
|
||||
use Endroid\QrCode\ErrorCorrectionLevel;
|
||||
use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelHigh;
|
||||
use Endroid\QrCode\Writer\PngWriter;
|
||||
|
||||
class ProvisioningController extends Controller
|
||||
{
|
||||
public function documentation(Request $request)
|
||||
{
|
||||
return view('provisioning.documentation', [
|
||||
'documentation' => markdownDocumentationView('provisioning.documentation_markdown')
|
||||
]);
|
||||
}
|
||||
|
||||
public function wizard(Request $request, string $provisioningToken)
|
||||
{
|
||||
return view('provisioning.wizard', [
|
||||
'token' => $provisioningToken
|
||||
]);
|
||||
}
|
||||
|
||||
public function qrcode(Request $request, string $provisioningToken)
|
||||
{
|
||||
$account = Account::withoutGlobalScopes()
|
||||
->where('provisioning_token', $provisioningToken)
|
||||
->where('id', function ($query) use ($provisioningToken) {
|
||||
$query->select('account_id')
|
||||
->from('provisioning_tokens')
|
||||
->where('used', false)
|
||||
->where('token', $provisioningToken);
|
||||
})
|
||||
->firstOrFail();
|
||||
|
||||
if ($account->activationExpired()) abort(404);
|
||||
|
||||
$params = ['provisioning_token' => $provisioningToken];
|
||||
|
||||
if ($request->has('reset_password')) {
|
||||
$params['reset_password'] = true;
|
||||
}
|
||||
|
||||
$url = route('provisioning.show', $params);
|
||||
$url = route('provisioning.provision', $params);
|
||||
|
||||
$result = Builder::create()
|
||||
->writer(new PngWriter())
|
||||
->data($url)
|
||||
->encoding(new Encoding('UTF-8'))
|
||||
->errorCorrectionLevel(new ErrorCorrectionLevelHigh())
|
||||
->errorCorrectionLevel(ErrorCorrectionLevel::High)
|
||||
->size(300)
|
||||
->margin(10)
|
||||
->build();
|
||||
|
|
@ -73,7 +91,7 @@ class ProvisioningController extends Controller
|
|||
$account = $authToken->account;
|
||||
$authToken->delete();
|
||||
|
||||
return $this->show($request, null, $account);
|
||||
return $this->generateProvisioning($request, $account);
|
||||
}
|
||||
|
||||
abort(404);
|
||||
|
|
@ -84,10 +102,61 @@ class ProvisioningController extends Controller
|
|||
*/
|
||||
public function me(Request $request)
|
||||
{
|
||||
return $this->show($request, null, $request->user());
|
||||
$this->checkProvisioningHeader($request);
|
||||
|
||||
return $this->generateProvisioning($request, $request->user());
|
||||
}
|
||||
|
||||
public function show(Request $request, $provisioningToken = null, Account $requestAccount = null)
|
||||
/**
|
||||
* Get the base provisioning, with authentication
|
||||
*/
|
||||
public function show(Request $request)
|
||||
{
|
||||
$this->checkProvisioningHeader($request);
|
||||
|
||||
return $this->generateProvisioning($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provisioning Token based provisioning
|
||||
*/
|
||||
public function provision(Request $request, string $provisioningToken)
|
||||
{
|
||||
$this->checkProvisioningHeader($request);
|
||||
|
||||
$account = Account::withoutGlobalScopes()
|
||||
->where('id', function ($query) use ($provisioningToken) {
|
||||
$query->select('account_id')
|
||||
->from('provisioning_tokens')
|
||||
->where('used', false)
|
||||
->where('token', $provisioningToken);
|
||||
})
|
||||
->firstOrFail();
|
||||
|
||||
if ($provisioningToken != $account->provisioning_token) {
|
||||
return abort(404);
|
||||
}
|
||||
|
||||
if ($account->currentProvisioningToken->expired()) {
|
||||
return abort(410, 'Expired');
|
||||
}
|
||||
|
||||
$account->activated = true;
|
||||
$account->currentProvisioningToken->consume();
|
||||
$account->save();
|
||||
|
||||
return $this->generateProvisioning($request, $account);
|
||||
}
|
||||
|
||||
private function checkProvisioningHeader(Request $request)
|
||||
{
|
||||
if (!$request->hasHeader('x-linphone-provisioning')
|
||||
&& $request->space->provisioning_use_linphone_provisioning_header) {
|
||||
abort(400, 'x-linphone-provisioning header is missing');
|
||||
}
|
||||
}
|
||||
|
||||
private function generateProvisioning(Request $request, Account $account = null)
|
||||
{
|
||||
// Load the hooks if they exists
|
||||
$provisioningHooks = config_path('provisioning_hooks.php');
|
||||
|
|
@ -97,31 +166,19 @@ class ProvisioningController extends Controller
|
|||
}
|
||||
|
||||
$dom = new \DOMDocument('1.0', 'UTF-8');
|
||||
$xpath = new \DOMXpath($dom);
|
||||
$config = $dom->createElement('config');
|
||||
$config->setAttribute('xmlns', 'http://www.linphone.org/xsds/lpconfig.xsd');
|
||||
//$config->setAttribute('xsi:schemaLocation', 'http://www.linphone.org/xsds/lpconfig.xsd lpconfig.xsd');
|
||||
//$config->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
|
||||
|
||||
$dom->appendChild($config);
|
||||
|
||||
// Default RC file handling
|
||||
$rcFile = config('app.provisioning_rc_file');
|
||||
$proxyConfigIndex = 0;
|
||||
$authInfoIndex = 0;
|
||||
|
||||
if (file_exists($rcFile)) {
|
||||
$rc = parse_ini_file($rcFile, true);
|
||||
if ($request->space?->custom_provisioning_entries) {
|
||||
$rc = parse_ini_string($request->space->custom_provisioning_entries, true);
|
||||
|
||||
foreach ($rc as $sectionName => $values) {
|
||||
$section = $dom->createElement('section');
|
||||
$section->setAttribute('name', $sectionName);
|
||||
|
||||
if (Str::startsWith($sectionName, "proxy_config_")) {
|
||||
$proxyConfigIndex++;
|
||||
} elseif (Str::startsWith($sectionName, "auth_info_")) {
|
||||
$authInfoIndex++;
|
||||
}
|
||||
|
||||
foreach ($values as $key => $value) {
|
||||
$entry = $dom->createElement('entry', $value);
|
||||
$entry->setAttribute('name', $key);
|
||||
|
|
@ -132,15 +189,42 @@ class ProvisioningController extends Controller
|
|||
}
|
||||
}
|
||||
|
||||
$account = null;
|
||||
$remoteContactDirectoryCounter = 0;
|
||||
$authInfoIndex = 0;
|
||||
|
||||
// Account handling
|
||||
if ($requestAccount) {
|
||||
$account = $requestAccount;
|
||||
} elseif ($provisioningToken) {
|
||||
$account = Account::withoutGlobalScopes()
|
||||
->where('provisioning_token', $provisioningToken)
|
||||
->first();
|
||||
// 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
|
||||
|
|
@ -157,42 +241,63 @@ class ProvisioningController extends Controller
|
|||
|
||||
$config->appendChild($section);
|
||||
|
||||
if ($account && !$account->activationExpired()) {
|
||||
$externalAccount = $account->externalAccount;
|
||||
if ($account) {
|
||||
$ui = $xpath->query("//section[@name='ui']")->item(0);
|
||||
|
||||
$section = $dom->createElement('section');
|
||||
$section->setAttribute('name', 'proxy_' . $proxyConfigIndex);
|
||||
if ($ui == null && $account->space) {
|
||||
$section = $dom->createElement('section');
|
||||
$section->setAttribute('name', 'ui');
|
||||
|
||||
foreach ([
|
||||
'super',
|
||||
'disable_chat_feature',
|
||||
'disable_meetings_feature',
|
||||
'disable_broadcast_feature',
|
||||
'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',
|
||||
'max_account',
|
||||
] as $key) {
|
||||
// Cast the booleans into integers
|
||||
$entry = $dom->createElement('entry', (int)$account->space->$key);
|
||||
$entry->setAttribute('name', $key);
|
||||
$section->appendChild($entry);
|
||||
}
|
||||
|
||||
$config->appendChild($section);
|
||||
}
|
||||
|
||||
$section = $xpath->query("//section[@name='proxy_0']")->item(0);
|
||||
|
||||
if ($section == null) {
|
||||
$section = $dom->createElement('section');
|
||||
$section->setAttribute('name', 'proxy_0');
|
||||
$config->appendChild($section);
|
||||
}
|
||||
|
||||
$entry = $dom->createElement('entry', $account->fullIdentifier);
|
||||
$entry->setAttribute('name', 'reg_identity');
|
||||
$section->appendChild($entry);
|
||||
|
||||
$entry = $dom->createElement('entry', 1);
|
||||
$entry->setAttribute('name', 'reg_sendregister');
|
||||
$section->appendChild($entry);
|
||||
|
||||
$entry = $dom->createElement('entry', 'push_notification');
|
||||
$entry->setAttribute('name', 'refkey');
|
||||
$section->appendChild($entry);
|
||||
|
||||
// Complete the section with the Proxy hook
|
||||
if (function_exists('provisioningProxyHook')) {
|
||||
provisioningProxyHook($section, $request, $account);
|
||||
}
|
||||
|
||||
if ($externalAccount) {
|
||||
$entry = $dom->createElement('entry', 'external_account');
|
||||
$entry->setAttribute('name', 'depends_on');
|
||||
$section->appendChild($entry);
|
||||
}
|
||||
|
||||
$config->appendChild($section);
|
||||
|
||||
$passwords = $account->passwords()->get();
|
||||
|
||||
foreach ($passwords as $password) {
|
||||
$section = $dom->createElement('section');
|
||||
$section->setAttribute('name', 'auth_info_' . $authInfoIndex);
|
||||
$section = $xpath->query("//section[@name='auth_info_" . $authInfoIndex . "']")->item(0);
|
||||
|
||||
if ($section == null) {
|
||||
$section = $dom->createElement('section');
|
||||
$section->setAttribute('name', 'auth_info_' . $authInfoIndex);
|
||||
$config->appendChild($section);
|
||||
}
|
||||
|
||||
$entry = $dom->createElement('entry', $account->username);
|
||||
$entry->setAttribute('name', 'username');
|
||||
|
|
@ -219,73 +324,8 @@ class ProvisioningController extends Controller
|
|||
provisioningAuthHook($section, $request, $password);
|
||||
}
|
||||
|
||||
$config->appendChild($section);
|
||||
|
||||
$authInfoIndex++;
|
||||
}
|
||||
|
||||
if ($provisioningToken) {
|
||||
// Activate the account
|
||||
if ($account->activated == false
|
||||
&& $provisioningToken == $account->provisioning_token
|
||||
) {
|
||||
$account->activated = true;
|
||||
}
|
||||
|
||||
$account->provisioning_token = null;
|
||||
$account->save();
|
||||
}
|
||||
|
||||
$proxyConfigIndex++;
|
||||
|
||||
// External Account handling
|
||||
if ($externalAccount) {
|
||||
$section = $dom->createElement('section');
|
||||
$section->setAttribute('name', 'proxy_' . $proxyConfigIndex);
|
||||
|
||||
$entry = $dom->createElement('entry', '<sip:' . $externalAccount->identifier . '>');
|
||||
$entry->setAttribute('name', 'reg_identity');
|
||||
$section->appendChild($entry);
|
||||
|
||||
$entry = $dom->createElement('entry', 1);
|
||||
$entry->setAttribute('name', 'reg_sendregister');
|
||||
$section->appendChild($entry);
|
||||
|
||||
$entry = $dom->createElement('entry', 'push_notification');
|
||||
$entry->setAttribute('name', 'refkey');
|
||||
$section->appendChild($entry);
|
||||
|
||||
$entry = $dom->createElement('entry', 'external_account');
|
||||
$entry->setAttribute('name', 'idkey');
|
||||
$section->appendChild($entry);
|
||||
|
||||
$config->appendChild($section);
|
||||
|
||||
$section = $dom->createElement('section');
|
||||
$section->setAttribute('name', 'auth_info_' . $authInfoIndex);
|
||||
|
||||
$entry = $dom->createElement('entry', $externalAccount->username);
|
||||
$entry->setAttribute('name', 'username');
|
||||
$section->appendChild($entry);
|
||||
|
||||
$entry = $dom->createElement('entry', $externalAccount->domain);
|
||||
$entry->setAttribute('name', 'domain');
|
||||
$section->appendChild($entry);
|
||||
|
||||
$entry = $dom->createElement('entry', $externalAccount->password);
|
||||
$entry->setAttribute('name', 'ha1');
|
||||
$section->appendChild($entry);
|
||||
|
||||
$entry = $dom->createElement('entry', $externalAccount->resolvedRealm);
|
||||
$entry->setAttribute('name', 'realm');
|
||||
$section->appendChild($entry);
|
||||
|
||||
$entry = $dom->createElement('entry', $externalAccount->algorithm);
|
||||
$entry->setAttribute('name', 'algorithm');
|
||||
$section->appendChild($entry);
|
||||
|
||||
$config->appendChild($section);
|
||||
}
|
||||
}
|
||||
|
||||
// Complete the section with the Auth hook
|
||||
|
|
@ -294,7 +334,7 @@ class ProvisioningController extends Controller
|
|||
}
|
||||
|
||||
// Overwrite all the entries
|
||||
if (config('app.provisioning_overwrite_all')) {
|
||||
if ($request->space?->custom_provisioning_overwrite_all) {
|
||||
$xpath = new \DOMXpath($dom);
|
||||
$entries = $xpath->query("//section/entry");
|
||||
if (!is_null($entries)) {
|
||||
|
|
|
|||
163
flexiapi/app/Http/Controllers/Account/RecoveryController.php
Normal file
163
flexiapi/app/Http/Controllers/Account/RecoveryController.php
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
<?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\Account;
|
||||
|
||||
use App\Account;
|
||||
use App\AccountRecoveryToken;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\AccountService;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use App\Http\Controllers\Account\AuthenticateController as WebAuthenticateController;
|
||||
|
||||
class RecoveryController extends Controller
|
||||
{
|
||||
public function showEmail(Request $request)
|
||||
{
|
||||
return view('account.recovery.show', [
|
||||
'method' => 'email',
|
||||
'domain' => resolveDomain($request)
|
||||
]);
|
||||
}
|
||||
|
||||
public function showPhone(Request $request, string $accountRecoveryToken)
|
||||
{
|
||||
$accountRecoveryToken = AccountRecoveryToken::where('token', $accountRecoveryToken)
|
||||
->where('used', false)
|
||||
->firstOrFail();
|
||||
|
||||
return view('account.recovery.show', [
|
||||
'method' => 'phone',
|
||||
'account_recovery_token' => $accountRecoveryToken->token,
|
||||
'phone' => $request->get('phone'),
|
||||
'domain' => resolveDomain($request)
|
||||
]);
|
||||
}
|
||||
|
||||
public function send(Request $request)
|
||||
{
|
||||
$rules = [
|
||||
'email' => 'required_without:phone|email|exists:accounts,email',
|
||||
'phone' => 'required_without:email|starts_with:+',
|
||||
'h-captcha-response' => captchaConfigured() ? 'required_with:email|HCaptcha' : '',
|
||||
'account_recovery_token' => 'required_with:phone',
|
||||
];
|
||||
|
||||
$account = null;
|
||||
|
||||
if ($request->get('email')) {
|
||||
if (config('app.account_email_unique') == false) {
|
||||
$rules['username'] = 'required';
|
||||
}
|
||||
|
||||
$request->validate($rules);
|
||||
|
||||
$account = Account::where('email', $request->get('email'));
|
||||
|
||||
/**
|
||||
* Because several accounts can have the same email
|
||||
*/
|
||||
if (config('app.account_email_unique') == false) {
|
||||
$account = $account->where('username', $request->get('username'));
|
||||
}
|
||||
|
||||
$account = $account->first();
|
||||
|
||||
if (!$account) {
|
||||
$account = Account::where('phone', $request->get('username'))
|
||||
->where('email', $request->get('email'))
|
||||
->first();
|
||||
}
|
||||
} elseif ($request->get('phone')) {
|
||||
$account = Account::where('username', $request->get('phone'))->first();
|
||||
|
||||
if (!$account) {
|
||||
$account = Account::where('phone', $request->get('phone'))->first();
|
||||
}
|
||||
}
|
||||
|
||||
if (!$account) {
|
||||
return redirect()->back()->withErrors(['identifier' => __("The account doesn't exists")]);
|
||||
}
|
||||
|
||||
if ($account->failedRecentRecovery()) {
|
||||
return redirect()->back()->withErrors(['code' => __('Account recovered recently, try again later')]);
|
||||
}
|
||||
|
||||
if ($request->get('email')) {
|
||||
$account = (new AccountService)->recoverByEmail($account, $request->get('email'));
|
||||
} elseif ($request->get('phone')) {
|
||||
$accountRecoveryToken = AccountRecoveryToken::where('token', $request->get('account_recovery_token'))
|
||||
->where('used', false)
|
||||
->first();
|
||||
|
||||
if (!$accountRecoveryToken) {
|
||||
abort(403, 'Wrong Account Recovery Token');
|
||||
}
|
||||
|
||||
$account = (new AccountService)->recoverByPhone($account, $request->get('phone'), $accountRecoveryToken);
|
||||
}
|
||||
|
||||
return view('account.recovery.confirm', [
|
||||
'method' => $request->get('phone') ? 'phone' : 'email',
|
||||
'account_id' => Crypt::encryptString($account->id)
|
||||
]);
|
||||
}
|
||||
|
||||
public function confirm(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'account_id' => 'required',
|
||||
'method' => 'in:phone,email',
|
||||
'number_1' => 'required|digits:1',
|
||||
'number_2' => 'required|digits:1',
|
||||
'number_3' => 'required|digits:1',
|
||||
'number_4' => 'required|digits:1'
|
||||
]);
|
||||
|
||||
$code = $request->get('number_1') . $request->get('number_2') . $request->get('number_3') . $request->get('number_4');
|
||||
|
||||
$account = Account::where('id', Crypt::decryptString($request->get('account_id')))->firstOrFail();
|
||||
|
||||
if ($account->currentRecoveryCode->expired()) {
|
||||
return redirect()->route($request->get('method') == 'phone'
|
||||
? 'account.recovery.show.phone'
|
||||
: 'account.recovery.show.email')->withErrors([
|
||||
'code' => __('The code has expired')
|
||||
]);
|
||||
}
|
||||
|
||||
if ($account->recovery_code != $code) {
|
||||
return redirect()->route($request->get('method') == 'phone'
|
||||
? 'account.recovery.show.phone'
|
||||
: 'account.recovery.show.email')->withErrors([
|
||||
'code' => 'The code is not valid'
|
||||
]);
|
||||
}
|
||||
|
||||
$account->currentRecoveryCode->consume();
|
||||
|
||||
Auth::login($account);
|
||||
return redirect()->route('account.password.update');
|
||||
}
|
||||
}
|
||||
|
|
@ -21,159 +21,20 @@ namespace App\Http\Controllers\Account;
|
|||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Carbon\Carbon;
|
||||
|
||||
use App\Account;
|
||||
use App\Alias;
|
||||
use App\Rules\WithoutSpaces;
|
||||
use App\Rules\IsNotPhoneNumber;
|
||||
use App\Rules\NoUppercase;
|
||||
use App\Libraries\OvhSMS;
|
||||
use App\Mail\RegisterConfirmation;
|
||||
use App\Mail\NewsletterRegistration;
|
||||
use App\Rules\BlacklistedUsername;
|
||||
use App\Rules\SIPUsername;
|
||||
|
||||
class RegisterController extends Controller
|
||||
{
|
||||
private $emailCodeSize = 13;
|
||||
|
||||
public function register(Request $request)
|
||||
{
|
||||
if (config('app.phone_authentication') == false) {
|
||||
return redirect()->route('account.register.email');
|
||||
}
|
||||
|
||||
return view('account.register');
|
||||
}
|
||||
|
||||
public function registerPhone(Request $request)
|
||||
{
|
||||
return view('account.register.phone', [
|
||||
'domain' => '@' . config('app.sip_domain')
|
||||
'domain' => resolveDomain($request)
|
||||
]);
|
||||
}
|
||||
|
||||
public function registerEmail(Request $request)
|
||||
{
|
||||
return view('account.register.email', [
|
||||
'domain' => '@' . config('app.sip_domain')
|
||||
]);
|
||||
}
|
||||
|
||||
public function storeEmail(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'terms' => 'accepted',
|
||||
'privacy' => 'accepted',
|
||||
'username' => [
|
||||
'required',
|
||||
new NoUppercase,
|
||||
Rule::unique('accounts', 'username')->where(function ($query) use ($request) {
|
||||
$query->where('domain', config('app.sip_domain'));
|
||||
}),
|
||||
Rule::unique('accounts_tombstones', 'username')->where(function ($query) use ($request) {
|
||||
$query->where('domain', config('app.sip_domain'));
|
||||
}),
|
||||
'filled',
|
||||
new WithoutSpaces,
|
||||
new IsNotPhoneNumber,
|
||||
new BlacklistedUsername,
|
||||
new SIPUsername
|
||||
],
|
||||
'g-recaptcha-response' => 'required|captcha',
|
||||
'email' => config('app.account_email_unique')
|
||||
? 'required|email|confirmed|unique:accounts,email'
|
||||
: 'required|email|confirmed',
|
||||
]);
|
||||
|
||||
$account = new Account;
|
||||
$account->username = $request->get('username');
|
||||
$account->email = $request->get('email');
|
||||
$account->activated = false;
|
||||
$account->domain = config('app.sip_domain');
|
||||
$account->ip_address = $request->ip();
|
||||
$account->creation_time = Carbon::now();
|
||||
$account->user_agent = $request->header('User-Agent') ?? config('app.name');
|
||||
$account->save();
|
||||
|
||||
$account->confirmation_key = Str::random($this->emailCodeSize);
|
||||
$account->save();
|
||||
|
||||
if (!empty(config('app.newsletter_registration_address')) && $request->has('newsletter')) {
|
||||
Mail::to(config('app.newsletter_registration_address'))->send(new NewsletterRegistration($account));
|
||||
}
|
||||
|
||||
Log::channel('events')->info('Web: Account created using an email confirmation', ['id' => $account->identifier]);
|
||||
|
||||
Mail::to($account)->send(new RegisterConfirmation($account));
|
||||
|
||||
return redirect()->route('account.check.email', $account->identifier);
|
||||
}
|
||||
|
||||
public function storePhone(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'terms' => 'accepted',
|
||||
'privacy' => 'accepted',
|
||||
'username' => [
|
||||
new NoUppercase,
|
||||
Rule::unique('accounts', 'username')->where(function ($query) use ($request) {
|
||||
$query->where('domain', config('app.sip_domain'));
|
||||
}),
|
||||
Rule::unique('accounts_tombstones', 'username')->where(function ($query) use ($request) {
|
||||
$query->where('domain', config('app.sip_domain'));
|
||||
}),
|
||||
'nullable',
|
||||
new WithoutSpaces,
|
||||
new IsNotPhoneNumber,
|
||||
new BlacklistedUsername,
|
||||
new SIPUsername
|
||||
],
|
||||
'phone' => [
|
||||
'required', 'unique:aliases,alias',
|
||||
'unique:accounts,username',
|
||||
new WithoutSpaces, 'starts_with:+'
|
||||
],
|
||||
'email' => config('app.account_email_unique')
|
||||
? 'nullable|email|unique:accounts,email'
|
||||
: 'nullable|email',
|
||||
'g-recaptcha-response' => 'required|captcha',
|
||||
]);
|
||||
|
||||
$account = new Account;
|
||||
$account->username = !empty($request->get('username'))
|
||||
? $request->get('username')
|
||||
: $request->get('phone');
|
||||
|
||||
$account->email = $request->get('email');
|
||||
$account->activated = false;
|
||||
$account->domain = config('app.sip_domain');
|
||||
$account->ip_address = $request->ip();
|
||||
$account->creation_time = Carbon::now();
|
||||
$account->user_agent = $request->header('User-Agent') ?? config('app.name');
|
||||
$account->save();
|
||||
|
||||
$alias = new Alias;
|
||||
$alias->alias = $request->get('phone');
|
||||
$alias->domain = config('app.sip_domain');
|
||||
$alias->account_id = $account->id;
|
||||
$alias->save();
|
||||
|
||||
$account->confirmation_key = generatePin();
|
||||
$account->save();
|
||||
|
||||
$ovhSMS = new OvhSMS;
|
||||
$ovhSMS->send($request->get('phone'), 'Your ' . config('app.name') . ' validation code is ' . $account->confirmation_key);
|
||||
|
||||
Log::channel('events')->info('Web: Account created using an SMS confirmation', ['id' => $account->identifier]);
|
||||
|
||||
return view('account.authenticate.phone', [
|
||||
'account' => $account
|
||||
'domain' => resolveDomain($request)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Account;
|
||||
|
||||
use App\ResetPasswordEmailToken;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ResetPasswordEmailController extends Controller
|
||||
{
|
||||
public function change(string $token)
|
||||
{
|
||||
$token = ResetPasswordEmailToken::where('token', $token)->firstOrFail();
|
||||
|
||||
return view('account.password_reset', [
|
||||
'token' => $token
|
||||
]);
|
||||
}
|
||||
|
||||
public function reset(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'token' => 'required|size:16',
|
||||
'password' => 'required|min:8|confirmed',
|
||||
'h-captcha-response' => captchaConfigured() ? 'required|HCaptcha' : ''
|
||||
]);
|
||||
|
||||
$token = ResetPasswordEmailToken::where('token', $request->get('token'))->firstOrFail();
|
||||
|
||||
if ($token->offed()) abort(403);
|
||||
|
||||
$token->account->updatePassword($request->get('password'));
|
||||
$token->consume();
|
||||
|
||||
return view('account.password_changed');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Account;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class VcardsStorageController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
//if ($this->vcardRequested($request)) {
|
||||
$vcards = '';
|
||||
|
||||
foreach ($request->user()->vcardsStorage()->get() as $vcard) {
|
||||
$vcards .= $vcard->vcard . "\n";
|
||||
}
|
||||
|
||||
return $vcards;
|
||||
/*}
|
||||
|
||||
abort(404);*/
|
||||
}
|
||||
|
||||
public function show(Request $request, string $uuid)
|
||||
{
|
||||
return /*($this->vcardRequested($request))
|
||||
?*/ $request->user()->vcardsStorage()->where('uuid', $uuid)->firstOrFail()->vcard
|
||||
/*: abort(404)*/;
|
||||
}
|
||||
|
||||
/*private function vcardRequested(Request $request): bool
|
||||
{
|
||||
return $request->hasHeader('content-type') == 'text/vcard'
|
||||
&& $request->hasHeader('accept') == 'text/vcard';
|
||||
}*/
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
namespace App\Http\Controllers\Admin\Account;
|
||||
|
||||
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 AccountAccountTypeController extends Controller
|
||||
class AccountTypeController extends Controller
|
||||
{
|
||||
public function create(int $id)
|
||||
{
|
||||
|
|
@ -53,10 +53,9 @@ class AccountAccountTypeController extends Controller
|
|||
$account->types()->detach($request->get('account_type_id'));
|
||||
$account->types()->attach($request->get('account_type_id'));
|
||||
|
||||
$request->session()->flash('success', 'Type successfully added');
|
||||
Log::channel('events')->info('Web Admin: Account type attached', ['id' => $account->identifier, 'type_id' => $request->get('account_type_id')]);
|
||||
|
||||
return redirect()->route('admin.account.show', $account);
|
||||
return redirect()->route('admin.account.show', $account)->withFragment('#types');
|
||||
}
|
||||
|
||||
public function destroy(Request $request, int $id, int $typeId)
|
||||
|
|
@ -65,9 +64,8 @@ class AccountAccountTypeController extends Controller
|
|||
|
||||
$account->types()->detach($typeId);
|
||||
|
||||
$request->session()->flash('success', 'Type successfully removed');
|
||||
Log::channel('events')->info('Web Admin: Account type detached', ['id' => $account->identifier, 'type_id' => $request->get('account_type_id')]);
|
||||
|
||||
return redirect()->route('admin.account.show', $account);
|
||||
return redirect()->route('admin.account.show', $account)->withFragment('#types');
|
||||
}
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
namespace App\Http\Controllers\Admin\Account;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
|
|
@ -27,11 +27,11 @@ use App\Account;
|
|||
use App\AccountAction;
|
||||
use App\Rules\NoUppercase;
|
||||
|
||||
class AccountActionController extends Controller
|
||||
class ActionController extends Controller
|
||||
{
|
||||
public function create(int $id)
|
||||
public function create(int $accountId)
|
||||
{
|
||||
$account = Account::findOrFail($id);
|
||||
$account = Account::findOrFail($accountId);
|
||||
|
||||
return view('admin.account.action.create_edit', [
|
||||
'action' => new AccountAction,
|
||||
|
|
@ -39,9 +39,9 @@ class AccountActionController extends Controller
|
|||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request, int $id)
|
||||
public function store(Request $request, int $accountId)
|
||||
{
|
||||
$account = Account::findOrFail($id);
|
||||
$account = Account::findOrFail($accountId);
|
||||
|
||||
$request->validate([
|
||||
'key' => ['required', 'alpha_dash', new NoUppercase],
|
||||
|
|
@ -54,15 +54,14 @@ class AccountActionController extends Controller
|
|||
$accountAction->code = $request->get('code');
|
||||
$accountAction->save();
|
||||
|
||||
$request->session()->flash('success', 'Action successfully created');
|
||||
Log::channel('events')->info('Web Admin: Account action created', ['id' => $account->identifier, 'action' => $accountAction->key]);
|
||||
|
||||
return redirect()->route('admin.account.show', $accountAction->account);
|
||||
return redirect()->route('admin.account.show', $accountAction->account)->withFragment('#actions');
|
||||
}
|
||||
|
||||
public function edit(int $id, int $actionId)
|
||||
public function edit(int $accountId, int $actionId)
|
||||
{
|
||||
$account = Account::findOrFail($id);
|
||||
$account = Account::findOrFail($accountId);
|
||||
|
||||
$accountAction = $account->actions()
|
||||
->where('id', $actionId)
|
||||
|
|
@ -74,9 +73,9 @@ class AccountActionController extends Controller
|
|||
]);
|
||||
}
|
||||
|
||||
public function update(Request $request, int $id, int $actionId)
|
||||
public function update(Request $request, int $accountId, int $actionId)
|
||||
{
|
||||
$account = Account::findOrFail($id);
|
||||
$account = Account::findOrFail($accountId);
|
||||
|
||||
$request->validate([
|
||||
'key' => ['alpha_dash', new NoUppercase],
|
||||
|
|
@ -90,36 +89,34 @@ class AccountActionController extends Controller
|
|||
$accountAction->code = $request->get('code');
|
||||
$accountAction->save();
|
||||
|
||||
$request->session()->flash('success', 'Action successfully updated');
|
||||
Log::channel('events')->info('Web Admin: Account action updated', ['id' => $account->identifier, 'action' => $accountAction->key]);
|
||||
|
||||
return redirect()->route('admin.account.show', $account);
|
||||
return redirect()->route('admin.account.show', $account)->withFragment('#actions');
|
||||
}
|
||||
|
||||
public function delete(int $id, int $actionId)
|
||||
public function delete(int $accountId, int $actionId)
|
||||
{
|
||||
$account = Account::findOrFail($id);
|
||||
$account = Account::findOrFail($accountId);
|
||||
|
||||
return view('admin.account.action.delete', [
|
||||
'account' => $account,
|
||||
'action' => $account->actions()
|
||||
->where('id', $actionId)
|
||||
->firstOrFail()
|
||||
]);
|
||||
}
|
||||
|
||||
public function destroy(Request $request, int $id, int $actionId)
|
||||
public function destroy(Request $request, int $accountId, int $actionId)
|
||||
{
|
||||
$account = Account::findOrFail($id);
|
||||
$account = Account::findOrFail($accountId);
|
||||
|
||||
$accountAction = $account->actions()
|
||||
->where('id', $actionId)
|
||||
->firstOrFail();
|
||||
$accountAction->delete();
|
||||
|
||||
$request->session()->flash('success', 'Action successfully destroyed');
|
||||
|
||||
Log::channel('events')->info('Web Admin: Account action deleted', ['id' => $accountAction->account->identifier, 'action_id' => $accountAction->key]);
|
||||
|
||||
return redirect()->route('admin.account.show', $accountAction->account);
|
||||
return redirect()->route('admin.account.show', $accountAction->account)->withFragment('#actions');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
<?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\Http\Controllers\Controller;
|
||||
|
||||
use App\Account;
|
||||
|
||||
class ActivityController extends Controller
|
||||
{
|
||||
public function index(int $accountId)
|
||||
{
|
||||
return view(
|
||||
'admin.account.activity.index',
|
||||
[
|
||||
'account' => Account::findOrFail($accountId)
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
<?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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -17,49 +17,66 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
namespace App\Http\Controllers\Admin\Account;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
use App\Account;
|
||||
use App\ContactsList;
|
||||
|
||||
class AccountContactController extends Controller
|
||||
class ContactController extends Controller
|
||||
{
|
||||
public function create(int $id)
|
||||
public function index(int $accountId)
|
||||
{
|
||||
$account = Account::findOrFail($id);
|
||||
$account = Account::findOrFail($accountId);
|
||||
|
||||
return view('admin.account.contact.index', [
|
||||
'account' => $account,
|
||||
'contacts_lists' => ContactsList::whereNotIn('id', function ($query) use ($accountId) {
|
||||
$query->select('contacts_list_id')
|
||||
->from('account_contacts_list')
|
||||
->where('account_id', $accountId);
|
||||
})->withCount('contacts')->get()
|
||||
]);
|
||||
}
|
||||
|
||||
public function create(int $accountId)
|
||||
{
|
||||
$account = Account::findOrFail($accountId);
|
||||
|
||||
return view('admin.account.contact.create', [
|
||||
'account' => $account
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request, int $id)
|
||||
public function store(Request $request, int $accountId)
|
||||
{
|
||||
$account = Account::findOrFail($id);
|
||||
$request->validate([
|
||||
'sip' => 'required',
|
||||
]);
|
||||
|
||||
$account = Account::findOrFail($accountId);
|
||||
$contact = Account::sip($request->get('sip'))->first();
|
||||
|
||||
if (!$contact) {
|
||||
$request->session()->flash('error', 'The contact SIP address doesn\'t exists');
|
||||
|
||||
return redirect()->route('admin.account.contact.create', $account);
|
||||
return redirect()->back()->withErrors([
|
||||
'sip' => __("The contact doesn't exists")
|
||||
]);
|
||||
}
|
||||
|
||||
$account->contacts()->detach($contact->id);
|
||||
$account->contacts()->attach($contact->id);
|
||||
|
||||
$request->session()->flash('success', 'Contact successfully added');
|
||||
|
||||
Log::channel('events')->info('Web Admin: Account contact added', ['id' => $account->identifier, 'contact' => $contact->identifier]);
|
||||
|
||||
return redirect()->route('admin.account.show', $account);
|
||||
return redirect()->route('admin.account.contact.index', $account);
|
||||
}
|
||||
|
||||
public function delete(int $id, int $contactId)
|
||||
public function delete(int $accountId, int $contactId)
|
||||
{
|
||||
$account = Account::findOrFail($id);
|
||||
$account = Account::findOrFail($accountId);
|
||||
$contact = $account->contacts()->where('id', $contactId)->firstOrFail();
|
||||
|
||||
return view('admin.account.contact.delete', [
|
||||
|
|
@ -68,16 +85,15 @@ class AccountContactController extends Controller
|
|||
]);
|
||||
}
|
||||
|
||||
public function destroy(Request $request, int $id)
|
||||
public function destroy(Request $request, int $accountId)
|
||||
{
|
||||
$account = Account::findOrFail($id);
|
||||
$account = Account::findOrFail($accountId);
|
||||
$contact = $account->contacts()->where('id', $request->get('contact_id'))->firstOrFail();
|
||||
|
||||
$account->contacts()->detach($contact->id);
|
||||
|
||||
$request->session()->flash('success', 'Type successfully removed');
|
||||
Log::channel('events')->info('Web Admin: Account contact removed', ['id' => $account->identifier, 'contact' => $contact->identifier]);
|
||||
|
||||
return redirect()->route('admin.account.show', $account);
|
||||
return redirect()->route('admin.account.contact.index', $account);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
<?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\Controllers\Admin\Account;
|
||||
|
||||
use App\Account;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Libraries\FlexisipRedisConnector;
|
||||
|
||||
class DeviceController extends Controller
|
||||
{
|
||||
public function index(int $accountId)
|
||||
{
|
||||
$connector = new FlexisipRedisConnector;
|
||||
$account = Account::findOrFail($accountId);
|
||||
|
||||
return view(
|
||||
'admin.account.device.index',
|
||||
[
|
||||
'account' => $account,
|
||||
'devices' => $connector->getDevices($account->identifier)
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function delete(int $accountId, string $uuid)
|
||||
{
|
||||
$connector = new FlexisipRedisConnector;
|
||||
$account = Account::findOrFail($accountId);
|
||||
|
||||
return view(
|
||||
'admin.account.device.delete',
|
||||
[
|
||||
'account' => $account,
|
||||
'device' => $connector->getDevices($account->identifier)
|
||||
->where('uuid', $uuid)->first()
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function destroy(Request $request, int $accountId)
|
||||
{
|
||||
$connector = new FlexisipRedisConnector;
|
||||
$account = Account::findOrFail($accountId);
|
||||
|
||||
$connector->deleteDevice($account->identifier, $request->get('uuid'));
|
||||
|
||||
return redirect()->route('admin.account.show', $account);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
<?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\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
use App\Account;
|
||||
use App\AccountDictionaryEntry;
|
||||
|
||||
class DictionaryController extends Controller
|
||||
{
|
||||
public function create(int $accountId)
|
||||
{
|
||||
return view('admin.account.dictionary.create_edit', [
|
||||
'account' => Account::findOrFail($accountId),
|
||||
'entry' => new AccountDictionaryEntry
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request, int $accountId)
|
||||
{
|
||||
$account = Account::findOrFail($accountId);
|
||||
|
||||
$request->validate([
|
||||
'key' => 'required',
|
||||
'value' => 'required'
|
||||
]);
|
||||
|
||||
$account->setDictionaryEntry($request->get('key'), $request->get('value'));
|
||||
|
||||
if (function_exists('accountServiceAccountEditedHook')) {
|
||||
$account->refresh();
|
||||
accountServiceAccountEditedHook($request, $account);
|
||||
}
|
||||
|
||||
return redirect()->route('admin.account.show', $account);
|
||||
}
|
||||
|
||||
public function edit(int $accountId, string $key)
|
||||
{
|
||||
$account = Account::findOrFail($accountId);
|
||||
|
||||
return view('admin.account.dictionary.create_edit', [
|
||||
'account' => $account,
|
||||
'entry' => $account->dictionaryEntries()->where('key', $key)->firstOrFail()
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(Request $request, int $accountId, int $entryId)
|
||||
{
|
||||
$request->validate([
|
||||
'value' => 'required'
|
||||
]);
|
||||
|
||||
$account = Account::findOrFail($accountId);
|
||||
|
||||
$entry = $account->dictionaryEntries()->findOrFail($entryId);
|
||||
$entry->value = $request->get('value');
|
||||
$entry->save();
|
||||
|
||||
if (function_exists('accountServiceAccountEditedHook')) {
|
||||
$account->refresh();
|
||||
accountServiceAccountEditedHook($request, $account);
|
||||
}
|
||||
|
||||
return redirect()->route('admin.account.show', $account);
|
||||
}
|
||||
|
||||
public function delete(int $accountId, string $key)
|
||||
{
|
||||
$account = Account::findOrFail($accountId);
|
||||
|
||||
return view(
|
||||
'admin.account.dictionary.delete',
|
||||
[
|
||||
'account' => $account,
|
||||
'entry' => $account->dictionaryEntries()->where('key', $key)->firstOrFail()
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function destroy(Request $request, int $accountId)
|
||||
{
|
||||
$account = Account::findOrFail($accountId);
|
||||
$account->dictionaryEntries()->where('key', $request->get('key'))->delete();
|
||||
|
||||
return redirect()->route('admin.account.show', $account);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?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');
|
||||
}
|
||||
}
|
||||
388
flexiapi/app/Http/Controllers/Admin/Account/ImportController.php
Normal file
388
flexiapi/app/Http/Controllers/Admin/Account/ImportController.php
Normal file
|
|
@ -0,0 +1,388 @@
|
|||
<?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\ExternalAccount;
|
||||
use App\Password;
|
||||
use App\PhoneCountry;
|
||||
use App\Space;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Validation\Rules\File;
|
||||
use Propaganistas\LaravelPhone\PhoneNumber;
|
||||
|
||||
class ImportController extends Controller
|
||||
{
|
||||
private Collection $errors;
|
||||
private string $importDirectory = 'imported_csv';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->errors = collect();
|
||||
}
|
||||
|
||||
public function create(Request $request)
|
||||
{
|
||||
return view('admin.account.import.create', [
|
||||
'domains' => $request->user()->superAdmin
|
||||
? Space::pluck('domain')
|
||||
: [$request->user()->domain]
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'csv' => ['required', File::types(['csv', 'txt'])],
|
||||
'domain' => 'required|exists:spaces,domain'
|
||||
]);
|
||||
|
||||
$domain = $request->user()->superAdmin
|
||||
? $request->get('domain')
|
||||
: $request->user()->domain;
|
||||
|
||||
/**
|
||||
* General formating checking
|
||||
*/
|
||||
$csv = fopen($request->file('csv'), 'r');
|
||||
$line = fgets($csv);
|
||||
fclose($csv);
|
||||
|
||||
$lines = collect();
|
||||
$this->errors['Wrong file format'] = "The number of columns doesn't matches the reference file. The first MUST be the same as the reference file";
|
||||
|
||||
$firstLine = 'Username,Password,Role,Status,Phone,Email,External Username,External Domain,External Password,External Realm, External Registrar,External Outbound Proxy,External Protocol';
|
||||
|
||||
if (substr($line, 0, strlen($firstLine)) == $firstLine) {
|
||||
$lines = $this->csvToCollection($request->file('csv'));
|
||||
unset($this->errors['Wrong file format']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Error checking
|
||||
*/
|
||||
|
||||
// Usernames
|
||||
|
||||
$existingUsernames = Account::where('domain', $domain)
|
||||
->whereIn('username', $lines->pluck('username')->all())
|
||||
->pluck('username');
|
||||
|
||||
if ($existingUsernames->isNotEmpty()) {
|
||||
$this->errors['Those usernames already exists'] = $existingUsernames->join(', ', ' and ');
|
||||
}
|
||||
|
||||
if ($duplicates = $lines->pluck('username')->duplicates()) {
|
||||
if ($duplicates->isNotEmpty()) {
|
||||
$this->errors['Those usernames are declared several times'] = $duplicates->join(', ', ' and ');
|
||||
}
|
||||
}
|
||||
|
||||
if ($lines->pluck('username')->contains(function ($value) {
|
||||
return strlen($value) < 2;
|
||||
})) {
|
||||
$this->errors['Some usernames are shorter than expected'] = '';
|
||||
}
|
||||
|
||||
// Passwords
|
||||
|
||||
if ($lines->pluck('password')->contains(function ($value) {
|
||||
return strlen($value) < 6;
|
||||
})) {
|
||||
$this->errors['Some passwords are shorter than expected'] = '';
|
||||
}
|
||||
|
||||
// Roles
|
||||
|
||||
if ($lines->pluck('role')->contains(function ($value) {
|
||||
return !in_array($value, ['admin', 'user']);
|
||||
})) {
|
||||
$this->errors['Some roles are not correct'] = '';
|
||||
}
|
||||
|
||||
// Status
|
||||
|
||||
if ($lines->pluck('status')->contains(function ($value) {
|
||||
return !in_array($value, ['active', 'inactive']);
|
||||
})) {
|
||||
$this->errors['Some statuses are not correct'] = '';
|
||||
}
|
||||
|
||||
// Phones
|
||||
|
||||
$phoneCountries = PhoneCountry::where('activated', true)->get();
|
||||
|
||||
if ($phones = $lines->pluck('phone')->filter(function ($value) {
|
||||
return !empty($value);
|
||||
})->filter(function ($value) use ($phoneCountries) {
|
||||
return !$phoneCountries->firstWhere('code', (new PhoneNumber($value))->getCountry());
|
||||
})) {
|
||||
if ($phones->isNotEmpty()) {
|
||||
$this->errors['Some phone numbers are not correct'] = $phones->join(', ', ' and ');
|
||||
}
|
||||
}
|
||||
|
||||
$existingPhones = Account::whereIn('phone', $lines->pluck('phone')->all())
|
||||
->pluck('phone');
|
||||
|
||||
if ($existingPhones->isNotEmpty()) {
|
||||
$this->errors['Those phones numbers already exists'] = $existingPhones->join(', ', ' and ');
|
||||
}
|
||||
|
||||
// Emails
|
||||
|
||||
if ($emails = $lines->pluck('email')->filter(function ($value) {
|
||||
return $value != '' && !filter_var($value, FILTER_VALIDATE_EMAIL);
|
||||
})) {
|
||||
if ($emails->isNotEmpty()) {
|
||||
$this->errors['Some emails are not correct'] = $emails->join(', ', ' and ');
|
||||
}
|
||||
}
|
||||
|
||||
$existingEmails = Account::whereIn('email', $lines->pluck('email')->all())
|
||||
->pluck('email');
|
||||
|
||||
if ($existingEmails->isNotEmpty()) {
|
||||
$this->errors['Those emails numbers already exists'] = $existingEmails->join(', ', ' and ');
|
||||
}
|
||||
|
||||
if ($emails = $lines->pluck('email')->filter(fn (string $value) => $value != '')->duplicates()) {
|
||||
if ($emails->isNotEmpty()) {
|
||||
$this->errors['Those emails are declared several times'] = $emails->join(', ', ' and ');
|
||||
}
|
||||
}
|
||||
|
||||
// External account
|
||||
|
||||
$checkExternalUsernameDomains = collect();
|
||||
|
||||
foreach ($lines as $line) {
|
||||
if ($line->external_username != null && ($line->external_password == null || $line->external_domain == null)) {
|
||||
$this->errors['Line ' . $line->line . ': The mandatory external account columns must be filled'] = '';
|
||||
}
|
||||
|
||||
if ($line->external_username != null && $line->external_password != null && $line->external_domain != null) {
|
||||
if ($line->external_domain == $line->external_realm
|
||||
|| $line->external_domain == $line->external_registrar
|
||||
|| $line->external_domain == $line->external_outbound_proxy) {
|
||||
$this->errors['Line ' . $line->line . ': External realm, registrar or outbound proxy must be different than domain'] = '';
|
||||
}
|
||||
|
||||
if (!in_array($line->external_protocol, ExternalAccount::PROTOCOLS)) {
|
||||
$this->errors['Line ' . $line->line . ': External protocol must be UDP, TCP or TLS'] = '';
|
||||
}
|
||||
}
|
||||
|
||||
$checkExternalUsernameDomains->push($line->external_username . ',' . $line->external_domain);
|
||||
}
|
||||
|
||||
foreach ($checkExternalUsernameDomains->duplicates() as $duplicate) {
|
||||
$this->errors['The following external account is used several times: ' . $duplicate] = '';
|
||||
}
|
||||
|
||||
$filePath = $this->errors->isEmpty()
|
||||
? Storage::putFile($this->importDirectory, $request->file('csv'))
|
||||
: null;
|
||||
|
||||
if ($filePath == false) {
|
||||
$this->errors['The CSV file was not imported properly on the server'] = '';
|
||||
}
|
||||
|
||||
return view('admin.account.import.check', [
|
||||
'linesCount' => $lines->count(),
|
||||
'errors' => $this->errors,
|
||||
'domain' => $domain,
|
||||
'filePath' => $filePath
|
||||
]);
|
||||
}
|
||||
|
||||
public function handle(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'file_path' => 'required',
|
||||
'domain' => 'required|exists:spaces,domain'
|
||||
]);
|
||||
|
||||
$domain = $request->user()->superAdmin
|
||||
? $request->get('domain')
|
||||
: $request->user()->domain;
|
||||
|
||||
$lines = $this->csvToCollection(storage_path('app/' . $request->get('file_path')));
|
||||
|
||||
$accounts = [];
|
||||
$now = \Carbon\Carbon::now();
|
||||
|
||||
$admins = $phones = $passwords = $externals = [];
|
||||
|
||||
$externalAlgorithm = 'MD5';
|
||||
|
||||
foreach ($lines as $line) {
|
||||
if ($line->role == 'admin') {
|
||||
array_push($admins, $line->username);
|
||||
}
|
||||
|
||||
if (!empty($line->phone)) {
|
||||
$phones[$line->username] = $line->phone;
|
||||
}
|
||||
|
||||
if (!empty($line->password)) {
|
||||
$passwords[$line->username] = $line->password;
|
||||
}
|
||||
|
||||
if (!empty($line->external_username)) {
|
||||
$externals[$line->username] = [
|
||||
'username' => $line->external_username,
|
||||
'domain' => $line->external_domain,
|
||||
'realm' => $line->external_realm,
|
||||
'registrar' => $line->external_registrar,
|
||||
'outbound_proxy' => $line->external_outbound_proxy,
|
||||
'protocol' => $line->external_protocol,
|
||||
'password' => bchash(
|
||||
$line->external_username,
|
||||
$line->external_realm ?? $line->external_domain,
|
||||
$line->external_password,
|
||||
$externalAlgorithm
|
||||
),
|
||||
'algorithm' => $externalAlgorithm,
|
||||
'created_at' => Carbon::now(),
|
||||
];
|
||||
}
|
||||
|
||||
array_push($accounts, [
|
||||
'username' => $line->username,
|
||||
'domain' => $domain,
|
||||
'email' => $line->email,
|
||||
'activated' => $line->status == 'active',
|
||||
'ip_address' => '127.0.0.1',
|
||||
'user_agent' => 'CSV Import',
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
}
|
||||
|
||||
Account::insert($accounts);
|
||||
|
||||
// Set admins accounts
|
||||
foreach ($admins as $username) {
|
||||
$account = Account::where('username', $username)
|
||||
->where('domain', $domain)
|
||||
->first();
|
||||
$account->admin = true;
|
||||
$account->save();
|
||||
}
|
||||
|
||||
// Set passwords
|
||||
|
||||
$passwordsToInsert = [];
|
||||
|
||||
$passwordAccounts = Account::whereIn('username', array_keys($passwords))
|
||||
->where('domain', $domain)
|
||||
->get();
|
||||
|
||||
$algorithm = config('app.account_default_password_algorithm');
|
||||
|
||||
foreach ($passwordAccounts as $passwordAccount) {
|
||||
array_push($passwordsToInsert, [
|
||||
'account_id' => $passwordAccount->id,
|
||||
'password' => bchash(
|
||||
$passwordAccount->username,
|
||||
$request->space?->account_realm ?? $domain,
|
||||
$passwords[$passwordAccount->username],
|
||||
$algorithm
|
||||
),
|
||||
'algorithm' => $algorithm
|
||||
]);
|
||||
}
|
||||
|
||||
Password::insert($passwordsToInsert);
|
||||
|
||||
// Set external account
|
||||
|
||||
$externalAccountsToInsert = [];
|
||||
|
||||
$externalAccounts = Account::whereIn('username', array_keys($externals))
|
||||
->where('domain', $domain)
|
||||
->get();
|
||||
|
||||
foreach ($externalAccounts as $externalAccount) {
|
||||
array_push($externalAccountsToInsert, [
|
||||
'account_id' => $externalAccount->id
|
||||
] + $externals[$externalAccount->username]);
|
||||
}
|
||||
|
||||
ExternalAccount::insert($externalAccountsToInsert);
|
||||
|
||||
// Set phone accounts
|
||||
foreach ($phones as $username => $phone) {
|
||||
$account = Account::where('username', $username)
|
||||
->where('domain', $domain)
|
||||
->first();
|
||||
$account->phone = $phone;
|
||||
}
|
||||
|
||||
return redirect()->route('admin.account.index');
|
||||
}
|
||||
|
||||
private function csvToCollection($file): Collection
|
||||
{
|
||||
$lines = collect();
|
||||
$csv = fopen($file, 'r');
|
||||
|
||||
$i = 1;
|
||||
while (!feof($csv)) {
|
||||
if ($line = fgetcsv($csv, 1000, ',')) {
|
||||
if (count($line) != 13) {
|
||||
$this->errors['Parsing error at line ' . $i] = 'The number of columns is incorrect';
|
||||
$i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$lines->push((object)[
|
||||
'line' => $i,
|
||||
'username' => !empty($line[0]) ? $line[0] : null,
|
||||
'password' => !empty($line[1]) ? $line[1] : null,
|
||||
'role' => $line[2],
|
||||
'status' => $line[3],
|
||||
'phone' => !empty($line[4]) ? $line[4] : null,
|
||||
'email' => $line[5],
|
||||
'external_username' => !empty($line[6]) ? $line[6] : null,
|
||||
'external_domain' => !empty($line[7]) ? $line[7] : null,
|
||||
'external_password' => !empty($line[8]) ? $line[8] : null,
|
||||
'external_realm' => !empty($line[9]) ? $line[9] : null,
|
||||
'external_registrar' => !empty($line[10]) ? $line[10] : null,
|
||||
'external_outbound_proxy' => !empty($line[11]) ? $line[11] : null,
|
||||
'external_protocol' => $line[12],
|
||||
]);
|
||||
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
|
||||
fclose($csv);
|
||||
|
||||
$lines->shift();
|
||||
|
||||
return $lines;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
<?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\Http\Controllers\Controller;
|
||||
use App\Libraries\StatisticsGraphFactory;
|
||||
use App\StatisticsCall;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class StatisticsController extends Controller
|
||||
{
|
||||
public function edit(Request $request, int $accountId)
|
||||
{
|
||||
$account = Account::findOrFail($accountId);
|
||||
|
||||
return redirect()->route('admin.account.statistics.show', [
|
||||
'account' => $account,
|
||||
'from' => $request->get('from'),
|
||||
'to' => $request->get('to'),
|
||||
'by' => $request->get('by'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(Request $request, int $accountId)
|
||||
{
|
||||
$request->validate([
|
||||
'from' => 'date_format:Y-m-d|before:to',
|
||||
'to' => 'date_format:Y-m-d|after:from',
|
||||
'by' => 'in:day,week,month,year',
|
||||
]);
|
||||
|
||||
$account = Account::findOrFail($accountId);
|
||||
|
||||
$messagesFromGraph = view('parts.graph', [
|
||||
'jsonConfig' => json_encode((new StatisticsGraphFactory($request, 'messages', fromUsername: $account->username, fromDomain: $account->domain))->getConfig()),
|
||||
'request' => $request
|
||||
])->render();
|
||||
|
||||
$messagesToGraph = view('parts.graph', [
|
||||
'jsonConfig' => json_encode((new StatisticsGraphFactory($request, 'messages', toUsername: $account->username, toDomain: $account->domain))->getConfig()),
|
||||
'request' => $request
|
||||
])->render();
|
||||
|
||||
$callsFromGraph = view('parts.graph', [
|
||||
'jsonConfig' => json_encode((new StatisticsGraphFactory($request, 'calls', fromUsername: $account->username, fromDomain: $account->domain))->getConfig()),
|
||||
'request' => $request
|
||||
])->render();
|
||||
|
||||
$callsToGraph = view('parts.graph', [
|
||||
'jsonConfig' => json_encode((new StatisticsGraphFactory($request, 'calls', toUsername: $account->username, toDomain: $account->domain))->getConfig()),
|
||||
'request' => $request
|
||||
])->render();
|
||||
|
||||
return view('admin.account.statistics.show', [
|
||||
'account' => $account,
|
||||
'messagesFromGraph' => $messagesFromGraph,
|
||||
'messagesToGraph' => $messagesToGraph,
|
||||
'callsFromGraph' => $callsFromGraph,
|
||||
'callsToGraph' => $callsToGraph,
|
||||
]);
|
||||
}
|
||||
|
||||
public function editCallLogs(Request $request, int $accountId)
|
||||
{
|
||||
return redirect()->route('admin.account.statistics.show_call_logs', [
|
||||
'from' => $request->get('from'),
|
||||
'to' => $request->get('to'),
|
||||
'account' => $accountId
|
||||
]);
|
||||
}
|
||||
|
||||
public function showCallLogs(Request $request, int $accountId)
|
||||
{
|
||||
$account = Account::findOrFail($accountId);
|
||||
$toQuery = DB::table('statistics_calls')
|
||||
->where('to_domain', $account->domain)
|
||||
->where('to_username', $account->username);
|
||||
$calls = StatisticsCall::where('from_domain', $account->domain)
|
||||
->where('from_username', $account->username);
|
||||
|
||||
if ($request->get('to')) {
|
||||
$toQuery = $toQuery->where('initiated_at', '<=', $request->get('to'));
|
||||
$calls = $calls->where('initiated_at', '<=', $request->get('to'));
|
||||
}
|
||||
|
||||
if ($request->get('from')) {
|
||||
$toQuery = $toQuery->where('initiated_at', '>=', $request->get('from'));
|
||||
$calls = $calls->where('initiated_at', '>=', $request->get('from'));
|
||||
}
|
||||
|
||||
$calls = $calls->union($toQuery);
|
||||
|
||||
return view('admin.account.statistics.show_call_logs', [
|
||||
'account' => $account,
|
||||
'calls' => $calls->orderBy('initiated_at', 'desc')->paginate(30),
|
||||
'request' => $request,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
namespace App\Http\Controllers\Admin\Account;
|
||||
|
||||
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 AccountTypeController extends Controller
|
||||
class TypeController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
|
|
@ -51,8 +51,6 @@ class AccountTypeController extends Controller
|
|||
$accountType->key = $request->get('key');
|
||||
$accountType->save();
|
||||
|
||||
$request->session()->flash('success', 'Type successfully created');
|
||||
|
||||
return redirect()->route('admin.account.type.index');
|
||||
}
|
||||
|
||||
|
|
@ -78,8 +76,6 @@ class AccountTypeController extends Controller
|
|||
$accountType->key = $request->get('key');
|
||||
$accountType->save();
|
||||
|
||||
$request->session()->flash('success', 'Type successfully updated');
|
||||
|
||||
return redirect()->route('admin.account.type.index');
|
||||
}
|
||||
|
||||
|
|
@ -90,13 +86,11 @@ class AccountTypeController extends Controller
|
|||
]);
|
||||
}
|
||||
|
||||
public function destroy(Request $request, int $typeId)
|
||||
public function destroy(int $typeId)
|
||||
{
|
||||
$type = AccountType::findOrFail($typeId);
|
||||
$type->delete();
|
||||
|
||||
$request->session()->flash('success', 'Type successfully destroyed');
|
||||
|
||||
Log::channel('events')->info('Web Admin: Account type deleted', ['type' => $type->key]);
|
||||
|
||||
return redirect()->route('admin.account.type.index');
|
||||
|
|
@ -22,171 +22,135 @@ namespace App\Http\Controllers\Admin;
|
|||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Carbon\Carbon;
|
||||
|
||||
use App\Account;
|
||||
use App\Admin;
|
||||
use App\Alias;
|
||||
use App\ExternalAccount;
|
||||
use App\Http\Requests\CreateAccountRequest;
|
||||
use App\Http\Requests\UpdateAccountRequest;
|
||||
use App\ContactsList;
|
||||
use App\Http\Requests\Account\Create\Web\AsAdminRequest;
|
||||
use App\Http\Requests\Account\Update\Web\AsAdminRequest as WebAsAdminRequest;
|
||||
use App\Libraries\FlexisipRedisConnector;
|
||||
use App\Services\AccountService;
|
||||
use App\Space;
|
||||
|
||||
class AccountController extends Controller
|
||||
{
|
||||
public function index(Request $request, $search = '')
|
||||
public function index(Request $request)
|
||||
{
|
||||
$accounts = Account::orderBy('creation_time', 'desc')->with('externalAccount');
|
||||
$request->validate([
|
||||
'order_by' => 'in:username,updated_at',
|
||||
'order_sort' => 'in:asc,desc',
|
||||
]);
|
||||
|
||||
if (!empty($search)) {
|
||||
$accounts = $accounts->where('username', 'like', '%'.$search.'%');
|
||||
$accounts = Account::orderBy($request->get('order_by', 'updated_at'), $request->get('order_sort', 'desc'));
|
||||
|
||||
if ($request->has('search')) {
|
||||
$accounts = $accounts->where('username', 'like', '%' . $request->get('search') . '%');
|
||||
}
|
||||
|
||||
if ($request->has('updated_date')) {
|
||||
$accounts->whereDate('updated_at', $request->get('updated_date'));
|
||||
}
|
||||
|
||||
if ($request->has('contacts_list')) {
|
||||
$accounts->whereHas('contactsLists', function ($query) use ($request) {
|
||||
$query->where('id', $request->get('contacts_list'));
|
||||
});
|
||||
}
|
||||
|
||||
if ($request->has('domain')) {
|
||||
$accounts->where('domain', $request->get('domain'));
|
||||
}
|
||||
|
||||
return view('admin.account.index', [
|
||||
'search' => $search,
|
||||
'accounts' => $accounts->paginate(30)->appends($request->query())
|
||||
'space' => (!$request->user()->superAdmin)
|
||||
? $request->user()->space
|
||||
: null,
|
||||
'domains' => Account::groupBy('domain')->pluck('domain'),
|
||||
'contacts_lists' => ContactsList::all()->pluck('title', 'id'),
|
||||
'accounts' => $accounts->paginate(20)->appends($request->query()),
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(int $id)
|
||||
public function search(Request $request)
|
||||
{
|
||||
return redirect()->route('admin.account.index', $request->except('_token', 'query'));
|
||||
}
|
||||
|
||||
public function show(Request $request, int $accountId)
|
||||
{
|
||||
$account = Account::findOrFail($accountId);
|
||||
|
||||
return view('admin.account.show', [
|
||||
'external_accounts_count' => ExternalAccount::where('used', false)->count(),
|
||||
'account' => Account::findOrFail($id)
|
||||
'account' => $account,
|
||||
'devices' => (new FlexisipRedisConnector)->getDevices($account->identifier)
|
||||
]);
|
||||
}
|
||||
|
||||
public function create(Request $request)
|
||||
{
|
||||
$account = new Account;
|
||||
|
||||
if ($request->has('admin')) {
|
||||
$account->admin = true;
|
||||
}
|
||||
|
||||
if ($request->has('domain')) {
|
||||
$account->domain = $request->get('domain');
|
||||
}
|
||||
|
||||
return view('admin.account.create_edit', [
|
||||
'account' => new Account,
|
||||
'account' => $account,
|
||||
'domains' => $request->user()?->superAdmin
|
||||
? Space::notFull()->get()
|
||||
: Space::where('domain', $request->user()->domain)->get(),
|
||||
'protocols' => [null => 'None'] + Account::$dtmfProtocols
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(CreateAccountRequest $request)
|
||||
public function store(AsAdminRequest $request)
|
||||
{
|
||||
$account = new Account;
|
||||
$account->username = $request->get('username');
|
||||
$account->email = $request->get('email');
|
||||
$account->display_name = $request->get('display_name');
|
||||
$account->domain = resolveDomain($request);
|
||||
$account->ip_address = $request->ip();
|
||||
$account->creation_time = Carbon::now();
|
||||
$account->user_agent = config('app.name');
|
||||
$account->dtmf_protocol = $request->get('dtmf_protocol');
|
||||
$account->save();
|
||||
|
||||
$account->phone = $request->get('phone');
|
||||
$account->fillPassword($request);
|
||||
$account = (new AccountService)->store($request);
|
||||
|
||||
Log::channel('events')->info('Web Admin: Account created', ['id' => $account->identifier]);
|
||||
|
||||
return redirect()->route('admin.account.show', $account->id);
|
||||
return redirect()->route('admin.account.show', $account);
|
||||
}
|
||||
|
||||
public function edit(int $id)
|
||||
public function edit(Request $request, int $accountId)
|
||||
{
|
||||
$account = Account::findOrFail($accountId);
|
||||
|
||||
return view('admin.account.create_edit', [
|
||||
'account' => Account::findOrFail($id),
|
||||
'protocols' => [null => 'None'] + Account::$dtmfProtocols
|
||||
'account' => $account,
|
||||
'protocols' => [null => __('Empty')] + Account::$dtmfProtocols,
|
||||
'domains' => $request->user()?->superAdmin
|
||||
? Space::all()
|
||||
: Space::where('domain', $account->domain)->get(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(UpdateAccountRequest $request, $id)
|
||||
public function update(WebAsAdminRequest $request, int $accountId)
|
||||
{
|
||||
$account = Account::findOrFail($id);
|
||||
$account->username = $request->get('username');
|
||||
$account->email = $request->get('email');
|
||||
$account->display_name = $request->get('display_name');
|
||||
$account->dtmf_protocol = $request->get('dtmf_protocol');
|
||||
$account->save();
|
||||
|
||||
$account->phone = $request->get('phone');
|
||||
$account->fillPassword($request);
|
||||
$account = (new AccountService)->update($request, $accountId);
|
||||
|
||||
Log::channel('events')->info('Web Admin: Account updated', ['id' => $account->identifier]);
|
||||
|
||||
return redirect()->route('admin.account.show', $id);
|
||||
return redirect()->route('admin.account.show', $accountId);
|
||||
}
|
||||
|
||||
public function search(Request $request)
|
||||
public function provision(int $accountId)
|
||||
{
|
||||
return redirect()->route('admin.account.index', $request->get('search'));
|
||||
}
|
||||
|
||||
public function attachExternalAccount(int $id)
|
||||
{
|
||||
$account = Account::findOrFail($id);
|
||||
$account->attachExternalAccount();
|
||||
|
||||
Log::channel('events')->info('Web Admin: ExternalAccount attached', ['id' => $account->identifier]);
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
public function activate(int $id)
|
||||
{
|
||||
$account = Account::findOrFail($id);
|
||||
$account->activated = true;
|
||||
$account->save();
|
||||
|
||||
Log::channel('events')->info('Web Admin: Account activated', ['id' => $account->identifier]);
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
public function deactivate(int $id)
|
||||
{
|
||||
$account = Account::findOrFail($id);
|
||||
$account->activated = false;
|
||||
$account->save();
|
||||
|
||||
Log::channel('events')->info('Web Admin: Account deactivated', ['id' => $account->identifier]);
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
public function provision(int $id)
|
||||
{
|
||||
$account = Account::findOrFail($id);
|
||||
$account = Account::findOrFail($accountId);
|
||||
$account->provision();
|
||||
$account->save();
|
||||
|
||||
Log::channel('events')->info('Web Admin: Account provisioned', ['id' => $account->identifier]);
|
||||
|
||||
return redirect()->back();
|
||||
return redirect()->back()->withFragment('provisioning');
|
||||
}
|
||||
|
||||
public function admin(int $id)
|
||||
public function delete(int $accountId)
|
||||
{
|
||||
$account = Account::findOrFail($id);
|
||||
|
||||
$admin = new Admin;
|
||||
$admin->account_id = $account->id;
|
||||
$admin->save();
|
||||
|
||||
Log::channel('events')->info('Web Admin: Account set as admin', ['id' => $account->identifier]);
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
public function unadmin(Request $request, $id)
|
||||
{
|
||||
$account = Account::findOrFail($id);
|
||||
|
||||
// An admin cannot remove it's own permission
|
||||
if ($account->id == $request->user()->id) abort(403);
|
||||
|
||||
if ($account->admin) $account->admin->delete();
|
||||
|
||||
Log::channel('events')->info('Web Admin: Account unset as admin', ['id' => $account->identifier]);
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
public function delete(int $id)
|
||||
{
|
||||
$account = Account::findOrFail($id);
|
||||
$account = Account::findOrFail($accountId);
|
||||
|
||||
return view('admin.account.delete', [
|
||||
'account' => $account
|
||||
|
|
@ -196,12 +160,32 @@ class AccountController extends Controller
|
|||
public function destroy(Request $request)
|
||||
{
|
||||
$account = Account::findOrFail($request->get('account_id'));
|
||||
$account->delete();
|
||||
|
||||
$request->session()->flash('success', 'Account successfully destroyed');
|
||||
(new AccountService)->destroy($request, $request->get('account_id'));
|
||||
|
||||
Log::channel('events')->info('Web Admin: Account deleted', ['id' => $account->identifier]);
|
||||
|
||||
return redirect()->route('admin.account.index');
|
||||
}
|
||||
|
||||
public function contactsListAdd(Request $request, int $accountId)
|
||||
{
|
||||
$request->validate([
|
||||
'contacts_list_id' => 'required|exists:contacts_lists,id'
|
||||
]);
|
||||
|
||||
$account = Account::findOrFail($accountId);
|
||||
$account->contactsLists()->detach([$request->get('contacts_list_id')]);
|
||||
$account->contactsLists()->attach([$request->get('contacts_list_id')]);
|
||||
|
||||
return redirect()->route('admin.account.contact.index', $accountId)->withFragment('#contacts_lists');
|
||||
}
|
||||
|
||||
public function contactsListRemove(Request $request, int $accountId)
|
||||
{
|
||||
$account = Account::findOrFail($accountId);
|
||||
$account->contactsLists()->detach([$request->get('contacts_list_id')]);
|
||||
|
||||
return redirect()->route('admin.account.contact.index', $accountId)->withFragment('#contacts_lists');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
95
flexiapi/app/Http/Controllers/Admin/ApiKeyController.php
Normal file
95
flexiapi/app/Http/Controllers/Admin/ApiKeyController.php
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
<?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\Controllers\Admin;
|
||||
|
||||
use App\ApiKey;
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class ApiKeyController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
return view('admin.api_key.index', [
|
||||
'api_keys' => $this->getApiKeysQuery($request)->with('account')->get()
|
||||
]);
|
||||
}
|
||||
|
||||
public function create(Request $request)
|
||||
{
|
||||
return view('admin.api_key.create', [
|
||||
'account' => $request->user()
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'name' => 'required|min:3',
|
||||
'expires_after_last_used_minutes' => 'integer|min:0'
|
||||
]);
|
||||
|
||||
$apiKey = new ApiKey;
|
||||
$apiKey->account_id = $request->user()->id;
|
||||
$apiKey->name = $request->get('name');
|
||||
$apiKey->expires_after_last_used_minutes = $request->get('expires_after_last_used_minutes');
|
||||
$apiKey->last_used_at = Carbon::now();
|
||||
$apiKey->key = Str::random(40);
|
||||
$apiKey->save();
|
||||
|
||||
return redirect()->route('admin.api_keys.index');
|
||||
}
|
||||
|
||||
public function delete(Request $request, string $key)
|
||||
{
|
||||
return view('admin.api_key.delete', [
|
||||
'api_key' => $this->getApiKeysQuery($request)->where('key', $key)->first()
|
||||
]);
|
||||
}
|
||||
|
||||
public function destroy(Request $request)
|
||||
{
|
||||
$this->getApiKeysQuery($request)->where('key', $request->get('key'))->delete();
|
||||
|
||||
return redirect()->route('admin.api_keys.index');
|
||||
}
|
||||
|
||||
private function getApiKeysQuery(Request $request)
|
||||
{
|
||||
$apiKeys = ApiKey::whereIn('account_id', function ($query) {
|
||||
$query->select('id')
|
||||
->from('accounts')
|
||||
->where('admin', true);
|
||||
})->whereNotNull('expires_after_last_used_minutes');
|
||||
|
||||
if (!$request->user()->superAdmin) {
|
||||
$apiKeys->whereIn('account_id', function ($query) use ($request) {
|
||||
$query->select('id')
|
||||
->from('accounts')
|
||||
->where('domain', $request->user()->domain);
|
||||
});
|
||||
}
|
||||
|
||||
return $apiKeys;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
<?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;
|
||||
|
||||
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(int $accountId)
|
||||
{
|
||||
$account = Account::findOrFail($accountId);
|
||||
|
||||
return view('admin.account.external.show', [
|
||||
'account' => $account,
|
||||
'externalAccount' => $account->external ?? new ExternalAccount,
|
||||
'protocols' => ExternalAccount::PROTOCOLS
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(CreateUpdate $request, int $accountId)
|
||||
{
|
||||
(new AccountService)->storeExternalAccount($request, $accountId);
|
||||
|
||||
return redirect()->route('admin.account.show', $accountId);
|
||||
}
|
||||
|
||||
public function delete(int $accountId)
|
||||
{
|
||||
return view('admin.account.external.delete', [
|
||||
'account' => Account::findOrFail($accountId)
|
||||
]);
|
||||
}
|
||||
|
||||
public function destroy(int $accountId)
|
||||
{
|
||||
(new AccountService)->deleteExternalAccount($accountId);
|
||||
|
||||
return redirect()->route('admin.account.show', $accountId);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\PhoneCountry;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class PhoneCountryController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return view('admin.phone_country.index', [
|
||||
'phone_countries' => PhoneCountry::all()
|
||||
]);
|
||||
}
|
||||
|
||||
public function activateAll()
|
||||
{
|
||||
PhoneCountry::query()->update(['activated' => true]);
|
||||
|
||||
return redirect()->route('admin.phone_countries.index');
|
||||
}
|
||||
|
||||
public function deactivateAll()
|
||||
{
|
||||
PhoneCountry::query()->update(['activated' => false]);
|
||||
|
||||
return redirect()->route('admin.phone_countries.index');
|
||||
}
|
||||
|
||||
public function activate(string $code)
|
||||
{
|
||||
$phoneCountry = PhoneCountry::where('code', $code)->firstOrFail();
|
||||
|
||||
PhoneCountry::where('country_code', $phoneCountry->country_code)->update(['activated' => true]);
|
||||
|
||||
return redirect()->route('admin.phone_countries.index');
|
||||
}
|
||||
|
||||
public function deactivate(string $code)
|
||||
{
|
||||
$phoneCountry = PhoneCountry::where('code', $code)->firstOrFail();
|
||||
|
||||
PhoneCountry::where('country_code', $phoneCountry->country_code)->update(['activated' => false]);
|
||||
|
||||
return redirect()->route('admin.phone_countries.index');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Account;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Mail\Provisioning;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class ProvisioningEmailController extends Controller
|
||||
{
|
||||
public function create(int $accountId)
|
||||
{
|
||||
$account = Account::findOrFail($accountId);
|
||||
|
||||
return view('admin.account.provisioning_email.create', [
|
||||
'account' => $account
|
||||
]);
|
||||
}
|
||||
|
||||
public function send(int $accountId)
|
||||
{
|
||||
$account = Account::findOrFail($accountId);
|
||||
$account->provision();
|
||||
|
||||
Mail::to($account)->send(new Provisioning($account));
|
||||
|
||||
Log::channel('events')->info('Web Admin: Sending provisioning email', ['id' => $account->identifier]);
|
||||
|
||||
return redirect()->route('admin.account.show', $account);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Account;
|
||||
use App\ResetPasswordEmailToken;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Mail\ResetPassword;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
class ResetPasswordEmailController extends Controller
|
||||
{
|
||||
public function create(int $accountId)
|
||||
{
|
||||
$account = Account::findOrFail($accountId);
|
||||
|
||||
return view('admin.account.reset_password_email.create', [
|
||||
'account' => $account
|
||||
]);
|
||||
}
|
||||
|
||||
public function send(int $accountId)
|
||||
{
|
||||
$account = Account::findOrFail($accountId);
|
||||
|
||||
$resetPasswordEmail = new ResetPasswordEmailToken;
|
||||
$resetPasswordEmail->account_id = $account->id;
|
||||
$resetPasswordEmail->token = Str::random(16);
|
||||
$resetPasswordEmail->email = $account->email;
|
||||
$resetPasswordEmail->save();
|
||||
|
||||
Mail::to($account)->send(new ResetPassword($account));
|
||||
|
||||
return redirect()->route('admin.account.activity.index', $account);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
<?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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
<?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\Space;
|
||||
|
||||
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)
|
||||
{
|
||||
$accounts = $space->accounts()->orderBy('updated_at', $request->get('updated_at_order', 'desc'));
|
||||
|
||||
if ($request->has('search')) {
|
||||
$accounts = $accounts->where('username', 'like', '%' . $request->get('search') . '%');
|
||||
}
|
||||
|
||||
if ($request->has('domain')) {
|
||||
$accounts = $accounts->where('domain', $request->get('domain'));
|
||||
}
|
||||
|
||||
return view('admin.space.contacts_list.contacts.add', [
|
||||
'space' => $space,
|
||||
'contacts_list' => $space->contactsLists()->findOrFail($contactsListId),
|
||||
'params' => [
|
||||
'contacts_list_id' => $contactsListId
|
||||
],
|
||||
'accounts' => $accounts->whereNotIn('id', function ($query) use ($contactsListId) {
|
||||
$query->select('contact_id')
|
||||
->from('contacts_list_contact')
|
||||
->where('contacts_list_id', $contactsListId);
|
||||
})->paginate(20)->appends($request->query()),
|
||||
]);
|
||||
}
|
||||
|
||||
public function search(Request $request, Space $space, int $contactsListId)
|
||||
{
|
||||
return redirect()->route('admin.spaces.contacts_lists.contacts.add', ['contacts_list_id' => $contactsListId] + $request->except('_token'));
|
||||
}
|
||||
|
||||
public function store(Request $request, Space $space, int $contactsListId)
|
||||
{
|
||||
$request->validate([
|
||||
'contacts_ids' => 'required|exists:accounts,id'
|
||||
]);
|
||||
|
||||
$contactsList = $space->contactsLists()->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]);
|
||||
}
|
||||
|
||||
public function destroy(Request $request, Space $space, int $contactsListId)
|
||||
{
|
||||
$request->validate([
|
||||
'contacts_ids' => 'required|exists:accounts,id'
|
||||
]);
|
||||
|
||||
$contactsList = $space->contactsLists()->findOrFail($contactsListId);
|
||||
$contactsList->contacts()->detach($request->get('contacts_ids'));
|
||||
|
||||
return redirect()->route('admin.spaces.contacts_lists.edit', [$space, $contactsList->id]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
<?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\Space;
|
||||
|
||||
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)
|
||||
{
|
||||
$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'));
|
||||
|
||||
return view('admin.space.contacts_list.index', [
|
||||
'space' => $space,
|
||||
'contacts_lists' => $contactsLists
|
||||
->paginate(20)
|
||||
->appends($request->query()),
|
||||
]);
|
||||
}
|
||||
|
||||
public function create(Request $request, Space $space)
|
||||
{
|
||||
return view('admin.space.contacts_list.create_edit', [
|
||||
'space' => $space,
|
||||
'contacts_list' => new ContactsList,
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request, Space $space)
|
||||
{
|
||||
$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]);
|
||||
}
|
||||
|
||||
public function search(Request $request, Space $space, int $contactsListId)
|
||||
{
|
||||
return redirect()->route('admin.spaces.contacts_lists.edit', [
|
||||
'space' => $space,
|
||||
'contacts_list_id' => $contactsListId] + $request->except('_token'));
|
||||
}
|
||||
|
||||
public function edit(Request $request, Space $space, int $id)
|
||||
{
|
||||
$contacts = $space->contactsLists()->findOrFail($id)->contacts();
|
||||
|
||||
if ($request->has('search')) {
|
||||
$contacts = $contacts->where('username', 'like', '%' . $request->get('search') . '%');
|
||||
}
|
||||
|
||||
if ($request->has('domain')) {
|
||||
$contact = $contacts->where('domain', $request->get('domain'));
|
||||
}
|
||||
|
||||
$contacts = $contacts->get();
|
||||
|
||||
return view('admin.space.contacts_list.create_edit', [
|
||||
'space' => $space,
|
||||
'domains' => Account::groupBy('domain')->pluck('domain'),
|
||||
'contacts_list' => $space->contactsLists()->findOrFail($id),
|
||||
'contacts' => $contacts
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(Request $request, Space $space, int $id)
|
||||
{
|
||||
$request->validate([
|
||||
'title' => [
|
||||
'required',
|
||||
Rule::unique('contacts_lists')->ignore($id),
|
||||
],
|
||||
]);
|
||||
|
||||
$contactsList = $space->contactsLists()->findOrFail($id);
|
||||
$contactsList->title = $request->get('title');
|
||||
$contactsList->description = $request->get('description');
|
||||
$contactsList->save();
|
||||
|
||||
return redirect()->route('admin.spaces.contacts_lists.index', $space);
|
||||
}
|
||||
|
||||
public function delete(Space $space, int $id)
|
||||
{
|
||||
return view('admin.space.contacts_list.delete', [
|
||||
'space' => $space,
|
||||
'contacts_list' => $space->contactsLists()->findOrFail($id),
|
||||
]);
|
||||
}
|
||||
|
||||
public function destroy(Request $request, Space $space)
|
||||
{
|
||||
$contactsList = $space->contactsLists()->findOrFail($request->get('contacts_lists_id'));
|
||||
$contactsList->delete();
|
||||
|
||||
return redirect()->route('admin.spaces.contacts_lists.index', $space);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin\Space;
|
||||
|
||||
use App\Space;
|
||||
use App\SpaceEmailServer;
|
||||
|
||||
use App\Http\Requests\EmailServer\CreateUpdate;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class EmailServerController extends Controller
|
||||
{
|
||||
public function show(int $spaceId)
|
||||
{
|
||||
$space = Space::findOrFail($spaceId);
|
||||
|
||||
return view('admin.space.email_server.show', [
|
||||
'space' => $space,
|
||||
'emailServer' => $space->emailServer ?? new SpaceEmailServer
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(CreateUpdate $request, int $spaceId)
|
||||
{
|
||||
$space = Space::findOrFail($spaceId);
|
||||
$emailServer = $space->emailServer ?? new SpaceEmailServer;
|
||||
|
||||
$emailServer->space_id = $space->id;
|
||||
$emailServer->host = $request->get('host');
|
||||
$emailServer->port = $request->get('port');
|
||||
$emailServer->username = $request->get('username');
|
||||
$emailServer->password = $request->get('password');
|
||||
$emailServer->from_address = $request->get('from_address') ?? null;
|
||||
$emailServer->from_name = $request->get('from_name') ?? null;
|
||||
$emailServer->signature = $request->get('signature') ?? null;
|
||||
|
||||
$emailServer->save();
|
||||
|
||||
return redirect()->route('admin.spaces.integration', $spaceId);
|
||||
}
|
||||
|
||||
public function delete(int $spaceId)
|
||||
{
|
||||
$space = Space::findOrFail($spaceId);
|
||||
|
||||
return view('admin.space.email_server.delete', [
|
||||
'space' => $space
|
||||
]);
|
||||
}
|
||||
|
||||
public function destroy(int $spaceId)
|
||||
{
|
||||
$space = Space::findOrFail($spaceId);
|
||||
$space->emailServer->delete();
|
||||
|
||||
return redirect()->route('admin.spaces.integration', $spaceId);
|
||||
}
|
||||
}
|
||||
226
flexiapi/app/Http/Controllers/Admin/SpaceController.php
Normal file
226
flexiapi/app/Http/Controllers/Admin/SpaceController.php
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
<?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;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Space\Create;
|
||||
use App\Space;
|
||||
use App\Rules\Ini;
|
||||
use App\Rules\Domain;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class SpaceController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return view('admin.space.index', ['spaces' => Space::withCount('accounts')->orderBy('host')->get()]);
|
||||
}
|
||||
|
||||
public function me(Request $request)
|
||||
{
|
||||
return view('admin.space.show', [
|
||||
'space' => $request->user()->space
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(Space $space)
|
||||
{
|
||||
return view('admin.space.show', [
|
||||
'space' => $space
|
||||
]);
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
return view('admin.space.create', [
|
||||
'space' => new Space()
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Create $request)
|
||||
{
|
||||
$fullHost = empty($request->get('host'))
|
||||
? config('app.root_host')
|
||||
: $request->get('host') . '.' . config('app.root_host');
|
||||
|
||||
$request->merge(['full_host' => $fullHost]);
|
||||
$request->validate([
|
||||
'host' => 'nullable|regex:/'. Space::HOST_REGEX . '/',
|
||||
'full_host' => ['required', 'unique:spaces,host', new Domain()],
|
||||
]);
|
||||
|
||||
$space = new Space();
|
||||
$space->name = $request->get('name');
|
||||
$space->domain = $request->get('domain');
|
||||
$space->host = $request->get('full_host');
|
||||
$space->account_realm = $request->get('account_realm');
|
||||
$space->save();
|
||||
|
||||
return redirect()->route('admin.spaces.index');
|
||||
}
|
||||
|
||||
public function edit(Space $space)
|
||||
{
|
||||
return view('admin.space.edit', [
|
||||
'space' => $space
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(Request $request, Space $space)
|
||||
{
|
||||
$request->validate([
|
||||
'max_account' => 'required|integer',
|
||||
]);
|
||||
|
||||
$space = $this->setAppConfiguration($request, $space);
|
||||
$space->save();
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
public function configuration(Space $space)
|
||||
{
|
||||
return view('admin.space.configuration', [
|
||||
'space' => $space
|
||||
]);
|
||||
}
|
||||
|
||||
public function integration(Space $space)
|
||||
{
|
||||
return view('admin.space.integration', [
|
||||
'space' => $space
|
||||
]);
|
||||
}
|
||||
|
||||
public function configurationUpdate(Request $request, Space $space)
|
||||
{
|
||||
$space = $this->setConfiguration($request, $space);
|
||||
$space->save();
|
||||
|
||||
return redirect()->route('admin.spaces.configuration', $space);
|
||||
}
|
||||
|
||||
public function administration(Space $space)
|
||||
{
|
||||
return view('admin.space.administration', [
|
||||
'space' => $space
|
||||
]);
|
||||
}
|
||||
|
||||
public function administrationUpdate(Request $request, Space $space)
|
||||
{
|
||||
$request->validate([
|
||||
'name' => ['required', Rule::unique('spaces')->ignore($space->id)],
|
||||
'max_accounts' => 'required|integer|min:0',
|
||||
'expire_at' => 'nullable|date|after_or_equal:today'
|
||||
]);
|
||||
|
||||
if ($request->get('max_accounts') > 0) {
|
||||
$request->validate([
|
||||
'max_accounts' => 'integer|min:' . $space->accounts()->count()
|
||||
]);
|
||||
}
|
||||
|
||||
$space->name = $request->get('name');
|
||||
$space->super = getRequestBoolean($request, 'super');
|
||||
$space->max_accounts = $request->get('max_accounts');
|
||||
$space->expire_at = $request->get('expire_at');
|
||||
$space->custom_theme = getRequestBoolean($request, 'custom_theme');
|
||||
$space->web_panel = getRequestBoolean($request, 'web_panel');
|
||||
$space->carddav_user_credentials = getRequestBoolean($request, 'carddav_user_credentials');
|
||||
$space->save();
|
||||
|
||||
return redirect()->route('admin.spaces.show', $space);
|
||||
}
|
||||
|
||||
private function setConfiguration(Request $request, Space $space)
|
||||
{
|
||||
$request->validate([
|
||||
'newsletter_registration_address' => 'nullable|email',
|
||||
'custom_provisioning_entries' => ['nullable', new Ini(Space::FORBIDDEN_KEYS)]
|
||||
]);
|
||||
|
||||
$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');
|
||||
|
||||
if ($space->accounts()->count() == 0) {
|
||||
$space->account_realm = $request->get('account_realm');
|
||||
}
|
||||
|
||||
$space->custom_provisioning_entries = $request->get('custom_provisioning_entries');
|
||||
$space->custom_provisioning_overwrite_all = getRequestBoolean($request, 'custom_provisioning_overwrite_all');
|
||||
$space->provisioning_use_linphone_provisioning_header = getRequestBoolean($request, 'provisioning_use_linphone_provisioning_header');
|
||||
|
||||
$space->public_registration = getRequestBoolean($request, 'public_registration');
|
||||
$space->phone_registration = getRequestBoolean($request, 'phone_registration');
|
||||
$space->intercom_features = getRequestBoolean($request, 'intercom_features');
|
||||
|
||||
return $space;
|
||||
}
|
||||
|
||||
private function setAppConfiguration(Request $request, Space $space)
|
||||
{
|
||||
$request->validate([
|
||||
'max_account' => 'required|integer',
|
||||
]);
|
||||
|
||||
$space->disable_chat_feature = getRequestBoolean($request, 'disable_chat_feature', reversed: true);
|
||||
$space->disable_meetings_feature = getRequestBoolean($request, 'disable_meetings_feature', reversed: true);
|
||||
$space->disable_broadcast_feature = getRequestBoolean($request, 'disable_broadcast_feature', reversed: true);
|
||||
$space->hide_settings = getRequestBoolean($request, 'hide_settings', reversed: true);
|
||||
$space->max_account = $request->get('max_account', 0);
|
||||
$space->hide_account_settings = getRequestBoolean($request, 'hide_account_settings', reversed: true);
|
||||
$space->disable_call_recordings_feature = getRequestBoolean($request, 'disable_call_recordings_feature', reversed: true);
|
||||
$space->only_display_sip_uri_username = getRequestBoolean($request, 'only_display_sip_uri_username');
|
||||
$space->assistant_hide_create_account = getRequestBoolean($request, 'assistant_hide_create_account', reversed: true);
|
||||
$space->assistant_disable_qr_code = getRequestBoolean($request, 'assistant_disable_qr_code', reversed: true);
|
||||
$space->assistant_hide_third_party_account = getRequestBoolean($request, 'assistant_hide_third_party_account', reversed: true);
|
||||
|
||||
return $space;
|
||||
}
|
||||
|
||||
public function delete(Request $request, int $id)
|
||||
{
|
||||
$space = Space::findOrFail($id);
|
||||
return view('admin.space.delete', [
|
||||
'space' => $space
|
||||
]);
|
||||
}
|
||||
|
||||
public function destroy(Request $request, int $id)
|
||||
{
|
||||
$space = Space::findOrFail($id);
|
||||
|
||||
$request->validate([
|
||||
'domain' => [
|
||||
'required',
|
||||
Rule::in(['first-zone', $space->domain]),
|
||||
]
|
||||
]);
|
||||
|
||||
$space->delete();
|
||||
|
||||
return redirect()->route('admin.spaces.index');
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue