. */ namespace App; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Auth; use Illuminate\Foundation\Auth\User as Authenticatable; use App\Http\Controllers\Account\AuthenticateController as WebAuthenticateController; use Illuminate\Support\Str; use App\ApiKey; use App\Password; use App\EmailChanged; use App\Mail\ChangingEmail; use Carbon\Carbon; class Account extends Authenticatable { use HasFactory; protected $with = ['passwords', 'admin', 'emailChanged', 'alias', 'activationExpiration', 'types', 'actions']; protected $hidden = ['alias', 'expire_time', 'confirmation_key', 'provisioning_token', 'pivot']; protected $dateTimes = ['creation_time']; protected $appends = ['realm', 'phone', 'confirmation_key_expires']; protected $casts = [ 'activated' => 'boolean', ]; public $timestamps = false; public static $dtmfProtocols = ['sipinfo' => 'SIPInfo', 'rfc2833' => 'RFC2833', 'sipmessage' => 'SIP Message']; /** * Scopes */ protected static function booted() { static::addGlobalScope('domain', function (Builder $builder) { if (Auth::hasUser()) { $user = Auth::user(); if (!$user->admin || !config('app.admins_manage_multi_domains')) { $builder->where('domain', config('app.sip_domain')); } return; } $builder->where('domain', config('app.sip_domain')); }); /** * External account handling */ static::creating(function ($account) { if (config('app.consume_external_account_on_create') && !getAvailableExternalAccount()) { abort(403, 'Accounts cannot be created on the server'); } }); static::created(function ($account) { if (config('app.consume_external_account_on_create')) { $account->attachExternalAccount(); } }); } public function scopeSip($query, string $sip) { if (\str_contains($sip, '@')) { list($usernane, $domain) = explode('@', $sip); return $query->where('username', $usernane) ->where('domain', $domain); }; return $query->where('id', '<', 0); } /** * Relations */ public function actions() { return $this->hasMany('App\AccountAction')->whereIn('account_id', function ($query) { $query->select('id') ->from('accounts') ->whereNotNull('dtmf_protocol'); }); } public function activationExpiration() { return $this->hasOne(ActivationExpiration::class); } public function admin() { return $this->hasOne(Admin::class); } public function alias() { return $this->hasOne(Alias::class); } public function apiKey() { return $this->hasOne(ApiKey::class); } public function externalAccount() { return $this->hasOne(ExternalAccount::class); } public function contacts() { return $this->belongsToMany(Account::class, 'contacts', 'account_id', 'contact_id'); } public function emailChanged() { return $this->hasOne(EmailChanged::class); } public function nonces() { return $this->hasMany(DigestNonce::class); } public function authTokens() { return $this->hasMany(AuthToken::class); } public function passwords() { return $this->hasMany(Password::class); } public function phoneChangeCode() { return $this->hasOne(PhoneChangeCode::class); } public function types() { return $this->belongsToMany(AccountType::class); } /** * Attributes */ public function getIdentifierAttribute() { return $this->attributes['username'] . '@' . $this->attributes['domain']; } public function getFullIdentifierAttribute() { $displayName = $this->attributes['display_name'] ? '"' . $this->attributes['display_name'] . '" ' : ''; return $displayName . 'getIdentifierAttribute() . '>'; } public function getRealmAttribute() { return config('app.realm'); } public function getResolvedRealmAttribute() { return config('app.realm') ?? $this->domain; } public function getPhoneAttribute() { if ($this->alias) { return $this->alias->alias; } return null; } public function getConfirmationKeyExpiresAttribute() { if ($this->activationExpiration) { return $this->activationExpiration->expires->format('Y-m-d H:i:s'); } return null; } public function getSha256PasswordAttribute() { return $this->passwords()->where('algorithm', 'SHA-256')->exists(); } public static function dtmfProtocolsRule() { return implode(',', array_keys(self::$dtmfProtocols)); } public function getResolvedDtmfProtocolAttribute() { return self::$dtmfProtocols[$this->attributes['dtmf_protocol']]; } /** * Utils */ public function activationExpired(): bool { return ($this->activationExpiration && $this->activationExpiration->isExpired()); } public function attachExternalAccount(): bool { $externalAccount = getAvailableExternalAccount(); if (!$externalAccount) abort(403, 'No External Account left'); $externalAccount->account_id = $this->id; $externalAccount->used = true; return $externalAccount->save(); } public function requestEmailUpdate(string $newEmail) { // Remove all the old requests $this->emailChanged()->delete(); // Create a new one $emailChanged = new EmailChanged; $emailChanged->new_email = $newEmail; $emailChanged->hash = Str::random(16); $emailChanged->account_id = $this->id; $emailChanged->save(); $this->refresh(); // Set it temporary to try to send the validation email $this->email = $newEmail; Mail::to($this)->send(new ChangingEmail($this)); } public function generateApiKey(): ApiKey { $this->apiKey()->delete(); $apiKey = new ApiKey; $apiKey->account_id = $this->id; $apiKey->last_used_at = Carbon::now(); $apiKey->key = Str::random(40); $apiKey->save(); return $apiKey; } public function generateAuthToken(): AuthToken { // Clean the expired and previous ones AuthToken::where( 'created_at', '<', Carbon::now()->subMinutes(AuthToken::$expirationMinutes) )->orWhere('account_id', $this->id) ->delete(); $authToken = new AuthToken; $authToken->account_id = $this->id; $authToken->token = Str::random(32); $authToken->save(); return $authToken; } public function provision(): string { $this->provisioning_token = Str::random(WebAuthenticateController::$emailCodeSize); return $this->provisioning_token; } public function isAdmin() { return ($this->admin); } public function hasTombstone() { return AccountTombstone::where('username', $this->attributes['username']) ->where('domain', $this->attributes['domain']) ->exists(); } public function updatePassword($newPassword, ?string $algorithm = 'SHA-256') { $this->passwords()->delete(); $password = new Password; $password->account_id = $this->id; $password->password = bchash($this->username, $this->resolvedRealm, $newPassword, $algorithm); $password->algorithm = $algorithm; $password->save(); } public function toVcard4() { $vcard = 'BEGIN:VCARD VERSION:4.0 KIND:individual IMPP:sip:' . $this->getIdentifierAttribute(); $vcard .= ' FN:'; $vcard .= !empty($this->attributes['display_name']) ? $this->attributes['display_name'] : $this->getIdentifierAttribute(); if ($this->dtmf_protocol) { $vcard .= ' X-LINPHONE-ACCOUNT-DTMF-PROTOCOL:' . $this->dtmf_protocol; } foreach ($this->types as $type) { $vcard .= ' X-LINPHONE-ACCOUNT-TYPE:' . $type->key; } foreach ($this->actions as $action) { $vcard .= ' X-LINPHONE-ACCOUNT-ACTION:' . $action->key . ';' . $action->code; } return $vcard . ' END:VCARD'; } }