diff --git a/bin/quickstart.sh b/bin/quickstart.sh --- a/bin/quickstart.sh +++ b/bin/quickstart.sh @@ -43,10 +43,10 @@ docker-compose up -d kolab mariadb redis pushd ${base_dir}/src/ +cp .env.example .env composer install npm install find bootstrap/cache/ -type f ! -name ".gitignore" -delete -cp .env.example .env ./artisan key:generate ./artisan jwt:secret -f ./artisan clear-compiled diff --git a/src/app/Auth/LDAPUserProvider.php b/src/app/Auth/LDAPUserProvider.php --- a/src/app/Auth/LDAPUserProvider.php +++ b/src/app/Auth/LDAPUserProvider.php @@ -3,32 +3,69 @@ namespace App\Auth; use App\User; -use Carbon\Carbon; use Illuminate\Auth\EloquentUserProvider; use Illuminate\Support\Facades\Hash; use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Contracts\Auth\UserProvider; +/** + * A user provider that integrates an LDAP deployment. + */ class LDAPUserProvider extends EloquentUserProvider implements UserProvider { + /** + * Retrieve the user by its ID. + * + * @param string $identifier The unique ID for the user to attempt to retrieve. + * + * @return User|null + */ public function retrieveById($identifier) { - return parent::retrieveById($identifier); + $authenticatable = parent::retrieveById($identifier); + return $authenticatable->user; } + /** + * Retrieve the user by its credentials. + * + * Please note that this function also validates the password. + * + * @param array $credentials An array containing the email and password. + * + * @return User|null + */ public function retrieveByCredentials(array $credentials) { - $entries = User::where('email', '=', $credentials['email']); + $entries = User::where('email', '=', $credentials['email'])->get(); if ($entries->count() == 1) { $user = $entries->select('id', 'email', 'password', 'password_ldap')->first(); + if (!$this->validateCredentials($user, $credentials)) { + return null; + } + return $user; + } else { + if ($entries->count() > 1) { + \Log::warning("Multiple entries for {$credentials['email']}"); + } else { + \Log::warning("No entries for {$credentials['email']}"); + } } return null; } + /** + * Validate the credentials for a user. + * + * @param Authenticatable $user The user. + * @param array $credentials The credentials. + * + * @return bool + */ public function validateCredentials(Authenticatable $user, array $credentials) { $authenticated = false; @@ -46,6 +83,8 @@ if ($hash == $user->password_ldap) { $authenticated = true; } + } else { + \Log::error("Incomplete credentials for {$user->email}"); } } @@ -53,6 +92,8 @@ // TODO: Update password if necessary, examine whether writing to // user->password is sufficient? if ($authenticated) { + \Log::info("Successful authentication for {$user->email}"); + $user->password = $credentials['password']; $user->save(); } else { diff --git a/src/app/Backends/LDAP.php b/src/app/Backends/LDAP.php --- a/src/app/Backends/LDAP.php +++ b/src/app/Backends/LDAP.php @@ -162,7 +162,7 @@ * * @param \App\User $user The user account to create. * - * @return void + * @return bool|void */ public static function createUser(User $user) { @@ -218,7 +218,7 @@ * * @param \App\User $user The user account to update. * - * @return void + * @return bool|void */ public static function updateUser(User $user) { diff --git a/src/app/Console/Commands/PackageSkusCommand.php b/src/app/Console/Commands/PackageSkusCommand.php --- a/src/app/Console/Commands/PackageSkusCommand.php +++ b/src/app/Console/Commands/PackageSkusCommand.php @@ -39,12 +39,12 @@ */ public function handle() { - $packages = Package::get(); + $packages = Package::all(); foreach ($packages as $package) { $this->info(sprintf("Package: %s", $package->title)); - foreach ($package->skus()->get() as $sku) { + foreach ($package->skus as $sku) { $this->info(sprintf(" SKU: %s (%d)", $sku->title, $sku->pivot->qty)); } } diff --git a/src/app/Console/Commands/PlanPackagesCommand.php b/src/app/Console/Commands/PlanPackagesCommand.php --- a/src/app/Console/Commands/PlanPackagesCommand.php +++ b/src/app/Console/Commands/PlanPackagesCommand.php @@ -39,14 +39,14 @@ */ public function handle() { - $plans = Plan::get(); + $plans = Plan::all(); foreach ($plans as $plan) { $this->info(sprintf("Plan: %s", $plan->title)); $plan_costs = 0; - foreach ($plan->packages()->get() as $package) { + foreach ($plan->packages as $package) { $qtyMin = $package->pivot->qty_min; $qtyMax = $package->pivot->qty_max; @@ -65,7 +65,7 @@ ) ); - foreach ($package->skus()->get() as $sku) { + foreach ($package->skus as $sku) { $this->info(sprintf(" SKU: %s (%d)", $sku->title, $sku->pivot->qty)); } diff --git a/src/app/Console/Commands/UserDomains.php b/src/app/Console/Commands/UserDomains.php --- a/src/app/Console/Commands/UserDomains.php +++ b/src/app/Console/Commands/UserDomains.php @@ -40,8 +40,6 @@ */ public function handle() { - DB::enableQueryLog(); - $user = User::where('email', $this->argument('userid'))->first(); $this->info("Found user: {$user->id}"); @@ -49,7 +47,5 @@ foreach ($user->domains() as $domain) { $this->info("Domain: {$domain->namespace}"); } - - dd(DB::getQueryLog()); } } diff --git a/src/app/Console/Commands/UserEntitlementsCommand.php b/src/app/Console/Commands/UserEntitlementsCommand.php --- a/src/app/Console/Commands/UserEntitlementsCommand.php +++ b/src/app/Console/Commands/UserEntitlementsCommand.php @@ -2,7 +2,7 @@ namespace App\Console\Commands; -use App\Domain; +use App\Sku; use App\User; use Illuminate\Console\Command; use Illuminate\Support\Facades\DB; @@ -44,19 +44,19 @@ $this->info("Found user: {$user->id}"); - $entitlements = $user->entitlements()->get(); + $skus_counted = []; - foreach ($entitlements as $entitlement) { - //yes: dd($entitlement); - $_entitleable = $entitlement->entitleable; - - if ($_entitleable instanceof Domain) { - $this->info(sprintf("Domain: %s", $_entitleable->namespace)); + foreach ($user->entitlements as $entitlement) { + if (!array_key_exists($entitlement->sku_id, $skus_counted)) { + $skus_counted[$entitlement->sku_id] = 1; + } else { + $skus_counted[$entitlement->sku_id] += 1; } + } - if ($_entitleable instanceof User) { - $this->info(sprintf("User: %s", $_entitleable->email)); - } + foreach ($skus_counted as $id => $qty) { + $sku = Sku::find($id); + $this->info("SKU: {$sku->title} ({$qty})"); } } } diff --git a/src/app/Domain.php b/src/app/Domain.php --- a/src/app/Domain.php +++ b/src/app/Domain.php @@ -4,6 +4,11 @@ use Illuminate\Database\Eloquent\Model; +/** + * The eloquent definition of a Domain. + * + * @property string $namespace + */ class Domain extends Model { // we've simply never heard of this domain @@ -17,7 +22,7 @@ // ownership of the domain has been confirmed public const STATUS_CONFIRMED = 1 << 4; // domain has been verified that it exists in DNS -// public const STATUS_VERIFIED = 1 << 5; + public const STATUS_VERIFIED = 1 << 5; // domain has been created in LDAP public const STATUS_LDAP_READY = 1 << 6; @@ -65,7 +70,7 @@ */ public function isActive(): bool { - return $this->status & self::STATUS_ACTIVE; + return ($this->status & self::STATUS_ACTIVE) == true; } /** @@ -75,7 +80,7 @@ */ public function isConfirmed(): bool { - return $this->status & self::STATUS_CONFIRMED; + return ($this->status & self::STATUS_CONFIRMED) == true; } /** @@ -85,7 +90,7 @@ */ public function isDeleted(): bool { - return $this->status & self::STATUS_DELETED; + return ($this->status & self::STATUS_DELETED) == true; } /** @@ -95,7 +100,7 @@ */ public function isExternal(): bool { - return $this->type & self::TYPE_EXTERNAL; + return ($this->type & self::TYPE_EXTERNAL) == true; } /** @@ -105,7 +110,7 @@ */ public function isHosted(): bool { - return $this->type & self::TYPE_HOSTED; + return ($this->type & self::TYPE_HOSTED) == true; } /** @@ -115,7 +120,7 @@ */ public function isNew(): bool { - return $this->status & self::STATUS_NEW; + return ($this->status & self::STATUS_NEW) == true; } /** @@ -125,7 +130,7 @@ */ public function isPublic(): bool { - return $this->type & self::TYPE_PUBLIC; + return ($this->type & self::TYPE_PUBLIC) == true; } /** @@ -135,7 +140,7 @@ */ public function isLdapReady(): bool { - return $this->status & self::STATUS_LDAP_READY; + return ($this->status & self::STATUS_LDAP_READY) == true; } /** @@ -145,7 +150,7 @@ */ public function isSuspended(): bool { - return $this->status & self::STATUS_SUSPENDED; + return ($this->status & self::STATUS_SUSPENDED) == true; } /** @@ -154,12 +159,11 @@ * * @return bool */ -/* public function isVerified(): bool { - return $this->status & self::STATUS_VERIFIED; + return ($this->status & self::STATUS_VERIFIED) == true; } -*/ + /** * Domain status mutator * @@ -176,7 +180,7 @@ self::STATUS_SUSPENDED, self::STATUS_DELETED, self::STATUS_LDAP_READY, -// self::STATUS_VERIFIED, + self::STATUS_VERIFIED, ]; foreach ($allowed_values as $value) { @@ -232,7 +236,7 @@ $records = \dns_get_record('kolab-verify.' . $this->namespace, DNS_CNAME); if ($records === false) { - throw new \Exception("Failed to get DNS record for $domain"); + throw new \Exception("Failed to get DNS record for {$this->namespace}"); } foreach ($records as $records) { @@ -277,14 +281,13 @@ * @return bool True if registered, False otherwise * @throws \Exception Throws exception on DNS or DB errors */ -/* public function verify(): bool { if ($this->isVerified()) { return true; } - $record = \dns_get_record($this->namespace, DNS_SOA); + $record = \dns_get_record($this->namespace, DNS_ANY); if ($record === false) { throw new \Exception("Failed to get DNS record for {$this->namespace}"); @@ -299,5 +302,4 @@ return false; } -*/ } diff --git a/src/app/Entitlement.php b/src/app/Entitlement.php --- a/src/app/Entitlement.php +++ b/src/app/Entitlement.php @@ -40,7 +40,7 @@ ]; /** - * Principally entitleable objects such as 'Domain' or 'Mailbox'. + * Principally entitleable objects such as 'Domain' or 'User'. * * @return mixed */ @@ -52,7 +52,7 @@ /** * The SKU concerned. * - * @return Sku + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ public function sku() { @@ -62,7 +62,7 @@ /** * The owner of this entitlement. * - * @return User + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ public function owner() { @@ -72,7 +72,7 @@ /** * The wallet this entitlement is being billed to * - * @return Wallet + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ public function wallet() { diff --git a/src/app/Handlers/Base.php b/src/app/Handlers/Base.php new file mode 100644 --- /dev/null +++ b/src/app/Handlers/Base.php @@ -0,0 +1,14 @@ +sku_id)->active) { \Log::error("Sku not active"); diff --git a/src/app/Handlers/Resource.php b/src/app/Handlers/Resource.php --- a/src/app/Handlers/Resource.php +++ b/src/app/Handlers/Resource.php @@ -4,7 +4,7 @@ use App\Sku; -class Resource +class Resource extends \App\Handlers\Base { public static function entitleableClass() { diff --git a/src/app/Handlers/SharedFolder.php b/src/app/Handlers/SharedFolder.php --- a/src/app/Handlers/SharedFolder.php +++ b/src/app/Handlers/SharedFolder.php @@ -4,7 +4,7 @@ use App\Sku; -class SharedFolder +class SharedFolder extends \App\Handlers\Base { public static function entitleableClass() { diff --git a/src/app/Handlers/Storage.php b/src/app/Handlers/Storage.php --- a/src/app/Handlers/Storage.php +++ b/src/app/Handlers/Storage.php @@ -2,32 +2,16 @@ namespace App\Handlers; -use App\Quota; -use App\Sku; -use App\User; - -class Storage +class Storage extends \App\Handlers\Base { - public static function createDefaultEntitleable(User $user) - { - $quota = new Quota(); - $quota->user_id = $user->id; - $quota->save(); - - return $quota->id; - } - public static function entitleableClass() { - return Quota::class; + return null; } - public static function preReq($entitlement, $user) + public static function preReq($entitlement, $object) { - if (!Sku::find($entitlement->sku_id)->active) { - \Log::error("Sku not active"); - return false; - } + // TODO: The storage can not be modified to below what is already consumed. return true; } diff --git a/src/app/Http/Controllers/API/DomainsController.php b/src/app/Http/Controllers/API/DomainsController.php --- a/src/app/Http/Controllers/API/DomainsController.php +++ b/src/app/Http/Controllers/API/DomainsController.php @@ -12,7 +12,7 @@ /** * Display a listing of the resource. * - * @return \Illuminate\Http\Response + * @return \Illuminate\Http\JsonResponse */ public function index() { @@ -22,7 +22,7 @@ /** * Show the form for creating a new resource. * - * @return \Illuminate\Http\Response + * @return \Illuminate\Http\JsonResponse */ public function create() { @@ -34,7 +34,7 @@ * * @param int $id Domain identifier * - * @return \Illuminate\Http\Response + * @return \Illuminate\Http\JsonResponse|void */ public function confirm($id) { @@ -57,7 +57,7 @@ * * @param int $id * - * @return \Illuminate\Http\Response + * @return \Illuminate\Http\JsonResponse */ public function destroy($id) { @@ -93,7 +93,7 @@ * * @param int $id Domain identifier * - * @return \Illuminate\Http\Response + * @return \Illuminate\Http\JsonResponse|void */ public function show($id) { @@ -197,7 +197,7 @@ /** * Check if the current user has access to the domain * - * @param \App\Domain Domain + * @param \App\Domain $domain The domain * * @return bool True if current user has access, False otherwise */ diff --git a/src/app/Http/Controllers/API/PasswordResetController.php b/src/app/Http/Controllers/API/PasswordResetController.php --- a/src/app/Http/Controllers/API/PasswordResetController.php +++ b/src/app/Http/Controllers/API/PasswordResetController.php @@ -24,7 +24,7 @@ * * Verifies user email, sends verification email message. * - * @param Illuminate\Http\Request HTTP request + * @param \Illuminate\Http\Request $request HTTP request * * @return \Illuminate\Http\JsonResponse JSON response */ @@ -63,7 +63,7 @@ /** * Validation of the verification code. * - * @param Illuminate\Http\Request HTTP request + * @param \Illuminate\Http\Request $request HTTP request * * @return \Illuminate\Http\JsonResponse JSON response */ @@ -106,7 +106,7 @@ /** * Password change * - * @param Illuminate\Http\Request HTTP request + * @param \Illuminate\Http\Request $request HTTP request * * @return \Illuminate\Http\JsonResponse JSON response */ diff --git a/src/app/Http/Controllers/API/SignupController.php b/src/app/Http/Controllers/API/SignupController.php --- a/src/app/Http/Controllers/API/SignupController.php +++ b/src/app/Http/Controllers/API/SignupController.php @@ -29,7 +29,7 @@ /** * Returns plans definitions for signup. * - * @param Illuminate\Http\Request HTTP request + * @param \Illuminate\Http\Request $request HTTP request * * @return \Illuminate\Http\JsonResponse JSON response */ @@ -55,7 +55,7 @@ * Verifies user name and email/phone, sends verification email/sms message. * Returns the verification code. * - * @param Illuminate\Http\Request HTTP request + * @param \Illuminate\Http\Request $request HTTP request * * @return \Illuminate\Http\JsonResponse JSON response */ @@ -102,7 +102,7 @@ /** * Validation of the verification code. * - * @param Illuminate\Http\Request HTTP request + * @param \Illuminate\Http\Request $request HTTP request * * @return \Illuminate\Http\JsonResponse JSON response */ @@ -153,7 +153,7 @@ /** * Finishes the signup process by creating the user account. * - * @param Illuminate\Http\Request HTTP request + * @param \Illuminate\Http\Request $request HTTP request * * @return \Illuminate\Http\JsonResponse JSON response */ @@ -222,9 +222,7 @@ // Create SKUs (after domain) foreach ($plan->packages as $package) { - foreach ($package->skus as $sku) { - $sku->registerEntitlement($user, is_object($domain) ? [$domain] : []); - } + $package->assign($user); } // Save the external email and plan in user settings @@ -244,8 +242,8 @@ /** * Checks if the input string is a valid email address or a phone number * - * @param string $email Email address or phone number - * @param bool &$is_phone Will be set to True if the string is valid phone number + * @param string $input Email address or phone number + * @param bool $is_phone Will have been set to True if the string is valid phone number * * @return string Error message label on validation error */ @@ -297,7 +295,7 @@ /** * Login (kolab identity) validation * - * @param string $email Login (local part of an email address) + * @param string $login Login (local part of an email address) * @param string $domain Domain name * @param bool $external Enables additional checks for domain part * diff --git a/src/app/Http/Controllers/API/UsersController.php b/src/app/Http/Controllers/API/UsersController.php --- a/src/app/Http/Controllers/API/UsersController.php +++ b/src/app/Http/Controllers/API/UsersController.php @@ -46,7 +46,7 @@ * * The user themself, and other user entitlements. * - * @return \Illuminate\Http\Response + * @return \Illuminate\Http\JsonResponse */ public function index() { @@ -141,11 +141,11 @@ } /** - * Display the specified resource. + * Display information on the user account specified by $id. * * @param int $id The account to show information for. * - * @return \Illuminate\Http\Response + * @return \Illuminate\Http\JsonResponse|void */ public function show($id) { @@ -155,25 +155,13 @@ return abort(403); } - $result = false; - - $user->entitlements()->each( - function ($entitlement) { - if ($entitlement->user_id == $id) { - $result = true; - } - } - ); - - if ($user->id == $id) { - $result = true; - } - - if (!$result) { + // TODO: check whether or not the user is allowed + // for now, only allow self. + if ($user->id != $id) { return abort(404); } - return \App\User::find($id); + return response()->json($user); } /** diff --git a/src/app/Mail/PasswordReset.php b/src/app/Mail/PasswordReset.php --- a/src/app/Mail/PasswordReset.php +++ b/src/app/Mail/PasswordReset.php @@ -19,7 +19,7 @@ /** * Create a new message instance. * - * @param \App\VerificationCode A verification code object + * @param \App\VerificationCode $code A verification code object * * @return void */ diff --git a/src/app/Observers/EntitlementObserver.php b/src/app/Observers/EntitlementObserver.php --- a/src/app/Observers/EntitlementObserver.php +++ b/src/app/Observers/EntitlementObserver.php @@ -19,7 +19,7 @@ * * @param Entitlement $entitlement The entitlement being created. * - * @return void + * @return bool|null */ public function creating(Entitlement $entitlement) { diff --git a/src/app/Package.php b/src/app/Package.php --- a/src/app/Package.php +++ b/src/app/Package.php @@ -34,6 +34,42 @@ 'discount_rate' ]; + public function assign($object, $user = null) + { + // if user == null, $object is a user ;-) + if ($user === null) { + $user = $object; + } + + $entitleable_type = null; + + if ($object instanceof \App\Domain) { + $entitleable_type = \App\Domain::class; + } + + if ($object instanceof \App\User) { + $entitleable_type = \App\User::class; + } + + $wallet_id = $user->wallets()->get()[0]->id; + + foreach ($this->skus()->get() as $sku) { + //\Log::debug(var_export($sku, true)); + + for ($i = $sku->pivot->qty; $i > 0; $i--) { + \App\Entitlement::create( + [ + 'owner_id' => $user->id, + 'wallet_id' => $wallet_id, + 'sku_id' => $sku->id, + 'entitleable_id' => $entitleable_type ? $object->id : null, + 'entitleable_type' => $entitleable_type + ] + ); + } + } + } + public function cost() { $costs = 0; diff --git a/src/app/PackageSku.php b/src/app/PackageSku.php --- a/src/app/PackageSku.php +++ b/src/app/PackageSku.php @@ -4,6 +4,9 @@ use Illuminate\Database\Eloquent\Relations\Pivot; +/** + * Link SKUs to Packages. + */ class PackageSku extends Pivot { protected $fillable = [ diff --git a/src/app/Plan.php b/src/app/Plan.php --- a/src/app/Plan.php +++ b/src/app/Plan.php @@ -11,6 +11,8 @@ * * A "Family Plan" as such may exist of "2 or more Kolab packages", * and apply a discount for the third and further Kolab packages. + * + * @property \App\Package[] $packages */ class Plan extends Model { diff --git a/src/app/PlanPackage.php b/src/app/PlanPackage.php --- a/src/app/PlanPackage.php +++ b/src/app/PlanPackage.php @@ -4,6 +4,12 @@ use Illuminate\Database\Eloquent\Relations\Pivot; +/** + * Link Packages to Plans. + * + * @property integer $qty_min + * @property \App\Package $package + */ class PlanPackage extends Pivot { protected $fillable = [ @@ -24,6 +30,11 @@ 'discount_rate' => 'integer' ]; + /** + * Calculate the costs for this plan. + * + * @return integer + */ public function cost() { $costs = 0; diff --git a/src/app/Quota.php b/src/app/Quota.php deleted file mode 100644 --- a/src/app/Quota.php +++ /dev/null @@ -1,29 +0,0 @@ - 'int', - ]; - - public function entitlement() - { - return $this->morphOne('App\Entitlement', 'entitleable'); - } - - /** - * The owner of this quota entry - * - * @return \App\User - */ - public function user() - { - return $this->belongsTo('App\User', 'user_id', 'id'); - } -} diff --git a/src/app/SignupCode.php b/src/app/SignupCode.php --- a/src/app/SignupCode.php +++ b/src/app/SignupCode.php @@ -7,6 +7,8 @@ /** * The eloquent definition of a SignupCode. + * + * @property datetime $expires_at */ class SignupCode extends Model { diff --git a/src/app/Sku.php b/src/app/Sku.php --- a/src/app/Sku.php +++ b/src/app/Sku.php @@ -29,7 +29,7 @@ /** * List the entitlements that consume this SKU. * - * @return Entitlement[] + * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function entitlements() { @@ -43,48 +43,4 @@ 'package_skus' )->using('App\PackageSku')->withPivot(['qty']); } - - /** - * Register (default) SKU entitlement for specified user. - * This method should be used e.g. on user creation when we have - * a set of SKUs and want to create entitlements for them (using - * default values). - */ - public function registerEntitlement(\App\User $user, array $params = []) - { - if (!$this->active) { - \Log::debug("Skipped registration of an entitlement for non-active SKU ($this->title)"); - return; - } - - $wallet = $user->wallets()->get()[0]; - - $entitlement = new \App\Entitlement(); - $entitlement->owner_id = $user->id; - $entitlement->wallet_id = $wallet->id; - $entitlement->sku_id = $this->id; - - $entitlement->entitleable_type = $this->handler_class::entitleableClass(); - - if ($user instanceof $entitlement->entitleable_type) { - $entitlement->entitleable_id = $user->id; - } else { - foreach ($params as $param) { - if ($param instanceof $entitlement->entitleable_type) { - $entitlement->entitleable_id = $param->id; - break; - } - } - } - - if (empty($entitlement->entitleable_id)) { - if (method_exists($this->handler_class, 'createDefaultEntitleable')) { - $entitlement->entitleable_id = $this->handler_class::createDefaultEntitleable($user); - } else { - throw new Exception("Failed to create an entitlement for SKU ($this->title). Missing entitleable_id."); - } - } - - $entitlement->save(); - } } diff --git a/src/app/User.php b/src/app/User.php --- a/src/app/User.php +++ b/src/app/User.php @@ -11,6 +11,9 @@ /** * The eloquent definition of a User. + * + * @property integer $id + * @property integer $status */ class User extends Authenticatable implements JWTSubject { @@ -78,15 +81,15 @@ /** * Any wallets on which this user is a controller. * - * @return Wallet[] + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ public function accounts() { return $this->belongsToMany( 'App\Wallet', // The foreign object definition 'user_accounts', // The table name - 'user_id', // The local foreign key - 'wallet_id' // The remote foreign key + 'user_id', // The local foreign key + 'wallet_id' // The remote foreign key ); } @@ -97,7 +100,7 @@ */ public function domains() { - $domains = Domain::whereRaw( + $dbdomains = Domain::whereRaw( sprintf( '(type & %s) AND (status & %s)', Domain::TYPE_PUBLIC, @@ -105,6 +108,12 @@ ) )->get(); + $domains = []; + + foreach ($dbdomains as $dbdomain) { + $domains[] = $dbdomain; + } + foreach ($this->entitlements()->get() as $entitlement) { if ($entitlement->entitleable instanceof Domain) { $domain = Domain::find($entitlement->entitleable_id); @@ -134,7 +143,7 @@ /** * Entitlements for this user. * - * @return Entitlement[] + * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function entitlements() { @@ -144,7 +153,7 @@ public function addEntitlement($entitlement) { // FIXME: This contains() check looks fishy - if (!$this->entitlements()->get()->contains($entitlement)) { + if (!$this->entitlements->contains($entitlement)) { return $this->entitlements()->save($entitlement); } } @@ -187,7 +196,7 @@ */ public function isActive(): bool { - return $this->status & self::STATUS_ACTIVE; + return ($this->status & self::STATUS_ACTIVE) == true; } /** @@ -197,7 +206,7 @@ */ public function isDeleted(): bool { - return $this->status & self::STATUS_DELETED; + return ($this->status & self::STATUS_DELETED) == true; } /** @@ -208,7 +217,7 @@ */ public function isImapReady(): bool { - return $this->status & self::STATUS_IMAP_READY; + return ($this->status & self::STATUS_IMAP_READY) == true; } /** @@ -218,7 +227,7 @@ */ public function isLdapReady(): bool { - return $this->status & self::STATUS_LDAP_READY; + return ($this->status & self::STATUS_LDAP_READY) == true; } /** @@ -228,7 +237,7 @@ */ public function isNew(): bool { - return $this->status & self::STATUS_NEW; + return ($this->status & self::STATUS_NEW) == true; } /** @@ -238,13 +247,13 @@ */ public function isSuspended(): bool { - return $this->status & self::STATUS_SUSPENDED; + return ($this->status & self::STATUS_SUSPENDED) == true; } /** * Any (additional) properties of this user. * - * @return \App\UserSetting[] + * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function settings() { @@ -254,7 +263,7 @@ /** * Verification codes for this user. * - * @return VerificationCode[] + * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function verificationcodes() { @@ -264,7 +273,7 @@ /** * Wallets this user owns. * - * @return Wallet[] + * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function wallets() { diff --git a/src/app/UserSetting.php b/src/app/UserSetting.php --- a/src/app/UserSetting.php +++ b/src/app/UserSetting.php @@ -21,10 +21,14 @@ /** * The user to which this setting belongs. * - * @return \App\User + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ public function user() { - return $this->belongsTo('\App\User', 'user_id' /* local */, 'id' /* remote */); + return $this->belongsTo( + '\App\User', + 'user_id', /* local */ + 'id' /* remote */ + ); } } diff --git a/src/app/Utils.php b/src/app/Utils.php --- a/src/app/Utils.php +++ b/src/app/Utils.php @@ -49,7 +49,7 @@ */ public static function uuidStr(): string { - return (string) Uuid::uuid4(); + return Uuid::uuid4()->toString(); } private static function combine($input, $r, $index, $data, $i, &$output): void diff --git a/src/app/VerificationCode.php b/src/app/VerificationCode.php --- a/src/app/VerificationCode.php +++ b/src/app/VerificationCode.php @@ -33,7 +33,7 @@ /** * The user to which this setting belongs. * - * @return \App\User + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ public function user() { diff --git a/src/app/Wallet.php b/src/app/Wallet.php --- a/src/app/Wallet.php +++ b/src/app/Wallet.php @@ -10,16 +10,13 @@ * The eloquent definition of a wallet -- a container with a chunk of change. * * A wallet is owned by an {@link \App\User}. + * + * @property integer $balance */ class Wallet extends Model { use NullableFields; - /** - Our table name for the shall be 'wallet'. - - @var string - */ public $incrementing = false; protected $keyType = 'string'; @@ -53,7 +50,7 @@ */ public function addController(User $user) { - if (!$this->controllers()->get()->contains($user)) { + if (!$this->controllers->contains($user)) { $this->controllers()->save($user); } } @@ -67,7 +64,7 @@ */ public function removeController(User $user) { - if ($this->controllers()->get()->contains($user)) { + if ($this->controllers->contains($user)) { $this->controllers()->detach($user); } } @@ -107,7 +104,7 @@ /** * Controllers of this wallet. * - * @return \App\User[] + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ public function controllers() { @@ -122,7 +119,7 @@ /** * Entitlements billed to this wallet. * - * @return Entitlement[] + * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function entitlements() { @@ -132,7 +129,7 @@ /** * The owner of the wallet -- the wallet is in his/her back pocket. * - * @return User + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ public function owner() { diff --git a/src/composer.json b/src/composer.json --- a/src/composer.json +++ b/src/composer.json @@ -37,6 +37,8 @@ "laravel/dusk": "^5.5", "mockery/mockery": "^1.0", "nunomaduro/collision": "^3.0", + "nunomaduro/larastan": "^0.4", + "phpstan/phpstan": "0.11.19", "phpunit/phpunit": "^7.5" }, "config": { diff --git a/src/database/migrations/2019_09_17_102628_create_sku_entitlements.php b/src/database/migrations/2019_09_17_102628_create_sku_entitlements.php --- a/src/database/migrations/2019_09_17_102628_create_sku_entitlements.php +++ b/src/database/migrations/2019_09_17_102628_create_sku_entitlements.php @@ -35,7 +35,9 @@ $table->string('id', 36)->primary(); $table->bigInteger('owner_id'); $table->bigInteger('entitleable_id'); + //$table->bigInteger('entitleable_id')->nullable(); $table->string('entitleable_type'); + //$table->string('entitleable_type')->nullable(); $table->string('wallet_id', 36); $table->string('sku_id', 36); $table->string('description')->nullable(); diff --git a/src/database/migrations/2019_10_10_095050_create_quota_table.php b/src/database/migrations/2019_10_10_095050_create_quota_table.php --- a/src/database/migrations/2019_10_10_095050_create_quota_table.php +++ b/src/database/migrations/2019_10_10_095050_create_quota_table.php @@ -13,14 +13,16 @@ */ public function up() { + /* Schema::create('quotas', function (Blueprint $table) { $table->bigIncrements('id'); $table->bigInteger('user_id'); - $table->integer('value')->default(2147483648); + $table->unsignedInteger('value')->default(2147483648); $table->timestamps(); $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); }); + */ } /** diff --git a/src/database/seeds/DatabaseSeeder.php b/src/database/seeds/DatabaseSeeder.php --- a/src/database/seeds/DatabaseSeeder.php +++ b/src/database/seeds/DatabaseSeeder.php @@ -15,9 +15,9 @@ [ DomainSeeder::class, SkuSeeder::class, - UserSeeder::class, PackageSeeder::class, - PlanSeeder::class + PlanSeeder::class, + UserSeeder::class ] ); } diff --git a/src/database/seeds/UserSeeder.php b/src/database/seeds/UserSeeder.php --- a/src/database/seeds/UserSeeder.php +++ b/src/database/seeds/UserSeeder.php @@ -33,31 +33,6 @@ ] ); - $user_wallets = $user->wallets()->get(); - - $sku_domain = Sku::where('title', 'domain')->first(); - $sku_mailbox = Sku::where('title', 'mailbox')->first(); - - $entitlement_domain = Entitlement::create( - [ - 'owner_id' => $user->id, - 'wallet_id' => $user_wallets[0]->id, - 'sku_id' => $sku_domain->id, - 'entitleable_id' => $domain->id, - 'entitleable_type' => Domain::class - ] - ); - - $entitlement_mailbox = Entitlement::create( - [ - 'owner_id' => $user->id, - 'wallet_id' => $user_wallets[0]->id, - 'sku_id' => $sku_mailbox->id, - 'entitleable_id' => $user->id, - 'entitleable_type' => User::class - ] - ); - $user->setSettings( [ "first_name" => "John", @@ -66,5 +41,14 @@ "country" => "US" ] ); + + $user_wallets = $user->wallets()->get(); + + $package_domain = \App\Package::where('title', 'domain-hosting')->first(); + $package_kolab = \App\Package::where('title', 'kolab')->first(); + + $package_domain->assign($domain, $user); + $package_kolab->assign($user); + } } diff --git a/src/phpstan.neon b/src/phpstan.neon new file mode 100644 --- /dev/null +++ b/src/phpstan.neon @@ -0,0 +1,6 @@ +includes: + - ./vendor/nunomaduro/larastan/extension.neon +parameters: + level: 3 + paths: + - app/ diff --git a/src/tests/Feature/Controller/SignupTest.php b/src/tests/Feature/Controller/SignupTest.php --- a/src/tests/Feature/Controller/SignupTest.php +++ b/src/tests/Feature/Controller/SignupTest.php @@ -418,7 +418,7 @@ }); // Check if the code has been removed - $this->assertNull(SignupCode::where($result['code'])->first()); + $this->assertNull(SignupCode::where('code', $result['code'])->first()); // Check if the user has been created $user = User::where('email', $identity)->first(); diff --git a/src/tests/Feature/SkuTest.php b/src/tests/Feature/SkuTest.php --- a/src/tests/Feature/SkuTest.php +++ b/src/tests/Feature/SkuTest.php @@ -5,7 +5,6 @@ use App\Domain; use App\Entitlement; use App\Handlers; -use App\Quota; use App\Sku; use App\User; use Tests\TestCase; @@ -41,7 +40,17 @@ // \App\Handlers\Mailbox SKU // Note, we're testing mailbox SKU before domain SKU as it may potentially fail in that order $sku = Sku::where('title', 'mailbox')->first(); - $sku->registerEntitlement($user); + $user->addEntitlement( + Entitlement::create( + [ + 'owner_id' => $user->id, + 'wallet_id' => $user->wallets()->get()[0]->id, + 'sku_id' => $sku->id, + 'entitleable_id' => $user->id, + 'entitleable_type' => User::class + ] + ) + ); $entitlements = $sku->entitlements()->where('owner_id', $user->id)->get(); $wallet->refresh(); @@ -59,7 +68,17 @@ // \App\Handlers\Domain SKU $sku = Sku::where('title', 'domain')->first(); - $sku->registerEntitlement($user, [$domain]); + $user->addEntitlement( + Entitlement::create( + [ + 'owner_id' => $user->id, + 'wallet_id' => $user->wallets()->get()[0]->id, + 'sku_id' => $sku->id, + 'entitleable_id' => $domain->id, + 'entitleable_type' => Domain::class + ] + ) + ); $entitlements = $sku->entitlements()->where('owner_id', $user->id)->get(); $wallet->refresh(); @@ -77,7 +96,17 @@ // \App\Handlers\DomainRegistration SKU $sku = Sku::where('title', 'domain-registration')->first(); - $sku->registerEntitlement($user, [$domain]); + $user->addEntitlement( + Entitlement::create( + [ + 'owner_id' => $user->id, + 'wallet_id' => $user->wallets()->get()[0]->id, + 'sku_id' => $sku->id, + 'entitleable_id' => $domain->id, + 'entitleable_type' => Domain::class + ] + ) + ); $entitlements = $sku->entitlements()->where('owner_id', $user->id)->get(); $wallet->refresh(); @@ -95,7 +124,17 @@ // \App\Handlers\DomainHosting SKU $sku = Sku::where('title', 'domain-hosting')->first(); - $sku->registerEntitlement($user, [$domain]); + $user->addEntitlement( + Entitlement::create( + [ + 'owner_id' => $user->id, + 'wallet_id' => $user->wallets()->get()[0]->id, + 'sku_id' => $sku->id, + 'entitleable_id' => $domain->id, + 'entitleable_type' => Domain::class + ] + ) + ); $entitlements = $sku->entitlements()->where('owner_id', $user->id)->get(); $wallet->refresh(); @@ -113,7 +152,17 @@ // \App\Handlers\Groupware SKU $sku = Sku::where('title', 'groupware')->first(); - $sku->registerEntitlement($user, [$domain]); + $user->addEntitlement( + Entitlement::create( + [ + 'owner_id' => $user->id, + 'wallet_id' => $user->wallets()->get()[0]->id, + 'sku_id' => $sku->id, + 'entitleable_id' => $user->id, + 'entitleable_type' => User::class + ] + ) + ); $entitlements = $sku->entitlements()->where('owner_id', $user->id)->get(); $wallet->refresh(); @@ -131,22 +180,24 @@ // \App\Handlers\Storage SKU $sku = Sku::where('title', 'storage')->first(); - $sku->registerEntitlement($user, [$domain]); + $user->addEntitlement( + Entitlement::create( + [ + 'owner_id' => $user->id, + 'wallet_id' => $user->wallets()->get()[0]->id, + 'sku_id' => $sku->id, + 'entitleable_id' => $user->id, + 'entitleable_type' => User::class + ] + ) + ); $entitlements = $sku->entitlements()->where('owner_id', $user->id)->get(); $wallet->refresh(); if ($sku->active) { $balance -= $sku->cost; - // For Storage entitlement we expect additional Quota record - $quota = Quota::where('user_id', $user->id)->first(); - $this->assertTrue(!empty($quota)); - // TODO: This should be a constant and/or config option, and probably - // quota should not be in bytes - $this->assertSame(2147483648, $quota->value); $this->assertCount(1, $entitlements); - $this->assertEquals($quota->id, $entitlements[0]->entitleable_id); - $this->assertSame(Handlers\Storage::entitleableClass(), $entitlements[0]->entitleable_type); } else { $this->assertCount(0, $entitlements); } diff --git a/src/tests/Feature/UserTest.php b/src/tests/Feature/UserTest.php --- a/src/tests/Feature/UserTest.php +++ b/src/tests/Feature/UserTest.php @@ -87,6 +87,24 @@ $this->assertContains('kolab.org', $domains); } + public function testUserQuota(): void + { + $user = $this->getTestUser('john@kolab.org'); + $storage_sku = \App\Sku::where('title', 'storage')->first(); + + $count = 0; + + foreach ($user->entitlements()->get() as $entitlement) { + \Log::debug(var_export($entitlement, true)); + + if ($entitlement->sku_id == $storage_sku->id) { + $count += 1; + } + } + + $this->assertTrue($count == 2, "count is ${count}"); + } + /** * Tests for User::findByEmail() */ diff --git a/src/tests/Feature/WalletTest.php b/src/tests/Feature/WalletTest.php --- a/src/tests/Feature/WalletTest.php +++ b/src/tests/Feature/WalletTest.php @@ -138,7 +138,10 @@ } ); - $this->assertTrue($userB->accounts()->count() == 1); + $this->assertTrue( + $userB->accounts()->count() == 1, + "number of accounts (1 expected): {$userB->accounts()->count()}" + ); $aWallet = $userA->wallets()->get(); $bAccount = $userB->accounts()->get();