mirror of https://github.com/sualko/cloud_bbb
parent
bee124d19f
commit
ad7eedf14b
|
@ -2,17 +2,29 @@
|
|||
|
||||
namespace OCA\BigBlueButton\Controller;
|
||||
|
||||
use OCA\BigBlueButton\Service\RoomService;
|
||||
use OCA\BigBlueButton\Permission;
|
||||
use OCP\IRequest;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
use OCP\AppFramework\Controller;
|
||||
|
||||
use OCA\BigBlueButton\Service\RoomService;
|
||||
use OCP\IGroupManager;
|
||||
use OCP\IUserManager;
|
||||
|
||||
class RoomController extends Controller
|
||||
{
|
||||
/** @var RoomService */
|
||||
private $service;
|
||||
|
||||
/** @var IUserManager */
|
||||
private $userManager;
|
||||
|
||||
/** @var IGroupManager */
|
||||
private $groupManager;
|
||||
|
||||
/** @var Permission */
|
||||
private $permission;
|
||||
|
||||
/** @var string */
|
||||
private $userId;
|
||||
|
||||
|
@ -22,10 +34,16 @@ class RoomController extends Controller
|
|||
$appName,
|
||||
IRequest $request,
|
||||
RoomService $service,
|
||||
IUserManager $userManager,
|
||||
IGroupManager $groupManager,
|
||||
Permission $permission,
|
||||
$userId
|
||||
) {
|
||||
parent::__construct($appName, $request);
|
||||
$this->service = $service;
|
||||
$this->userManager = $userManager;
|
||||
$this->groupManager = $groupManager;
|
||||
$this->permission = $permission;
|
||||
$this->userId = $userId;
|
||||
}
|
||||
|
||||
|
@ -34,17 +52,10 @@ class RoomController extends Controller
|
|||
*/
|
||||
public function index(): DataResponse
|
||||
{
|
||||
return new DataResponse($this->service->findAll($this->userId));
|
||||
}
|
||||
$user = $this->userManager->get($this->userId);
|
||||
$groupIds = $this->groupManager->getUserGroupIds($user);
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
*/
|
||||
public function show(int $id): DataResponse
|
||||
{
|
||||
return $this->handleNotFound(function () use ($id) {
|
||||
return $this->service->find($id, $this->userId);
|
||||
});
|
||||
return new DataResponse($this->service->findAll($this->userId, $groupIds));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -77,8 +88,14 @@ class RoomController extends Controller
|
|||
string $access,
|
||||
bool $everyoneIsModerator
|
||||
): DataResponse {
|
||||
$room = $this->service->find($id);
|
||||
|
||||
if (!$this->permission->isAdmin($room, $this->userId)) {
|
||||
return new DataResponse(null, Http::STATUS_FORBIDDEN);
|
||||
}
|
||||
|
||||
return $this->handleNotFound(function () use ($id, $name, $welcome, $maxParticipants, $record, $everyoneIsModerator, $access) {
|
||||
return $this->service->update($id, $name, $welcome, $maxParticipants, $record, $access, $everyoneIsModerator, $this->userId);
|
||||
return $this->service->update($id, $name, $welcome, $maxParticipants, $record, $access, $everyoneIsModerator);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -87,8 +104,15 @@ class RoomController extends Controller
|
|||
*/
|
||||
public function destroy(int $id): DataResponse
|
||||
{
|
||||
$room = $this->service->find($id);
|
||||
|
||||
if (!$this->permission->isAdmin($room, $this->userId)) {
|
||||
return new DataResponse(null, Http::STATUS_FORBIDDEN);
|
||||
}
|
||||
|
||||
return $this->handleNotFound(function () use ($id) {
|
||||
return $this->service->delete($id, $this->userId);
|
||||
//@TODO delete shares
|
||||
return $this->service->delete($id);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -143,9 +143,9 @@ class RoomShareController extends Controller
|
|||
private function isUserAllowed(int $roomId): bool
|
||||
{
|
||||
try {
|
||||
$room = $this->roomService->find($roomId, $this->userId);
|
||||
$room = $this->roomService->find($roomId);
|
||||
|
||||
return $room !== null;
|
||||
return $room->getUserId() === $this->userId;
|
||||
} catch (RoomShareNotFound $e) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace OCA\BigBlueButton\Controller;
|
||||
|
||||
use OCA\BigBlueButton\BigBlueButton\API;
|
||||
use OCA\BigBlueButton\Permission;
|
||||
use OCP\IRequest;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
|
@ -18,6 +19,9 @@ class ServerController extends Controller
|
|||
/** @var API */
|
||||
private $server;
|
||||
|
||||
/** @var Permission */
|
||||
private $permission;
|
||||
|
||||
/** @var string */
|
||||
private $userId;
|
||||
|
||||
|
@ -26,12 +30,14 @@ class ServerController extends Controller
|
|||
IRequest $request,
|
||||
RoomService $service,
|
||||
API $server,
|
||||
Permission $permission,
|
||||
$UserId
|
||||
) {
|
||||
parent::__construct($appName, $request);
|
||||
|
||||
$this->service = $service;
|
||||
$this->server = $server;
|
||||
$this->permission = $permission;
|
||||
$this->userId = $UserId;
|
||||
}
|
||||
|
||||
|
@ -46,7 +52,7 @@ class ServerController extends Controller
|
|||
return new DataResponse([], Http::STATUS_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($room->userId !== $this->userId) {
|
||||
if (!$this->permission->isAdmin($room, $this->userId)) {
|
||||
return new DataResponse([], Http::STATUS_FORBIDDEN);
|
||||
}
|
||||
|
||||
|
@ -68,7 +74,7 @@ class ServerController extends Controller
|
|||
return new DataResponse(false, Http::STATUS_NOT_FOUND);
|
||||
}
|
||||
|
||||
if ($room->userId !== $this->userId) {
|
||||
if (!$this->permission->isAdmin($room, $this->userId)) {
|
||||
return new DataResponse(false, Http::STATUS_FORBIDDEN);
|
||||
}
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ class Room extends Entity implements JsonSerializable
|
|||
return [
|
||||
'id' => $this->id,
|
||||
'uid' => $this->uid,
|
||||
'userId' => $this->userId,
|
||||
'name' => $this->name,
|
||||
'welcome' => $this->welcome,
|
||||
'maxParticipants' => (int) $this->maxParticipants,
|
||||
|
|
|
@ -21,14 +21,13 @@ class RoomMapper extends QBMapper
|
|||
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
|
||||
* @throws DoesNotExistException
|
||||
*/
|
||||
public function find(int $id, string $userId): Room
|
||||
public function find(int $id): Room
|
||||
{
|
||||
/* @var $qb IQueryBuilder */
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from('bbb_rooms')
|
||||
->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)))
|
||||
->andWhere($qb->expr()->eq('user_id', $qb->createNamedParameter($userId)));
|
||||
->from($this->tableName)
|
||||
->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
|
||||
return $this->findEntity($qb);
|
||||
}
|
||||
|
||||
|
@ -43,22 +42,39 @@ class RoomMapper extends QBMapper
|
|||
/* @var $qb IQueryBuilder */
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from('bbb_rooms')
|
||||
->from($this->tableName)
|
||||
->where($qb->expr()->eq('uid', $qb->createNamedParameter($uid)));
|
||||
return $this->findEntity($qb);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $userId
|
||||
* @param array $groupIds
|
||||
* @return array
|
||||
*/
|
||||
public function findAll(string $userId): array
|
||||
public function findAll(string $userId, array $groupIds): array
|
||||
{
|
||||
/* @var $qb IQueryBuilder */
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from('bbb_rooms')
|
||||
->where($qb->expr()->eq('user_id', $qb->createNamedParameter($userId)));
|
||||
$qb->select('r.*')
|
||||
->from($this->tableName, 'r')
|
||||
->leftJoin('r', 'bbb_room_shares', 's', $qb->expr()->eq('r.id', 's.room_id'))
|
||||
->where(
|
||||
$qb->expr()->orX(
|
||||
$qb->expr()->eq('r.user_id', $qb->createNamedParameter($userId)),
|
||||
$qb->expr()->andX(
|
||||
$qb->expr()->eq('s.permission', $qb->createNamedParameter(RoomShare::PERMISSION_ADMIN, IQueryBuilder::PARAM_INT)),
|
||||
$qb->expr()->eq('s.share_type', $qb->createNamedParameter(RoomShare::SHARE_TYPE_USER, IQueryBuilder::PARAM_INT)),
|
||||
$qb->expr()->eq('s.share_with', $qb->createNamedParameter($userId))
|
||||
),
|
||||
$qb->expr()->andX(
|
||||
$qb->expr()->eq('s.permission', $qb->createNamedParameter(RoomShare::PERMISSION_ADMIN, IQueryBuilder::PARAM_INT)),
|
||||
$qb->expr()->eq('s.share_type', $qb->createNamedParameter(RoomShare::SHARE_TYPE_GROUP, IQueryBuilder::PARAM_INT)),
|
||||
$qb->expr()->in('s.share_with', $groupIds)
|
||||
)
|
||||
)
|
||||
)
|
||||
->groupBy('r.id');
|
||||
return $this->findEntities($qb);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
|||
|
||||
use OCA\BigBlueButton\Db\Room;
|
||||
use OCA\BigBlueButton\Db\RoomMapper;
|
||||
use OCA\BigBlueButton\NoPermissionException;
|
||||
|
||||
class RoomService
|
||||
{
|
||||
|
@ -20,9 +21,9 @@ class RoomService
|
|||
$this->mapper = $mapper;
|
||||
}
|
||||
|
||||
public function findAll(string $userId): array
|
||||
public function findAll(string $userId, array $groupIds): array
|
||||
{
|
||||
return $this->mapper->findAll($userId);
|
||||
return $this->mapper->findAll($userId, $groupIds);
|
||||
}
|
||||
|
||||
private function handleException(Exception $e): void
|
||||
|
@ -35,10 +36,10 @@ class RoomService
|
|||
}
|
||||
}
|
||||
|
||||
public function find($id, $userId)
|
||||
public function find($id): Room
|
||||
{
|
||||
try {
|
||||
return $this->mapper->find($id, $userId);
|
||||
return $this->mapper->find($id);
|
||||
|
||||
// in order to be able to plug in different storage backends like files
|
||||
// for instance it is a good idea to turn storage related exceptions
|
||||
|
@ -75,10 +76,10 @@ class RoomService
|
|||
return $this->mapper->insert($room);
|
||||
}
|
||||
|
||||
public function update($id, $name, $welcome, $maxParticipants, $record, $access, $everyoneIsModerator, $userId)
|
||||
public function update($id, $name, $welcome, $maxParticipants, $record, $access, $everyoneIsModerator)
|
||||
{
|
||||
try {
|
||||
$room = $this->mapper->find($id, $userId);
|
||||
$room = $this->mapper->find($id);
|
||||
|
||||
if ($room->access !== $access) {
|
||||
$room->setPassword($access === Room::ACCESS_PASSWORD ? $this->humanReadableRandom(8) : null);
|
||||
|
@ -90,7 +91,6 @@ class RoomService
|
|||
$room->setRecord($record);
|
||||
$room->setAccess($access);
|
||||
$room->setEveryoneIsModerator($everyoneIsModerator);
|
||||
$room->setUserId($userId);
|
||||
|
||||
return $this->mapper->update($room);
|
||||
} catch (Exception $e) {
|
||||
|
@ -98,10 +98,10 @@ class RoomService
|
|||
}
|
||||
}
|
||||
|
||||
public function delete($id, $userId)
|
||||
public function delete($id)
|
||||
{
|
||||
try {
|
||||
$room = $this->mapper->find($id, $userId);
|
||||
$room = $this->mapper->find($id);
|
||||
$this->mapper->delete($room);
|
||||
return $room;
|
||||
} catch (Exception $e) {
|
||||
|
|
|
@ -21,6 +21,8 @@ class RoomShareControllerTest extends TestCase
|
|||
private $userManager;
|
||||
private $controller;
|
||||
|
||||
private $userId = 'user_foo';
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
@ -36,7 +38,7 @@ class RoomShareControllerTest extends TestCase
|
|||
$this->service,
|
||||
$this->userManager,
|
||||
$this->roomService,
|
||||
'user_foo'
|
||||
$this->userId
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -55,10 +57,14 @@ class RoomShareControllerTest extends TestCase
|
|||
->with('id')
|
||||
->willReturn(1234);
|
||||
|
||||
$room = new Room();
|
||||
$room->setUserId('user_bar');
|
||||
|
||||
$this->roomService
|
||||
->expects($this->once())
|
||||
->method('find')
|
||||
->will($this->throwException(new RoomShareNotFound));
|
||||
->with(1234)
|
||||
->willReturn($room);
|
||||
|
||||
$response = $this->controller->index();
|
||||
|
||||
|
@ -74,10 +80,13 @@ class RoomShareControllerTest extends TestCase
|
|||
->with('id')
|
||||
->willReturn($roomId);
|
||||
|
||||
$room = new Room();
|
||||
$room->setUserId($this->userId);
|
||||
|
||||
$this->roomService
|
||||
->expects($this->once())
|
||||
->method('find')
|
||||
->willReturn(new Room());
|
||||
->willReturn($room);
|
||||
|
||||
$this->service
|
||||
->expects($this->once())
|
||||
|
@ -100,10 +109,13 @@ class RoomShareControllerTest extends TestCase
|
|||
->with('id')
|
||||
->willReturn($roomId);
|
||||
|
||||
$room = new Room();
|
||||
$room->setUserId($this->userId);
|
||||
|
||||
$this->roomService
|
||||
->expects($this->once())
|
||||
->method('find')
|
||||
->willReturn(new Room());
|
||||
->willReturn($room);
|
||||
|
||||
$this->service
|
||||
->expects($this->once())
|
||||
|
|
|
@ -15,6 +15,7 @@ export enum Access {
|
|||
export interface Room {
|
||||
id: number;
|
||||
uid: string;
|
||||
userId: string;
|
||||
name: string;
|
||||
welcome: string;
|
||||
maxParticipants: number;
|
||||
|
|
|
@ -15,6 +15,10 @@
|
|||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.bbb-avatar {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
#bbb-warning {
|
||||
padding: 1em;
|
||||
background-color: rgb(255, 255, 123);
|
||||
|
|
|
@ -107,6 +107,7 @@ const App: React.FC<Props> = () => {
|
|||
<th onClick={() => onOrderBy('name')}>
|
||||
{t('bbb', 'Name')} <SortArrow name='name' value={orderBy} direction={sortOrder} />
|
||||
</th>
|
||||
<th />
|
||||
<th onClick={() => onOrderBy('maxParticipants')}>
|
||||
{t('bbb', 'Max')} <SortArrow name='maxParticipants' value={orderBy} direction={sortOrder} />
|
||||
</th>
|
||||
|
|
|
@ -158,6 +158,12 @@ const RoomRow: React.FC<Props> = (props) => {
|
|||
return <EditableValue field={field} value={room[field]} setValue={updateRoom} type={type} />;
|
||||
}
|
||||
|
||||
const avatarUrl = OC.generateUrl('/avatar/' + encodeURIComponent(room.userId) + '/' + 24, {
|
||||
user: room.userId,
|
||||
size: 24,
|
||||
requesttoken: OC.requestToken,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<tr className={showRecordings ? 'selected-row' : ''}>
|
||||
|
@ -175,6 +181,9 @@ const RoomRow: React.FC<Props> = (props) => {
|
|||
<td className="name">
|
||||
{edit('name')}
|
||||
</td>
|
||||
<td>
|
||||
{room.userId !== OC.currentUser && <img src={avatarUrl} alt="Avatar" className="bbb-avatar" />}
|
||||
</td>
|
||||
<td className="max-participants">
|
||||
{edit('maxParticipants', 'number')}
|
||||
</td>
|
||||
|
@ -193,7 +202,7 @@ const RoomRow: React.FC<Props> = (props) => {
|
|||
</td>
|
||||
</tr>
|
||||
{showRecordings && <tr className="recordings-row">
|
||||
<td colSpan={9}>
|
||||
<td colSpan={10}>
|
||||
<table>
|
||||
<tbody>
|
||||
{recordings?.map(recording => <RecordingRow key={recording.id} recording={recording} deleteRecording={deleteRecording} storeRecording={storeRecording} />)}
|
||||
|
|
|
@ -61,4 +61,12 @@
|
|||
|
||||
.bbb-form-shareWith {
|
||||
margin-top: -1.5em;
|
||||
}
|
||||
|
||||
.bbb-icon-unselected {
|
||||
opacity: 0.2 !important;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.5 !important;
|
||||
}
|
||||
}
|
|
@ -15,6 +15,8 @@ const ShareWith: React.FC<Props> = ({ room, permission, shares: allShares, setSh
|
|||
const [recommendations, setRecommendations] = useState<ShareWith>();
|
||||
const [searchResults, setSearchResults] = useState<ShareWith>();
|
||||
|
||||
const isOwner = room.userId === OC.currentUser;
|
||||
|
||||
const shares = (allShares && permission === Permission.Moderator) ?
|
||||
allShares.filter(share => share.permission !== Permission.User) : allShares;
|
||||
|
||||
|
@ -31,7 +33,7 @@ const ShareWith: React.FC<Props> = ({ room, permission, shares: allShares, setSh
|
|||
api.getRecommendedShareWith().then(result => setRecommendations(result));
|
||||
}, []);
|
||||
|
||||
async function addRoomShare(shareWith: string, shareType: number, displayName: string) {
|
||||
async function addRoomShare(shareWith: string, shareType: number, displayName: string, permission: Permission) {
|
||||
const roomShare = await api.createRoomShare(room.id, shareType, shareWith, permission);
|
||||
|
||||
roomShare.shareWithDisplayName = displayName;
|
||||
|
@ -60,6 +62,12 @@ const ShareWith: React.FC<Props> = ({ room, permission, shares: allShares, setSh
|
|||
setShares((allShares ? [...allShares] : []).filter(share => share.id !== id));
|
||||
}
|
||||
|
||||
async function toggleAdminShare(share: RoomShare) {
|
||||
const newPermission = share.permission === Permission.Admin ? Permission.Moderator : Permission.Admin;
|
||||
|
||||
return addRoomShare(share.shareWith, share.shareType, share.shareWithDisplayName || share.shareWith, newPermission);
|
||||
}
|
||||
|
||||
function renderSearchResults(options: ShareWith) {
|
||||
return (
|
||||
<ul className="bbb-selection">
|
||||
|
@ -67,7 +75,7 @@ const ShareWith: React.FC<Props> = ({ room, permission, shares: allShares, setSh
|
|||
...options.users.filter(user => !sharedUserIds.includes(user.value.shareWith)),
|
||||
...options.groups.filter(group => !sharedGroupIds.includes(group.value.shareWith)),
|
||||
].map(option => {
|
||||
return (<li key={option.value.shareWith} onClick={() => addRoomShare(option.value.shareWith, option.value.shareType, option.label)}>
|
||||
return (<li key={option.value.shareWith} onClick={() => addRoomShare(option.value.shareWith, option.value.shareType, option.label, permission)}>
|
||||
{option.label}{option.value.shareType === ShareType.Group ? ` (${t('bbb', 'Group')})` : ''}
|
||||
</li>);
|
||||
})}
|
||||
|
@ -103,9 +111,16 @@ const ShareWith: React.FC<Props> = ({ room, permission, shares: allShares, setSh
|
|||
{share.shareType === ShareType.Group && <span className="icon-group-white"></span>}
|
||||
</div>
|
||||
<div className="bbb-shareWith__item__label">
|
||||
<h5>{displayName}{(share.permission === Permission.Moderator && permission === Permission.User) ? ` (${t('bbb', 'moderator')})` : ''}</h5>
|
||||
<h5>{displayName}
|
||||
{(share.permission === Permission.Moderator && permission === Permission.User) && ` (${t('bbb', 'moderator')})`}
|
||||
{(share.permission === Permission.Admin) && ` (${t('bbb', 'admin')})`}</h5>
|
||||
</div>
|
||||
{share.id > -1 && <div className="bbb-shareWith__item__action">
|
||||
{(share.id > -1 && permission === Permission.Moderator && isOwner) && <div className="bbb-shareWith__item__action">
|
||||
<a className={`icon icon-shared icon-visible ${share.permission === Permission.Admin ? 'bbb-icon-selected' : 'bbb-icon-unselected'}`}
|
||||
onClick={ev => {ev.preventDefault(); toggleAdminShare(share);}}
|
||||
title={t('bbb', 'Share')} />
|
||||
</div>}
|
||||
{(share.id > -1 && isOwner) && <div className="bbb-shareWith__item__action">
|
||||
<a className="icon icon-delete icon-visible"
|
||||
onClick={ev => {ev.preventDefault(); deleteRoomShare(share.id);}}
|
||||
title={t('bbb', 'Delete')} />
|
||||
|
@ -124,13 +139,14 @@ const ShareWith: React.FC<Props> = ({ room, permission, shares: allShares, setSh
|
|||
{shares ? renderShares(shares) : loading}
|
||||
|
||||
<div className="bbb-selection-container">
|
||||
<input
|
||||
{isOwner ? <input
|
||||
type="text"
|
||||
value={search}
|
||||
onChange={ev => setSearch(ev.currentTarget.value)}
|
||||
onFocus={() => setFocus(true)}
|
||||
onBlur={() => setTimeout(() => setFocus(false), 100)}
|
||||
placeholder={t('bbb', 'Name, group, ...')} />
|
||||
placeholder={t('bbb', 'Name, group, ...')} /> :
|
||||
<em><span className="icon icon-details icon-visible"></span> {t('bbb', 'You are not allowed to change this option, because this room is shared with you.')}</em>}
|
||||
{hasFocus && (searchResults ? renderSearchResults(searchResults) : (recommendations ? renderSearchResults(recommendations) : loading))}
|
||||
</div>
|
||||
</>
|
||||
|
|
Loading…
Reference in New Issue