Browse Source

Merge pull request #402 from nextcloud/room-password

Room passwords
pull/424/head
Ivan Sein 8 years ago
committed by GitHub
parent
commit
734900bc5c
  1. 2
      appinfo/info.xml
  2. 9
      appinfo/routes.php
  3. 7
      css/style.css
  4. 23
      docs/api-v1.md
  5. 48
      js/signaling.js
  6. 56
      js/views/roomlistview.js
  7. 61
      lib/Controller/CallController.php
  8. 113
      lib/Controller/RoomController.php
  9. 27
      lib/Exceptions/InvalidPasswordException.php
  10. 14
      lib/Manager.php
  11. 53
      lib/Migration/Version2001Date20170921145301.php
  12. 56
      lib/Room.php
  13. 26
      tests/integration/features/bootstrap/FeatureContext.php
  14. 130
      tests/integration/features/callapi/password.feature
  15. 70
      tests/integration/features/set-password.feature

2
appinfo/info.xml

@ -42,7 +42,7 @@ And in the works for the [coming versions](https://github.com/nextcloud/spreed/m
<bugs>https://github.com/nextcloud/spreed/issues</bugs>
<repository type="git">https://github.com/nextcloud/spreed.git</repository>
<version>2.1.3</version>
<version>2.1.4</version>
<dependencies>
<nextcloud min-version="13" max-version="13" />

9
appinfo/routes.php

@ -139,6 +139,15 @@ return [
'token' => '^[a-z0-9]{4,30}$',
],
],
[
'name' => 'Room#setPassword',
'url' => '/api/{apiVersion}/room/{token}/password',
'verb' => 'PUT',
'requirements' => [
'apiVersion' => 'v1',
'token' => '^[a-z0-9]{4,30}$',
],
],
[
'name' => 'Room#getParticipants',
'url' => '/api/{apiVersion}/room/{token}/participants',

7
css/style.css

@ -126,21 +126,24 @@
opacity: .5;
}
.password-input,
.rename-input {
margin-top: 0px !important;
margin-bottom: 4px !important;
}
.icon-confirm.password-confirm,
.icon-confirm.rename-confirm {
background-size: 16px;
background-color: transparent;
border: none;
position: absolute;
right: -2px;
top: 42px;
right: 0;
bottom: 3px;
padding: 16px;
opacity: .5;
}
.icon-confirm.password-confirm:hover,
.icon-confirm.rename-confirm:hover {
opacity: 1;
}

23
docs/api-v1.md

@ -157,6 +157,23 @@ Base endpoint is: `/ocs/v2.php/apps/spreed/api/v1`
+ `403 Forbidden` When the current user is not a moderator/owner
+ `404 Not Found` When the room could not be found for the participant
### Set room password
* Method: `PUT`
* Endpoint: `/room/{token}/password`
* Data:
field | type | Description
------|------|------------
`password` | string | Set a new password for the room
* Response:
- Header:
+ `200 OK`
+ `403 Forbidden` When the current user is not a moderator/owner
+ `403 Forbidden` When the room is not a public room
+ `404 Not Found` When the room could not be found for the participant
## Participant management
@ -296,10 +313,16 @@ Base endpoint is: `/ocs/v2.php/apps/spreed/api/v1`
* Method: `POST`
* Endpoint: `/call/{token}`
* Data:
field | type | Description
------|------|------------
`password` | string | Optional: Password is only required for users which are of type `4` or `5` and only when the room has `hasPassword` set to true.
* Response:
- Header:
+ `200 OK`
+ `403 Forbidden` When the password is required and didn't match
+ `404 Not Found` When the room could not be found for the participant
- Data:

48
js/signaling.js

@ -93,7 +93,6 @@
function InternalSignaling() {
SignalingBase.prototype.constructor.apply(this, arguments);
this.spreedArrayConnection = [];
this._openEventSource();
this.pingFails = 0;
this.pingInterval = null;
@ -157,7 +156,7 @@
}.bind(this));
};
InternalSignaling.prototype.joinCall = function(token, callback) {
InternalSignaling.prototype.joinCall = function(token, callback, password) {
// The client is joining a new call, in this case we need
// to do the following:
//
@ -173,11 +172,15 @@
beforeSend: function (request) {
request.setRequestHeader('Accept', 'application/json');
},
data: {
password: password
},
success: function (result) {
console.log("Joined", result);
this.sessionId = result.ocs.data.sessionId;
this.currentCallToken = token;
this._startPingCall();
this._openEventSource();
this._getCallPeers(token).then(function(peers) {
var callDescription = {
'clients': {}
@ -192,6 +195,35 @@
}.bind(this));
callback('', callDescription);
}.bind(this));
}.bind(this),
error: function (result) {
if (result.status === 404 || result.status === 503) {
// Room not found or maintenance mode
OC.redirect(OC.generateUrl('apps/spreed'));
}
if (result.status === 403) {
// Invalid password
OC.dialogs.prompt(
t('spreed', 'Please enter the password for this call'),
t('spreed','Password required'),
function (result, password) {
if (result && password !== '') {
this.joinCall(token, callback, password);
}
}.bind(this),
true,
t('spreed','Password'),
true
).then(function() {
var $dialog = $('.oc-dialog:visible');
$dialog.find('.ui-icon').remove();
var $buttons = $dialog.find('button');
$buttons.eq(0).text(t('core', 'Cancel'));
$buttons.eq(1).text(t('core', 'Submit'));
});
}
}.bind(this)
});
};
@ -199,6 +231,7 @@
InternalSignaling.prototype.leaveCall = function(token) {
if (token === this.currentCallToken) {
this._stopPingCall();
this._closeEventSource();
}
$.ajax({
url: OC.linkToOCS('apps/spreed/api/v1/call', 2) + token,
@ -278,6 +311,16 @@
}.bind(this));
};
/**
* @private
*/
InternalSignaling.prototype._closeEventSource = function() {
if (this.source) {
this.source.close();
this.source = null;
}
};
/**
* @private
*/
@ -297,6 +340,7 @@
*/
InternalSignaling.prototype._startPingCall = function() {
this._pingCall();
// Send a ping to the server all 5 seconds to ensure that the connection
// is still alive.
this.pingInterval = window.setInterval(function() {

56
js/views/roomlistview.js

@ -55,9 +55,9 @@
'<span class="icon-rename"></span>'+
'<span>'+t('spreed', 'Rename')+'</span>'+
'</button>'+
'<input class="hidden-important rename-element rename-input" maxlength="200" type="text"/>'+
'<div class="icon-confirm hidden-important rename-element rename-confirm"></div>'+
'</li>'+
'<input class="hidden-important rename-element rename-input" maxlength="200" type="text"/>'+
'<button class="icon-confirm hidden-important rename-element rename-confirm"></button>'+
'{{/if}}'+
'<li>'+
'<button class="share-link-button">'+
@ -68,6 +68,14 @@
'<div class="clipboardButton icon-clippy private-room" data-clipboard-target="#shareInput-{{id}}"></div>'+
'<div class="icon-delete private-room"></div>'+
'</li>'+
'<li>'+
'<button class="password-room-button">'+
'<span class="icon-password"></span>'+
'<span>{{#if hasPassword}}'+t('spreed', 'Change password')+'{{else}}'+t('spreed', 'Set password')+'{{/if}}</span>'+
'</button>'+
'<input class="hidden-important password-element password-input" maxlength="200" type="text"/>'+
'<div class="icon-confirm hidden-important password-element password-confirm"></div>'+
'</li>'+
'{{/if}}'+
'{{#if showShareLink}}'+
'<li>'+
@ -198,6 +206,9 @@
'click .app-navigation-entry-menu .rename-room-button': 'showRenameInput',
'click .app-navigation-entry-menu .rename-confirm': 'confirmRoomRename',
'keyup .rename-input': 'renameKeyUp',
'click .app-navigation-entry-menu .password-room-button': 'showPasswordInput',
'click .app-navigation-entry-menu .password-confirm': 'confirmRoomPassword',
'keyup .password-input': 'passwordKeyUp',
'click .app-navigation-entry-menu .share-link-button': 'shareGroup',
'click .app-navigation-entry-menu .leave-room-button': 'leaveRoom',
'click .app-navigation-entry-menu .delete-room-button': 'deleteRoom',
@ -222,10 +233,12 @@
toggleMenuClass: function() {
this.ui.menu.toggleClass('open', this.menuShown);
// Hide rename input and show button when opening menu
// Hide rename and password input and show button when opening menu
if (this.menuShown) {
this.$el.find('.rename-element').addClass('hidden-important');
this.$el.find('.rename-room-button').removeClass('hidden-important');
this.$el.find('.password-element').addClass('hidden-important');
this.$el.find('.password-room-button').removeClass('hidden-important');
}
},
checkSharingStatus: function() {
@ -312,6 +325,43 @@
});
}
},
showPasswordInput: function() {
this.$el.find('.password-element').removeClass('hidden-important');
this.$el.find('.password-room-button').addClass('hidden-important');
this.$el.find('.password-input').focus();
this.$el.find('.password-input').select();
},
hidePasswordInput: function() {
this.$el.find('.password-element').addClass('hidden-important');
this.$el.find('.password-room-button').removeClass('hidden-important');
},
confirmRoomPassword: function() {
var newRoomPassword = $.trim(this.$el.find('.password-input').val());
this.passwordRoom(newRoomPassword);
this.hidePasswordInput();
},
passwordKeyUp: function(e) {
if (e.keyCode === 13) {
this.confirmRoomPassword();
} else if (e.keyCode === 27) {
this.hidePasswordInput();
}
},
passwordRoom: function(roomPassword) {
var app = OCA.SpreedMe.app;
if (this.model.get('type') === ROOM_TYPE_PUBLIC_CALL) {
$.ajax({
url: OC.linkToOCS('apps/spreed/api/v1/room', 2) + this.model.get('token') + '/password',
type: 'PUT',
data: 'password='+roomPassword,
success: function() {
app.syncRooms();
}
});
}
},
shareGroup: function() {
var app = OCA.SpreedMe.app;

61
lib/Controller/CallController.php

@ -25,8 +25,11 @@
namespace OCA\Spreed\Controller;
use OCA\Spreed\Exceptions\InvalidPasswordException;
use OCA\Spreed\Exceptions\ParticipantNotFoundException;
use OCA\Spreed\Exceptions\RoomNotFoundException;
use OCA\Spreed\Manager;
use OCA\Spreed\Participant;
use OCA\Spreed\Signalling\Messages;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
@ -79,8 +82,24 @@ class CallController extends OCSController {
*/
public function getPeersForCall($token) {
try {
$room = $this->manager->getRoomForParticipantByToken($token, $this->userId);
$room = $this->manager->getRoomForSession($this->userId, $this->session->get('spreed-session'));
} catch (RoomNotFoundException $e) {
if ($this->userId === null) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
}
// For logged in users we search for rooms where they are real participants
try {
$room = $this->manager->getRoomForParticipantByToken($token, $this->userId);
$room->getParticipant($this->userId);
} catch (RoomNotFoundException $e) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
} catch (ParticipantNotFoundException $e) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
}
}
if ($room->getToken() !== $token) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
}
@ -118,24 +137,29 @@ class CallController extends OCSController {
* @UseSession
*
* @param string $token
* @param string $password
* @return DataResponse
*/
public function joinCall($token) {
public function joinCall($token, $password) {
try {
$room = $this->manager->getRoomForParticipantByToken($token, $this->userId);
} catch (RoomNotFoundException $e) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
}
if ($this->userId !== null) {
$sessionIds = $this->manager->getSessionIdsForUser($this->userId);
$newSessionId = $room->enterRoomAsUser($this->userId);
if (!empty($sessionIds)) {
$this->messages->deleteMessages($sessionIds);
try {
if ($this->userId !== null) {
$sessionIds = $this->manager->getSessionIdsForUser($this->userId);
$newSessionId = $room->enterRoomAsUser($this->userId, $password);
if (!empty($sessionIds)) {
$this->messages->deleteMessages($sessionIds);
}
} else {
$newSessionId = $room->enterRoomAsGuest($password);
}
} else {
$newSessionId = $room->enterRoomAsGuest();
} catch (InvalidPasswordException $e) {
return new DataResponse([], Http::STATUS_FORBIDDEN);
}
$this->session->set('spreed-session', $newSessionId);
@ -153,6 +177,10 @@ class CallController extends OCSController {
* @return DataResponse
*/
public function pingCall($token) {
if (!$this->session->exists('spreed-session')) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
}
try {
$room = $this->manager->getRoomForParticipantByToken($token, $this->userId);
} catch (RoomNotFoundException $e) {
@ -174,7 +202,18 @@ class CallController extends OCSController {
*/
public function leaveCall($token) {
if ($this->userId !== null) {
// TODO: Currently we ignore $token, should be fixed at some point
try {
$room = $this->manager->getRoomForParticipantByToken($token, $this->userId);
$participant = $room->getParticipant($this->userId);
if ($participant->getParticipantType() === Participant::USER_SELF_JOINED) {
$room->removeParticipantBySession($participant);
}
} catch (RoomNotFoundException $e) {
} catch (\RuntimeException $e) {
}
// As a pre-caution we simply disconnect the user from all rooms
$this->manager->disconnectUserFromAllRooms($this->userId);
} else {
$sessionId = $this->session->get('spreed-session');

113
lib/Controller/RoomController.php

@ -37,6 +37,7 @@ use OCP\AppFramework\OCSController;
use OCP\IL10N;
use OCP\ILogger;
use OCP\IRequest;
use OCP\ISession;
use OCP\IUser;
use OCP\IUserManager;
use OCP\IGroup;
@ -46,6 +47,8 @@ use OCP\Notification\IManager as INotificationManager;
class RoomController extends OCSController {
/** @var string */
private $userId;
/** @var ISession */
private $session;
/** @var IUserManager */
private $userManager;
/** @var IGroupManager */
@ -65,6 +68,7 @@ class RoomController extends OCSController {
* @param string $appName
* @param string $UserId
* @param IRequest $request
* @param ISession $session
* @param IUserManager $userManager
* @param IGroupManager $groupManager
* @param ILogger $logger
@ -76,6 +80,7 @@ class RoomController extends OCSController {
public function __construct($appName,
$UserId,
IRequest $request,
ISession $session,
IUserManager $userManager,
IGroupManager $groupManager,
ILogger $logger,
@ -84,6 +89,7 @@ class RoomController extends OCSController {
IActivityManager $activityManager,
IL10N $l10n) {
parent::__construct($appName, $request);
$this->session = $session;
$this->userId = $UserId;
$this->userManager = $userManager;
$this->groupManager = $groupManager;
@ -107,8 +113,9 @@ class RoomController extends OCSController {
$return = [];
foreach ($rooms as $room) {
try {
$return[] = $this->formatRoom($room);
$return[] = $this->formatRoom($room, $room->getParticipant($this->userId));
} catch (RoomNotFoundException $e) {
} catch (\RuntimeException $e) {
}
}
@ -124,7 +131,18 @@ class RoomController extends OCSController {
public function getRoom($token) {
try {
$room = $this->manager->getRoomForParticipantByToken($token, $this->userId);
return new DataResponse($this->formatRoom($room));
$participant = null;
try {
$participant = $room->getParticipant($this->userId);
} catch (ParticipantNotFoundException $e) {
try {
$participant = $room->getParticipantBySession($this->session->get('spreed-session'));
} catch (ParticipantNotFoundException $e) {
}
}
return new DataResponse($this->formatRoom($room, $participant));
} catch (RoomNotFoundException $e) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
}
@ -132,10 +150,42 @@ class RoomController extends OCSController {
/**
* @param Room $room
* @param Participant $participant
* @return array
* @throws RoomNotFoundException
*/
protected function formatRoom(Room $room) {
protected function formatRoom(Room $room, Participant $participant = null) {
if ($participant instanceof Participant) {
$participantType = $participant->getParticipantType();
} else {
$participantType = Participant::GUEST;
}
$roomData = [
'id' => $room->getId(),
'token' => $room->getToken(),
'type' => $room->getType(),
'name' => $room->getName(),
'displayName' => $room->getName(),
'participantType' => $participantType,
'count' => $room->getNumberOfParticipants(time() - 30),
'hasPassword' => $room->hasPassword(),
];
if (!$participant instanceof Participant) {
// Unauthenticated user without a session (didn't enter password)
$roomData = array_merge($roomData, [
'lastPing' => 0,
'sessionId' => '0',
'participants' => [],
'numGuests' => 0,
'guestList' => '',
]);
return $roomData;
}
// Sort by lastPing
/** @var array[] $participants */
$participants = $room->getParticipants();
@ -146,23 +196,16 @@ class RoomController extends OCSController {
uasort($participants['guests'], $sortParticipants);
$participantList = [];
foreach ($participants['users'] as $participant => $data) {
$user = $this->userManager->get($participant);
foreach ($participants['users'] as $userId => $data) {
$user = $this->userManager->get($userId);
if ($user instanceof IUser) {
$participantList[$participant] = [
$participantList[$user->getUID()] = [
'name' => $user->getDisplayName(),
'type' => $data['participantType'],
];
}
}
try {
$participant = $room->getParticipant($this->userId);
$participantType = $participant->getParticipantType();
} catch (ParticipantNotFoundException $e) {
$participantType = Participant::GUEST;
}
$activeGuests = array_filter($participants['guests'], function($data) {
return $data['lastPing'] > time() - 30;
});
@ -172,19 +215,12 @@ class RoomController extends OCSController {
$room->cleanGuestParticipants();
}
$roomData = [
'id' => $room->getId(),
'token' => $room->getToken(),
'type' => $room->getType(),
'name' => $room->getName(),
'displayName' => $room->getName(),
'participantType' => $participantType,
'count' => $room->getNumberOfParticipants(time() - 30),
'lastPing' => isset($participants['users'][$this->userId]['lastPing']) ? $participants['users'][$this->userId]['lastPing'] : 0,
'sessionId' => isset($participants['users'][$this->userId]['sessionId']) ? $participants['users'][$this->userId]['sessionId'] : '0',
$roomData = array_merge($roomData, [
'lastPing' => $participant->getLastPing(),
'sessionId' => $participant->getSessionId(),
'participants' => $participantList,
'numGuests' => $numActiveGuests,
];
]);
if ($this->userId !== null) {
unset($participantList[$this->userId]);
@ -675,6 +711,35 @@ class RoomController extends OCSController {
return new DataResponse();
}
/**
* @NoAdminRequired
*
* @param string $token
* @param string $password
* @return DataResponse
*/
public function setPassword($token, $password) {
try {
$room = $this->manager->getRoomForParticipantByToken($token, $this->userId);
$participant = $room->getParticipant($this->userId);
} catch (RoomNotFoundException $e) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
} catch (\RuntimeException $e) {
return new DataResponse([], Http::STATUS_NOT_FOUND);
}
if (!in_array($participant->getParticipantType(), [Participant::OWNER, Participant::MODERATOR], true)) {
return new DataResponse([], Http::STATUS_FORBIDDEN);
}
if ($room->getType() !== Room::PUBLIC_CALL) {
return new DataResponse([], Http::STATUS_FORBIDDEN);
}
$room->setPassword($password);
return new DataResponse();
}
/**
* @NoAdminRequired
*

27
lib/Exceptions/InvalidPasswordException.php

@ -0,0 +1,27 @@
<?php
/**
* @copyright Copyright (c) 2017 Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Spreed\Exceptions;
class InvalidPasswordException extends \Exception {
}

14
lib/Manager.php

@ -26,6 +26,7 @@ use OCA\Spreed\Exceptions\RoomNotFoundException;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\Security\IHasher;
use OCP\Security\ISecureRandom;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
@ -39,6 +40,8 @@ class Manager {
private $secureRandom;
/** @var EventDispatcherInterface */
private $dispatcher;
/** @var IHasher */
private $hasher;
/**
* Manager constructor.
@ -47,12 +50,14 @@ class Manager {
* @param IConfig $config
* @param ISecureRandom $secureRandom
* @param EventDispatcherInterface $dispatcher
* @param IHasher $hasher
*/
public function __construct(IDBConnection $db, IConfig $config, ISecureRandom $secureRandom, EventDispatcherInterface $dispatcher) {
public function __construct(IDBConnection $db, IConfig $config, ISecureRandom $secureRandom, EventDispatcherInterface $dispatcher, IHasher $hasher) {
$this->db = $db;
$this->config = $config;
$this->secureRandom = $secureRandom;
$this->dispatcher = $dispatcher;
$this->hasher = $hasher;
}
/**
@ -60,7 +65,7 @@ class Manager {
* @return Room
*/
protected function createRoomObject(array $row) {
return new Room($this->db, $this->secureRandom, $this->dispatcher, (int) $row['id'], (int) $row['type'], $row['token'], $row['name']);
return new Room($this->db, $this->secureRandom, $this->dispatcher, $this->hasher, (int) $row['id'], (int) $row['type'], $row['token'], $row['name'], $row['password']);
}
/**
@ -242,7 +247,7 @@ class Manager {
* @throws RoomNotFoundException
*/
public function getRoomForSession($userId, $sessionId) {
if ($sessionId === '' || $sessionId === '0') {
if ((string) $sessionId === '' || $sessionId === '0') {
throw new RoomNotFoundException();
}
@ -263,7 +268,7 @@ class Manager {
throw new RoomNotFoundException();
}
if ($userId !== $row['userId']) {
if ((string) $userId !== $row['userId']) {
throw new RoomNotFoundException();
}
@ -358,6 +363,7 @@ class Manager {
'type' => $type,
'token' => $token,
'name' => $name,
'password' => '',
]);
}

53
lib/Migration/Version2001Date20170921145301.php

@ -0,0 +1,53 @@
<?php
/**
* @copyright Copyright (c) 2017 Joas Schilling <coding@schilljs.com>
*
* @author Joas Schilling <coding@schilljs.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Spreed\Migration;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Types\Type;
use OCP\Migration\SimpleMigrationStep;
use OCP\Migration\IOutput;
class Version2001Date20170921145301 extends SimpleMigrationStep {
/**
* @param IOutput $output
* @param \Closure $schemaClosure The `\Closure` returns a `Schema`
* @param array $options
* @return null|Schema
* @since 13.0.0
*/
public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options) {
/** @var Schema $schema */
$schema = $schemaClosure();
$table = $schema->getTable('spreedme_rooms');
$table->addColumn('password', Type::STRING, [
'notnull' => false,
'length' => 255,
'default' => '',
]);
return $schema;
}
}

56
lib/Room.php

@ -25,10 +25,12 @@
namespace OCA\Spreed;
use OCA\Spreed\Exceptions\InvalidPasswordException;
use OCA\Spreed\Exceptions\ParticipantNotFoundException;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
use OCP\IUser;
use OCP\Security\IHasher;
use OCP\Security\ISecureRandom;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\GenericEvent;
@ -44,6 +46,8 @@ class Room {
private $secureRandom;
/** @var EventDispatcherInterface */
private $dispatcher;
/** @var IHasher */
private $hasher;
/** @var int */
private $id;
@ -53,6 +57,8 @@ class Room {
private $token;
/** @var string */
private $name;
/** @var string */
private $password;
/** @var string */
protected $currentUser;
@ -65,19 +71,23 @@ class Room {
* @param IDBConnection $db
* @param ISecureRandom $secureRandom
* @param EventDispatcherInterface $dispatcher
* @param IHasher $hasher
* @param int $id
* @param int $type
* @param string $token
* @param string $name
* @param string $password
*/
public function __construct(IDBConnection $db, ISecureRandom $secureRandom, EventDispatcherInterface $dispatcher, $id, $type, $token, $name) {
public function __construct(IDBConnection $db, ISecureRandom $secureRandom, EventDispatcherInterface $dispatcher, IHasher $hasher, $id, $type, $token, $name, $password) {
$this->db = $db;
$this->secureRandom = $secureRandom;
$this->dispatcher = $dispatcher;
$this->hasher = $hasher;
$this->id = $id;
$this->type = $type;
$this->token = $token;
$this->name = $name;
$this->password = $password;
}
/**
@ -108,6 +118,13 @@ class Room {
return $this->name;
}
/**
* @return bool
*/
public function hasPassword() {
return $this->password !== '';
}
/**
* @param string $userId
* @param Participant $participant
@ -215,6 +232,27 @@ class Room {
return true;
}
/**
* @param string $password Currently it is only allowed to have a password for Room::PUBLIC_CALL
* @return bool True when the change was valid, false otherwise
*/
public function setPassword($password) {
if ($this->getType() !== self::PUBLIC_CALL) {
return false;
}
$hash = $this->hasher->hash($password);
$query = $this->db->getQueryBuilder();
$query->update('spreedme_rooms')
->set('password', $query->createNamedParameter($hash))
->where($query->expr()->eq('id', $query->createNamedParameter($this->getId(), IQueryBuilder::PARAM_INT)));
$query->execute();
$this->password = $hash;
return true;
}
/**
* @param int $newType Currently it is only allowed to change to: Room::GROUP_CALL, Room::PUBLIC_CALL
* @return bool True when the change was valid, false otherwise
@ -314,9 +352,11 @@ class Room {
/**
* @param string $userId
* @param string $password
* @return string
* @throws InvalidPasswordException
*/
public function enterRoomAsUser($userId) {
public function enterRoomAsUser($userId, $password) {
$this->dispatcher->dispatch(self::class . '::preUserEnterRoom', new GenericEvent($this));
$query = $this->db->getQueryBuilder();
@ -330,6 +370,10 @@ class Room {
$result = $query->execute();
if ($result === 0) {
if ($this->hasPassword() && !$this->hasher->verify($password, $this->password)) {
throw new InvalidPasswordException();
}
// User joining a public room, without being invited
$this->addParticipant($userId, Participant::USER_SELF_JOINED, $sessionId);
}
@ -353,11 +397,17 @@ class Room {
}
/**
* @param string $password
* @return string
* @throws InvalidPasswordException
*/
public function enterRoomAsGuest() {
public function enterRoomAsGuest($password) {
$this->dispatcher->dispatch(self::class . '::preGuestEnterRoom', new GenericEvent($this));
if ($this->hasPassword() && !$this->hasher->verify($password, $this->password)) {
throw new InvalidPasswordException();
}
$sessionId = $this->secureRandom->generate(255);
while (!$this->db->insertIfNotExist('*PREFIX*spreedme_room_participants', [
'userId' => '',

26
tests/integration/features/bootstrap/FeatureContext.php

@ -221,6 +221,24 @@ class FeatureContext implements Context, SnippetAcceptingContext {
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @When /^user "([^"]*)" sets password "([^"]*)" for room "([^"]*)" with (\d+)$/
*
* @param string $user
* @param string $password
* @param string $identifier
* @param string $statusCode
* @param TableNode
*/
public function userSetsTheRoomPassword($user, $password, $identifier, $statusCode) {
$this->setCurrentUser($user);
$this->sendRequest(
'PUT', '/apps/spreed/api/v1/room/' . self::$identifierToToken[$identifier] . '/password',
new TableNode([['password', $password]])
);
$this->assertStatusCode($this->response, $statusCode);
}
/**
* @Then /^user "([^"]*)" makes room "([^"]*)" (public|private) with (\d+)$/
*
@ -293,10 +311,14 @@ class FeatureContext implements Context, SnippetAcceptingContext {
* @param string $user
* @param string $identifier
* @param string $statusCode
* @param TableNode|null $formData
*/
public function userJoinsCall($user, $identifier, $statusCode) {
public function userJoinsCall($user, $identifier, $statusCode, TableNode $formData = null) {
$this->setCurrentUser($user);
$this->sendRequest('POST', '/apps/spreed/api/v1/call/' . self::$identifierToToken[$identifier]);
$this->sendRequest(
'POST', '/apps/spreed/api/v1/call/' . self::$identifierToToken[$identifier],
$formData
);
$this->assertStatusCode($this->response, $statusCode);
}

130
tests/integration/features/callapi/password.feature

@ -0,0 +1,130 @@
Feature: callapi/public
Background:
Given user "participant1" exists
And user "participant2" exists
And user "participant3" exists
Scenario: User has no rooms
Then user "participant1" is participant of the following rooms
Then user "participant2" is participant of the following rooms
Then user "participant3" is participant of the following rooms
Scenario: User1 invites user2 to a public room and they can do everything
When user "participant1" creates room "room"
| roomType | 3 |
And user "participant1" sets password "foobar" for room "room" with 200
And user "participant1" adds "participant2" to room "room" with 200
Then user "participant1" is participant of room "room"
And user "participant2" is participant of room "room"
Then user "participant1" sees 0 peers in call "room" with 200
And user "participant2" sees 0 peers in call "room" with 200
Then user "participant1" joins call "room" with 200
Then user "participant1" sees 1 peers in call "room" with 200
And user "participant2" sees 1 peers in call "room" with 200
And user "participant2" joins call "room" with 200
Then user "participant1" sees 2 peers in call "room" with 200
And user "participant2" sees 2 peers in call "room" with 200
Then user "participant1" pings call "room" with 200
And user "participant2" pings call "room" with 200
Then user "participant1" leaves call "room" with 200
Then user "participant1" sees 1 peers in call "room" with 200
And user "participant2" sees 1 peers in call "room" with 200
Then user "participant2" leaves call "room" with 200
Then user "participant1" sees 0 peers in call "room" with 200
And user "participant2" sees 0 peers in call "room" with 200
Scenario: User1 invites user2 to a public room and user3 can not join without password
When user "participant1" creates room "room"
| roomType | 3 |
And user "participant1" sets password "foobar" for room "room" with 200
And user "participant1" adds "participant2" to room "room" with 200
Then user "participant1" is participant of room "room"
Then user "participant3" is not participant of room "room"
And user "participant3" sees 0 peers in call "room" with 404
Then user "participant1" joins call "room" with 200
Then user "participant1" sees 1 peers in call "room" with 200
And user "participant3" sees 0 peers in call "room" with 404
And user "participant3" joins call "room" with 403
Then user "participant1" sees 1 peers in call "room" with 200
And user "participant3" sees 0 peers in call "room" with 404
And user "participant3" pings call "room" with 404
Then user "participant1" sees 1 peers in call "room" with 200
And user "participant3" sees 0 peers in call "room" with 404
Then user "participant3" leaves call "room" with 200
Then user "participant1" sees 1 peers in call "room" with 200
And user "participant3" sees 0 peers in call "room" with 404
Then user "participant1" leaves call "room" with 200
Then user "participant1" sees 0 peers in call "room" with 200
And user "participant3" sees 0 peers in call "room" with 404
Scenario: User1 invites user2 to a public room and user3 can join with password
When user "participant1" creates room "room"
| roomType | 3 |
And user "participant1" sets password "foobar" for room "room" with 200
And user "participant1" adds "participant2" to room "room" with 200
Then user "participant1" is participant of room "room"
Then user "participant3" is not participant of room "room"
And user "participant3" sees 0 peers in call "room" with 404
Then user "participant1" joins call "room" with 200
Then user "participant1" sees 1 peers in call "room" with 200
And user "participant3" sees 0 peers in call "room" with 404
And user "participant3" joins call "room" with 200
| password | foobar |
Then user "participant1" sees 2 peers in call "room" with 200
And user "participant3" sees 2 peers in call "room" with 200
And user "participant3" pings call "room" with 200
Then user "participant1" sees 2 peers in call "room" with 200
And user "participant3" sees 2 peers in call "room" with 200
Then user "participant3" leaves call "room" with 200
Then user "participant1" sees 1 peers in call "room" with 200
And user "participant3" sees 0 peers in call "room" with 404
Then user "participant1" leaves call "room" with 200
Then user "participant1" sees 0 peers in call "room" with 200
And user "participant3" sees 0 peers in call "room" with 404
Scenario: User1 invites user2 to a public room and guest can not join without password
When user "participant1" creates room "room"
| roomType | 3 |
And user "participant1" sets password "foobar" for room "room" with 200
And user "participant1" adds "participant2" to room "room" with 200
Then user "participant1" is participant of room "room"
And user "guest" sees 0 peers in call "room" with 404
Then user "participant1" joins call "room" with 200
Then user "participant1" sees 1 peers in call "room" with 200
And user "guest" sees 0 peers in call "room" with 404
And user "guest" joins call "room" with 403
Then user "participant1" sees 1 peers in call "room" with 200
And user "guest" sees 0 peers in call "room" with 404
And user "guest" pings call "room" with 404
Then user "participant1" sees 1 peers in call "room" with 200
And user "guest" sees 0 peers in call "room" with 404
Then user "guest" leaves call "room" with 200
Then user "participant1" sees 1 peers in call "room" with 200
And user "guest" sees 0 peers in call "room" with 404
Then user "participant1" leaves call "room" with 200
Then user "participant1" sees 0 peers in call "room" with 200
And user "guest" sees 0 peers in call "room" with 404
Scenario: User1 invites user2 to a public room and guest can join with password
When user "participant1" creates room "room"
| roomType | 3 |
And user "participant1" sets password "foobar" for room "room" with 200
And user "participant1" adds "participant2" to room "room" with 200
Then user "participant1" is participant of room "room"
And user "guest" sees 0 peers in call "room" with 404
Then user "participant1" joins call "room" with 200
Then user "participant1" sees 1 peers in call "room" with 200
And user "guest" sees 0 peers in call "room" with 404
And user "guest" joins call "room" with 200
| password | foobar |
Then user "participant1" sees 2 peers in call "room" with 200
And user "guest" sees 2 peers in call "room" with 200
And user "guest" pings call "room" with 200
Then user "participant1" sees 2 peers in call "room" with 200
And user "guest" sees 2 peers in call "room" with 200
Then user "guest" leaves call "room" with 200
Then user "participant1" sees 1 peers in call "room" with 200
And user "guest" sees 0 peers in call "room" with 404
Then user "participant1" leaves call "room" with 200
Then user "participant1" sees 0 peers in call "room" with 200
And user "guest" sees 0 peers in call "room" with 404

70
tests/integration/features/set-password.feature

@ -0,0 +1,70 @@
Feature: public
Background:
Given user "participant1" exists
Given user "participant2" exists
Given user "participant3" exists
Scenario: Owner sets a room password
Given user "participant1" creates room "room"
| roomType | 3 |
And user "participant1" is participant of the following rooms
| id | type | participantType | participants |
| room | 3 | 1 | participant1-displayname |
When user "participant1" sets password "foobar" for room "room" with 200
Then user "participant3" joins call "room" with 403
Then user "participant3" joins call "room" with 200
| password | foobar |
And user "participant3" leaves call "room" with 200
When user "participant1" sets password "" for room "room" with 200
Then user "participant3" joins call "room" with 200
Scenario: Moderator sets a room password
Given user "participant1" creates room "room"
| roomType | 3 |
And user "participant1" is participant of the following rooms
| id | type | participantType | participants |
| room | 3 | 1 | participant1-displayname |
And user "participant1" adds "participant2" to room "room" with 200
And user "participant1" promotes "participant2" in room "room" with 200
When user "participant2" sets password "foobar" for room "room" with 200
Then user "participant3" joins call "room" with 403
Then user "participant3" joins call "room" with 200
| password | foobar |
And user "participant3" leaves call "room" with 200
When user "participant2" sets password "" for room "room" with 200
Then user "participant3" joins call "room" with 200
Scenario: User sets a room password
Given user "participant1" creates room "room"
| roomType | 3 |
And user "participant1" is participant of the following rooms
| id | type | participantType | participants |
| room | 3 | 1 | participant1-displayname |
And user "participant1" adds "participant2" to room "room" with 200
When user "participant2" sets password "foobar" for room "room" with 403
Then user "participant3" joins call "room" with 200
And user "participant3" leaves call "room" with 200
When user "participant1" sets password "foobar" for room "room" with 200
Then user "participant3" joins call "room" with 403
Then user "participant3" joins call "room" with 200
| password | foobar |
And user "participant3" leaves call "room" with 200
When user "participant2" sets password "" for room "room" with 403
Then user "participant3" joins call "room" with 403
Scenario: Stranger sets a room password
Given user "participant1" creates room "room"
| roomType | 3 |
And user "participant1" is participant of the following rooms
| id | type | participantType | participants |
| room | 3 | 1 | participant1-displayname |
When user "participant2" sets password "foobar" for room "room" with 404
Then user "participant3" joins call "room" with 200
And user "participant3" leaves call "room" with 200
When user "participant1" sets password "foobar" for room "room" with 200
Then user "participant3" joins call "room" with 403
Then user "participant3" joins call "room" with 200
| password | foobar |
And user "participant3" leaves call "room" with 200
When user "participant2" sets password "" for room "room" with 404
Then user "participant3" joins call "room" with 403
Loading…
Cancel
Save