diff --git a/src/app/Handlers/Activesync.php b/src/app/Handlers/Activesync.php index 9864c934..0937ffae 100644 --- a/src/app/Handlers/Activesync.php +++ b/src/app/Handlers/Activesync.php @@ -1,43 +1,58 @@ entitleable_id); + } + } + + /** + * Handle entitlement deletion event. + */ + public static function entitlementDeleted(Entitlement $entitlement): void + { + if (\config('app.with_ldap')) { + \App\Jobs\User\UpdateJob::dispatch($entitlement->entitleable_id); + } + } + /** * SKU handler metadata. - * - * @param \App\Sku $sku The SKU object - * - * @return array */ - public static function metadata(\App\Sku $sku): array + public static function metadata(Sku $sku): array { $data = parent::metadata($sku); $data['required'] = ['Groupware']; return $data; } /** * The priority that specifies the order of SKUs in UI. * Higher number means higher on the list. - * - * @return int */ public static function priority(): int { return 70; } } diff --git a/src/app/Handlers/Auth2F.php b/src/app/Handlers/Auth2F.php index d2d067ae..8db1efde 100644 --- a/src/app/Handlers/Auth2F.php +++ b/src/app/Handlers/Auth2F.php @@ -1,58 +1,65 @@ entitleable_id); + } + } + /** * Handle entitlement deletion event. */ public static function entitlementDeleted(Entitlement $entitlement): void { // Remove all configured 2FA methods from Roundcube database if ($entitlement->entitleable && !$entitlement->entitleable->trashed()) { // TODO: This should be an async job $sf = new \App\Auth\SecondFactor($entitlement->entitleable); $sf->removeFactors(); + + if (\config('app.with_ldap')) { + \App\Jobs\User\UpdateJob::dispatch($entitlement->entitleable_id); + } } } /** * SKU handler metadata. - * - * @param \App\Sku $sku The SKU object - * - * @return array */ - public static function metadata(\App\Sku $sku): array + public static function metadata(Sku $sku): array { $data = parent::metadata($sku); $data['forbidden'] = ['Activesync']; return $data; } /** * The priority that specifies the order of SKUs in UI. * Higher number means higher on the list. - * - * @return int */ public static function priority(): int { return 60; } } diff --git a/src/app/Handlers/Base.php b/src/app/Handlers/Base.php index 5f17b358..aa47d8d7 100644 --- a/src/app/Handlers/Base.php +++ b/src/app/Handlers/Base.php @@ -1,119 +1,102 @@ active) { if (!$object->entitlements()->where('sku_id', $sku->id)->first()) { return false; } } return true; } /** * Metadata of this SKU handler. - * - * @param \App\Sku $sku The SKU object - * - * @return array */ - public static function metadata(\App\Sku $sku): array + public static function metadata(Sku $sku): array { return [ // entitleable type 'type' => \lcfirst(\class_basename(static::entitleableClass())), // handler 'handler' => str_replace("App\\Handlers\\", '', static::class), // readonly entitlement state cannot be changed 'readonly' => false, // is entitlement enabled by default? 'enabled' => false, // priority on the entitlements list 'prio' => static::priority(), ]; } /** * Prerequisites for the Entitlement to be applied to the object. - * - * @param \App\Entitlement $entitlement - * @param mixed $object - * - * @return bool */ - public static function preReq($entitlement, $object): bool + public static function preReq(Entitlement $entitlement, $object): bool { $type = static::entitleableClass(); if (empty($type) || empty($entitlement->entitleable_type)) { \Log::error("Entitleable class/type not specified"); return false; } if ($type !== $entitlement->entitleable_type) { \Log::error("Entitleable class mismatch"); return false; } if (!$entitlement->sku->active) { \Log::error("Sku not active"); return false; } return true; } /** * The priority that specifies the order of SKUs in UI. * Higher number means higher on the list. - * - * @return int */ public static function priority(): int { return 0; } } diff --git a/src/app/Handlers/Beta.php b/src/app/Handlers/Beta.php index f54d3302..8dd10bd8 100644 --- a/src/app/Handlers/Beta.php +++ b/src/app/Handlers/Beta.php @@ -1,48 +1,41 @@ entitleable_type) { \Log::error("Entitleable class mismatch"); return false; } return true; } /** * The priority that specifies the order of SKUs in UI. * Higher number means higher on the list. - * - * @return int */ public static function priority(): int { // Just above all other beta SKUs, please return 10; } } diff --git a/src/app/Handlers/Domain.php b/src/app/Handlers/Domain.php index 7bb76797..0da84d36 100644 --- a/src/app/Handlers/Domain.php +++ b/src/app/Handlers/Domain.php @@ -1,16 +1,14 @@ entitleable_id); + } + } + + /** + * Handle entitlement deletion event. + */ + public static function entitlementDeleted(Entitlement $entitlement): void + { + if (\config('app.with_ldap')) { + \App\Jobs\User\UpdateJob::dispatch($entitlement->entitleable_id); + } + } + /** * The priority that specifies the order of SKUs in UI. * Higher number means higher on the list. - * - * @return int */ public static function priority(): int { return 80; } } diff --git a/src/app/Handlers/Mailbox.php b/src/app/Handlers/Mailbox.php index b28a1d88..35c88af5 100644 --- a/src/app/Handlers/Mailbox.php +++ b/src/app/Handlers/Mailbox.php @@ -1,45 +1,42 @@ entitleable_id); } /** * Handle entitlement deletion event. */ public static function entitlementDeleted(Entitlement $entitlement): void { // Update the user IMAP mailbox quota \App\Jobs\User\UpdateJob::dispatch($entitlement->entitleable_id); } /** * SKU handler metadata. - * - * @param \App\Sku $sku The SKU object - * - * @return array */ - public static function metadata(\App\Sku $sku): array + public static function metadata(Sku $sku): array { $data = parent::metadata($sku); $data['readonly'] = true; // only the checkbox will be disabled, not range $data['enabled'] = true; $data['range'] = [ 'min' => $sku->units_free, 'max' => self::MAX_ITEMS, 'unit' => self::ITEM_UNIT, ]; return $data; } /** * The priority that specifies the order of SKUs in UI. * Higher number means higher on the list. - * - * @return int */ public static function priority(): int { return 90; } } diff --git a/src/tests/Feature/EntitlementTest.php b/src/tests/Feature/EntitlementTest.php index 4c04a455..ab195e3d 100644 --- a/src/tests/Feature/EntitlementTest.php +++ b/src/tests/Feature/EntitlementTest.php @@ -1,206 +1,245 @@ deleteTestUser('entitlement-test@kolabnow.com'); $this->deleteTestUser('entitled-user@custom-domain.com'); $this->deleteTestGroup('test-group@custom-domain.com'); $this->deleteTestDomain('custom-domain.com'); } /** * {@inheritDoc} */ public function tearDown(): void { $this->deleteTestUser('entitlement-test@kolabnow.com'); $this->deleteTestUser('entitled-user@custom-domain.com'); $this->deleteTestGroup('test-group@custom-domain.com'); $this->deleteTestDomain('custom-domain.com'); parent::tearDown(); } /** * Tests for EntitlementObserver - * - * @group skipci */ public function testEntitlementObserver(): void { $skuStorage = Sku::withEnvTenantContext()->where('title', 'storage')->first(); $skuMailbox = Sku::withEnvTenantContext()->where('title', 'mailbox')->first(); + $skuGroupware = Sku::withEnvTenantContext()->where('title', 'groupware')->first(); + $skuActivesync = Sku::withEnvTenantContext()->where('title', 'activesync')->first(); + $sku2FA = Sku::withEnvTenantContext()->where('title', '2fa')->first(); + $skuBeta = Sku::withEnvTenantContext()->where('title', 'beta')->first(); $user = $this->getTestUser('entitlement-test@kolabnow.com'); $wallet = $user->wallets->first(); - // Test dispatching update jobs for the user, on quota update + $assertPushedUserUpdateJob = function ($ifLdap = false) use ($user) { + if ($ifLdap && !\config('app.with_ldap')) { + Queue::assertPushed(\App\Jobs\User\UpdateJob::class, 0); + return; + } + + Queue::assertPushed(\App\Jobs\User\UpdateJob::class, 1); + Queue::assertPushed( + \App\Jobs\User\UpdateJob::class, + function ($job) use ($user) { + return $user->id === TestCase::getObjectProperty($job, 'userId'); + } + ); + }; + + // Note: This also is testing SKU handlers + + // 'mailbox' SKU should not dispatch update jobs $this->fakeQueueReset(); $user->assignSku($skuMailbox, 1, $wallet); Queue::assertPushed(\App\Jobs\User\UpdateJob::class, 0); + $this->fakeQueueReset(); + $user->entitlements()->where('sku_id', $skuMailbox->id)->first()->delete(); + Queue::assertPushed(\App\Jobs\User\UpdateJob::class, 0); + // Test dispatching update jobs for the user - 'storage' SKU $this->fakeQueueReset(); $user->assignSku($skuStorage, 1, $wallet); - Queue::assertPushed(\App\Jobs\User\UpdateJob::class, 1); - Queue::assertPushed( - \App\Jobs\User\UpdateJob::class, - function ($job) use ($user) { - return $user->id === TestCase::getObjectProperty($job, 'userId'); - } - ); + $assertPushedUserUpdateJob(); + $this->fakeQueueReset(); + $user->entitlements()->where('sku_id', $skuStorage->id)->first()->delete(); + $assertPushedUserUpdateJob(); + // Test dispatching update jobs for the user - 'groupware' SKU $this->fakeQueueReset(); - $user->entitlements()->where('sku_id', $skuMailbox->id)->first()->delete(); - //FIXME this sometimes gives 1? - Queue::assertPushed(\App\Jobs\User\UpdateJob::class, 0); + $user->assignSku($skuGroupware, 1, $wallet); + $assertPushedUserUpdateJob(true); + $this->fakeQueueReset(); + $user->entitlements()->where('sku_id', $skuGroupware->id)->first()->delete(); + $assertPushedUserUpdateJob(true); + // Test dispatching update jobs for the user - 'activesync' SKU $this->fakeQueueReset(); - $user->entitlements()->where('sku_id', $skuStorage->id)->first()->delete(); - //FIXME this sometimes gives 2? - Queue::assertPushed(\App\Jobs\User\UpdateJob::class, 1); - Queue::assertPushed( - \App\Jobs\User\UpdateJob::class, - function ($job) use ($user) { - return $user->id === TestCase::getObjectProperty($job, 'userId'); - } - ); + $user->assignSku($skuActivesync, 1, $wallet); + $assertPushedUserUpdateJob(true); + $this->fakeQueueReset(); + $user->entitlements()->where('sku_id', $skuActivesync->id)->first()->delete(); + $assertPushedUserUpdateJob(true); + + // Test dispatching update jobs for the user - '2fa' SKU + $this->fakeQueueReset(); + $user->assignSku($sku2FA, 1, $wallet); + $assertPushedUserUpdateJob(true); + $this->fakeQueueReset(); + $user->entitlements()->where('sku_id', $sku2FA->id)->first()->delete(); + $assertPushedUserUpdateJob(true); + + // Beta SKU should not trigger a user update job + $this->fakeQueueReset(); + $user->assignSku($skuBeta, 1, $wallet); + Queue::assertPushed(\App\Jobs\User\UpdateJob::class, 0); + $this->fakeQueueReset(); + $user->entitlements()->where('sku_id', $skuBeta->id)->first()->delete(); + Queue::assertPushed(\App\Jobs\User\UpdateJob::class, 0); - // TODO: Test all events in the observer in more detail + // TODO: Assert 'creating' checks + // TODO: Assert transaction records being created + // TODO: Assert timestamps not updated on delete + $this->markTestIncomplete(); } /** * Tests for entitlements * @todo This really should be in User or Wallet tests file */ public function testEntitlements(): void { $packageDomain = Package::withEnvTenantContext()->where('title', 'domain-hosting')->first(); $packageKolab = Package::withEnvTenantContext()->where('title', 'kolab')->first(); $skuDomain = Sku::withEnvTenantContext()->where('title', 'domain-hosting')->first(); $skuMailbox = Sku::withEnvTenantContext()->where('title', 'mailbox')->first(); $owner = $this->getTestUser('entitlement-test@kolabnow.com'); $user = $this->getTestUser('entitled-user@custom-domain.com'); $domain = $this->getTestDomain( 'custom-domain.com', [ 'status' => Domain::STATUS_NEW, 'type' => Domain::TYPE_EXTERNAL, ] ); $domain->assignPackage($packageDomain, $owner); $owner->assignPackage($packageKolab); $owner->assignPackage($packageKolab, $user); $wallet = $owner->wallets->first(); $this->assertCount(7, $owner->entitlements()->get()); $this->assertCount(1, $skuDomain->entitlements()->where('wallet_id', $wallet->id)->get()); $this->assertCount(2, $skuMailbox->entitlements()->where('wallet_id', $wallet->id)->get()); $this->assertCount(15, $wallet->entitlements); $this->backdateEntitlements( $owner->entitlements, Carbon::now()->subMonthsWithoutOverflow(1) ); $wallet->chargeEntitlements(); $this->assertTrue($wallet->fresh()->balance < 0); } /** * Test EntitleableTrait::toString() */ public function testEntitleableTitle(): void { $packageDomain = Package::withEnvTenantContext()->where('title', 'domain-hosting')->first(); $packageKolab = Package::withEnvTenantContext()->where('title', 'kolab')->first(); $user = $this->getTestUser('entitled-user@custom-domain.com'); $group = $this->getTestGroup('test-group@custom-domain.com'); $domain = $this->getTestDomain( 'custom-domain.com', [ 'status' => Domain::STATUS_NEW, 'type' => Domain::TYPE_EXTERNAL, ] ); $wallet = $user->wallets->first(); $domain->assignPackage($packageDomain, $user); $user->assignPackage($packageKolab); $group->assignToWallet($wallet); $sku_mailbox = \App\Sku::withEnvTenantContext()->where('title', 'mailbox')->first(); $sku_group = \App\Sku::withEnvTenantContext()->where('title', 'group')->first(); $sku_domain = \App\Sku::withEnvTenantContext()->where('title', 'domain-hosting')->first(); $entitlement = Entitlement::where('wallet_id', $wallet->id) ->where('sku_id', $sku_mailbox->id)->first(); $this->assertSame($user->email, $entitlement->entitleable->toString()); $entitlement = Entitlement::where('wallet_id', $wallet->id) ->where('sku_id', $sku_group->id)->first(); $this->assertSame($group->email, $entitlement->entitleable->toString()); $entitlement = Entitlement::where('wallet_id', $wallet->id) ->where('sku_id', $sku_domain->id)->first(); $this->assertSame($domain->namespace, $entitlement->entitleable->toString()); // Make sure it still works if the entitleable is deleted $domain->delete(); $entitlement->refresh(); $this->assertSame($domain->namespace, $entitlement->entitleable->toString()); $this->assertNotNull($entitlement->entitleable); } /** * Test for EntitleableTrait::removeSku() */ public function testEntitleableRemoveSku(): void { $user = $this->getTestUser('entitlement-test@kolabnow.com'); $storage = \App\Sku::withEnvTenantContext()->where('title', 'storage')->first(); $user->assignSku($storage, 6); $this->assertCount(6, $user->fresh()->entitlements); $backdate = Carbon::now()->subWeeks(7); $this->backdateEntitlements($user->entitlements, $backdate); $user->removeSku($storage, 2); // Expect free units to be not deleted $this->assertCount(5, $user->fresh()->entitlements); // Here we make sure that updated_at does not change on delete $this->assertSame(6, $user->entitlements()->withTrashed()->whereDate('updated_at', $backdate)->count()); } }