diff --git a/bin/quickstart.sh b/bin/quickstart.sh index 67885bf2..d2621dbf 100755 --- a/bin/quickstart.sh +++ b/bin/quickstart.sh @@ -1,82 +1,82 @@ #!/bin/bash set -e function die() { echo "$1" exit 1 } rpm -qv composer >/dev/null 2>&1 || \ test ! -z "$(which composer 2>/dev/null)" || \ die "Is composer installed?" rpm -qv docker-compose >/dev/null 2>&1 || \ test ! -z "$(which docker-compose 2>/dev/null)" || \ die "Is docker-compose installed?" rpm -qv npm >/dev/null 2>&1 || \ test ! -z "$(which npm 2>/dev/null)" || \ die "Is npm installed?" rpm -qv php >/dev/null 2>&1 || \ test ! -z "$(which php 2>/dev/null)" || \ die "Is php installed?" rpm -qv php-ldap >/dev/null 2>&1 || \ test ! -z "$(php --ini | grep ldap)" || \ die "Is php-ldap installed?" rpm -qv php-mysqlnd >/dev/null 2>&1 || \ test ! -z "$(php --ini | grep mysql)" || \ die "Is php-mysqlnd installed?" base_dir=$(dirname $(dirname $0)) bin/regen-certs docker pull kolab/centos7:latest docker-compose down docker-compose build docker-compose up -d kolab mariadb redis pushd ${base_dir}/src/ cp .env.example .env if [ -f ".env.local" ]; then # Ensure there's a line ending echo "" >> .env cat .env.local >> .env fi rm -rf vendor/ composer.lock composer install npm install find bootstrap/cache/ -type f ! -name ".gitignore" -delete ./artisan key:generate ./artisan jwt:secret -f ./artisan clear-compiled ./artisan cache:clear if [ ! -z "$(rpm -qv chromium 2>/dev/null)" ]; then chver=$(rpmquery --queryformat="%{VERSION}" chromium | awk -F'.' '{print $1}') ./artisan dusk:chrome-driver ${chver} fi if [ ! -f 'resources/countries.php' ]; then ./artisan data:countries fi npm run dev popd -docker-compose up -d worker +docker-compose up -d worker nginx pushd ${base_dir}/src/ rm -rf database/database.sqlite php -dmemory_limit=512M ./artisan migrate:refresh --seed ./artisan serve popd diff --git a/bin/regen-certs b/bin/regen-certs index 30e76274..c02869a9 100755 --- a/bin/regen-certs +++ b/bin/regen-certs @@ -1,65 +1,65 @@ #!/bin/bash base_dir=$(dirname $(dirname $0)) base_dir="${base_dir}/docker/certs/" if [ ! -d "${base_dir}" ]; then mkdir -p ${base_dir} fi if [ ! -f "${base_dir}/ca.key" ]; then openssl genrsa -out ${base_dir}/ca.key 4096 openssl req \ -new \ -x509 \ -nodes \ -days 3650 \ -key ${base_dir}/ca.key \ -out ${base_dir}/ca.cert \ -subj '/O=Example CA/' fi if [ -f /etc/pki/tls/openssl.cnf ]; then openssl_cnf="/etc/pki/tls/openssl.cnf" elif [ -f /etc/ssl/openssl.cnf ]; then openssl_cnf="/etc/ssl/openssl.cnf" else echo "No openssl.cnf" exit 1 fi -for name in kolab.mgmt.com kolab.hosted.com; do +for name in kolab.mgmt.com kolab.hosted.com imap.hosted.com; do openssl genrsa -out ${base_dir}/${name}.key 4096 openssl req \ -new \ -key ${base_dir}/${name}.key \ -out ${base_dir}/${name}.csr \ -subj "/O=Example CA/CN=${name}/" \ -reqexts SAN \ -config <(cat ${openssl_cnf} \ <(printf "[SAN]\nsubjectAltName=DNS:${name}")) openssl x509 \ -req \ -in ${base_dir}/${name}.csr \ -CA ${base_dir}/ca.cert \ -CAkey ${base_dir}/ca.key \ -CAcreateserial \ -out ${base_dir}/${name}.cert \ -days 28 \ -extfile <(cat ${openssl_cnf} \ <(printf "[SAN]\nsubjectAltName=DNS:${name}")) \ -extensions SAN # 'cause java ... openssl pkcs8 \ -topk8 \ -inform pem \ -in ${base_dir}/${name}.key \ -outform pem \ -nocrypt \ -out ${base_dir}/${name}_p8.key done diff --git a/docker-compose.yml b/docker-compose.yml index 76e13a90..310ece71 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,73 +1,90 @@ version: '2.1' services: kolab: build: context: ./docker/kolab/ container_name: kolab depends_on: mariadb: condition: service_healthy extra_hosts: - "kolab.mgmt.com:127.0.0.1" healthcheck: interval: 10s test: test -f /tmp/kolab-init.done timeout: 5s retries: 30 hostname: kolab.mgmt.com image: kolab network_mode: host tmpfs: - /run - /tmp - /var/run - /var/tmp tty: true volumes: - ./docker/certs/ca.cert:/etc/pki/tls/certs/ca.cert:ro - ./docker/certs/ca.cert:/etc/pki/ca-trust/source/anchors/ca.cert:ro - ./docker/certs/kolab.hosted.com.cert:/etc/pki/tls/certs/kolab.hosted.com.cert - ./docker/certs/kolab.hosted.com.key:/etc/pki/tls/certs/kolab.hosted.com.key - ./docker/certs/kolab.mgmt.com.cert:/etc/pki/tls/certs/kolab.mgmt.com.cert - ./docker/certs/kolab.mgmt.com.key:/etc/pki/tls/certs/kolab.mgmt.com.key - ./docker/kolab/utils:/root/utils:ro - /sys/fs/cgroup:/sys/fs/cgroup:ro mariadb: container_name: kolab-mariadb environment: MYSQL_ROOT_PASSWORD: Welcome2KolabSystems healthcheck: interval: 10s test: test -e /var/run/mysqld/mysqld.sock timeout: 5s retries: 30 image: mariadb network_mode: host + nginx: + build: + context: ./docker/nginx/ + container_name: kolab-nginx + hostname: nginx.hosted.com + image: kolab-nginx + network_mode: host + tmpfs: + - /run + - /tmp + - /var/run + - /var/tmp + tty: true + volumes: + - ./docker/certs/imap.hosted.com.cert:/etc/pki/tls/certs/imap.hosted.com.cert + - ./docker/certs/imap.hosted.com.key:/etc/pki/tls/private/imap.hosted.com.key + - /sys/fs/cgroup:/sys/fs/cgroup:ro redis: build: context: ./docker/redis/ container_name: kolab-redis hostname: redis image: redis network_mode: host volumes: - ./docker/redis/redis.conf:/usr/local/etc/redis/redis.conf:ro worker: build: context: ./docker/worker/ container_name: kolab-worker depends_on: kolab: condition: service_healthy hostname: worker image: kolab-worker network_mode: host tmpfs: - /run - /tmp - /var/run - /var/tmp tty: true volumes: - ./src:/home/worker/src.orig:ro - /sys/fs/cgroup:/sys/fs/cgroup:ro diff --git a/docker/kolab/Dockerfile b/docker/kolab/Dockerfile index 9b0a6aa7..7dc12fe9 100644 --- a/docker/kolab/Dockerfile +++ b/docker/kolab/Dockerfile @@ -1,25 +1,25 @@ FROM kolab/centos7:latest RUN yum -y install rsyslog && \ yum --enablerepo=kolab-16-updates-testing -y update pykolab && \ yum clean all COPY kolab-init.service /etc/systemd/system/kolab-init.service COPY kolab-vlv.service /etc/systemd/system/kolab-vlv.service RUN rm -rf /etc/systemd/system/multi-user.target.wants/{avahi-daemon,sshd}.* && \ ln -s /etc/systemd/system/kolab-init.service \ /etc/systemd/system/multi-user.target.wants/kolab-init.service && \ ln -s /etc/systemd/system/kolab-vlv.service \ /etc/systemd/system/multi-user.target.wants/kolab-vlv.service RUN sed -i -r -e 's/^SELINUX=.*$/SELINUX=permissive/g' /etc/selinux/config 2>/dev/null || : COPY kolab-init.sh /usr/local/sbin/ RUN chmod 750 /usr/local/sbin/kolab-init.sh COPY kolab-vlv.sh /usr/local/sbin/ RUN chmod 750 /usr/local/sbin/kolab-vlv.sh CMD ["/lib/systemd/systemd"] -EXPOSE 21/tcp 22/tcp 25/tcp 53/tcp 53/udp 80/tcp 110/tcp 143/tcp 389/tcp 443/tcp 465/tcp 587/tcp 993/tcp 995/tcp 5353/udp 8880/tcp 8443/tcp 8447/tcp +EXPOSE 21/tcp 22/tcp 25/tcp 53/tcp 53/udp 80/tcp 110/tcp 143/tcp 389/tcp 443/tcp 465/tcp 587/tcp 993/tcp 995/tcp 5353/udp 8880/tcp 8443/tcp 8447/tcp 9143/tcp 9993/tcp 10143/tcp diff --git a/docker/kolab/utils/09-enable-debugging.sh b/docker/kolab/utils/09-enable-debugging.sh index 81b612e1..7cfcd9aa 100755 --- a/docker/kolab/utils/09-enable-debugging.sh +++ b/docker/kolab/utils/09-enable-debugging.sh @@ -1,8 +1,22 @@ #!/bin/bash echo "chatty: 1" >> /etc/imapd.conf echo "debug: 1" >> /etc/imapd.conf +sed -i -r \ + -e '/allowplaintext/ a\ +imaplain_allowplaintext: yes' \ + /etc/imapd.conf + +cp /etc/cyrus.conf /etc/cyrus.conf.orig + +sed -i \ + -e '/SERVICES/ a\ + imaplain cmd="imapd" listen=127.0.0.1:10143 prefork=1' \ + -e '/SERVICES/ a\ + imap cmd="imapd" listen=127.0.0.1:9143 prefork=1' \ + /etc/cyrus.conf + systemctl restart cyrus-imapd sed -i -r -e "s/_debug'] = (.*);/_debug'] = true;/g" /etc/roundcubemail/config.inc.php diff --git a/docker/nginx/Dockerfile b/docker/nginx/Dockerfile new file mode 100644 index 00000000..6449a7df --- /dev/null +++ b/docker/nginx/Dockerfile @@ -0,0 +1,20 @@ +FROM fedora:31 + +RUN dnf -y install \ + nginx \ + nginx-mod-mail \ + rsyslog && \ + dnf clean all + +RUN sed -i -r -e 's/^SELINUX=.*$/SELINUX=permissive/g' /etc/selinux/config 2>/dev/null || : + +COPY nginx.conf /etc/nginx/nginx.conf + +#RUN /usr/sbin/nginx -t + +RUN ln -s /usr/lib/systemd/system/nginx.service \ + /etc/systemd/system/multi-user.target.wants/nginx.service + +CMD ["/lib/systemd/systemd"] + +EXPOSE 110/tcp 143/tcp 993/tcp 995/tcp diff --git a/docker/nginx/nginx.conf b/docker/nginx/nginx.conf new file mode 100644 index 00000000..8ad776f2 --- /dev/null +++ b/docker/nginx/nginx.conf @@ -0,0 +1,45 @@ +user nginx; +worker_processes auto; +error_log /var/log/nginx/error.log debug; +pid /run/nginx.pid; + +# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic. +include /usr/share/nginx/modules/*.conf; + +events { + worker_connections 1024; +} + +mail { + server_name imap.hosted.com; + auth_http 127.0.0.1:8000/api/webhooks/nginx; + + proxy_pass_error_message on; + + server { + listen 1143; + protocol imap; + + proxy on; + starttls on; + + ssl_certificate /etc/pki/tls/certs/imap.hosted.com.cert; + ssl_certificate_key /etc/pki/tls/private/imap.hosted.com.key; + + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_ciphers HIGH:!aNULL:!MD5; + } + + server { + listen 1993 ssl; + protocol imap; + + proxy on; + + ssl_certificate /etc/pki/tls/certs/imap.hosted.com.cert; + ssl_certificate_key /etc/pki/tls/private/imap.hosted.com.key; + + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_ciphers HIGH:!aNULL:!MD5; + } +} diff --git a/src/.gitignore b/src/.gitignore index 266ea4c4..18fe9c80 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -1,20 +1,21 @@ database/database.sqlite node_modules/ package-lock.json public/css/app.css public/hot public/js/app.js public/storage/ storage/*.key storage/export/ vendor .env .env.backup +.env.local .env.testing .phpunit.result.cache Homestead.json Homestead.yaml npm-debug.log yarn-error.log composer.lock resources/countries.php diff --git a/src/app/Console/Commands/UserStatus.php b/src/app/Console/Commands/UserStatus.php new file mode 100644 index 00000000..10eff915 --- /dev/null +++ b/src/app/Console/Commands/UserStatus.php @@ -0,0 +1,51 @@ +argument('user'))->first(); + + if (!$user) { + return 1; + } + + $this->info("Found user: {$user->id}"); + + $this->info($user->status); + } +} diff --git a/src/app/Console/Commands/UserSuspend.php b/src/app/Console/Commands/UserSuspend.php new file mode 100644 index 00000000..d0f90ede --- /dev/null +++ b/src/app/Console/Commands/UserSuspend.php @@ -0,0 +1,51 @@ +argument('user'))->first(); + + if (!$user) { + return 1; + } + + $this->info("Found user: {$user->id}"); + + $user->suspend(); + } +} diff --git a/src/app/Console/Commands/UserUnsuspend.php b/src/app/Console/Commands/UserUnsuspend.php new file mode 100644 index 00000000..8497de6a --- /dev/null +++ b/src/app/Console/Commands/UserUnsuspend.php @@ -0,0 +1,51 @@ +argument('user'))->first(); + + if (!$user) { + return 1; + } + + $this->info("Found user {$user->id}"); + + $user->unsuspend(); + } +} diff --git a/src/app/Http/Controllers/API/NGINXController.php b/src/app/Http/Controllers/API/NGINXController.php new file mode 100644 index 00000000..73caf4f0 --- /dev/null +++ b/src/app/Http/Controllers/API/NGINXController.php @@ -0,0 +1,53 @@ +headers); + + $response = response("")->withHeaders( + [ + "Auth-Status" => 'OK', + "Auth-Server" => '127.0.0.1', + "Auth-Port" => '10143', + "Auth-Pass" => $request->headers->get('Auth-Pass') + ] + ); + + \Log::debug($response->headers); + + return $response; + } +} diff --git a/src/routes/api.php b/src/routes/api.php index ab75bd80..9571281a 100644 --- a/src/routes/api.php +++ b/src/routes/api.php @@ -1,57 +1,58 @@ 'api', 'prefix' => 'auth' ], function ($router) { Route::get('info', 'API\UsersController@info'); Route::post('login', 'API\UsersController@login'); Route::post('logout', 'API\UsersController@logout'); Route::post('refresh', 'API\UsersController@refresh'); Route::post('password-reset/init', 'API\PasswordResetController@init'); Route::post('password-reset/verify', 'API\PasswordResetController@verify'); Route::post('password-reset', 'API\PasswordResetController@reset'); Route::get('signup/plans', 'API\SignupController@plans'); Route::post('signup/init', 'API\SignupController@init'); Route::post('signup/verify', 'API\SignupController@verify'); Route::post('signup', 'API\SignupController@signup'); } ); Route::group( [ 'middleware' => 'auth:api', 'prefix' => 'v4' ], function () { Route::apiResource('domains', API\DomainsController::class); Route::get('domains/{id}/confirm', 'API\DomainsController@confirm'); Route::apiResource('entitlements', API\EntitlementsController::class); Route::apiResource('packages', API\PackagesController::class); Route::apiResource('skus', API\SkusController::class); Route::apiResource('users', API\UsersController::class); Route::apiResource('wallets', API\WalletsController::class); Route::post('payments', 'API\PaymentsController@store'); } ); +Route::get('webhooks/nginx', 'API\NGINXController@authenticate'); Route::post('webhooks/payment/mollie', 'API\PaymentsController@webhook'); diff --git a/src/tests/Feature/UserTest.php b/src/tests/Feature/UserTest.php index a80c8961..984f1cb9 100644 --- a/src/tests/Feature/UserTest.php +++ b/src/tests/Feature/UserTest.php @@ -1,371 +1,377 @@ deleteTestUser('user-create-test@' . \config('app.domain')); - $this->deleteTestUser('userdeletejob@kolabnow.com'); + $this->deleteTestUser('test@' . \config('app.domain')); + $this->deleteTestUser('userdeletejob@' . \config('app.domain')); $this->deleteTestUser('UserAccountA@UserAccount.com'); $this->deleteTestUser('UserAccountB@UserAccount.com'); $this->deleteTestUser('UserAccountC@UserAccount.com'); $this->deleteTestDomain('UserAccount.com'); } public function tearDown(): void { - $this->deleteTestUser('user-create-test@' . \config('app.domain')); - $this->deleteTestUser('userdeletejob@kolabnow.com'); + $this->deleteTestUser('test@' . \config('app.domain')); + $this->deleteTestUser('userdeletejob@' . \config('app.domain')); $this->deleteTestUser('UserAccountA@UserAccount.com'); $this->deleteTestUser('UserAccountB@UserAccount.com'); $this->deleteTestUser('UserAccountC@UserAccount.com'); $this->deleteTestDomain('UserAccount.com'); parent::tearDown(); } /** * Tests for User::assignPackage() */ public function testAssignPackage(): void { $this->markTestIncomplete(); + + $package = \App\Package::where('title', 'kolab')->first(); + + $user = $this->getTestUser('test@' . \config('app.domain')); + + $user->assignPackage($package); } /** * Tests for User::assignPlan() */ public function testAssignPlan(): void { $this->markTestIncomplete(); } /** * Tests for User::assignSku() */ public function testAssignSku(): void { $this->markTestIncomplete(); } /** * Verify user creation process */ public function testUserCreateJob(): void { // Fake the queue, assert that no jobs were pushed... Queue::fake(); Queue::assertNothingPushed(); $user = User::create([ - 'email' => 'user-create-test@' . \config('app.domain') + 'email' => 'test@' . \config('app.domain') ]); Queue::assertPushed(\App\Jobs\UserCreate::class, 1); Queue::assertPushed(\App\Jobs\UserCreate::class, function ($job) use ($user) { $job_user = TestCase::getObjectProperty($job, 'user'); return $job_user->id === $user->id && $job_user->email === $user->email; }); Queue::assertPushedWithChain(\App\Jobs\UserCreate::class, [ \App\Jobs\UserVerify::class, ]); /* FIXME: Looks like we can't really do detailed assertions on chained jobs Another thing to consider is if we maybe should run these jobs independently (not chained) and make sure there's no race-condition in status update Queue::assertPushed(\App\Jobs\UserVerify::class, 1); Queue::assertPushed(\App\Jobs\UserVerify::class, function ($job) use ($user) { $job_user = TestCase::getObjectProperty($job, 'user'); return $job_user->id === $user->id && $job_user->email === $user->email; }); */ } /** * Verify a wallet assigned a controller is among the accounts of the assignee. */ public function testListUserAccounts(): void { $userA = $this->getTestUser('UserAccountA@UserAccount.com'); $userB = $this->getTestUser('UserAccountB@UserAccount.com'); $this->assertTrue($userA->wallets()->count() == 1); $userA->wallets()->each( function ($wallet) use ($userB) { $wallet->addController($userB); } ); $this->assertTrue($userB->accounts()->get()[0]->id === $userA->wallets()->get()[0]->id); } public function testAccounts(): void { $this->markTestIncomplete(); } public function testCanDelete(): void { $this->markTestIncomplete(); } public function testCanRead(): void { $this->markTestIncomplete(); } public function testCanUpdate(): void { $this->markTestIncomplete(); } /** * Tests for User::domains() */ public function testDomains(): void { $user = $this->getTestUser('john@kolab.org'); $domains = []; foreach ($user->domains() as $domain) { $domains[] = $domain->namespace; } $this->assertContains(\config('app.domain'), $domains); $this->assertContains('kolab.org', $domains); // Jack is not the wallet controller, so for him the list should not // include John's domains, kolab.org specifically $user = $this->getTestUser('jack@kolab.org'); $domains = []; foreach ($user->domains() as $domain) { $domains[] = $domain->namespace; } $this->assertContains(\config('app.domain'), $domains); $this->assertNotContains('kolab.org', $domains); } public function testUserQuota(): void { // TODO: This test does not test much, probably could be removed // or moved to somewhere else, or extended with // other entitlements() related cases. $user = $this->getTestUser('john@kolab.org'); $storage_sku = \App\Sku::where('title', 'storage')->first(); $count = 0; foreach ($user->entitlements()->get() as $entitlement) { if ($entitlement->sku_id == $storage_sku->id) { $count += 1; } } $this->assertTrue($count == 2); } /** * Test user deletion */ public function testDelete(): void { Queue::fake(); - $user = $this->getTestUser('userdeletejob@kolabnow.com'); + $user = $this->getTestUser('userdeletejob@' . \config('app.domain')); $package = \App\Package::where('title', 'kolab')->first(); $user->assignPackage($package); $id = $user->id; $this->assertCount(4, $user->entitlements()->get()); $user->delete(); $this->assertCount(0, $user->entitlements()->get()); $this->assertTrue($user->fresh()->trashed()); $this->assertFalse($user->fresh()->isDeleted()); // Delete the user for real $job = new \App\Jobs\UserDelete($id); $job->handle(); $this->assertTrue(User::withTrashed()->where('id', $id)->first()->isDeleted()); $user->forceDelete(); $this->assertCount(0, User::withTrashed()->where('id', $id)->get()); // Test an account with users $userA = $this->getTestUser('UserAccountA@UserAccount.com'); $userB = $this->getTestUser('UserAccountB@UserAccount.com'); $userC = $this->getTestUser('UserAccountC@UserAccount.com'); $package_kolab = \App\Package::where('title', 'kolab')->first(); $package_domain = \App\Package::where('title', 'domain-hosting')->first(); $domain = $this->getTestDomain('UserAccount.com', [ 'status' => Domain::STATUS_NEW, 'type' => Domain::TYPE_HOSTED, ]); $userA->assignPackage($package_kolab); $domain->assignPackage($package_domain, $userA); $userA->assignPackage($package_kolab, $userB); $userA->assignPackage($package_kolab, $userC); $entitlementsA = \App\Entitlement::where('entitleable_id', $userA->id); $entitlementsB = \App\Entitlement::where('entitleable_id', $userB->id); $entitlementsC = \App\Entitlement::where('entitleable_id', $userC->id); $entitlementsDomain = \App\Entitlement::where('entitleable_id', $domain->id); $this->assertSame(4, $entitlementsA->count()); $this->assertSame(4, $entitlementsB->count()); $this->assertSame(4, $entitlementsC->count()); $this->assertSame(1, $entitlementsDomain->count()); // Delete non-controller user $userC->delete(); $this->assertTrue($userC->fresh()->trashed()); $this->assertFalse($userC->fresh()->isDeleted()); $this->assertSame(0, $entitlementsC->count()); // Delete the controller (and expect "sub"-users to be deleted too) $userA->delete(); $this->assertSame(0, $entitlementsA->count()); $this->assertSame(0, $entitlementsB->count()); $this->assertSame(0, $entitlementsDomain->count()); $this->assertTrue($userA->fresh()->trashed()); $this->assertTrue($userB->fresh()->trashed()); $this->assertTrue($domain->fresh()->trashed()); $this->assertFalse($userA->isDeleted()); $this->assertFalse($userB->isDeleted()); $this->assertFalse($domain->isDeleted()); } /** * Tests for User::findByEmail() */ public function testFindByEmail(): void { $user = $this->getTestUser('john@kolab.org'); $result = User::findByEmail('john'); $this->assertNull($result); $result = User::findByEmail('non-existing@email.com'); $this->assertNull($result); $result = User::findByEmail('john@kolab.org'); $this->assertInstanceOf(User::class, $result); $this->assertSame($user->id, $result->id); // Use an alias $result = User::findByEmail('john.doe@kolab.org'); $this->assertInstanceOf(User::class, $result); $this->assertSame($user->id, $result->id); // TODO: searching by external email (setting) $this->markTestIncomplete(); } /** * Tests for UserAliasesTrait::setAliases() */ public function testSetAliases(): void { Queue::fake(); $user = $this->getTestUser('UserAccountA@UserAccount.com'); $this->assertCount(0, $user->aliases->all()); // Add an alias $user->setAliases(['UserAlias1@UserAccount.com']); $aliases = $user->aliases()->get(); $this->assertCount(1, $aliases); $this->assertSame('useralias1@useraccount.com', $aliases[0]['alias']); // Add another alias $user->setAliases(['UserAlias1@UserAccount.com', 'UserAlias2@UserAccount.com']); $aliases = $user->aliases()->orderBy('alias')->get(); $this->assertCount(2, $aliases); $this->assertSame('useralias1@useraccount.com', $aliases[0]->alias); $this->assertSame('useralias2@useraccount.com', $aliases[1]->alias); // Remove an alias $user->setAliases(['UserAlias1@UserAccount.com']); $aliases = $user->aliases()->get(); $this->assertCount(1, $aliases); $this->assertSame('useralias1@useraccount.com', $aliases[0]['alias']); // Remove all aliases $user->setAliases([]); $this->assertCount(0, $user->aliases()->get()); // TODO: Test that the changes are propagated to ldap } /** * Tests for UserSettingsTrait::setSettings() */ public function testSetSettings(): void { $this->markTestIncomplete(); } /** * Tests for User::users() */ public function testUsers(): void { $john = $this->getTestUser('john@kolab.org'); $jack = $this->getTestUser('jack@kolab.org'); $ned = $this->getTestUser('ned@kolab.org'); $wallet = $john->wallets()->first(); $users = $john->users()->orderBy('email')->get(); $this->assertCount(3, $users); $this->assertEquals($jack->id, $users[0]->id); $this->assertEquals($john->id, $users[1]->id); $this->assertEquals($ned->id, $users[2]->id); $this->assertSame($wallet->id, $users[0]->wallet_id); $this->assertSame($wallet->id, $users[1]->wallet_id); $this->assertSame($wallet->id, $users[2]->wallet_id); $users = $jack->users()->orderBy('email')->get(); $this->assertCount(0, $users); $users = $ned->users()->orderBy('email')->get(); $this->assertCount(3, $users); } public function testWallets(): void { $this->markTestIncomplete(); } }