diff --git a/meet/server/config/config.js b/meet/server/config/config.js new file mode 100644 index 00000000..7ab2ddc8 --- /dev/null +++ b/meet/server/config/config.js @@ -0,0 +1,481 @@ +const os = require('os'); +// const fs = require('fs'); + +const userRoles = require('../userRoles'); + +const { + BYPASS_ROOM_LOCK, + BYPASS_LOBBY +} = require('../access'); + +const { + CHANGE_ROOM_LOCK, + PROMOTE_PEER, + MODIFY_ROLE, + SEND_CHAT, + MODERATE_CHAT, + SHARE_AUDIO, + SHARE_VIDEO, + SHARE_SCREEN, + EXTRA_VIDEO, + SHARE_FILE, + MODERATE_FILES, + MODERATE_ROOM +} = require('../permissions'); + +// const AwaitQueue = require('awaitqueue'); +// const axios = require('axios'); + +module.exports = +{ + + // Auth conf + /* + auth : + { + // Always enabled if configured + lti : + { + consumerKey : 'key', + consumerSecret : 'secret' + }, + + // Auth strategy to use (default oidc) + strategy : 'oidc', + oidc : + { + // The issuer URL for OpenID Connect discovery + // The OpenID Provider Configuration Document + // could be discovered on: + // issuerURL + '/.well-known/openid-configuration' + + // e.g. google OIDC config + // Follow this guide to get credential: + // https://developers.google.com/identity/protocols/oauth2/openid-connect + // use this issuerURL + // issuerURL : 'https://accounts.google.com/', + + issuerURL : 'https://example.com', + clientOptions : + { + client_id : '', + client_secret : '', + scope : 'openid email profile', + // where client.example.com is your edumeet server + redirect_uri : 'https://client.example.com/auth/callback' + } + + }, + saml : + { + // where edumeet.example.com is your edumeet server + callbackUrl : 'https://edumeet.example.com/auth/callback', + issuer : 'https://edumeet.example.com', + entryPoint : 'https://openidp.feide.no/simplesaml/saml2/idp/SSOService.php', + privateCert : fs.readFileSync('config/saml_privkey.pem', 'utf-8'), + signingCert : fs.readFileSync('config/saml_cert.pem', 'utf-8'), + decryptionPvk : fs.readFileSync('config/saml_privkey.pem', 'utf-8'), + decryptionCert : fs.readFileSync('config/saml_cert.pem', 'utf-8'), + // Federation cert + cert : fs.readFileSync('config/federation_cert.pem', 'utf-8') + }, + + // to create password hash use: node server/utils/password_encode.js cleartextpassword + local : + { + users : [ + { + id : 1, + username : 'alice', + passwordHash : '$2b$10$PAXXw.6cL3zJLd7ZX.AnL.sFg2nxjQPDmMmGSOQYIJSa0TrZ9azG6', + displayName : 'Alice', + emails : [ { value: 'alice@atlanta.com' } ] + }, + { + id : 2, + username : 'bob', + passwordHash : '$2b$10$BzAkXcZ54JxhHTqCQcFn8.H6klY/G48t4jDBeTE2d2lZJk/.tvv0G', + displayName : 'Bob', + emails : [ { value: 'bob@biloxi.com' } ] + } + ] + } + }, + */ + // URI and key for requesting geoip-based TURN server closest to the client + turnAPIKey : 'examplekey', + turnAPIURI : 'https://example.com/api/turn', + turnAPIparams : { + 'uri_schema' : 'turn', + 'transport' : 'tcp', + 'ip_ver' : 'ipv4', + 'servercount' : '2' + }, + turnAPITimeout : 2 * 1000, + // Backup turnservers if REST fails or is not configured + backupTurnServers : [ + { + urls : [ + 'turn:turn.example.com:443?transport=tcp' + ], + username : 'example', + credential : 'example' + } + ], + // bittorrent tracker: please replace this if you want a more private file sharing service inside eduMEET + // have a look at https://github.com/webtorrent/bittorrent-tracker for setup your own tracker + fileTracker : 'wss://tracker.openwebtorrent.com', + // redis server options + redisOptions : {}, + // session cookie secret + cookieSecret : 'T0P-S3cR3t_cook!e', + cookieName : 'edumeet.sid', + // if you use encrypted private key the set the passphrase + tls : + { + //cert : `${__dirname}/../certs/mediasoup-demo.localhost.cert.pem`, + // passphrase: 'key_password' + //key : `${__dirname}/../certs/mediasoup-demo.localhost.key.pem` + cert : `/etc/pki/tls/certs/kolab.hosted.com.cert`, + key : `/etc/pki/tls/certs/kolab.hosted.com.key`, + }, + // listening Host or IP + // If omitted listens on every IP. ("0.0.0.0" and "::") + //listeningHost: 'localhost', + // Listening port for https server. + listeningPort : 12443, + // Any http request is redirected to https. + // Listening port for http server. + listeningRedirectPort : 12080, + // Listens only on http, only on listeningPort + // listeningRedirectPort disabled + // use case: loadbalancer backend + httpOnly : true, + // WebServer/Express trust proxy config for httpOnly mode + // You can find more info: + // - https://expressjs.com/en/guide/behind-proxies.html + // - https://www.npmjs.com/package/proxy-addr + // use case: loadbalancer backend + trustProxy : '', + // This logger class will have the log function + // called every time there is a room created or destroyed, + // or peer created or destroyed. This would then be able + // to log to a file or external service. + /* StatusLogger : class + { + constructor() + { + this._queue = new AwaitQueue(); + } + + // rooms: rooms object + // peers: peers object + // eslint-disable-next-line no-unused-vars + async log({ rooms, peers }) + { + this._queue.push(async () => + { + // Do your logging in here, use queue to keep correct order + + // eslint-disable-next-line no-console + console.log('Number of rooms: ', rooms.size); + // eslint-disable-next-line no-console + console.log('Number of peers: ', peers.size); + }) + .catch((error) => + { + // eslint-disable-next-line no-console + console.log('error in log', error); + }); + } + }, */ + // This function will be called on successful login through oidc. + // Use this function to map your oidc userinfo to the Peer object. + // The roomId is equal to the room name. + // See examples below. + // Examples: + /* + // All authenicated users will be MODERATOR and AUTHENTICATED + userMapping : async ({ peer, room, roomId, userinfo }) => + { + peer.addRole(userRoles.MODERATOR); + peer.addRole(userRoles.AUTHENTICATED); + }, + // All authenicated users will be AUTHENTICATED, + // and those with the moderator role set in the userinfo + // will also be MODERATOR + userMapping : async ({ peer, room, roomId, userinfo }) => + { + if ( + Array.isArray(userinfo.meet_roles) && + userinfo.meet_roles.includes('moderator') + ) + { + peer.addRole(userRoles.MODERATOR); + } + + if ( + Array.isArray(userinfo.meet_roles) && + userinfo.meet_roles.includes('meetingadmin') + ) + { + peer.addRole(userRoles.ADMIN); + } + + peer.addRole(userRoles.AUTHENTICATED); + }, + // First authenticated user will be moderator, + // all others will be AUTHENTICATED + userMapping : async ({ peer, room, roomId, userinfo }) => + { + if (room) + { + const peers = room.getJoinedPeers(); + + if (peers.some((_peer) => _peer.authenticated)) + peer.addRole(userRoles.AUTHENTICATED); + else + { + peer.addRole(userRoles.MODERATOR); + peer.addRole(userRoles.AUTHENTICATED); + } + } + }, + // All authenicated users will be AUTHENTICATED, + // and those with email ending with @example.com + // will also be MODERATOR + userMapping : async ({ peer, room, roomId, userinfo }) => + { + if (userinfo.email && userinfo.email.endsWith('@example.com')) + { + peer.addRole(userRoles.MODERATOR); + } + + peer.addRole(userRoles.AUTHENTICATED); + }, + // All authenicated users will be AUTHENTICATED, + // and those with email ending with @example.com + // will also be MODERATOR + userMapping : async ({ peer, room, roomId, userinfo }) => + { + if (userinfo.email && userinfo.email.endsWith('@example.com')) + { + peer.addRole(userRoles.MODERATOR); + } + + peer.addRole(userRoles.AUTHENTICATED); + }, + */ + // eslint-disable-next-line no-unused-vars + userMapping : async ({ peer, room, roomId, userinfo }) => + { + if (userinfo.picture != null) + { + if (!userinfo.picture.match(/^http/g)) + { + peer.picture = `data:image/jpeg;base64, ${userinfo.picture}`; + } + else + { + peer.picture = userinfo.picture; + } + } + if (userinfo['urn:oid:0.9.2342.19200300.100.1.60'] != null) + { + peer.picture = `data:image/jpeg;base64, ${userinfo['urn:oid:0.9.2342.19200300.100.1.60']}`; + } + + if (userinfo.nickname != null) + { + peer.displayName = userinfo.nickname; + } + + if (userinfo.name != null) + { + peer.displayName = userinfo.name; + } + + if (userinfo.displayName != null) + { + peer.displayName = userinfo.displayName; + } + + if (userinfo['urn:oid:2.16.840.1.113730.3.1.241'] != null) + { + peer.displayName = userinfo['urn:oid:2.16.840.1.113730.3.1.241']; + } + + if (userinfo.email != null) + { + peer.email = userinfo.email; + } + }, + // All users have the role "NORMAL" by default. Other roles need to be + // added in the "userMapping" function. The following accesses and + // permissions are arrays of roles. Roles can be changed in userRoles.js + // + // Example: + // [ userRoles.MODERATOR, userRoles.AUTHENTICATED ] + accessFromRoles : { + // The role(s) will gain access to the room + // even if it is locked (!) + [BYPASS_ROOM_LOCK] : [ userRoles.ADMIN ], + // The role(s) will gain access to the room without + // going into the lobby. If you want to restrict access to your + // server to only directly allow authenticated users, you could + // add the userRoles.AUTHENTICATED to the user in the userMapping + // function, and change to BYPASS_LOBBY : [ userRoles.AUTHENTICATED ] + [BYPASS_LOBBY] : [ userRoles.NORMAL ] + }, + permissionsFromRoles : { + // The role(s) have permission to lock/unlock a room + [CHANGE_ROOM_LOCK] : [ userRoles.MODERATOR ], + // The role(s) have permission to promote a peer from the lobby + [PROMOTE_PEER] : [ userRoles.NORMAL ], + // The role(s) have permission to give/remove other peers roles + [MODIFY_ROLE] : [ userRoles.NORMAL ], + // The role(s) have permission to send chat messages + [SEND_CHAT] : [ userRoles.NORMAL ], + // The role(s) have permission to moderate chat + [MODERATE_CHAT] : [ userRoles.MODERATOR ], + // The role(s) have permission to share audio + [SHARE_AUDIO] : [ userRoles.NORMAL ], + // The role(s) have permission to share video + [SHARE_VIDEO] : [ userRoles.NORMAL ], + // The role(s) have permission to share screen + [SHARE_SCREEN] : [ userRoles.NORMAL ], + // The role(s) have permission to produce extra video + [EXTRA_VIDEO] : [ userRoles.NORMAL ], + // The role(s) have permission to share files + [SHARE_FILE] : [ userRoles.NORMAL ], + // The role(s) have permission to moderate files + [MODERATE_FILES] : [ userRoles.MODERATOR ], + // The role(s) have permission to moderate room (e.g. kick user) + [MODERATE_ROOM] : [ userRoles.MODERATOR ] + }, + // Array of permissions. If no peer with the permission in question + // is in the room, all peers are permitted to do the action. The peers + // that are allowed because of this rule will not be able to do this + // action as soon as a peer with the permission joins. In this example + // everyone will be able to lock/unlock room until a MODERATOR joins. + allowWhenRoleMissing : [ CHANGE_ROOM_LOCK ], + // When truthy, the room will be open to all users when as long as there + // are allready users in the room + activateOnHostJoin : true, + // When set, maxUsersPerRoom defines how many users can join + // a single room. If not set, there is no limit. + // maxUsersPerRoom : 20, + // Room size before spreading to new router + routerScaleSize : 40, + // Socket timout value + requestTimeout : 20000, + // Socket retries when timeout + requestRetries : 3, + // Mediasoup settings + mediasoup : + { + numWorkers : Object.keys(os.cpus()).length, + // mediasoup Worker settings. + worker : + { + logLevel : 'warn', + logTags : + [ + 'info', + 'ice', + 'dtls', + 'rtp', + 'srtp', + 'rtcp' + ], + rtcMinPort : 40000, + rtcMaxPort : 49999 + }, + // mediasoup Router settings. + router : + { + // Router media codecs. + mediaCodecs : + [ + { + kind : 'audio', + mimeType : 'audio/opus', + clockRate : 48000, + channels : 2 + }, + { + kind : 'video', + mimeType : 'video/VP8', + clockRate : 90000, + parameters : + { + 'x-google-start-bitrate' : 1000 + } + }, + { + kind : 'video', + mimeType : 'video/VP9', + clockRate : 90000, + parameters : + { + 'profile-id' : 2, + 'x-google-start-bitrate' : 1000 + } + }, + { + kind : 'video', + mimeType : 'video/h264', + clockRate : 90000, + parameters : + { + 'packetization-mode' : 1, + 'profile-level-id' : '4d0032', + 'level-asymmetry-allowed' : 1, + 'x-google-start-bitrate' : 1000 + } + }, + { + kind : 'video', + mimeType : 'video/h264', + clockRate : 90000, + parameters : + { + 'packetization-mode' : 1, + 'profile-level-id' : '42e01f', + 'level-asymmetry-allowed' : 1, + 'x-google-start-bitrate' : 1000 + } + } + ] + }, + // mediasoup WebRtcTransport settings. + webRtcTransport : + { + listenIps : + [ + // change 192.0.2.1 IPv4 to your server's IPv4 address!! + { ip: '192.168.1.81', announcedIp: null } + + // Can have multiple listening interfaces + // change 2001:DB8::1 IPv6 to your server's IPv6 address!! + // { ip: '2001:DB8::1', announcedIp: null } + ], + initialAvailableOutgoingBitrate : 1000000, + minimumAvailableOutgoingBitrate : 600000, + // Additional options that are not part of WebRtcTransportOptions. + maxIncomingBitrate : 1500000 + } + } + + /* + , + // Prometheus exporter + prometheus : { + deidentify : false, // deidentify IP addresses + // listen : 'localhost', // exporter listens on this address + numeric : false, // show numeric IP addresses + port : 8889, // allocated port + quiet : false // include fewer labels + } + */ +}; diff --git a/src/app/Http/Controllers/API/V4/Admin/UsersController.php b/src/app/Http/Controllers/API/V4/Admin/UsersController.php index 0caefacf..494ad9ed 100644 --- a/src/app/Http/Controllers/API/V4/Admin/UsersController.php +++ b/src/app/Http/Controllers/API/V4/Admin/UsersController.php @@ -1,308 +1,333 @@ errorResponse(404); } /** * Searching of user accounts. * * @return \Illuminate\Http\JsonResponse */ public function index() { $search = trim(request()->input('search')); $owner = trim(request()->input('owner')); $result = collect([]); if ($owner) { $owner = User::find($owner); if ($owner) { $result = $owner->users(false)->orderBy('email')->get(); } } elseif (strpos($search, '@')) { // Search by email $result = User::withTrashed()->where('email', $search) ->orderBy('email') ->get(); if ($result->isEmpty()) { // Search by an alias $user_ids = UserAlias::where('alias', $search)->get()->pluck('user_id'); // Search by an external email $ext_user_ids = UserSetting::where('key', 'external_email') ->where('value', $search) ->get() ->pluck('user_id'); $user_ids = $user_ids->merge($ext_user_ids)->unique(); // Search by a distribution list email if ($group = Group::withTrashed()->where('email', $search)->first()) { $user_ids = $user_ids->merge([$group->wallet()->user_id])->unique(); } if (!$user_ids->isEmpty()) { $result = User::withTrashed()->whereIn('id', $user_ids) ->orderBy('email') ->get(); } } } elseif (is_numeric($search)) { // Search by user ID $user = User::withTrashed()->where('id', $search) ->first(); if ($user) { $result->push($user); } } elseif (strpos($search, '.') !== false) { // Search by domain $domain = Domain::withTrashed()->where('namespace', $search) ->first(); if ($domain) { if (($wallet = $domain->wallet()) && ($owner = $wallet->owner()->withTrashed()->first())) { $result->push($owner); } } + // A mollie customer ID + } elseif (substr($search, 0, 4) == 'cst_') { + $setting = \App\WalletSetting::where( + [ + 'key' => 'mollie_id', + 'value' => $search + ] + )->first(); + + if ($setting) { + if ($wallet = $setting->wallet) { + if ($owner = $wallet->owner()->withTrashed()->first()) { + $result->push($owner); + } + } + } + // A mollie transaction ID + } elseif (substr($search, 0, 3) == 'tr_') { + $payment = \App\Payment::find($search); + + if ($payment) { + if ($owner = $payment->wallet->owner()->withTrashed()->first()) { + $result->push($owner); + } + } } elseif (!empty($search)) { $wallet = Wallet::find($search); if ($wallet) { if ($owner = $wallet->owner()->withTrashed()->first()) { $result->push($owner); } } } // Process the result $result = $result->map( function ($user) { $data = $user->toArray(); $data = array_merge($data, self::userStatuses($user)); return $data; } ); $result = [ 'list' => $result, 'count' => count($result), 'message' => \trans('app.search-foundxusers', ['x' => count($result)]), ]; return response()->json($result); } /** * Reset 2-Factor Authentication for the user * * @param \Illuminate\Http\Request $request The API request. * @param string $id User identifier * * @return \Illuminate\Http\JsonResponse The response */ public function reset2FA(Request $request, $id) { $user = User::find($id); if (!$this->checkTenant($user)) { return $this->errorResponse(404); } if (!$this->guard()->user()->canUpdate($user)) { return $this->errorResponse(403); } $sku = Sku::withObjectTenantContext($user)->where('title', '2fa')->first(); // Note: we do select first, so the observer can delete // 2FA preferences from Roundcube database, so don't // be tempted to replace first() with delete() below $entitlement = $user->entitlements()->where('sku_id', $sku->id)->first(); $entitlement->delete(); return response()->json([ 'status' => 'success', 'message' => __('app.user-reset-2fa-success'), ]); } /** * Display information on the user account specified by $id. * * @param int $id The account to show information for. * * @return \Illuminate\Http\JsonResponse */ public function show($id) { $user = User::find($id); if (!$this->checkTenant($user)) { return $this->errorResponse(404); } if (!$this->guard()->user()->canRead($user)) { return $this->errorResponse(403); } $response = $this->userResponse($user); // Simplified Entitlement/SKU information, // TODO: I agree this format may need to be extended in future $response['skus'] = []; foreach ($user->entitlements as $ent) { $sku = $ent->sku; if (!isset($response['skus'][$sku->id])) { $response['skus'][$sku->id] = ['costs' => [], 'count' => 0]; } $response['skus'][$sku->id]['count']++; $response['skus'][$sku->id]['costs'][] = $ent->cost; } $response['config'] = $user->getConfig(); return response()->json($response); } /** * Create a new user record. * * @param \Illuminate\Http\Request $request The API request. * * @return \Illuminate\Http\JsonResponse The response */ public function store(Request $request) { return $this->errorResponse(404); } /** * Suspend the user * * @param \Illuminate\Http\Request $request The API request. * @param string $id User identifier * * @return \Illuminate\Http\JsonResponse The response */ public function suspend(Request $request, $id) { $user = User::find($id); if (!$this->checkTenant($user)) { return $this->errorResponse(404); } if (!$this->guard()->user()->canUpdate($user)) { return $this->errorResponse(403); } $user->suspend(); return response()->json([ 'status' => 'success', 'message' => __('app.user-suspend-success'), ]); } /** * Un-Suspend the user * * @param \Illuminate\Http\Request $request The API request. * @param string $id User identifier * * @return \Illuminate\Http\JsonResponse The response */ public function unsuspend(Request $request, $id) { $user = User::find($id); if (!$this->checkTenant($user)) { return $this->errorResponse(404); } if (!$this->guard()->user()->canUpdate($user)) { return $this->errorResponse(403); } $user->unsuspend(); return response()->json([ 'status' => 'success', 'message' => __('app.user-unsuspend-success'), ]); } /** * Update user data. * * @param \Illuminate\Http\Request $request The API request. * @param string $id User identifier * * @return \Illuminate\Http\JsonResponse The response */ public function update(Request $request, $id) { $user = User::find($id); if (!$this->checkTenant($user)) { return $this->errorResponse(404); } if (!$this->guard()->user()->canUpdate($user)) { return $this->errorResponse(403); } // For now admins can change only user external email address $rules = []; if (array_key_exists('external_email', $request->input())) { $rules['external_email'] = 'email'; } // Validate input $v = Validator::make($request->all(), $rules); if ($v->fails()) { return response()->json(['status' => 'error', 'errors' => $v->errors()], 422); } // Update user settings $settings = $request->only(array_keys($rules)); if (!empty($settings)) { $user->setSettings($settings); } return response()->json([ 'status' => 'success', 'message' => __('app.user-update-success'), ]); } } diff --git a/src/config/database.php b/src/config/database.php index b0b4cc2d..57137ff4 100644 --- a/src/config/database.php +++ b/src/config/database.php @@ -1,156 +1,153 @@ env('DB_CONNECTION', 'mysql'), /* |-------------------------------------------------------------------------- | Database Connections |-------------------------------------------------------------------------- | | Here are each of the database connections setup for your application. | Of course, examples of configuring each database platform that is | supported by Laravel is shown below to make development simple. | | | All database work in Laravel is done through the PHP PDO facilities | so make sure you have the driver for your particular database of | choice installed on your machine before you begin development. | */ 'connections' => [ 'sqlite' => [ 'driver' => 'sqlite', 'url' => env('DATABASE_URL'), 'database' => env('DB_DATABASE', database_path('database.sqlite')), 'prefix' => '', 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true), ], - '2fa' => [ - 'driver' => 'mysql', - 'url' => env('MFA_DSN') - ], - 'mysql' => [ 'driver' => 'mysql', 'url' => env('DATABASE_URL'), 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '3306'), 'database' => env('DB_DATABASE', 'forge'), 'username' => env('DB_USERNAME', 'forge'), 'password' => env('DB_PASSWORD', ''), 'unix_socket' => env('DB_SOCKET', ''), 'charset' => 'utf8', 'collation' => 'utf8_unicode_ci', 'prefix' => '', 'prefix_indexes' => true, 'strict' => true, 'timezone' => '+00:00', 'engine' => null, 'options' => extension_loaded('pdo_mysql') ? array_filter([ PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), ]) : [], ], 'pgsql' => [ 'driver' => 'pgsql', 'url' => env('DATABASE_URL'), 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '5432'), 'database' => env('DB_DATABASE', 'forge'), 'username' => env('DB_USERNAME', 'forge'), 'password' => env('DB_PASSWORD', ''), 'charset' => 'utf8', 'prefix' => '', 'prefix_indexes' => true, 'schema' => 'public', 'sslmode' => 'prefer', ], 'sqlsrv' => [ 'driver' => 'sqlsrv', 'url' => env('DATABASE_URL'), 'host' => env('DB_HOST', 'localhost'), 'port' => env('DB_PORT', '1433'), 'database' => env('DB_DATABASE', 'forge'), 'username' => env('DB_USERNAME', 'forge'), 'password' => env('DB_PASSWORD', ''), 'charset' => 'utf8', 'prefix' => '', 'prefix_indexes' => true, ], 'roundcube' => [ 'url' => env('DB_ROUNDCUBE_URL', env('MFA_DSN')), + 'charset' => 'utf8', + 'collation' => 'utf8_unicode_ci', ], ], /* |-------------------------------------------------------------------------- | Migration Repository Table |-------------------------------------------------------------------------- | | This table keeps track of all the migrations that have already run for | your application. Using this information, we can determine which of | the migrations on disk haven't actually been run in the database. | */ 'migrations' => 'migrations', /* |-------------------------------------------------------------------------- | Redis Databases |-------------------------------------------------------------------------- | | Redis is an open source, fast, and advanced key-value store that also | provides a richer body of commands than a typical key-value system | such as APC or Memcached. Laravel makes it easy to dig right in. | */ 'redis' => [ 'client' => env('REDIS_CLIENT', 'predis'), 'options' => [ 'cluster' => env('REDIS_CLUSTER', 'predis'), 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_') . '_database_'), ], 'default' => [ 'url' => env('REDIS_URL'), 'host' => env('REDIS_HOST', '127.0.0.1'), 'password' => env('REDIS_PASSWORD', null), 'port' => env('REDIS_PORT', 6379), 'database' => env('REDIS_DB', 0), ], 'cache' => [ 'url' => env('REDIS_URL'), 'host' => env('REDIS_HOST', '127.0.0.1'), 'password' => env('REDIS_PASSWORD', null), 'port' => env('REDIS_PORT', 6379), 'database' => env('REDIS_CACHE_DB', 1), ], ], ]; diff --git a/src/tests/Feature/Controller/Admin/UsersTest.php b/src/tests/Feature/Controller/Admin/UsersTest.php index a5f0a74a..b5431433 100644 --- a/src/tests/Feature/Controller/Admin/UsersTest.php +++ b/src/tests/Feature/Controller/Admin/UsersTest.php @@ -1,385 +1,436 @@ deleteTestUser('UsersControllerTest1@userscontroller.com'); $this->deleteTestUser('test@testsearch.com'); $this->deleteTestDomain('testsearch.com'); $this->deleteTestGroup('group-test@kolab.org'); $jack = $this->getTestUser('jack@kolab.org'); $jack->setSetting('external_email', null); } /** * {@inheritDoc} */ public function tearDown(): void { $this->deleteTestUser('UsersControllerTest1@userscontroller.com'); $this->deleteTestUser('test@testsearch.com'); $this->deleteTestDomain('testsearch.com'); $jack = $this->getTestUser('jack@kolab.org'); $jack->setSetting('external_email', null); parent::tearDown(); } /** * Test user deleting (DELETE /api/v4/users/) */ public function testDestroy(): void { $john = $this->getTestUser('john@kolab.org'); $user = $this->getTestUser('UsersControllerTest1@userscontroller.com'); $admin = $this->getTestUser('jeroen@jeroen.jeroen'); // Test unauth access $response = $this->delete("api/v4/users/{$user->id}"); $response->assertStatus(401); // The end-point does not exist $response = $this->actingAs($admin)->delete("api/v4/users/{$user->id}"); $response->assertStatus(404); } /** * Test users searching (/api/v4/users) */ public function testIndex(): void { $user = $this->getTestUser('john@kolab.org'); $admin = $this->getTestUser('jeroen@jeroen.jeroen'); $group = $this->getTestGroup('group-test@kolab.org'); $group->assignToWallet($user->wallets->first()); // Non-admin user $response = $this->actingAs($user)->get("api/v4/users"); $response->assertStatus(403); // Search with no search criteria $response = $this->actingAs($admin)->get("api/v4/users"); $response->assertStatus(200); $json = $response->json(); $this->assertSame(0, $json['count']); $this->assertSame([], $json['list']); // Search with no matches expected $response = $this->actingAs($admin)->get("api/v4/users?search=abcd1234efgh5678"); $response->assertStatus(200); $json = $response->json(); $this->assertSame(0, $json['count']); $this->assertSame([], $json['list']); // Search by domain $response = $this->actingAs($admin)->get("api/v4/users?search=kolab.org"); $response->assertStatus(200); $json = $response->json(); $this->assertSame(1, $json['count']); $this->assertCount(1, $json['list']); $this->assertSame($user->id, $json['list'][0]['id']); $this->assertSame($user->email, $json['list'][0]['email']); // Search by user ID $response = $this->actingAs($admin)->get("api/v4/users?search={$user->id}"); $response->assertStatus(200); $json = $response->json(); $this->assertSame(1, $json['count']); $this->assertCount(1, $json['list']); $this->assertSame($user->id, $json['list'][0]['id']); $this->assertSame($user->email, $json['list'][0]['email']); // Search by email (primary) $response = $this->actingAs($admin)->get("api/v4/users?search=john@kolab.org"); $response->assertStatus(200); $json = $response->json(); $this->assertSame(1, $json['count']); $this->assertCount(1, $json['list']); $this->assertSame($user->id, $json['list'][0]['id']); $this->assertSame($user->email, $json['list'][0]['email']); // Search by email (alias) $response = $this->actingAs($admin)->get("api/v4/users?search=john.doe@kolab.org"); $response->assertStatus(200); $json = $response->json(); $this->assertSame(1, $json['count']); $this->assertCount(1, $json['list']); $this->assertSame($user->id, $json['list'][0]['id']); $this->assertSame($user->email, $json['list'][0]['email']); // Search by email (external), expect two users in a result $jack = $this->getTestUser('jack@kolab.org'); $jack->setSetting('external_email', 'john.doe.external@gmail.com'); $response = $this->actingAs($admin)->get("api/v4/users?search=john.doe.external@gmail.com"); $response->assertStatus(200); $json = $response->json(); $this->assertSame(2, $json['count']); $this->assertCount(2, $json['list']); $emails = array_column($json['list'], 'email'); $this->assertContains($user->email, $emails); $this->assertContains($jack->email, $emails); // Search by owner $response = $this->actingAs($admin)->get("api/v4/users?owner={$user->id}"); $response->assertStatus(200); $json = $response->json(); $this->assertSame(4, $json['count']); $this->assertCount(4, $json['list']); // Search by owner (Ned is a controller on John's wallets, // here we expect only users assigned to Ned's wallet(s)) $ned = $this->getTestUser('ned@kolab.org'); $response = $this->actingAs($admin)->get("api/v4/users?owner={$ned->id}"); $response->assertStatus(200); $json = $response->json(); $this->assertSame(0, $json['count']); $this->assertCount(0, $json['list']); // Search by distribution list email $response = $this->actingAs($admin)->get("api/v4/users?search=group-test@kolab.org"); $response->assertStatus(200); $json = $response->json(); $this->assertSame(1, $json['count']); $this->assertCount(1, $json['list']); $this->assertSame($user->id, $json['list'][0]['id']); $this->assertSame($user->email, $json['list'][0]['email']); // Deleted users/domains $domain = $this->getTestDomain('testsearch.com', ['type' => \App\Domain::TYPE_EXTERNAL]); $user = $this->getTestUser('test@testsearch.com'); $plan = \App\Plan::where('title', 'group')->first(); $user->assignPlan($plan, $domain); $user->setAliases(['alias@testsearch.com']); + + $wallet = $user->wallets()->first(); + $wallet->setSetting('mollie_id', 'cst_nonsense'); + + \App\Payment::create( + [ + 'id' => 'tr_nonsense', + 'wallet_id' => $wallet->id, + 'status' => 'paid', + 'amount' => 1337, + 'description' => 'nonsense transaction for testing', + 'provider' => 'self', + 'type' => 'oneoff', + 'currency' => 'CHF', + 'currency_amount' => 1337 + ] + ); + Queue::fake(); $user->delete(); $response = $this->actingAs($admin)->get("api/v4/users?search=test@testsearch.com"); $response->assertStatus(200); $json = $response->json(); $this->assertSame(1, $json['count']); $this->assertCount(1, $json['list']); $this->assertSame($user->id, $json['list'][0]['id']); $this->assertSame($user->email, $json['list'][0]['email']); $this->assertTrue($json['list'][0]['isDeleted']); $response = $this->actingAs($admin)->get("api/v4/users?search=alias@testsearch.com"); $response->assertStatus(200); $json = $response->json(); $this->assertSame(1, $json['count']); $this->assertCount(1, $json['list']); $this->assertSame($user->id, $json['list'][0]['id']); $this->assertSame($user->email, $json['list'][0]['email']); $this->assertTrue($json['list'][0]['isDeleted']); $response = $this->actingAs($admin)->get("api/v4/users?search=testsearch.com"); $response->assertStatus(200); $json = $response->json(); $this->assertSame(1, $json['count']); $this->assertCount(1, $json['list']); $this->assertSame($user->id, $json['list'][0]['id']); $this->assertSame($user->email, $json['list'][0]['email']); $this->assertTrue($json['list'][0]['isDeleted']); + + $response = $this->actingAs($admin)->get("api/v4/users?search={$wallet->id}"); + $response->assertStatus(200); + + $json = $response->json(); + + $this->assertSame(1, $json['count']); + $this->assertCount(1, $json['list']); + $this->assertSame($user->id, $json['list'][0]['id']); + $this->assertSame($user->email, $json['list'][0]['email']); + $this->assertTrue($json['list'][0]['isDeleted']); + + $response = $this->actingAs($admin)->get("api/v4/users?search=tr_nonsense"); + $response->assertStatus(200); + + $json = $response->json(); + + $this->assertSame(1, $json['count']); + $this->assertCount(1, $json['list']); + $this->assertSame($user->id, $json['list'][0]['id']); + $this->assertSame($user->email, $json['list'][0]['email']); + $this->assertTrue($json['list'][0]['isDeleted']); + + $response = $this->actingAs($admin)->get("api/v4/users?search=cst_nonsense"); + $response->assertStatus(200); + + $json = $response->json(); + + $this->assertSame(1, $json['count']); + $this->assertCount(1, $json['list']); + $this->assertSame($user->id, $json['list'][0]['id']); + $this->assertSame($user->email, $json['list'][0]['email']); + $this->assertTrue($json['list'][0]['isDeleted']); } /** * Test reseting 2FA (POST /api/v4/users//reset2FA) */ public function testReset2FA(): void { $user = $this->getTestUser('UsersControllerTest1@userscontroller.com'); $admin = $this->getTestUser('jeroen@jeroen.jeroen'); $sku2fa = Sku::withEnvTenantContext()->where(['title' => '2fa'])->first(); $user->assignSku($sku2fa); SecondFactor::seed('userscontrollertest1@userscontroller.com'); // Test unauthorized access to admin API $response = $this->actingAs($user)->post("/api/v4/users/{$user->id}/reset2FA", []); $response->assertStatus(403); $entitlements = $user->fresh()->entitlements()->where('sku_id', $sku2fa->id)->get(); $this->assertCount(1, $entitlements); $sf = new SecondFactor($user); $this->assertCount(1, $sf->factors()); // Test reseting 2FA $response = $this->actingAs($admin)->post("/api/v4/users/{$user->id}/reset2FA", []); $response->assertStatus(200); $json = $response->json(); $this->assertSame('success', $json['status']); $this->assertSame("2-Factor authentication reset successfully.", $json['message']); $this->assertCount(2, $json); $entitlements = $user->fresh()->entitlements()->where('sku_id', $sku2fa->id)->get(); $this->assertCount(0, $entitlements); $sf = new SecondFactor($user); $this->assertCount(0, $sf->factors()); } /** * Test user creation (POST /api/v4/users) */ public function testStore(): void { $admin = $this->getTestUser('jeroen@jeroen.jeroen'); // The end-point does not exist $response = $this->actingAs($admin)->post("/api/v4/users", []); $response->assertStatus(404); } /** * Test user suspending (POST /api/v4/users//suspend) */ public function testSuspend(): void { Queue::fake(); // disable jobs $user = $this->getTestUser('UsersControllerTest1@userscontroller.com'); $admin = $this->getTestUser('jeroen@jeroen.jeroen'); // Test unauthorized access to admin API $response = $this->actingAs($user)->post("/api/v4/users/{$user->id}/suspend", []); $response->assertStatus(403); $this->assertFalse($user->isSuspended()); // Test suspending the user $response = $this->actingAs($admin)->post("/api/v4/users/{$user->id}/suspend", []); $response->assertStatus(200); $json = $response->json(); $this->assertSame('success', $json['status']); $this->assertSame("User suspended successfully.", $json['message']); $this->assertCount(2, $json); $this->assertTrue($user->fresh()->isSuspended()); } /** * Test user un-suspending (POST /api/v4/users//unsuspend) */ public function testUnsuspend(): void { Queue::fake(); // disable jobs $user = $this->getTestUser('UsersControllerTest1@userscontroller.com'); $admin = $this->getTestUser('jeroen@jeroen.jeroen'); // Test unauthorized access to admin API $response = $this->actingAs($user)->post("/api/v4/users/{$user->id}/unsuspend", []); $response->assertStatus(403); $this->assertFalse($user->isSuspended()); $user->suspend(); $this->assertTrue($user->isSuspended()); // Test suspending the user $response = $this->actingAs($admin)->post("/api/v4/users/{$user->id}/unsuspend", []); $response->assertStatus(200); $json = $response->json(); $this->assertSame('success', $json['status']); $this->assertSame("User unsuspended successfully.", $json['message']); $this->assertCount(2, $json); $this->assertFalse($user->fresh()->isSuspended()); } /** * Test user update (PUT /api/v4/users/) */ public function testUpdate(): void { $user = $this->getTestUser('UsersControllerTest1@userscontroller.com'); $admin = $this->getTestUser('jeroen@jeroen.jeroen'); // Test unauthorized access to admin API $response = $this->actingAs($user)->put("/api/v4/users/{$user->id}", []); $response->assertStatus(403); // Test updatig the user data (empty data) $response = $this->actingAs($admin)->put("/api/v4/users/{$user->id}", []); $response->assertStatus(200); $json = $response->json(); $this->assertSame('success', $json['status']); $this->assertSame("User data updated successfully.", $json['message']); $this->assertCount(2, $json); // Test error handling $post = ['external_email' => 'aaa']; $response = $this->actingAs($admin)->put("/api/v4/users/{$user->id}", $post); $response->assertStatus(422); $json = $response->json(); $this->assertSame('error', $json['status']); $this->assertSame("The external email must be a valid email address.", $json['errors']['external_email'][0]); $this->assertCount(2, $json); // Test real update $post = ['external_email' => 'modified@test.com']; $response = $this->actingAs($admin)->put("/api/v4/users/{$user->id}", $post); $response->assertStatus(200); $json = $response->json(); $this->assertSame('success', $json['status']); $this->assertSame("User data updated successfully.", $json['message']); $this->assertCount(2, $json); $this->assertSame('modified@test.com', $user->getSetting('external_email')); } } diff --git a/src/tests/Feature/Controller/OpenViduTest.php b/src/tests/Feature/Controller/OpenViduTest.php index 73c5eaac..515db1fd 100644 --- a/src/tests/Feature/Controller/OpenViduTest.php +++ b/src/tests/Feature/Controller/OpenViduTest.php @@ -1,785 +1,785 @@ clearMeetEntitlements(); $room = Room::where('name', 'john')->first(); $room->setSettings(['password' => null, 'locked' => null, 'nomedia' => null]); } public function tearDown(): void { $this->clearMeetEntitlements(); $room = Room::where('name', 'john')->first(); $room->setSettings(['password' => null, 'locked' => null, 'nomedia' => null]); parent::tearDown(); } /** * Test listing user rooms * * @group openvidu */ public function testIndex(): void { $john = $this->getTestUser('john@kolab.org'); $jack = $this->getTestUser('jack@kolab.org'); Room::where('user_id', $jack->id)->delete(); // Unauth access not allowed $response = $this->get("api/v4/openvidu/rooms"); $response->assertStatus(401); // John has one room $response = $this->actingAs($john)->get("api/v4/openvidu/rooms"); $response->assertStatus(200); $json = $response->json(); $this->assertSame(1, $json['count']); $this->assertCount(1, $json['list']); $this->assertSame('john', $json['list'][0]['name']); // Jack has no room, but it will be auto-created $response = $this->actingAs($jack)->get("api/v4/openvidu/rooms"); $response->assertStatus(200); $json = $response->json(); $this->assertSame(1, $json['count']); $this->assertCount(1, $json['list']); $this->assertMatchesRegularExpression('/^[0-9a-z-]{11}$/', $json['list'][0]['name']); } /** * Test joining the room * * @group openvidu */ public function testJoinRoom(): void { $john = $this->getTestUser('john@kolab.org'); $jack = $this->getTestUser('jack@kolab.org'); $room = Room::where('name', 'john')->first(); $room->session_id = null; $room->save(); $this->assignMeetEntitlement($john); // Unauth access, no session yet $response = $this->post("api/v4/openvidu/rooms/{$room->name}"); $response->assertStatus(422); $json = $response->json(); $this->assertSame(323, $json['code']); // Non-existing room name $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/non-existing"); $response->assertStatus(404); // TODO: Test accessing an existing room of deleted owner // Non-owner, no session yet $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}"); $response->assertStatus(422); $json = $response->json(); $this->assertSame(323, $json['code']); // Room owner, no session yet $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}"); $response->assertStatus(422); $json = $response->json(); $this->assertSame(324, $json['code']); $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}", ['init' => 1]); $response->assertStatus(200); $json = $response->json(); $session_id = $room->fresh()->session_id; $this->assertSame(Room::ROLE_SUBSCRIBER | Room::ROLE_MODERATOR | Room::ROLE_OWNER, $json['role']); $this->assertSame($session_id, $json['session']); $this->assertTrue(is_string($session_id) && !empty($session_id)); $this->assertTrue(strpos($json['token'], 'wss://') === 0); $john_token = $json['token']; // Non-owner, now the session exists, no 'init' argument $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}"); $response->assertStatus(422); $json = $response->json(); $this->assertSame(322, $json['code']); $this->assertTrue(empty($json['token'])); // Non-owner, now the session exists, with 'init', but no 'canPublish' argument $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", ['init' => 1]); $response->assertStatus(200); $json = $response->json(); $this->assertSame(Room::ROLE_SUBSCRIBER, $json['role']); $this->assertSame($session_id, $json['session']); $this->assertTrue(strpos($json['token'], 'wss://') === 0); $this->assertTrue($json['token'] != $john_token); // Non-owner, now the session exists, with 'init', and with 'role=PUBLISHER' $post = ['canPublish' => true, 'init' => 1]; $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", $post); $response->assertStatus(200); $json = $response->json(); $this->assertSame(Room::ROLE_PUBLISHER, $json['role']); $this->assertSame($session_id, $json['session']); $this->assertTrue(strpos($json['token'], 'wss://') === 0); $this->assertTrue($json['token'] != $john_token); $this->assertEmpty($json['config']['password']); $this->assertEmpty($json['config']['requires_password']); // Non-owner, password protected room, password not provided $room->setSettings(['password' => 'pass']); $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}"); $response->assertStatus(422); $json = $response->json(); $this->assertCount(4, $json); $this->assertSame(325, $json['code']); $this->assertSame('error', $json['status']); $this->assertSame('Failed to join the session. Invalid password.', $json['message']); $this->assertEmpty($json['config']['password']); $this->assertTrue($json['config']['requires_password']); // Non-owner, password protected room, invalid provided $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", ['password' => 'aa']); $response->assertStatus(422); $json = $response->json(); $this->assertSame(325, $json['code']); // Non-owner, password protected room, valid password provided // TODO: Test without init=1 $post = ['password' => 'pass', 'init' => 'init']; $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", $post); $response->assertStatus(200); $json = $response->json(); $this->assertSame($session_id, $json['session']); // Make sure the room owner can access the password protected room w/o password // TODO: Test without init=1 $post = ['init' => 'init']; $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}", $post); $response->assertStatus(200); // Test 'nomedia' room option $room->setSettings(['nomedia' => 'true', 'password' => null]); $post = ['init' => 'init', 'canPublish' => true]; $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}", $post); $response->assertStatus(200); $json = $response->json(); $this->assertSame(Room::ROLE_PUBLISHER & $json['role'], Room::ROLE_PUBLISHER); $post = ['init' => 'init', 'canPublish' => true]; $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", $post); $response->assertStatus(200); $json = $response->json(); $this->assertSame(Room::ROLE_PUBLISHER & $json['role'], 0); } /** * Test locked room and join requests * * @group openvidu */ /* public function testJoinRequests(): void */ /* { */ /* $john = $this->getTestUser('john@kolab.org'); */ /* $jack = $this->getTestUser('jack@kolab.org'); */ /* $room = Room::where('name', 'john')->first(); */ /* $room->session_id = null; */ /* $room->save(); */ /* $room->setSettings(['password' => null, 'locked' => 'true']); */ /* $this->assignMeetEntitlement($john); */ /* // Create the session (also makes sure the owner can access a locked room) */ /* $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}", ['init' => 1]); */ /* $response->assertStatus(200); */ /* // Non-owner, locked room, invalid/missing input */ /* $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}"); */ /* $response->assertStatus(422); */ /* $json = $response->json(); */ /* $this->assertCount(4, $json); */ /* $this->assertSame(326, $json['code']); */ /* $this->assertSame('error', $json['status']); */ /* $this->assertSame('Failed to join the session. Room locked.', $json['message']); */ /* $this->assertTrue($json['config']['locked']); */ /* // Non-owner, locked room, invalid requestId */ /* $post = ['nickname' => 'name', 'requestId' => '-----', 'init' => 1]; */ /* $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", $post); */ /* $response->assertStatus(422); */ /* $json = $response->json(); */ /* $this->assertSame(326, $json['code']); */ /* // Non-owner, locked room, invalid requestId */ /* $post = ['nickname' => 'name', 'init' => 1]; */ /* $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", $post); */ /* $response->assertStatus(422); */ /* $json = $response->json(); */ /* $this->assertSame(326, $json['code']); */ /* // Non-owner, locked room, valid input */ /* $reqId = '12345678'; */ /* $post = ['nickname' => 'name', 'requestId' => $reqId, 'picture' => 'data:image/png;base64,01234']; */ /* $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", $post); */ /* $response->assertStatus(422); */ /* $json = $response->json(); */ /* $this->assertCount(4, $json); */ /* $this->assertSame(327, $json['code']); */ /* $this->assertSame('error', $json['status']); */ /* $this->assertSame('Failed to join the session. Room locked.', $json['message']); */ /* $this->assertTrue($json['config']['locked']); */ /* // TODO: How do we assert that a signal has been sent to the owner? */ /* // Test denying a request */ /* // Unknown room */ /* $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/unknown/request/unknown/deny"); */ /* $response->assertStatus(404); */ /* // Unknown request Id */ /* $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/request/unknown/deny"); */ /* $response->assertStatus(500); */ /* $json = $response->json(); */ /* $this->assertCount(2, $json); */ /* $this->assertSame('error', $json['status']); */ /* $this->assertSame('Failed to deny the join request.', $json['message']); */ /* // Non-owner access forbidden */ /* $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}/request/{$reqId}/deny"); */ /* $response->assertStatus(403); */ /* // Valid request */ /* $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/request/{$reqId}/deny"); */ /* $response->assertStatus(200); */ /* $json = $response->json(); */ /* $this->assertSame('success', $json['status']); */ /* // Non-owner, locked room, join request denied */ /* $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", $post); */ /* $response->assertStatus(422); */ /* $json = $response->json(); */ /* $this->assertSame(327, $json['code']); */ /* // Test accepting a request */ /* // Unknown room */ /* $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/unknown/request/unknown/accept"); */ /* $response->assertStatus(404); */ /* // Unknown request Id */ /* $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/request/unknown/accept"); */ /* $response->assertStatus(500); */ /* $json = $response->json(); */ /* $this->assertCount(2, $json); */ /* $this->assertSame('error', $json['status']); */ /* $this->assertSame('Failed to accept the join request.', $json['message']); */ /* // Non-owner access forbidden */ /* $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}/request/{$reqId}/accept"); */ /* $response->assertStatus(403); */ /* // Valid request */ /* $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/request/{$reqId}/accept"); */ /* $response->assertStatus(200); */ /* $json = $response->json(); */ /* $this->assertSame('success', $json['status']); */ /* // Non-owner, locked room, join request accepted */ /* $post['init'] = 1; */ /* $post['canPublish'] = true; */ /* $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", $post); */ /* $response->assertStatus(200); */ /* $json = $response->json(); */ /* $this->assertSame(Room::ROLE_PUBLISHER, $json['role']); */ /* $this->assertTrue(strpos($json['token'], 'wss://') === 0); */ /* // TODO: Test a scenario where both password and lock are enabled */ /* // TODO: Test accepting/denying as a non-owner moderator */ /* } */ /** * Test joining the room * * @group openvidu * @depends testJoinRoom */ public function testJoinRoomGuest(): void { $this->assignMeetEntitlement('john@kolab.org'); // There's no asy way to logout the user in the same test after // using actingAs(). That's why this is moved to a separate test $room = Room::where('name', 'john')->first(); // Guest, request with screenShare token $post = ['canPublish' => true, 'screenShare' => 1, 'init' => 1]; $response = $this->post("api/v4/openvidu/rooms/{$room->name}", $post); $response->assertStatus(200); $json = $response->json(); $this->assertSame(Room::ROLE_PUBLISHER, $json['role']); $this->assertSame($room->session_id, $json['session']); $this->assertTrue(strpos($json['token'], 'wss://') === 0); } /** * Test closing the room (session) * * @group openvidu * @depends testJoinRoom */ /* public function testCloseRoom(): void */ /* { */ /* $john = $this->getTestUser('john@kolab.org'); */ /* $jack = $this->getTestUser('jack@kolab.org'); */ /* $room = Room::where('name', 'john')->first(); */ /* // Unauth access not allowed */ /* $response = $this->post("api/v4/openvidu/rooms/{$room->name}/close", []); */ /* $response->assertStatus(401); */ /* // Non-existing room name */ /* $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/non-existing/close", []); */ /* $response->assertStatus(404); */ /* // Non-owner */ /* $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}/close", []); */ /* $response->assertStatus(403); */ /* // Room owner */ /* $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/close", []); */ /* $response->assertStatus(200); */ /* $json = $response->json(); */ /* $this->assertNull($room->fresh()->session_id); */ /* $this->assertSame('success', $json['status']); */ /* $this->assertSame("The session has been closed successfully.", $json['message']); */ /* $this->assertCount(2, $json); */ /* // TODO: Test if the session is removed from the OpenVidu server too */ /* // Test error handling when it's not possible to delete the session on */ /* // the OpenVidu server (use fake session_id) */ /* $room->session_id = 'aaa'; */ /* $room->save(); */ /* $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/close", []); */ /* $response->assertStatus(500); */ /* $json = $response->json(); */ /* $this->assertSame('aaa', $room->fresh()->session_id); */ /* $this->assertSame('error', $json['status']); */ /* $this->assertSame("Failed to close the session.", $json['message']); */ /* $this->assertCount(2, $json); */ /* } */ /** * Test creating an extra connection for screen sharing * * @group openvidu */ /* public function testCreateConnection(): void */ /* { */ /* $john = $this->getTestUser('john@kolab.org'); */ /* $jack = $this->getTestUser('jack@kolab.org'); */ /* $room = Room::where('name', 'john')->first(); */ /* $room->session_id = null; */ /* $room->save(); */ /* $this->assignMeetEntitlement($john); */ /* // First we create the session */ /* $post = ['init' => 1, 'canPublish' => 1]; */ /* $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}", $post); */ /* $response->assertStatus(200); */ /* $json = $response->json(); */ /* $owner_auth_token = $json['authToken']; */ /* // And the other user connection */ /* $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", ['init' => 1]); */ /* $response->assertStatus(200); */ /* $json = $response->json(); */ /* $conn_id = $json['connectionId']; */ /* $auth_token = $json['authToken']; */ /* // Non-existing room name */ /* $response = $this->post("api/v4/openvidu/rooms/non-existing/connections", []); */ /* $response->assertStatus(404); */ /* // No connection token provided */ /* $response = $this->post("api/v4/openvidu/rooms/{$room->name}/connections", []); */ /* $response->assertStatus(403); */ /* // Invalid token */ /* $response = $this->actingAs($jack) */ /* ->withHeaders([OpenViduController::AUTH_HEADER => '123']) */ /* ->post("api/v4/openvidu/rooms/{$room->name}/connections", []); */ /* $response->assertStatus(403); */ /* // Subscriber can't get the screen-sharing connection */ /* // Note: We're acting as Jack because there's no easy way to unset the 'actingAs' user */ /* // throughout the test */ /* $response = $this->actingAs($jack) */ /* ->withHeaders([OpenViduController::AUTH_HEADER => $auth_token]) */ /* ->post("api/v4/openvidu/rooms/{$room->name}/connections", []); */ /* $response->assertStatus(403); */ /* // Publisher can get the connection */ /* $response = $this->actingAs($jack) */ /* ->withHeaders([OpenViduController::AUTH_HEADER => $owner_auth_token]) */ /* ->post("api/v4/openvidu/rooms/{$room->name}/connections", []); */ /* $response->assertStatus(200); */ /* $json = $response->json(); */ /* $this->assertSame('success', $json['status']); */ /* $this->assertTrue(strpos($json['token'], 'wss://') === 0); */ /* // OpenVidu 2.18 does not send 'role' param in the token uri */ /* // $this->assertTrue(strpos($json['token'], 'role=PUBLISHER') !== false); */ /* } */ /** * Test dismissing a participant (closing a connection) * * @group openvidu */ public function testDismissConnection(): void { /* $john = $this->getTestUser('john@kolab.org'); $jack = $this->getTestUser('jack@kolab.org'); $room = Room::where('name', 'john')->first(); $room->session_id = null; $room->save(); $this->assignMeetEntitlement($john); // First we create the session $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}", ['init' => 1]); $response->assertStatus(200); $json = $response->json(); // And the other user connection $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", ['init' => 1]); $response->assertStatus(200); $json = $response->json(); $conn_id = $json['connectionId']; $room->refresh(); $conn_data = $room->getOVConnection($conn_id); $this->assertSame($conn_id, $conn_data['connectionId']); // Non-existing room name $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/non-existing/connections/{$conn_id}/dismiss"); $response->assertStatus(404); // TODO: Test accessing an existing room of deleted owner // Non-existing connection $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/connections/123/dismiss"); $response->assertStatus(404); $json = $response->json(); $this->assertCount(2, $json); $this->assertSame('error', $json['status']); $this->assertSame('The connection does not exist.', $json['message']); // Non-owner access $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}/connections/{$conn_id}/dismiss"); $response->assertStatus(403); // Expected success $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/connections/{$conn_id}/dismiss"); $response->assertStatus(200); $json = $response->json(); $this->assertSame('success', $json['status']); $this->assertNull($room->getOVConnection($conn_id)); // Test acting as a moderator $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", ['init' => 1]); $response->assertStatus(200); $json = $response->json(); $conn_id = $json['connectionId']; // Note: We're acting as Jack because there's no easy way to unset a 'actingAs' user // throughout the test $response = $this->actingAs($jack) ->withHeaders([OpenViduController::AUTH_HEADER => $this->getModeratorToken($room)]) ->post("api/v4/openvidu/rooms/{$room->name}/connections/{$conn_id}/dismiss"); $response->assertStatus(200); */ } /** * Test configuring the room (session) * * @group openvidu */ /* public function testSetRoomConfig(): void */ /* { */ /* $john = $this->getTestUser('john@kolab.org'); */ /* $jack = $this->getTestUser('jack@kolab.org'); */ /* $room = Room::where('name', 'john')->first(); */ /* // Unauth access not allowed */ /* $response = $this->post("api/v4/openvidu/rooms/{$room->name}/config", []); */ /* $response->assertStatus(401); */ /* // Non-existing room name */ /* $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/non-existing/config", []); */ /* $response->assertStatus(404); */ /* // TODO: Test a room with a deleted owner */ /* // Non-owner */ /* $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}/config", []); */ /* $response->assertStatus(403); */ /* // Room owner */ /* $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/config", []); */ /* $response->assertStatus(200); */ /* $json = $response->json(); */ /* $this->assertCount(2, $json); */ /* $this->assertSame('success', $json['status']); */ /* $this->assertSame("Room configuration updated successfully.", $json['message']); */ /* // Set password and room lock */ /* $post = ['password' => 'aaa', 'locked' => 1]; */ /* $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/config", $post); */ /* $response->assertStatus(200); */ /* $json = $response->json(); */ /* $this->assertCount(2, $json); */ /* $this->assertSame('success', $json['status']); */ /* $this->assertSame("Room configuration updated successfully.", $json['message']); */ /* $room->refresh(); */ /* $this->assertSame('aaa', $room->getSetting('password')); */ /* $this->assertSame('true', $room->getSetting('locked')); */ /* // Unset password and room lock */ /* $post = ['password' => '', 'locked' => 0]; */ /* $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/config", $post); */ /* $response->assertStatus(200); */ /* $json = $response->json(); */ /* $this->assertCount(2, $json); */ /* $this->assertSame('success', $json['status']); */ /* $this->assertSame("Room configuration updated successfully.", $json['message']); */ /* $room->refresh(); */ /* $this->assertSame(null, $room->getSetting('password')); */ /* $this->assertSame(null, $room->getSetting('locked')); */ /* // Test invalid option error */ /* $post = ['password' => 'eee', 'unknown' => 0]; */ /* $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}/config", $post); */ /* $response->assertStatus(422); */ /* $json = $response->json(); */ /* $this->assertCount(2, $json); */ /* $this->assertSame('error', $json['status']); */ /* $this->assertSame("Invalid room configuration option.", $json['errors']['unknown']); */ /* $room->refresh(); */ /* $this->assertSame(null, $room->getSetting('password')); */ /* } */ /** * Test updating a participant (connection) * * @group openvidu */ + /* public function testUpdateConnection(): void { - /* $john = $this->getTestUser('john@kolab.org'); $jack = $this->getTestUser('jack@kolab.org'); $room = Room::where('name', 'john')->first(); $room->session_id = null; $room->save(); $this->assignMeetEntitlement($john); // First we create the session $response = $this->actingAs($john)->post("api/v4/openvidu/rooms/{$room->name}", ['init' => 1]); $response->assertStatus(200); $json = $response->json(); $owner_conn_id = $json['connectionId']; // And the other user connection $response = $this->actingAs($jack)->post("api/v4/openvidu/rooms/{$room->name}", ['init' => 1]); $response->assertStatus(200); $json = $response->json(); $conn_id = $json['connectionId']; $auth_token = $json['authToken']; $room->refresh(); $conn_data = $room->getOVConnection($conn_id); $this->assertSame($conn_id, $conn_data['connectionId']); // Non-existing room name $response = $this->actingAs($john)->put("api/v4/openvidu/rooms/non-existing/connections/{$conn_id}", []); $response->assertStatus(404); // Non-existing connection $response = $this->actingAs($john)->put("api/v4/openvidu/rooms/{$room->name}/connections/123", []); $response->assertStatus(404); $json = $response->json(); $this->assertCount(2, $json); $this->assertSame('error', $json['status']); $this->assertSame('The connection does not exist.', $json['message']); // Non-owner access (empty post) $response = $this->actingAs($jack)->put("api/v4/openvidu/rooms/{$room->name}/connections/{$conn_id}", []); $response->assertStatus(200); // Non-owner access (role update) $post = ['role' => Room::ROLE_PUBLISHER | Room::ROLE_MODERATOR]; $response = $this->actingAs($jack)->put("api/v4/openvidu/rooms/{$room->name}/connections/{$conn_id}", $post); $response->assertStatus(403); // Expected success $post = ['role' => Room::ROLE_PUBLISHER | Room::ROLE_MODERATOR]; $response = $this->actingAs($john)->put("api/v4/openvidu/rooms/{$room->name}/connections/{$conn_id}", $post); $response->assertStatus(200); + */ - $json = $response->json(); + /* $json = $response->json(); */ - $this->assertSame('success', $json['status']); - $this->assertSame($post['role'], Connection::find($conn_id)->role); + /* $this->assertSame('success', $json['status']); */ + /* $this->assertSame($post['role'], Connection::find($conn_id)->role); */ - // Access as moderator - // Note: We're acting as Jack because there's no easy way to unset a 'actingAs' user - // throughout the test - $token = $this->getModeratorToken($room); - $post = ['role' => Room::ROLE_PUBLISHER]; - $response = $this->actingAs($jack)->withHeaders([OpenViduController::AUTH_HEADER => $token]) - ->put("api/v4/openvidu/rooms/{$room->name}/connections/{$conn_id}", $post); - $response->assertStatus(200); + /* // Access as moderator */ + /* // Note: We're acting as Jack because there's no easy way to unset a 'actingAs' user */ + /* // throughout the test */ + /* $token = $this->getModeratorToken($room); */ + /* $post = ['role' => Room::ROLE_PUBLISHER]; */ + /* $response = $this->actingAs($jack)->withHeaders([OpenViduController::AUTH_HEADER => $token]) */ + /* ->put("api/v4/openvidu/rooms/{$room->name}/connections/{$conn_id}", $post); */ + /* $response->assertStatus(200); */ - $this->assertSame('success', $json['status']); - $this->assertSame($post['role'], Connection::find($conn_id)->role); + /* $this->assertSame('success', $json['status']); */ + /* $this->assertSame($post['role'], Connection::find($conn_id)->role); */ - // Assert that it's not possible to add/remove the 'owner' role - $post = ['role' => Room::ROLE_PUBLISHER | Room::ROLE_OWNER]; - $response = $this->actingAs($jack)->withHeaders([OpenViduController::AUTH_HEADER => $token]) - ->put("api/v4/openvidu/rooms/{$room->name}/connections/{$conn_id}", $post); + /* // Assert that it's not possible to add/remove the 'owner' role */ + /* $post = ['role' => Room::ROLE_PUBLISHER | Room::ROLE_OWNER]; */ + /* $response = $this->actingAs($jack)->withHeaders([OpenViduController::AUTH_HEADER => $token]) */ + /* ->put("api/v4/openvidu/rooms/{$room->name}/connections/{$conn_id}", $post); */ - $response->assertStatus(403); + /* $response->assertStatus(403); */ - $post = ['role' => Room::ROLE_PUBLISHER]; - $response = $this->actingAs($jack)->withHeaders([OpenViduController::AUTH_HEADER => $token]) - ->put("api/v4/openvidu/rooms/{$room->name}/connections/{$owner_conn_id}", $post); + /* $post = ['role' => Room::ROLE_PUBLISHER]; */ + /* $response = $this->actingAs($jack)->withHeaders([OpenViduController::AUTH_HEADER => $token]) */ + /* ->put("api/v4/openvidu/rooms/{$room->name}/connections/{$owner_conn_id}", $post); */ - $response->assertStatus(403); + /* $response->assertStatus(403); */ - // Assert that removing a 'moderator' role from the owner is not possible - $post = ['role' => Room::ROLE_PUBLISHER | Room::ROLE_OWNER]; - $response = $this->actingAs($jack)->withHeaders([OpenViduController::AUTH_HEADER => $token]) - ->put("api/v4/openvidu/rooms/{$room->name}/connections/{$owner_conn_id}", $post); + /* // Assert that removing a 'moderator' role from the owner is not possible */ + /* $post = ['role' => Room::ROLE_PUBLISHER | Room::ROLE_OWNER]; */ + /* $response = $this->actingAs($jack)->withHeaders([OpenViduController::AUTH_HEADER => $token]) */ + /* ->put("api/v4/openvidu/rooms/{$room->name}/connections/{$owner_conn_id}", $post); */ - $response->assertStatus(200); + /* $response->assertStatus(200); */ - $this->assertSame($post['role'] | Room::ROLE_MODERATOR, Connection::find($owner_conn_id)->role); + /* $this->assertSame($post['role'] | Room::ROLE_MODERATOR, Connection::find($owner_conn_id)->role); */ - // Assert that non-moderator token does not allow access - $post = ['role' => Room::ROLE_SUBSCRIBER]; - $response = $this->actingAs($jack)->withHeaders([OpenViduController::AUTH_HEADER => $auth_token]) - ->put("api/v4/openvidu/rooms/{$room->name}/connections/{$conn_id}", $post); + /* // Assert that non-moderator token does not allow access */ + /* $post = ['role' => Room::ROLE_SUBSCRIBER]; */ + /* $response = $this->actingAs($jack)->withHeaders([OpenViduController::AUTH_HEADER => $auth_token]) */ + /* ->put("api/v4/openvidu/rooms/{$room->name}/connections/{$conn_id}", $post); */ - $response->assertStatus(403); + /* $response->assertStatus(403); */ // TODO: Test updating 'language' and 'hand' properties - */ - } + /* } */ /** * Create a moderator connection to the room session. * * @param \App\OpenVidu\Room $room The room * * @return string The connection authentication token */ /* private function getModeratorToken(Room $room): string */ /* { */ /* $result = $room->getSessionToken(Room::ROLE_MODERATOR); */ /* return $result['authToken']; */ /* } */ }