Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F1841777
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
38 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/app/Console/Commands/Status/Health.php b/src/app/Console/Commands/Status/Health.php
index f9d82889..758693b3 100644
--- a/src/app/Console/Commands/Status/Health.php
+++ b/src/app/Console/Commands/Status/Health.php
@@ -1,198 +1,201 @@
<?php
namespace App\Console\Commands\Status;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redis;
use App\Backends\DAV;
use App\Backends\IMAP;
use App\Backends\LDAP;
use App\Backends\OpenExchangeRates;
use App\Backends\Roundcube;
use App\Providers\Payment\Mollie;
//TODO stripe
//TODO firebase
class Health extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
- protected $signature = 'status:health {--check=*: Valid checks are DB, Redis, IMAP, LDAP, Roundcube, Meet, DAV, Mollie, OpenExchangeRates}';
+ protected $signature = 'status:health
+ {--check=*: One of DB, Redis, IMAP, LDAP, Roundcube, Meet, DAV, Mollie, OpenExchangeRates}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Check health of backends';
private function checkDB()
{
try {
$result = DB::select("SELECT 1");
return true;
} catch (\Exception $exception) {
$this->line($exception);
return false;
}
}
private function checkOpenExchangeRates()
{
try {
OpenExchangeRates::healthcheck();
return true;
} catch (\Exception $exception) {
$this->line($exception);
return false;
}
}
private function checkMollie()
{
try {
return Mollie::healthcheck();
} catch (\Exception $exception) {
$this->line($exception);
return false;
}
}
private function checkDAV()
{
try {
DAV::healthcheck();
return true;
} catch (\Exception $exception) {
$this->line($exception);
return false;
}
}
private function checkLDAP()
{
try {
LDAP::healthcheck();
return true;
} catch (\Exception $exception) {
$this->line($exception);
return false;
}
}
private function checkIMAP()
{
try {
IMAP::healthcheck();
return true;
} catch (\Exception $exception) {
$this->line($exception);
return false;
}
}
private function checkRoundcube()
{
try {
//TODO maybe run a select?
Roundcube::dbh();
return true;
} catch (\Exception $exception) {
$this->line($exception);
return false;
}
}
private function checkRedis()
{
try {
Redis::connection();
return true;
} catch (\Exception $exception) {
$this->line($exception);
return false;
}
}
private function checkMeet()
{
$urls = \config('meet.api_urls');
$success = true;
+
foreach ($urls as $url) {
$this->line("Checking $url");
try {
$client = new \GuzzleHttp\Client(
[
'http_errors' => false, // No exceptions from Guzzle
'base_uri' => $url,
'verify' => \config('meet.api_verify_tls'),
'headers' => [
'X-Auth-Token' => \config('meet.api_token'),
],
'connect_timeout' => 10,
'timeout' => 10,
'on_stats' => function (\GuzzleHttp\TransferStats $stats) {
$threshold = \config('logging.slow_log');
if ($threshold && ($sec = $stats->getTransferTime()) > $threshold) {
$url = $stats->getEffectiveUri();
$method = $stats->getRequest()->getMethod();
\Log::warning(sprintf("[STATS] %s %s: %.4f sec.", $method, $url, $sec));
}
},
]
);
$response = $client->request('GET', "ping");
if ($response->getStatusCode() != 200) {
- $this->line("Backend {$url} not available. Status: {$response->getStatusCode()} Reason: {$response->getReasonPhrase()}");
+ $code = $response->getStatusCode();
+ $reason = $response->getReasonPhrase();
$success = false;
+ $this->line("Backend {$url} not available. Status: {$code} Reason: {$reason}");
}
} catch (\Exception $exception) {
- $this->line("Backend {$url} not available:");
- $this->line($exception);
$success = false;
+ $this->line("Backend {$url} not available. Error: {$exception}");
}
}
return $success;
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$result = 0;
$steps = $this->option('check');
if (empty($steps)) {
$steps = [
'DB', 'Redis', 'IMAP', 'Roundcube', 'Meet', 'DAV', 'Mollie', 'OpenExchangeRates',
];
if (\config('app.with_ldap')) {
array_unshift($steps, 'LDAP');
}
}
foreach ($steps as $step) {
$func = "check{$step}";
$this->line("Checking {$step}...");
if ($this->{$func}()) {
$this->info("OK");
} else {
$this->error("Not found");
$result = 1;
}
}
return $result;
}
}
diff --git a/src/app/Http/Controllers/API/SignupController.php b/src/app/Http/Controllers/API/SignupController.php
index 49a89d18..b1abca6b 100644
--- a/src/app/Http/Controllers/API/SignupController.php
+++ b/src/app/Http/Controllers/API/SignupController.php
@@ -1,616 +1,617 @@
<?php
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Jobs\SignupVerificationEmail;
use App\Discount;
use App\Domain;
use App\Plan;
use App\Providers\PaymentProvider;
use App\Rules\SignupExternalEmail;
use App\Rules\SignupToken;
use App\Rules\Password;
use App\Rules\UserEmailDomain;
use App\Rules\UserEmailLocal;
use App\SignupCode;
use App\SignupInvitation;
use App\User;
use App\Utils;
use App\VatRate;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
/**
* Signup process API
*/
class SignupController extends Controller
{
/**
* Returns plans definitions for signup.
*
* @param \Illuminate\Http\Request $request HTTP request
*
* @return \Illuminate\Http\JsonResponse JSON response
*/
public function plans(Request $request)
{
// Use reverse order just to have individual on left, group on right ;)
// But prefer monthly on left, yearly on right
$plans = Plan::withEnvTenantContext()->orderBy('months')->orderByDesc('title')->get()
->map(function ($plan) {
$button = self::trans("app.planbutton-{$plan->title}");
if (strpos($button, 'app.planbutton') !== false) {
$button = self::trans('app.planbutton', ['plan' => $plan->name]);
}
return [
'title' => $plan->title,
'name' => $plan->name,
'button' => $button,
'description' => $plan->description,
'mode' => $plan->mode ?: Plan::MODE_EMAIL,
'isDomain' => $plan->hasDomain(),
];
})
->all();
return response()->json(['status' => 'success', 'plans' => $plans]);
}
/**
* Returns list of public domains for signup.
*
* @param \Illuminate\Http\Request $request HTTP request
*
* @return \Illuminate\Http\JsonResponse JSON response
*/
public function domains(Request $request)
{
return response()->json(['status' => 'success', 'domains' => Domain::getPublicDomains()]);
}
/**
* Starts signup process.
*
* Verifies user name and email/phone, sends verification email/sms message.
* Returns the verification code.
*
* @param \Illuminate\Http\Request $request HTTP request
*
* @return \Illuminate\Http\JsonResponse JSON response
*/
public function init(Request $request)
{
$rules = [
'first_name' => 'max:128',
'last_name' => 'max:128',
'voucher' => 'max:32',
];
$plan = $this->getPlan();
if ($plan->mode == Plan::MODE_TOKEN) {
$rules['token'] = ['required', 'string', new SignupToken()];
} else {
$rules['email'] = ['required', 'string', new SignupExternalEmail()];
}
// Check required fields, validate input
$v = Validator::make($request->all(), $rules);
if ($v->fails()) {
return response()->json(['status' => 'error', 'errors' => $v->errors()->toArray()], 422);
}
// Generate the verification code
$code = SignupCode::create([
'email' => $plan->mode == Plan::MODE_TOKEN ? $request->token : $request->email,
'first_name' => $request->first_name,
'last_name' => $request->last_name,
'plan' => $plan->title,
'voucher' => $request->voucher,
]);
$response = [
'status' => 'success',
'code' => $code->code,
'mode' => $plan->mode ?: 'email',
];
if ($plan->mode == Plan::MODE_TOKEN) {
// Token verification, jump to the last step
$has_domain = $plan->hasDomain();
$response['short_code'] = $code->short_code;
$response['is_domain'] = $has_domain;
$response['domains'] = $has_domain ? [] : Domain::getPublicDomains();
} else {
// External email verification, send an email message
SignupVerificationEmail::dispatch($code);
}
return response()->json($response);
}
/**
* Returns signup invitation information.
*
* @param string $id Signup invitation identifier
*
* @return \Illuminate\Http\JsonResponse|void
*/
public function invitation($id)
{
$invitation = SignupInvitation::withEnvTenantContext()->find($id);
if (empty($invitation) || $invitation->isCompleted()) {
return $this->errorResponse(404);
}
$has_domain = $this->getPlan()->hasDomain();
$result = [
'id' => $id,
'is_domain' => $has_domain,
'domains' => $has_domain ? [] : Domain::getPublicDomains(),
];
return response()->json($result);
}
/**
* Validation of the verification code.
*
* @param \Illuminate\Http\Request $request HTTP request
* @param bool $update Update the signup code record
*
* @return \Illuminate\Http\JsonResponse JSON response
*/
public function verify(Request $request, $update = true)
{
// Validate the request args
$v = Validator::make(
$request->all(),
[
'code' => 'required',
'short_code' => 'required',
]
);
if ($v->fails()) {
return response()->json(['status' => 'error', 'errors' => $v->errors()], 422);
}
// Validate the verification code
$code = SignupCode::find($request->code);
if (
empty($code)
|| $code->isExpired()
|| Str::upper($request->short_code) !== Str::upper($code->short_code)
) {
$errors = ['short_code' => "The code is invalid or expired."];
return response()->json(['status' => 'error', 'errors' => $errors], 422);
}
// For signup last-step mode remember the code object, so we can delete it
// with single SQL query (->delete()) instead of two
$request->code = $code;
if ($update) {
$code->verify_ip_address = $request->ip();
$code->save();
}
$has_domain = $this->getPlan()->hasDomain();
// Return user name and email/phone/voucher from the codes database,
// domains list for selection and "plan type" flag
return response()->json([
'status' => 'success',
'email' => $code->email,
'first_name' => $code->first_name,
'last_name' => $code->last_name,
'voucher' => $code->voucher,
'is_domain' => $has_domain,
'domains' => $has_domain ? [] : Domain::getPublicDomains(),
]);
}
/**
* Validates the input to the final signup request.
*
* @param \Illuminate\Http\Request $request HTTP request
*
* @return \Illuminate\Http\JsonResponse JSON response
*/
public function signupValidate(Request $request)
{
// Validate input
$v = Validator::make(
$request->all(),
[
'login' => 'required|min:2',
'password' => ['required', 'confirmed', new Password()],
'domain' => 'required',
'voucher' => 'max:32',
]
);
if ($v->fails()) {
return response()->json(['status' => 'error', 'errors' => $v->errors()], 422);
}
$settings = [];
// Plan parameter is required/allowed in mandate mode
if (!empty($request->plan) && empty($request->code) && empty($request->invitation)) {
$plan = Plan::withEnvTenantContext()->where('title', $request->plan)->first();
if (!$plan || $plan->mode != Plan::MODE_MANDATE) {
$msg = self::trans('validation.exists', ['attribute' => 'plan']);
return response()->json(['status' => 'error', 'errors' => ['plan' => $msg]], 422);
}
} elseif ($request->invitation) {
// Signup via invitation
$invitation = SignupInvitation::withEnvTenantContext()->find($request->invitation);
if (empty($invitation) || $invitation->isCompleted()) {
return $this->errorResponse(404);
}
// Check required fields
$v = Validator::make(
$request->all(),
[
'first_name' => 'max:128',
'last_name' => 'max:128',
]
);
$errors = $v->fails() ? $v->errors()->toArray() : [];
if (!empty($errors)) {
return response()->json(['status' => 'error', 'errors' => $errors], 422);
}
$settings = [
'external_email' => $invitation->email,
'first_name' => $request->first_name,
'last_name' => $request->last_name,
];
} else {
// Validate verification codes (again)
$v = $this->verify($request, false);
if ($v->status() !== 200) {
return $v;
}
$plan = $this->getPlan();
// Get user name/email from the verification code database
$code_data = $v->getData();
$settings = [
'first_name' => $code_data->first_name,
'last_name' => $code_data->last_name,
];
if ($plan->mode == Plan::MODE_TOKEN) {
$settings['signup_token'] = $code_data->email;
} else {
$settings['external_email'] = $code_data->email;
}
}
// Find the voucher discount
if ($request->voucher) {
$discount = Discount::where('code', \strtoupper($request->voucher))
->where('active', true)->first();
if (!$discount) {
$errors = ['voucher' => self::trans('validation.voucherinvalid')];
return response()->json(['status' => 'error', 'errors' => $errors], 422);
}
}
if (empty($plan)) {
$plan = $this->getPlan();
}
$is_domain = $plan->hasDomain();
// Validate login
if ($errors = self::validateLogin($request->login, $request->domain, $is_domain)) {
return response()->json(['status' => 'error', 'errors' => $errors], 422);
}
// Set some properties for signup() method
$request->settings = $settings;
$request->plan = $plan;
$request->discount = $discount ?? null;
$request->invitation = $invitation ?? null;
$result = [];
if ($plan->mode == Plan::MODE_MANDATE) {
$result = $this->mandateForPlan($plan, $request->discount);
}
return response()->json($result + ['status' => 'success']);
}
/**
* Finishes the signup process by creating the user account.
*
* @param \Illuminate\Http\Request $request HTTP request
*
* @return \Illuminate\Http\JsonResponse JSON response
*/
public function signup(Request $request)
{
$v = $this->signupValidate($request);
if ($v->status() !== 200) {
return $v;
}
$is_domain = $request->plan->hasDomain();
// We allow only ASCII, so we can safely lower-case the email address
$login = Str::lower($request->login);
$domain_name = Str::lower($request->domain);
$domain = null;
$user_status = User::STATUS_RESTRICTED;
- if ($request->discount && $request->discount->discount == 100
+ if (
+ $request->discount && $request->discount->discount == 100
&& $request->plan->mode == Plan::MODE_MANDATE
) {
$user_status = User::STATUS_ACTIVE;
}
DB::beginTransaction();
// Create domain record
if ($is_domain) {
$domain = Domain::create([
'namespace' => $domain_name,
'type' => Domain::TYPE_EXTERNAL,
]);
}
// Create user record
$user = User::create([
'email' => $login . '@' . $domain_name,
'password' => $request->password,
'status' => $user_status,
]);
if ($request->discount) {
$wallet = $user->wallets()->first();
$wallet->discount()->associate($request->discount);
$wallet->save();
}
$user->assignPlan($request->plan, $domain);
// Save the external email and plan in user settings
$user->setSettings($request->settings);
// Update the invitation
if ($request->invitation) {
$request->invitation->status = SignupInvitation::STATUS_COMPLETED;
$request->invitation->user_id = $user->id;
$request->invitation->save();
}
// Soft-delete the verification code, and store some more info with it
if ($request->code) {
$request->code->user_id = $user->id;
$request->code->submit_ip_address = $request->ip();
$request->code->deleted_at = \now();
$request->code->timestamps = false;
$request->code->save();
}
DB::commit();
$response = AuthController::logonResponse($user, $request->password);
if ($request->plan->mode == Plan::MODE_MANDATE) {
$data = $response->getData(true);
$data['checkout'] = $this->mandateForPlan($request->plan, $request->discount, $user);
$response->setData($data);
}
return $response;
}
/**
* Collects some content to display to the user before redirect to a checkout page.
* Optionally creates a recurrent payment mandate for specified user/plan.
*/
protected function mandateForPlan(Plan $plan, Discount $discount = null, User $user = null): array
{
$result = [];
$min = \App\Payment::MIN_AMOUNT;
$planCost = $cost = $plan->cost();
$disc = 0;
if ($discount) {
// Free accounts don't need the auto-payment mandate
// Note: This means the voucher code is the only point of user verification
if ($discount->discount == 100) {
return [
'content' => self::trans('app.signup-account-free'),
'cost' => 0,
];
}
$planCost = (int) ($planCost * (100 - $discount->discount) / 100);
$disc = $cost - $planCost;
}
if ($planCost > $min) {
$min = $planCost;
}
if ($user) {
$wallet = $user->wallets()->first();
$wallet->setSettings([
'mandate_amount' => sprintf('%.2f', round($min / 100, 2)),
'mandate_balance' => 0,
]);
$mandate = [
'currency' => $wallet->currency,
'description' => \App\Tenant::getConfig($user->tenant_id, 'app.name')
. ' ' . self::trans('app.mandate-description-suffix'),
'methodId' => PaymentProvider::METHOD_CREDITCARD,
'redirectUrl' => Utils::serviceUrl('/payment/status', $user->tenant_id),
];
$provider = PaymentProvider::factory($wallet);
$result = $provider->createMandate($wallet, $mandate);
}
$country = Utils::countryForRequest();
$period = $plan->months == 12 ? 'yearly' : 'monthly';
$currency = \config('app.currency');
$rate = VatRate::where('country', $country)
->where('start', '<=', now()->format('Y-m-d h:i:s'))
->orderByDesc('start')
->limit(1)
->first();
$summary = '<tr class="subscription">'
. '<td>' . self::trans("app.signup-subscription-{$period}") . '</td>'
. '<td class="money">' . Utils::money($cost, $currency) . '</td>'
. '</tr>';
if ($discount) {
$summary .= '<tr class="discount">'
. '<td>' . self::trans('app.discount-code', ['code' => $discount->code]) . '</td>'
. '<td class="money">' . Utils::money(-$disc, $currency) . '</td>'
. '</tr>';
}
$summary .= '<tr class="sep"><td colspan="2"></td></tr>'
. '<tr class="total">'
. '<td>' . self::trans('app.total') . '</td>'
. '<td class="money">' . Utils::money($planCost, $currency) . '</td>'
. '</tr>';
if ($rate && $rate->rate > 0) {
// TODO: app.vat.mode
$vat = round($planCost * $rate->rate / 100);
$content = self::trans('app.vat-incl', [
'rate' => Utils::percent($rate->rate),
'cost' => Utils::money($planCost - $vat, $currency),
'vat' => Utils::money($vat, $currency),
]);
$summary .= '<tr class="vat-summary"><td colspan="2">*' . $content . '</td></tr>';
}
$trialEnd = $plan->free_months ? now()->copy()->addMonthsWithoutOverflow($plan->free_months) : now();
$params = [
'cost' => Utils::money($planCost, $currency),
'date' => $trialEnd->toDateString(),
];
$result['title'] = self::trans("app.signup-plan-{$period}");
$result['content'] = self::trans('app.signup-account-mandate', $params);
$result['summary'] = '<table>' . $summary . '</table>';
$result['cost'] = $planCost;
return $result;
}
/**
* Returns plan for the signup process
*
* @returns \App\Plan Plan object selected for current signup process
*/
protected function getPlan()
{
$request = request();
if (!$request->plan || !$request->plan instanceof Plan) {
// Get the plan if specified and exists...
if (($request->code instanceof SignupCode) && $request->code->plan) {
$plan = Plan::withEnvTenantContext()->where('title', $request->code->plan)->first();
} elseif ($request->plan) {
$plan = Plan::withEnvTenantContext()->where('title', $request->plan)->first();
}
// ...otherwise use the default plan
if (empty($plan)) {
// TODO: Get default plan title from config
$plan = Plan::withEnvTenantContext()->where('title', 'individual')->first();
}
$request->plan = $plan;
}
return $request->plan;
}
/**
* Login (kolab identity) validation
*
* @param string $login Login (local part of an email address)
* @param string $domain Domain name
* @param bool $external Enables additional checks for domain part
*
* @return array Error messages on validation error
*/
protected static function validateLogin($login, $domain, $external = false): ?array
{
// Validate login part alone
$v = Validator::make(
['login' => $login],
['login' => ['required', 'string', new UserEmailLocal($external)]]
);
if ($v->fails()) {
return ['login' => $v->errors()->toArray()['login'][0]];
}
$domains = $external ? null : Domain::getPublicDomains();
// Validate the domain
$v = Validator::make(
['domain' => $domain],
['domain' => ['required', 'string', new UserEmailDomain($domains)]]
);
if ($v->fails()) {
return ['domain' => $v->errors()->toArray()['domain'][0]];
}
$domain = Str::lower($domain);
// Check if domain is already registered with us
if ($external) {
if (Domain::withTrashed()->where('namespace', $domain)->exists()) {
return ['domain' => self::trans('validation.domainexists')];
}
}
// Check if user with specified login already exists
$email = $login . '@' . $domain;
if (User::emailExists($email) || User::aliasExists($email) || \App\Group::emailExists($email)) {
return ['login' => self::trans('validation.loginexists')];
}
return null;
}
}
diff --git a/src/app/Http/Controllers/API/V4/MeetController.php b/src/app/Http/Controllers/API/V4/MeetController.php
index e4d03df8..bbb10fbf 100644
--- a/src/app/Http/Controllers/API/V4/MeetController.php
+++ b/src/app/Http/Controllers/API/V4/MeetController.php
@@ -1,190 +1,191 @@
<?php
namespace App\Http\Controllers\API\V4;
use App\Http\Controllers\Controller;
use App\Meet\Room;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class MeetController extends Controller
{
/**
* Join the room session. Each room has one owner, and the room isn't open until the owner
* joins (and effectively creates the session).
*
* @param string $id Room identifier (name)
*
* @return \Illuminate\Http\JsonResponse
*/
public function joinRoom($id)
{
$room = Room::where('name', $id)->first();
// Room does not exist, or the owner is deleted
if (!$room || !($wallet = $room->wallet()) || !$wallet->owner || $wallet->owner->isDegraded(true)) {
return $this->errorResponse(404, self::trans('meet.room-not-found'));
}
$user = Auth::guard()->user();
$isOwner = $user && (
$user->id == $wallet->owner->id || $room->permissions()->where('user', $user->email)->exists()
);
$init = !empty(request()->input('init'));
// There's no existing session
if (!$room->hasSession()) {
// Participants can't join the room until the session is created by the owner
if (!$isOwner) {
return $this->errorResponse(422, self::trans('meet.session-not-found'), ['code' => 323]);
}
// The room owner can create the session on request
if (!$init) {
return $this->errorResponse(422, self::trans('meet.session-not-found'), ['code' => 324]);
}
$session = $room->createSession();
if (empty($session)) {
return $this->errorResponse(500, self::trans('meet.session-create-error'));
}
}
$settings = $room->getSettings(['locked', 'nomedia', 'password']);
$password = (string) $settings['password'];
$config = [
'locked' => $settings['locked'] === 'true',
'nomedia' => $settings['nomedia'] === 'true',
'password' => $isOwner ? $password : '',
'requires_password' => !$isOwner && strlen($password),
];
$response = ['config' => $config];
// Validate room password
if (!$isOwner && strlen($password)) {
$request_password = request()->input('password');
if ($request_password !== $password) {
- return $this->errorResponse(422, self::trans('meet.session-password-error'), $response + ['code' => 325]);
+ $response['code'] = 325;
+ return $this->errorResponse(422, self::trans('meet.session-password-error'), $response);
}
}
// Handle locked room
if (!$isOwner && $config['locked']) {
$nickname = request()->input('nickname');
$picture = request()->input('picture');
$requestId = request()->input('requestId');
$request = $requestId ? $room->requestGet($requestId) : null;
$error = self::trans('meet.session-room-locked-error');
// Request already has been processed (not accepted yet, but it could be denied)
if (empty($request['status']) || $request['status'] != Room::REQUEST_ACCEPTED) {
if (!$request) {
if (empty($nickname) || empty($requestId) || !preg_match('/^[a-z0-9]{8,32}$/i', $requestId)) {
return $this->errorResponse(422, $error, $response + ['code' => 326]);
}
if (empty($picture)) {
$svg = file_get_contents(resource_path('images/user.svg'));
$picture = 'data:image/svg+xml;base64,' . base64_encode($svg);
} elseif (!preg_match('|^data:image/png;base64,[a-zA-Z0-9=+/]+$|', $picture)) {
return $this->errorResponse(422, $error, $response + ['code' => 326]);
}
// TODO: Resize when big/make safe the user picture?
$request = ['nickname' => $nickname, 'requestId' => $requestId, 'picture' => $picture];
if (!$room->requestSave($requestId, $request)) {
// FIXME: should we use error code 500?
return $this->errorResponse(422, $error, $response + ['code' => 326]);
}
// Send the request (signal) to all moderators
$result = $room->signal('joinRequest', $request, Room::ROLE_MODERATOR);
}
return $this->errorResponse(422, $error, $response + ['code' => 327]);
}
}
// Initialize connection tokens
if ($init) {
// Choose the connection role
$canPublish = !empty(request()->input('canPublish')) && (empty($config['nomedia']) || $isOwner);
$role = $canPublish ? Room::ROLE_PUBLISHER : Room::ROLE_SUBSCRIBER;
if ($isOwner) {
$role |= Room::ROLE_MODERATOR;
$role |= Room::ROLE_OWNER;
}
// Create session token for the current user/connection
$response = $room->getSessionToken($role);
if (empty($response)) {
return $this->errorResponse(500, self::trans('meet.session-join-error'));
}
$response_code = 200;
$response['role'] = $role;
$response['config'] = $config;
} else {
$response_code = 422;
$response['code'] = 322;
}
return response()->json($response, $response_code);
}
/**
* Webhook as triggered from the Meet server
*
* @param \Illuminate\Http\Request $request The API request.
*
* @return \Illuminate\Http\Response The response
*/
public function webhook(Request $request)
{
\Log::debug($request->getContent());
// Authenticate the request
if ($request->headers->get('X-Auth-Token') != \config('meet.webhook_token')) {
return response('Unauthorized', 403);
}
$sessionId = (string) $request->input('roomId');
$event = (string) $request->input('event');
switch ($event) {
case 'roomClosed':
// When all participants left the room the server will dispatch roomClosed
// event. We'll remove the session reference from the database.
$room = Room::where('session_id', $sessionId)->first();
if ($room) {
$room->session_id = null;
$room->save();
}
break;
case 'joinRequestAccepted':
case 'joinRequestDenied':
$room = Room::where('session_id', $sessionId)->first();
if ($room) {
$method = $event == 'joinRequestAccepted' ? 'requestAccept' : 'requestDeny';
$room->{$method}($request->input('requestId'));
}
break;
}
return response('Success', 200);
}
}
diff --git a/src/app/Jobs/User/CreateJob.php b/src/app/Jobs/User/CreateJob.php
index 7724dec2..22bf16dc 100644
--- a/src/app/Jobs/User/CreateJob.php
+++ b/src/app/Jobs/User/CreateJob.php
@@ -1,115 +1,116 @@
<?php
namespace App\Jobs\User;
use App\Jobs\UserJob;
/**
* Create the \App\User in LDAP.
*
* Throws exceptions for the following reasons:
*
* * The user is marked as deleted (`$user->isDeleted()`), or
* * the user is actually deleted (`$user->deleted_at`), or
* * the user is already marked as ready in LDAP (`$user->isLdapReady()`).
*
*/
class CreateJob extends UserJob
{
/**
* Execute the job.
*
* @return void
*
* @throws \Exception
*/
public function handle()
{
$user = $this->getUser();
if (!$user) {
return;
}
if ($user->role) {
// Admins/resellers don't reside in LDAP (for now)
return;
}
if ($user->email == \config('imap.admin_login')) {
// Ignore Cyrus admin account
return;
}
// sanity checks
if ($user->isDeleted()) {
$this->fail(new \Exception("User {$this->userId} is marked as deleted."));
return;
}
if ($user->trashed()) {
$this->fail(new \Exception("User {$this->userId} is actually deleted."));
return;
}
$withLdap = \config('app.with_ldap');
// see if the domain is ready
$domain = $user->domain();
if (!$domain) {
$this->fail(new \Exception("The domain for {$this->userId} does not exist."));
return;
}
if ($domain->isDeleted()) {
$this->fail(new \Exception("The domain for {$this->userId} is marked as deleted."));
return;
}
if ($withLdap && !$domain->isLdapReady()) {
$this->release(60);
return;
}
if (\config('abuse.suspend_enabled') && !$user->isSuspended()) {
$code = \Artisan::call("user:abuse-check {$this->userId}");
if ($code == 2) {
\Log::info("Suspending user due to suspected abuse: {$this->userId} {$user->email}");
$user->status |= \App\User::STATUS_SUSPENDED;
}
}
if ($withLdap && !$user->isLdapReady()) {
\App\Backends\LDAP::createUser($user);
$user->status |= \App\User::STATUS_LDAP_READY;
$user->save();
}
if (!$user->isImapReady()) {
if (\config('app.with_imap')) {
if (!\App\Backends\IMAP::createUser($user)) {
throw new \Exception("Failed to create mailbox for user {$this->userId}.");
}
} else {
if (!\App\Backends\IMAP::verifyAccount($user->email)) {
$this->release(15);
return;
}
}
$user->status |= \App\User::STATUS_IMAP_READY;
}
// Make user active in non-mandate mode only
- if (!($wallet = $user->wallet())
+ if (
+ !($wallet = $user->wallet())
|| !($plan = $user->wallet()->plan())
|| $plan->mode != \App\Plan::MODE_MANDATE
) {
$user->status |= \App\User::STATUS_ACTIVE;
}
$user->save();
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Mon, Aug 25, 4:26 PM (23 h, 27 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
257704
Default Alt Text
(38 KB)
Attached To
Mode
R2 kolab
Attached
Detach File
Event Timeline
Log In to Comment