Page MenuHomePhorge

No OneTemporary

Size
21 KB
Referenced Files
None
Subscribers
None
diff --git a/src/.env.example b/src/.env.example
index 69ee05d9..d084b377 100644
--- a/src/.env.example
+++ b/src/.env.example
@@ -1,147 +1,148 @@
APP_NAME=Kolab
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://127.0.0.1:8000
APP_PUBLIC_URL=
APP_DOMAIN=kolabnow.com
APP_THEME=default
APP_LOCALE=en
APP_LOCALES=en,de
ASSET_URL=http://127.0.0.1:8000
WEBMAIL_URL=/apps
SUPPORT_URL=/support
SUPPORT_EMAIL=
LOG_CHANNEL=stack
+LOG_SLOW_REQUESTS=5
DB_CONNECTION=mysql
DB_DATABASE=kolabdev
DB_HOST=127.0.0.1
DB_PASSWORD=kolab
DB_PORT=3306
DB_USERNAME=kolabdev
BROADCAST_DRIVER=redis
CACHE_DRIVER=redis
QUEUE_CONNECTION=redis
SESSION_DRIVER=file
SESSION_LIFETIME=120
MFA_DSN=mysql://roundcube:Welcome2KolabSystems@127.0.0.1/roundcube
MFA_TOTP_DIGITS=6
MFA_TOTP_INTERVAL=30
MFA_TOTP_DIGEST=sha1
IMAP_URI=ssl://127.0.0.1:993
IMAP_ADMIN_LOGIN=cyrus-admin
IMAP_ADMIN_PASSWORD=Welcome2KolabSystems
IMAP_VERIFY_HOST=false
IMAP_VERIFY_PEER=false
LDAP_BASE_DN="dc=mgmt,dc=com"
LDAP_DOMAIN_BASE_DN="ou=Domains,dc=mgmt,dc=com"
LDAP_HOSTS=127.0.0.1
LDAP_PORT=389
LDAP_SERVICE_BIND_DN="uid=kolab-service,ou=Special Users,dc=mgmt,dc=com"
LDAP_SERVICE_BIND_PW="Welcome2KolabSystems"
LDAP_USE_SSL=false
LDAP_USE_TLS=false
# Administrative
LDAP_ADMIN_BIND_DN="cn=Directory Manager"
LDAP_ADMIN_BIND_PW="Welcome2KolabSystems"
LDAP_ADMIN_ROOT_DN="dc=mgmt,dc=com"
# Hosted (public registration)
LDAP_HOSTED_BIND_DN="uid=hosted-kolab-service,ou=Special Users,dc=mgmt,dc=com"
LDAP_HOSTED_BIND_PW="Welcome2KolabSystems"
LDAP_HOSTED_ROOT_DN="dc=hosted,dc=com"
OPENVIDU_API_PASSWORD=MY_SECRET
OPENVIDU_API_URL=http://localhost:8080/api/
OPENVIDU_API_USERNAME=OPENVIDUAPP
OPENVIDU_API_VERIFY_TLS=true
OPENVIDU_COTURN_IP=127.0.0.1
OPENVIDU_COTURN_REDIS_DATABASE=2
OPENVIDU_COTURN_REDIS_IP=127.0.0.1
OPENVIDU_COTURN_REDIS_PASSWORD=turn
# Used as COTURN_IP, TURN_PUBLIC_IP, for KMS_TURN_URL
OPENVIDU_PUBLIC_IP=127.0.0.1
OPENVIDU_PUBLIC_PORT=3478
OPENVIDU_SERVER_PORT=8080
OPENVIDU_WEBHOOK=true
OPENVIDU_WEBHOOK_ENDPOINT=http://127.0.0.1:8000/webhooks/meet/openvidu
# "CDR" events, see https://docs.openvidu.io/en/2.13.0/reference-docs/openvidu-server-cdr/
#OPENVIDU_WEBHOOK_EVENTS=[sessionCreated,sessionDestroyed,participantJoined,participantLeft,webrtcConnectionCreated,webrtcConnectionDestroyed,recordingStatusChanged,filterEventDispatched,mediaNodeStatusChanged]
#OPENVIDU_WEBHOOK_HEADERS=[\"Authorization:\ Basic\ SOMETHING\"]
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
SWOOLE_HOT_RELOAD_ENABLE=true
SWOOLE_HTTP_ACCESS_LOG=true
SWOOLE_HTTP_HOST=127.0.0.1
SWOOLE_HTTP_PORT=8000
SWOOLE_HTTP_REACTOR_NUM=1
SWOOLE_HTTP_WEBSOCKET=true
SWOOLE_HTTP_WORKER_NUM=1
SWOOLE_OB_OUTPUT=true
PAYMENT_PROVIDER=
MOLLIE_KEY=
STRIPE_KEY=
STRIPE_PUBLIC_KEY=
STRIPE_WEBHOOK_SECRET=
MAIL_DRIVER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="noreply@example.com"
MAIL_FROM_NAME="Example.com"
MAIL_REPLYTO_ADDRESS=null
MAIL_REPLYTO_NAME=null
DNS_TTL=3600
DNS_SPF="v=spf1 mx -all"
DNS_STATIC="%s. MX 10 ext-mx01.mykolab.com."
DNS_COPY_FROM=null
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=mt1
MIX_ASSET_PATH='/'
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
JWT_SECRET=
JWT_TTL=60
COMPANY_NAME=
COMPANY_ADDRESS=
COMPANY_DETAILS=
COMPANY_EMAIL=
COMPANY_LOGO=
COMPANY_FOOTER=
VAT_COUNTRIES=CH,LI
VAT_RATE=7.7
KB_ACCOUNT_DELETE=
KB_ACCOUNT_SUSPENDED=
diff --git a/src/app/Http/Middleware/RequestLogger.php b/src/app/Http/Middleware/RequestLogger.php
index 59d30556..37904d61 100644
--- a/src/app/Http/Middleware/RequestLogger.php
+++ b/src/app/Http/Middleware/RequestLogger.php
@@ -1,31 +1,38 @@
<?php
namespace App\Http\Middleware;
use Closure;
class RequestLogger
{
private static $start;
public function handle($request, Closure $next)
{
// FIXME: This is not really a request start, but we can't
// use LARAVEL_START constant when working with swoole
self::$start = microtime(true);
return $next($request);
}
public function terminate($request, $response)
{
if (\App::environment('local')) {
$url = $request->fullUrl();
$method = $request->getMethod();
$mem = round(memory_get_peak_usage() / 1024 / 1024, 1);
$time = microtime(true) - self::$start;
\Log::debug(sprintf("C: %s %s [%sM]: %.4f sec.", $method, $url, $mem, $time));
+ } else {
+ $threshold = \config('logging.slow_log');
+ if ($threshold && ($time = microtime(true) - self::$start) > $threshold) {
+ $url = $request->fullUrl();
+ $method = $request->getMethod();
+ \Log::warning(sprintf("[STATS] %s %s: %.4f sec.", $method, $url, $time));
+ }
}
}
}
diff --git a/src/app/OpenVidu/Room.php b/src/app/OpenVidu/Room.php
index 572061d0..5a483390 100644
--- a/src/app/OpenVidu/Room.php
+++ b/src/app/OpenVidu/Room.php
@@ -1,419 +1,427 @@
<?php
namespace App\OpenVidu;
use App\Traits\SettingsTrait;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Cache;
/**
* The eloquent definition of a Room.
*
* @property int $id Room identifier
* @property string $name Room name
* @property int $user_id Room owner
* @property ?string $session_id OpenVidu session identifier
*/
class Room extends Model
{
use SettingsTrait;
public const ROLE_SUBSCRIBER = 1 << 0;
public const ROLE_PUBLISHER = 1 << 1;
public const ROLE_MODERATOR = 1 << 2;
public const ROLE_SCREEN = 1 << 3;
public const ROLE_OWNER = 1 << 4;
public const REQUEST_ACCEPTED = 'accepted';
public const REQUEST_DENIED = 'denied';
private const OV_ROLE_MODERATOR = 'MODERATOR';
private const OV_ROLE_PUBLISHER = 'PUBLISHER';
private const OV_ROLE_SUBSCRIBER = 'SUBSCRIBER';
protected $fillable = [
'user_id',
'name'
];
protected $table = 'openvidu_rooms';
/** @var \GuzzleHttp\Client|null HTTP client instance */
private static $client = null;
/**
* Creates HTTP client for connections to OpenVidu server
*
* @return \GuzzleHttp\Client HTTP client instance
*/
private function client()
{
if (!self::$client) {
self::$client = new \GuzzleHttp\Client(
[
'http_errors' => false, // No exceptions from Guzzle
'base_uri' => \config('openvidu.api_url'),
'verify' => \config('openvidu.api_verify_tls'),
'auth' => [
\config('openvidu.api_username'),
\config('openvidu.api_password')
- ]
+ ],
+ '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));
+ }
+ },
]
);
}
return self::$client;
}
/**
* Destroy a OpenVidu connection
*
* @param string $conn Connection identifier
*
* @return bool True on success, False otherwise
* @throws \Exception if session does not exist
*/
public function closeOVConnection($conn): bool
{
if (!$this->session_id) {
throw new \Exception("The room session does not exist");
}
$url = 'sessions/' . $this->session_id . '/connection/' . urlencode($conn);
$response = $this->client()->request('DELETE', $url);
return $response->getStatusCode() == 204;
}
/**
* Fetch a OpenVidu connection information.
*
* @param string $conn Connection identifier
*
* @return ?array Connection data on success, Null otherwise
* @throws \Exception if session does not exist
*/
public function getOVConnection($conn): ?array
{
// Note: getOVConnection() not getConnection() because Eloquent\Model::getConnection() exists
// TODO: Maybe use some other name? getParticipant?
if (!$this->session_id) {
throw new \Exception("The room session does not exist");
}
$url = 'sessions/' . $this->session_id . '/connection/' . urlencode($conn);
$response = $this->client()->request('GET', $url);
if ($response->getStatusCode() == 200) {
return json_decode($response->getBody(), true);
}
return null;
}
/**
* Create a OpenVidu session
*
* @return array|null Session data on success, NULL otherwise
*/
public function createSession(): ?array
{
$response = $this->client()->request(
'POST',
"sessions",
[
'json' => [
'mediaMode' => 'ROUTED',
'recordingMode' => 'MANUAL'
]
]
);
if ($response->getStatusCode() !== 200) {
$this->session_id = null;
$this->save();
return null;
}
$session = json_decode($response->getBody(), true);
$this->session_id = $session['id'];
$this->save();
return $session;
}
/**
* Delete a OpenVidu session
*
* @return bool
*/
public function deleteSession(): bool
{
if (!$this->session_id) {
return true;
}
$response = $this->client()->request(
'DELETE',
"sessions/" . $this->session_id,
);
if ($response->getStatusCode() == 204) {
$this->session_id = null;
$this->save();
return true;
}
return false;
}
/**
* Returns metadata for every connection in a session.
*
* @return array Connections metadata, indexed by connection identifier
* @throws \Exception if session does not exist
*/
public function getSessionConnections(): array
{
if (!$this->session_id) {
throw new \Exception("The room session does not exist");
}
return Connection::where('session_id', $this->session_id)
// Ignore screen sharing connection for now
->whereRaw("(role & " . self::ROLE_SCREEN . ") = 0")
->get()
->keyBy('id')
->map(function ($item) {
// Warning: Make sure to not return all metadata here as it might contain sensitive data.
return [
'role' => $item->role,
'hand' => $item->metadata['hand'] ?? 0,
'language' => $item->metadata['language'] ?? null,
];
})
// Sort by order in the queue, so UI can re-build the existing queue in order
->sort(function ($a, $b) {
return $a['hand'] <=> $b['hand'];
})
->all();
}
/**
* Create a OpenVidu session (connection) token
*
* @param int $role User role (see self::ROLE_* constants)
*
* @return array|null Token data on success, NULL otherwise
* @throws \Exception if session does not exist
*/
public function getSessionToken($role = self::ROLE_SUBSCRIBER): ?array
{
if (!$this->session_id) {
throw new \Exception("The room session does not exist");
}
// FIXME: Looks like passing the role in 'data' param is the only way
// to make it visible for everyone in a room. So, for example we can
// handle/style subscribers/publishers/moderators differently on the
// client-side. Is this a security issue?
$data = ['role' => $role];
$url = 'sessions/' . $this->session_id . '/connection';
$post = [
'json' => [
'role' => self::OV_ROLE_PUBLISHER,
'data' => json_encode($data)
]
];
$response = $this->client()->request('POST', $url, $post);
if ($response->getStatusCode() == 200) {
$json = json_decode($response->getBody(), true);
$authToken = base64_encode($json['id'] . ':' . \random_bytes(16));
// Extract the 'token' part of the token, it will be used to authenticate the connection.
// It will be needed in next iterations e.g. to authenticate moderators that aren't
// Kolab4 users (or are just not logged in to Kolab4).
// FIXME: we could as well generate our own token for auth purposes
parse_str(parse_url($json['token'], PHP_URL_QUERY), $url);
// Create the connection reference in our database
$conn = new Connection();
$conn->id = $json['id'];
$conn->session_id = $this->session_id;
$conn->room_id = $this->id;
$conn->role = $role;
$conn->metadata = ['token' => $url['token'], 'authToken' => $authToken];
$conn->save();
return [
'session' => $this->session_id,
'token' => $json['token'],
'authToken' => $authToken,
'connectionId' => $json['id'],
'role' => $role,
];
}
return null;
}
/**
* Check if the room has an active session
*
* @return bool True when the session exists, False otherwise
*/
public function hasSession(): bool
{
if (!$this->session_id) {
return false;
}
$response = $this->client()->request('GET', "sessions/{$this->session_id}");
return $response->getStatusCode() == 200;
}
/**
* The room owner.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function owner()
{
return $this->belongsTo('\App\User', 'user_id', 'id');
}
/**
* Accept the join request.
*
* @param string $id Request identifier
*
* @return bool True on success, False on failure
*/
public function requestAccept(string $id): bool
{
$request = Cache::get($this->session_id . '-' . $id);
if ($request) {
$request['status'] = self::REQUEST_ACCEPTED;
return Cache::put($this->session_id . '-' . $id, $request, now()->addHours(1));
}
return false;
}
/**
* Deny the join request.
*
* @param string $id Request identifier
*
* @return bool True on success, False on failure
*/
public function requestDeny(string $id): bool
{
$request = Cache::get($this->session_id . '-' . $id);
if ($request) {
$request['status'] = self::REQUEST_DENIED;
return Cache::put($this->session_id . '-' . $id, $request, now()->addHours(1));
}
return false;
}
/**
* Get the join request data.
*
* @param string $id Request identifier
*
* @return array|null Request data (e.g. nickname, status, picture?)
*/
public function requestGet(string $id): ?array
{
return Cache::get($this->session_id . '-' . $id);
}
/**
* Save the join request.
*
* @param string $id Request identifier
* @param array $request Request data
*
* @return bool True on success, False on failure
*/
public function requestSave(string $id, array $request): bool
{
// We don't really need the picture in the cache
// As we use this cache for the request status only
unset($request['picture']);
return Cache::put($this->session_id . '-' . $id, $request, now()->addHours(1));
}
/**
* Any (additional) properties of this room.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function settings()
{
return $this->hasMany('App\OpenVidu\RoomSetting', 'room_id');
}
/**
* Send a OpenVidu signal to the session participants (connections)
*
* @param string $name Signal name (type)
* @param array $data Signal data array
* @param null|int|string[] $target List of target connections, Null for all connections.
* It can be also a participant role.
*
* @return bool True on success, False on failure
* @throws \Exception if session does not exist
*/
public function signal(string $name, array $data = [], $target = null): bool
{
if (!$this->session_id) {
throw new \Exception("The room session does not exist");
}
$post = [
'session' => $this->session_id,
'type' => $name,
'data' => $data ? json_encode($data) : '',
];
// Get connection IDs by participant role
if (is_int($target)) {
$connections = Connection::where('session_id', $this->session_id)
->whereRaw("(role & $target)")
->pluck('id')
->all();
if (empty($connections)) {
return false;
}
$target = $connections;
}
if (!empty($target)) {
$post['to'] = $target;
}
$response = $this->client()->request('POST', 'signal', ['json' => $post]);
return $response->getStatusCode() == 200;
}
}
diff --git a/src/config/logging.php b/src/config/logging.php
index d09cd7d2..0dcdb218 100644
--- a/src/config/logging.php
+++ b/src/config/logging.php
@@ -1,94 +1,96 @@
<?php
use Monolog\Handler\StreamHandler;
use Monolog\Handler\SyslogUdpHandler;
return [
/*
|--------------------------------------------------------------------------
| Default Log Channel
|--------------------------------------------------------------------------
|
| This option defines the default log channel that gets used when writing
| messages to the logs. The name specified in this option should match
| one of the channels defined in the "channels" configuration array.
|
*/
'default' => env('LOG_CHANNEL', 'stack'),
/*
|--------------------------------------------------------------------------
| Log Channels
|--------------------------------------------------------------------------
|
| Here you may configure the log channels for your application. Out of
| the box, Laravel uses the Monolog PHP logging library. This gives
| you a variety of powerful log handlers / formatters to utilize.
|
| Available Drivers: "single", "daily", "slack", "syslog",
| "errorlog", "monolog",
| "custom", "stack"
|
*/
'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['daily'],
'ignore_exceptions' => false,
],
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => 'debug',
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => 'debug',
'days' => 14,
],
'slack' => [
'driver' => 'slack',
'url' => env('LOG_SLACK_WEBHOOK_URL'),
'username' => 'Laravel Log',
'emoji' => ':boom:',
'level' => 'critical',
],
'papertrail' => [
'driver' => 'monolog',
'level' => 'debug',
'handler' => SyslogUdpHandler::class,
'handler_with' => [
'host' => env('PAPERTRAIL_URL'),
'port' => env('PAPERTRAIL_PORT'),
],
],
'stderr' => [
'driver' => 'monolog',
'handler' => StreamHandler::class,
'formatter' => env('LOG_STDERR_FORMATTER'),
'with' => [
'stream' => 'php://stderr',
],
],
'syslog' => [
'driver' => 'syslog',
'level' => 'debug',
],
'errorlog' => [
'driver' => 'errorlog',
'level' => 'debug',
],
],
+ 'slow_log' => (float) env('LOG_SLOW_REQUESTS', 5),
+
];

File Metadata

Mime Type
text/x-diff
Expires
Sun, Feb 1, 7:51 AM (1 d, 17 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
426654
Default Alt Text
(21 KB)

Event Timeline