Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2528412
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
43 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/app/Domain.php b/src/app/Domain.php
index b8a0375d..21e292ec 100644
--- a/src/app/Domain.php
+++ b/src/app/Domain.php
@@ -1,450 +1,450 @@
<?php
namespace App;
use App\Wallet;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
/**
* The eloquent definition of a Domain.
*
* @property string $namespace
*/
class Domain extends Model
{
use SoftDeletes;
// we've simply never heard of this domain
public const STATUS_NEW = 1 << 0;
// it's been activated
public const STATUS_ACTIVE = 1 << 1;
// domain has been suspended.
public const STATUS_SUSPENDED = 1 << 2;
// domain has been deleted
public const STATUS_DELETED = 1 << 3;
// 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;
// domain has been created in LDAP
public const STATUS_LDAP_READY = 1 << 6;
// open for public registration
public const TYPE_PUBLIC = 1 << 0;
// zone hosted with us
public const TYPE_HOSTED = 1 << 1;
// zone registered externally
public const TYPE_EXTERNAL = 1 << 2;
public const HASH_CODE = 1;
public const HASH_TEXT = 2;
public const HASH_CNAME = 3;
public $incrementing = false;
protected $keyType = 'bigint';
protected $fillable = [
'namespace',
'status',
'type'
];
/**
* Assign a package to a domain. The domain should not belong to any existing entitlements.
*
* @param \App\Package $package The package to assign.
* @param \App\User $user The wallet owner.
*
* @return \App\Domain Self
*/
public function assignPackage($package, $user)
{
// If this domain is public it can not be assigned to a user.
if ($this->isPublic()) {
return $this;
}
// See if this domain is already owned by another user.
$wallet = $this->wallet();
if ($wallet) {
\Log::error(
"Domain {$this->namespace} is already assigned to {$wallet->owner->email}"
);
return $this;
}
$wallet_id = $user->wallets()->first()->id;
foreach ($package->skus as $sku) {
for ($i = $sku->pivot->qty; $i > 0; $i--) {
\App\Entitlement::create(
[
'wallet_id' => $wallet_id,
'sku_id' => $sku->id,
'cost' => $sku->pivot->cost(),
'entitleable_id' => $this->id,
'entitleable_type' => Domain::class
]
);
}
}
return $this;
}
/**
* The domain entitlement.
*
* @return \Illuminate\Database\Eloquent\Relations\MorphOne
*/
public function entitlement()
{
return $this->morphOne('App\Entitlement', 'entitleable');
}
/**
* Return list of public+active domain names
*/
public static function getPublicDomains(): array
{
$where = sprintf('(type & %s)', Domain::TYPE_PUBLIC);
return self::whereRaw($where)->get(['namespace'])->pluck('namespace')->toArray();
}
/**
* Returns whether this domain is active.
*
* @return bool
*/
public function isActive(): bool
{
return ($this->status & self::STATUS_ACTIVE) > 0;
}
/**
* Returns whether this domain is confirmed the ownership of.
*
* @return bool
*/
public function isConfirmed(): bool
{
return ($this->status & self::STATUS_CONFIRMED) > 0;
}
/**
* Returns whether this domain is deleted.
*
* @return bool
*/
public function isDeleted(): bool
{
return ($this->status & self::STATUS_DELETED) > 0;
}
/**
* Returns whether this domain is registered with us.
*
* @return bool
*/
public function isExternal(): bool
{
return ($this->type & self::TYPE_EXTERNAL) > 0;
}
/**
* Returns whether this domain is hosted with us.
*
* @return bool
*/
public function isHosted(): bool
{
return ($this->type & self::TYPE_HOSTED) > 0;
}
/**
* Returns whether this domain is new.
*
* @return bool
*/
public function isNew(): bool
{
return ($this->status & self::STATUS_NEW) > 0;
}
/**
* Returns whether this domain is public.
*
* @return bool
*/
public function isPublic(): bool
{
return ($this->type & self::TYPE_PUBLIC) > 0;
}
/**
* Returns whether this domain is registered in LDAP.
*
* @return bool
*/
public function isLdapReady(): bool
{
return ($this->status & self::STATUS_LDAP_READY) > 0;
}
/**
* Returns whether this domain is suspended.
*
* @return bool
*/
public function isSuspended(): bool
{
return ($this->status & self::STATUS_SUSPENDED) > 0;
}
/**
* Returns whether this (external) domain has been verified
* to exist in DNS.
*
* @return bool
*/
public function isVerified(): bool
{
return ($this->status & self::STATUS_VERIFIED) > 0;
}
/**
* Ensure the namespace is appropriately cased.
*/
public function setNamespaceAttribute($namespace)
{
$this->attributes['namespace'] = strtolower($namespace);
}
/**
* Domain status mutator
*
* @throws \Exception
*/
public function setStatusAttribute($status)
{
$new_status = 0;
$allowed_values = [
self::STATUS_NEW,
self::STATUS_ACTIVE,
self::STATUS_SUSPENDED,
self::STATUS_DELETED,
self::STATUS_CONFIRMED,
self::STATUS_VERIFIED,
self::STATUS_LDAP_READY,
];
foreach ($allowed_values as $value) {
if ($status & $value) {
$new_status |= $value;
$status ^= $value;
}
}
if ($status > 0) {
throw new \Exception("Invalid domain status: {$status}");
}
if ($this->isPublic()) {
$this->attributes['status'] = $new_status;
return;
}
if ($new_status & self::STATUS_CONFIRMED) {
// if we have confirmed ownership of or management access to the domain, then we have
// also confirmed the domain exists in DNS.
$new_status |= self::STATUS_VERIFIED;
$new_status |= self::STATUS_ACTIVE;
}
if ($new_status & self::STATUS_DELETED && $new_status & self::STATUS_ACTIVE) {
$new_status ^= self::STATUS_ACTIVE;
}
if ($new_status & self::STATUS_SUSPENDED && $new_status & self::STATUS_ACTIVE) {
$new_status ^= self::STATUS_ACTIVE;
}
// if the domain is now active, it is not new anymore.
if ($new_status & self::STATUS_ACTIVE && $new_status & self::STATUS_NEW) {
$new_status ^= self::STATUS_NEW;
}
$this->attributes['status'] = $new_status;
}
/**
* Ownership verification by checking for a TXT (or CNAME) record
* in the domain's DNS (that matches the verification hash).
*
* @return bool True if verification was successful, false otherwise
* @throws \Exception Throws exception on DNS or DB errors
*/
public function confirm(): bool
{
if ($this->isConfirmed()) {
return true;
}
$hash = $this->hash(self::HASH_TEXT);
$confirmed = false;
// Get DNS records and find a matching TXT entry
$records = \dns_get_record($this->namespace, DNS_TXT);
if ($records === false) {
throw new \Exception("Failed to get DNS record for {$this->namespace}");
}
foreach ($records as $record) {
if ($record['txt'] === $hash) {
$confirmed = true;
break;
}
}
// Get DNS records and find a matching CNAME entry
// Note: some servers resolve every non-existing name
// so we need to define left and right side of the CNAME record
// i.e.: kolab-verify IN CNAME <hash>.domain.tld.
if (!$confirmed) {
$cname = $this->hash(self::HASH_CODE) . '.' . $this->namespace;
$records = \dns_get_record('kolab-verify.' . $this->namespace, DNS_CNAME);
if ($records === false) {
throw new \Exception("Failed to get DNS record for {$this->namespace}");
}
foreach ($records as $records) {
if ($records['target'] === $cname) {
$confirmed = true;
break;
}
}
}
if ($confirmed) {
$this->status |= Domain::STATUS_CONFIRMED;
$this->save();
}
return $confirmed;
}
/**
* Generate a verification hash for this domain
*
* @param int $mod One of: HASH_CNAME, HASH_CODE (Default), HASH_TEXT
*
* @return string Verification hash
*/
public function hash($mod = null): string
{
$cname = 'kolab-verify';
if ($mod === self::HASH_CNAME) {
return $cname;
}
$hash = \md5('hkccp-verify-' . $this->namespace);
return $mod === self::HASH_TEXT ? "$cname=$hash" : $hash;
}
/**
* Suspend this domain.
*
* @return void
*/
public function suspend(): void
{
if ($this->isSuspended()) {
return;
}
$this->status |= Domain::STATUS_SUSPENDED;
$this->save();
}
/**
* Unsuspend this domain.
*
* The domain is unsuspended through either of the following courses of actions;
*
* * The account balance has been topped up, or
* * a suspected spammer has resolved their issues, or
* * the command-line is triggered.
*
* Therefore, we can also confidently set the domain status to 'active' should the ownership of or management
* access to have been confirmed before.
*
* @return void
*/
public function unsuspend(): void
{
if (!$this->isSuspended()) {
return;
}
$this->status ^= Domain::STATUS_SUSPENDED;
if ($this->isConfirmed() && $this->isVerified()) {
$this->status |= Domain::STATUS_ACTIVE;
}
$this->save();
}
/**
* Verify if a domain exists in DNS
*
* @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;
}
$records = \dns_get_record($this->namespace, DNS_ANY);
if ($records === false) {
throw new \Exception("Failed to get DNS record for {$this->namespace}");
}
// It may happen that result contains other domains depending on the host DNS setup
// that's why in_array() and not just !empty()
if (in_array($this->namespace, array_column($records, 'host'))) {
$this->status |= Domain::STATUS_VERIFIED;
$this->save();
return true;
}
return false;
}
/**
* Returns the wallet by which the domain is controlled
*
* @return \App\Wallet A wallet object
*/
public function wallet(): ?Wallet
{
// Note: Not all domains have a entitlement/wallet
- $entitlement = $this->entitlement()->withTrashed()->first();
+ $entitlement = $this->entitlement()->withTrashed()->orderBy('created_at', 'desc')->first();
return $entitlement ? $entitlement->wallet : null;
}
}
diff --git a/src/app/Group.php b/src/app/Group.php
index 5661f232..872fce3c 100644
--- a/src/app/Group.php
+++ b/src/app/Group.php
@@ -1,280 +1,280 @@
<?php
namespace App;
use App\Wallet;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
/**
* The eloquent definition of a Group.
*
* @property int $id The group identifier
* @property string $email An email address
* @property string $members A comma-separated list of email addresses
* @property int $status The group status
*/
class Group extends Model
{
use SoftDeletes;
// we've simply never heard of this domain
public const STATUS_NEW = 1 << 0;
// it's been activated
public const STATUS_ACTIVE = 1 << 1;
// domain has been suspended.
public const STATUS_SUSPENDED = 1 << 2;
// domain has been deleted
public const STATUS_DELETED = 1 << 3;
// domain has been created in LDAP
public const STATUS_LDAP_READY = 1 << 4;
public $incrementing = false;
protected $keyType = 'bigint';
protected $fillable = [
'email',
'status',
'members'
];
/**
* Assign the group to a wallet.
*
* @param \App\Wallet $wallet The wallet
*
* @return \App\Group Self
* @throws \Exception
*/
public function assignToWallet(Wallet $wallet): Group
{
if (empty($this->id)) {
throw new \Exception("Group not yet exists");
}
if ($this->entitlement()->count()) {
throw new \Exception("Group already assigned to a wallet");
}
$sku = \App\Sku::where('title', 'group')->first();
$exists = $wallet->entitlements()->where('sku_id', $sku->id)->count();
\App\Entitlement::create([
'wallet_id' => $wallet->id,
'sku_id' => $sku->id,
'cost' => $exists >= $sku->units_free ? $sku->cost : 0,
'entitleable_id' => $this->id,
'entitleable_type' => Group::class
]);
return $this;
}
/**
* Returns group domain.
*
* @return ?\App\Domain The domain group belongs to, NULL if it does not exist
*/
public function domain(): ?Domain
{
list($local, $domainName) = explode('@', $this->email);
return Domain::where('namespace', $domainName)->first();
}
/**
* Find whether an email address exists as a group (including deleted groups).
*
* @param string $email Email address
* @param bool $return_group Return Group instance instead of boolean
*
* @return \App\Group|bool True or Group model object if found, False otherwise
*/
public static function emailExists(string $email, bool $return_group = false)
{
if (strpos($email, '@') === false) {
return false;
}
$email = \strtolower($email);
$group = self::withTrashed()->where('email', $email)->first();
if ($group) {
return $return_group ? $group : true;
}
return false;
}
/**
* The group entitlement.
*
* @return \Illuminate\Database\Eloquent\Relations\MorphOne
*/
public function entitlement()
{
return $this->morphOne('App\Entitlement', 'entitleable');
}
/**
* Group members propert accessor. Converts internal comma-separated list into an array
*
* @param string $members Comma-separated list of email addresses
*
* @return array Email addresses of the group members, as an array
*/
public function getMembersAttribute($members): array
{
return $members ? explode(',', $members) : [];
}
/**
* Returns whether this domain is active.
*
* @return bool
*/
public function isActive(): bool
{
return ($this->status & self::STATUS_ACTIVE) > 0;
}
/**
* Returns whether this domain is deleted.
*
* @return bool
*/
public function isDeleted(): bool
{
return ($this->status & self::STATUS_DELETED) > 0;
}
/**
* Returns whether this domain is new.
*
* @return bool
*/
public function isNew(): bool
{
return ($this->status & self::STATUS_NEW) > 0;
}
/**
* Returns whether this domain is registered in LDAP.
*
* @return bool
*/
public function isLdapReady(): bool
{
return ($this->status & self::STATUS_LDAP_READY) > 0;
}
/**
* Returns whether this domain is suspended.
*
* @return bool
*/
public function isSuspended(): bool
{
return ($this->status & self::STATUS_SUSPENDED) > 0;
}
/**
* Ensure the email is appropriately cased.
*
* @param string $email Group email address
*/
public function setEmailAttribute(string $email)
{
$this->attributes['email'] = strtolower($email);
}
/**
* Ensure the members are appropriately formatted.
*
* @param array $members Email addresses of the group members
*/
public function setMembersAttribute(array $members): void
{
$members = array_unique(array_filter(array_map('strtolower', $members)));
sort($members);
$this->attributes['members'] = implode(',', $members);
}
/**
* Group status mutator
*
* @throws \Exception
*/
public function setStatusAttribute($status)
{
$new_status = 0;
$allowed_values = [
self::STATUS_NEW,
self::STATUS_ACTIVE,
self::STATUS_SUSPENDED,
self::STATUS_DELETED,
self::STATUS_LDAP_READY,
];
foreach ($allowed_values as $value) {
if ($status & $value) {
$new_status |= $value;
$status ^= $value;
}
}
if ($status > 0) {
throw new \Exception("Invalid group status: {$status}");
}
$this->attributes['status'] = $new_status;
}
/**
* Suspend this group.
*
* @return void
*/
public function suspend(): void
{
if ($this->isSuspended()) {
return;
}
$this->status |= Group::STATUS_SUSPENDED;
$this->save();
}
/**
* Unsuspend this group.
*
* @return void
*/
public function unsuspend(): void
{
if (!$this->isSuspended()) {
return;
}
$this->status ^= Group::STATUS_SUSPENDED;
$this->save();
}
/**
* Returns the wallet by which the group is controlled
*
* @return \App\Wallet A wallet object
*/
public function wallet(): ?Wallet
{
// Note: Not all domains have a entitlement/wallet
- $entitlement = $this->entitlement()->withTrashed()->first();
+ $entitlement = $this->entitlement()->withTrashed()->orderBy('created_at', 'desc')->first();
return $entitlement ? $entitlement->wallet : null;
}
}
diff --git a/src/app/OpenVidu/RoomSetting.php b/src/app/OpenVidu/RoomSetting.php
index 4b4ac9ca..c731d707 100644
--- a/src/app/OpenVidu/RoomSetting.php
+++ b/src/app/OpenVidu/RoomSetting.php
@@ -1,36 +1,32 @@
<?php
namespace App\OpenVidu;
use Illuminate\Database\Eloquent\Model;
/**
* A collection of settings for a Room.
*
* @property int $id
* @property int $room_id
* @property string $key
* @property string $value
*/
class RoomSetting extends Model
{
protected $fillable = [
'room_id', 'key', 'value'
];
protected $table = 'openvidu_room_settings';
/**
- * The user to which this setting belongs.
+ * The room to which this setting belongs.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function room()
{
- return $this->belongsTo(
- '\App\OpenVidu\Room',
- 'room_id', /* local */
- 'id' /* remote */
- );
+ return $this->belongsTo('\App\OpenVidu\Room', 'room_id', 'id');
}
}
diff --git a/src/app/Payment.php b/src/app/Payment.php
index ba088b68..6d126130 100644
--- a/src/app/Payment.php
+++ b/src/app/Payment.php
@@ -1,61 +1,57 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
/**
* A payment operation on a wallet.
*
* @property int $amount Amount of money in cents of CHF
* @property string $description Payment description
* @property string $id Mollie's Payment ID
* @property \App\Wallet $wallet The wallet
* @property string $wallet_id The ID of the wallet
* @property string $currency Currency of this payment
* @property int $currency_amount Amount of money in cents of $currency
*/
class Payment extends Model
{
public $incrementing = false;
protected $keyType = 'string';
protected $casts = [
'amount' => 'integer'
];
protected $fillable = [
'id',
'wallet_id',
'amount',
'description',
'provider',
'status',
'type',
'currency',
'currency_amount',
];
/**
* Ensure the currency is appropriately cased.
*/
public function setCurrencyAttribute($currency)
{
$this->attributes['currency'] = strtoupper($currency);
}
/**
* The wallet to which this payment belongs.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function wallet()
{
- return $this->belongsTo(
- '\App\Wallet',
- 'wallet_id', /* local */
- 'id' /* remote */
- );
+ return $this->belongsTo('\App\Wallet', 'wallet_id', 'id');
}
}
diff --git a/src/app/User.php b/src/app/User.php
index 0b8c4685..dc4605ed 100644
--- a/src/app/User.php
+++ b/src/app/User.php
@@ -1,714 +1,714 @@
<?php
namespace App;
use App\Entitlement;
use App\UserAlias;
use App\Sku;
use App\Traits\UserAliasesTrait;
use App\Traits\SettingsTrait;
use App\Wallet;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Iatstuti\Database\Support\NullableFields;
use Tymon\JWTAuth\Contracts\JWTSubject;
/**
* The eloquent definition of a User.
*
* @property string $email
* @property int $id
* @property string $password
* @property int $status
*/
class User extends Authenticatable implements JWTSubject
{
use NullableFields;
use UserAliasesTrait;
use SettingsTrait;
use SoftDeletes;
// a new user, default on creation
public const STATUS_NEW = 1 << 0;
// it's been activated
public const STATUS_ACTIVE = 1 << 1;
// user has been suspended
public const STATUS_SUSPENDED = 1 << 2;
// user has been deleted
public const STATUS_DELETED = 1 << 3;
// user has been created in LDAP
public const STATUS_LDAP_READY = 1 << 4;
// user mailbox has been created in IMAP
public const STATUS_IMAP_READY = 1 << 5;
// change the default primary key type
public $incrementing = false;
protected $keyType = 'bigint';
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'id',
'email',
'password',
'password_ldap',
'status'
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password',
'password_ldap',
'role'
];
protected $nullable = [
'password',
'password_ldap'
];
/**
* Any wallets on which this user is a controller.
*
* This does not include wallets owned by the user.
*
* @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
);
}
/**
* Email aliases of this user.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function aliases()
{
return $this->hasMany('App\UserAlias', 'user_id');
}
/**
* Assign a package to a user. The user should not have any existing entitlements.
*
* @param \App\Package $package The package to assign.
* @param \App\User|null $user Assign the package to another user.
*
* @return \App\User
*/
public function assignPackage($package, $user = null)
{
if (!$user) {
$user = $this;
}
$wallet_id = $this->wallets()->first()->id;
foreach ($package->skus as $sku) {
for ($i = $sku->pivot->qty; $i > 0; $i--) {
\App\Entitlement::create(
[
'wallet_id' => $wallet_id,
'sku_id' => $sku->id,
'cost' => $sku->pivot->cost(),
'entitleable_id' => $user->id,
'entitleable_type' => User::class
]
);
}
}
return $user;
}
/**
* Assign a package plan to a user.
*
* @param \App\Plan $plan The plan to assign
* @param \App\Domain $domain Optional domain object
*
* @return \App\User Self
*/
public function assignPlan($plan, $domain = null): User
{
$this->setSetting('plan_id', $plan->id);
foreach ($plan->packages as $package) {
if ($package->isDomain()) {
$domain->assignPackage($package, $this);
} else {
$this->assignPackage($package);
}
}
return $this;
}
/**
* Assign a Sku to a user.
*
* @param \App\Sku $sku The sku to assign.
* @param int $count Count of entitlements to add
*
* @return \App\User Self
* @throws \Exception
*/
public function assignSku(Sku $sku, int $count = 1): User
{
// TODO: I guess wallet could be parametrized in future
$wallet = $this->wallet();
$exists = $this->entitlements()->where('sku_id', $sku->id)->count();
while ($count > 0) {
\App\Entitlement::create([
'wallet_id' => $wallet->id,
'sku_id' => $sku->id,
'cost' => $exists >= $sku->units_free ? $sku->cost : 0,
'entitleable_id' => $this->id,
'entitleable_type' => User::class
]);
$exists++;
$count--;
}
return $this;
}
/**
* Check if current user can delete another object.
*
* @param \App\User|\App\Domain $object A user|domain object
*
* @return bool True if he can, False otherwise
*/
public function canDelete($object): bool
{
if (!method_exists($object, 'wallet')) {
return false;
}
$wallet = $object->wallet();
// TODO: For now controller can delete/update the account owner,
// this may change in future, controllers are not 0-regression feature
return $this->wallets->contains($wallet) || $this->accounts->contains($wallet);
}
/**
* Check if current user can read data of another object.
*
* @param \App\User|\App\Domain|\App\Wallet $object A user|domain|wallet object
*
* @return bool True if he can, False otherwise
*/
public function canRead($object): bool
{
if ($this->role == "admin") {
return true;
}
if ($object instanceof User && $this->id == $object->id) {
return true;
}
if ($object instanceof Wallet) {
return $object->user_id == $this->id || $object->controllers->contains($this);
}
if (!method_exists($object, 'wallet')) {
return false;
}
$wallet = $object->wallet();
return $this->wallets->contains($wallet) || $this->accounts->contains($wallet);
}
/**
* Check if current user can update data of another object.
*
* @param \App\User|\App\Domain $object A user|domain object
*
* @return bool True if he can, False otherwise
*/
public function canUpdate($object): bool
{
if (!method_exists($object, 'wallet')) {
return false;
}
if ($object instanceof User && $this->id == $object->id) {
return true;
}
return $this->canDelete($object);
}
/**
* Return the \App\Domain for this user.
*
* @return \App\Domain|null
*/
public function domain()
{
list($local, $domainName) = explode('@', $this->email);
$domain = \App\Domain::withTrashed()->where('namespace', $domainName)->first();
return $domain;
}
/**
* List the domains to which this user is entitled.
*
* @return Domain[]
*/
public function domains()
{
$domains = Domain::whereRaw(sprintf('(type & %s)', Domain::TYPE_PUBLIC))
->whereRaw(sprintf('(status & %s)', Domain::STATUS_ACTIVE))
->get()
->all();
foreach ($this->wallets as $wallet) {
$entitlements = $wallet->entitlements()->where('entitleable_type', Domain::class)->get();
foreach ($entitlements as $entitlement) {
$domains[] = $entitlement->entitleable;
}
}
foreach ($this->accounts as $wallet) {
$entitlements = $wallet->entitlements()->where('entitleable_type', Domain::class)->get();
foreach ($entitlements as $entitlement) {
$domains[] = $entitlement->entitleable;
}
}
return $domains;
}
/**
* The user entitlement.
*
* @return \Illuminate\Database\Eloquent\Relations\MorphOne
*/
public function entitlement()
{
return $this->morphOne('App\Entitlement', 'entitleable');
}
/**
* Entitlements for this user.
*
* Note that these are entitlements that apply to the user account, and not entitlements that
* this user owns.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function entitlements()
{
return $this->hasMany('App\Entitlement', 'entitleable_id', 'id')
->where('entitleable_type', User::class);
}
/**
* Find whether an email address exists as a user (including deleted users).
*
* @param string $email Email address
* @param bool $return_user Return User instance instead of boolean
*
* @return \App\User|bool True or User model object if found, False otherwise
*/
public static function emailExists(string $email, bool $return_user = false)
{
if (strpos($email, '@') === false) {
return false;
}
$email = \strtolower($email);
$user = self::withTrashed()->where('email', $email)->first();
if ($user) {
return $return_user ? $user : true;
}
return false;
}
/**
* Helper to find user by email address, whether it is
* main email address, alias or an external email.
*
* If there's more than one alias NULL will be returned.
*
* @param string $email Email address
* @param bool $external Search also for an external email
*
* @return \App\User User model object if found
*/
public static function findByEmail(string $email, bool $external = false): ?User
{
if (strpos($email, '@') === false) {
return null;
}
$email = \strtolower($email);
$user = self::where('email', $email)->first();
if ($user) {
return $user;
}
$aliases = UserAlias::where('alias', $email)->get();
if (count($aliases) == 1) {
return $aliases->first()->user;
}
// TODO: External email
return null;
}
public function getJWTIdentifier()
{
return $this->getKey();
}
public function getJWTCustomClaims()
{
return [];
}
/**
* Return groups controlled by the current user.
*
* @param bool $with_accounts Include groups assigned to wallets
* the current user controls but not owns.
*
* @return \Illuminate\Database\Eloquent\Builder Query builder
*/
public function groups($with_accounts = true)
{
$wallets = $this->wallets()->pluck('id')->all();
if ($with_accounts) {
$wallets = array_merge($wallets, $this->accounts()->pluck('wallet_id')->all());
}
return Group::select(['groups.*', 'entitlements.wallet_id'])
->distinct()
->join('entitlements', 'entitlements.entitleable_id', '=', 'groups.id')
->whereIn('entitlements.wallet_id', $wallets)
->where('entitlements.entitleable_type', Group::class);
}
/**
* Check if user has an entitlement for the specified SKU.
*
* @param string $title The SKU title
*
* @return bool True if specified SKU entitlement exists
*/
public function hasSku($title): bool
{
$sku = Sku::where('title', $title)->first();
if (!$sku) {
return false;
}
return $this->entitlements()->where('sku_id', $sku->id)->count() > 0;
}
/**
* Returns whether this domain is active.
*
* @return bool
*/
public function isActive(): bool
{
return ($this->status & self::STATUS_ACTIVE) > 0;
}
/**
* Returns whether this domain is deleted.
*
* @return bool
*/
public function isDeleted(): bool
{
return ($this->status & self::STATUS_DELETED) > 0;
}
/**
* Returns whether this (external) domain has been verified
* to exist in DNS.
*
* @return bool
*/
public function isImapReady(): bool
{
return ($this->status & self::STATUS_IMAP_READY) > 0;
}
/**
* Returns whether this user is registered in LDAP.
*
* @return bool
*/
public function isLdapReady(): bool
{
return ($this->status & self::STATUS_LDAP_READY) > 0;
}
/**
* Returns whether this user is new.
*
* @return bool
*/
public function isNew(): bool
{
return ($this->status & self::STATUS_NEW) > 0;
}
/**
* Returns whether this domain is suspended.
*
* @return bool
*/
public function isSuspended(): bool
{
return ($this->status & self::STATUS_SUSPENDED) > 0;
}
/**
* A shortcut to get the user name.
*
* @param bool $fallback Return "<aa.name> User" if there's no name
*
* @return string Full user name
*/
public function name(bool $fallback = false): string
{
$firstname = $this->getSetting('first_name');
$lastname = $this->getSetting('last_name');
$name = trim($firstname . ' ' . $lastname);
if (empty($name) && $fallback) {
return \config('app.name') . ' User';
}
return $name;
}
/**
* Remove a number of entitlements for the SKU.
*
* @param \App\Sku $sku The SKU
* @param int $count The number of entitlements to remove
*
* @return User Self
*/
public function removeSku(Sku $sku, int $count = 1): User
{
$entitlements = $this->entitlements()
->where('sku_id', $sku->id)
->orderBy('cost', 'desc')
->orderBy('created_at')
->get();
$entitlements_count = count($entitlements);
foreach ($entitlements as $entitlement) {
if ($entitlements_count <= $sku->units_free) {
continue;
}
if ($count > 0) {
$entitlement->delete();
$entitlements_count--;
$count--;
}
}
return $this;
}
/**
* Any (additional) properties of this user.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function settings()
{
return $this->hasMany('App\UserSetting', 'user_id');
}
/**
* Suspend this domain.
*
* @return void
*/
public function suspend(): void
{
if ($this->isSuspended()) {
return;
}
$this->status |= User::STATUS_SUSPENDED;
$this->save();
}
/**
* Unsuspend this domain.
*
* @return void
*/
public function unsuspend(): void
{
if (!$this->isSuspended()) {
return;
}
$this->status ^= User::STATUS_SUSPENDED;
$this->save();
}
/**
* Return users controlled by the current user.
*
* @param bool $with_accounts Include users assigned to wallets
* the current user controls but not owns.
*
* @return \Illuminate\Database\Eloquent\Builder Query builder
*/
public function users($with_accounts = true)
{
$wallets = $this->wallets()->pluck('id')->all();
if ($with_accounts) {
$wallets = array_merge($wallets, $this->accounts()->pluck('wallet_id')->all());
}
return $this->select(['users.*', 'entitlements.wallet_id'])
->distinct()
->leftJoin('entitlements', 'entitlements.entitleable_id', '=', 'users.id')
->whereIn('entitlements.wallet_id', $wallets)
->where('entitlements.entitleable_type', User::class);
}
/**
* Verification codes for this user.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function verificationcodes()
{
return $this->hasMany('App\VerificationCode', 'user_id', 'id');
}
/**
* Returns the wallet by which the user is controlled
*
* @return ?\App\Wallet A wallet object
*/
public function wallet(): ?Wallet
{
- $entitlement = $this->entitlement()->withTrashed()->first();
+ $entitlement = $this->entitlement()->withTrashed()->orderBy('created_at', 'desc')->first();
// TODO: No entitlement should not happen, but in tests we have
// such cases, so we fallback to the user's wallet in this case
return $entitlement ? $entitlement->wallet : $this->wallets()->first();
}
/**
* Wallets this user owns.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function wallets()
{
return $this->hasMany('App\Wallet');
}
/**
* User password mutator
*
* @param string $password The password in plain text.
*
* @return void
*/
public function setPasswordAttribute($password)
{
if (!empty($password)) {
$this->attributes['password'] = bcrypt($password, [ "rounds" => 12 ]);
$this->attributes['password_ldap'] = '{SSHA512}' . base64_encode(
pack('H*', hash('sha512', $password))
);
}
}
/**
* User LDAP password mutator
*
* @param string $password The password in plain text.
*
* @return void
*/
public function setPasswordLdapAttribute($password)
{
$this->setPasswordAttribute($password);
}
/**
* User status mutator
*
* @throws \Exception
*/
public function setStatusAttribute($status)
{
$new_status = 0;
$allowed_values = [
self::STATUS_NEW,
self::STATUS_ACTIVE,
self::STATUS_SUSPENDED,
self::STATUS_DELETED,
self::STATUS_LDAP_READY,
self::STATUS_IMAP_READY,
];
foreach ($allowed_values as $value) {
if ($status & $value) {
$new_status |= $value;
$status ^= $value;
}
}
if ($status > 0) {
throw new \Exception("Invalid user status: {$status}");
}
$this->attributes['status'] = $new_status;
}
}
diff --git a/src/app/UserAlias.php b/src/app/UserAlias.php
index caae7739..15ca4c39 100644
--- a/src/app/UserAlias.php
+++ b/src/app/UserAlias.php
@@ -1,33 +1,29 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
/**
* A email address alias for a User.
*
* @property string $alias
* @property int $id
* @property int $user_id
*/
class UserAlias extends Model
{
protected $fillable = [
'user_id', 'alias'
];
/**
* The user to which this alias belongs.
*
* @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', 'id');
}
}
diff --git a/src/app/UserSetting.php b/src/app/UserSetting.php
index 0733b1f7..d160e504 100644
--- a/src/app/UserSetting.php
+++ b/src/app/UserSetting.php
@@ -1,34 +1,30 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
/**
* A collection of settings for a User.
*
* @property int $id
* @property int $user_id
* @property string $key
* @property string $value
*/
class UserSetting extends Model
{
protected $fillable = [
'user_id', 'key', 'value'
];
/**
* The user to which this setting belongs.
*
* @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', 'id');
}
}
diff --git a/src/app/WalletSetting.php b/src/app/WalletSetting.php
index c75fda6f..1f3f6e0a 100644
--- a/src/app/WalletSetting.php
+++ b/src/app/WalletSetting.php
@@ -1,34 +1,30 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
/**
* A collection of settings for a Wallet.
*
* @property int $id
* @property string $wallet_id
* @property string $key
* @property string $value
*/
class WalletSetting extends Model
{
protected $fillable = [
'wallet_id', 'key', 'value'
];
/**
* The wallet to which this setting belongs.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function wallet()
{
- return $this->belongsTo(
- '\App\Wallet',
- 'wallet_id', /* local */
- 'id' /* remote */
- );
+ return $this->belongsTo('\App\Wallet', 'wallet_id', 'id');
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Feb 1, 9:07 AM (1 d, 18 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
426644
Default Alt Text
(43 KB)
Attached To
Mode
R2 kolab
Attached
Detach File
Event Timeline
Log In to Comment