Messages: Add MessageTagVoter.php, add tests, remove unused code

Minor UI changes
pull/3924/head
Julio Montoya 4 years ago
parent a8c2f6840f
commit 8ae7ab69b7
  1. 12
      assets/vue/components/Toolbar.vue
  2. 18
      assets/vue/components/layout/DashboardLayout.vue
  3. 22
      assets/vue/views/message/List.vue
  4. 24
      public/main/inc/ajax/message.ajax.php
  5. 2
      src/CoreBundle/DataProvider/Extension/CDocumentExtension.php
  6. 8
      src/CoreBundle/DataProvider/Extension/MessageExtension.php
  7. 52
      src/CoreBundle/DataProvider/Extension/MessageTagExtension.php
  8. 7
      src/CoreBundle/Entity/Message.php
  9. 10
      src/CoreBundle/Entity/MessageTag.php
  10. 27
      src/CoreBundle/Entity/PersonalFile.php
  11. 19
      src/CoreBundle/Entity/User.php
  12. 33
      src/CoreBundle/Entity/UserRelUser.php
  13. 84
      src/CoreBundle/Security/Authorization/Voter/MessageTagVoter.php
  14. 10
      src/CoreBundle/Security/Authorization/Voter/MessageVoter.php
  15. 8
      tests/AbstractApiTest.php
  16. 72
      tests/CoreBundle/Repository/MessageRepositoryTest.php
  17. 113
      tests/CoreBundle/Repository/MessageTagRepositoryTest.php
  18. 126
      tests/CoreBundle/Repository/Node/PersonalFileRepositoryTest.php

@ -104,6 +104,18 @@
File upload File upload
</q-btn> </q-btn>
<!-- <v-btn-->
<!-- v-if="handleUploadDocument"-->
<!-- :loading="isLoading"-->
<!-- tile-->
<!-- icon-->
<!-- @click="uploadDocument"-->
<!-- >-->
<!-- <v-icon icon="mdi-cloud-upload"/>-->
<!-- </v-btn>-->
<!-- <DataFilter--> <!-- <DataFilter-->
<!-- v-if="filters"--> <!-- v-if="filters"-->
<!-- :handle-filter="onSendFilter"--> <!-- :handle-filter="onSendFilter"-->

@ -33,13 +33,9 @@
<q-space /> <q-space />
<div class="q-gutter-sm row items-center no-wrap"> <div class="q-gutter-sm row items-center no-wrap">
<!-- <q-btn v-if="$q.screen.gt.sm" round dense flat color="text-grey-7" icon="apps">--> <!-- <q-btn v-if="isAuthenticated" round dense flat color="grey-8" icon="people">-->
<!-- <q-tooltip>Google Apps</q-tooltip>--> <!-- <q-tooltip>Friends</q-tooltip>-->
<!-- </q-btn>--> <!-- </q-btn>-->
<q-btn v-if="isAuthenticated" round dense flat color="grey-8" icon="person">
<q-tooltip>Account</q-tooltip>
</q-btn>
<q-btn v-if="isAuthenticated" round dense flat color="grey-8" <q-btn v-if="isAuthenticated" round dense flat color="grey-8"
icon="inbox" icon="inbox"
@ -51,6 +47,14 @@
<q-tooltip>Inbox</q-tooltip> <q-tooltip>Inbox</q-tooltip>
</q-btn> </q-btn>
<q-btn
v-if="isAuthenticated" round dense flat color="grey-8" icon="folder"
:to="'/resources/personal_files/' + currentUser.resourceNode.id"
>
<q-tooltip>Files</q-tooltip>
</q-btn>
<q-btn v-if="isAuthenticated" round dense flat color="grey-8" icon="notifications"> <q-btn v-if="isAuthenticated" round dense flat color="grey-8" icon="notifications">
<q-badge color="red" text-color="white" floating> <q-badge color="red" text-color="white" floating>
2 2

@ -20,25 +20,31 @@
<v-btn <v-btn
tile tile
icon icon
@click="confirmDeleteMultiple" :disabled="!selectedItems || !selectedItems.length" > @click="confirmDeleteMultiple"
:class="[ !selectedItems || !selectedItems.length ? 'hidden': '']"
>
<v-icon icon="mdi-delete" /> <v-icon icon="mdi-delete" />
</v-btn> </v-btn>
<!-- :disabled="!selectedItems || !selectedItems.length"-->
<v-btn <v-btn
icon icon
tile tile
@click="markAsUnReadMultiple" :disabled="!selectedItems || !selectedItems.length" > @click="markAsUnReadMultiple"
:class="[ !selectedItems || !selectedItems.length ? 'hidden': '']"
>
<v-icon icon="mdi-email" /> <v-icon icon="mdi-email" />
</v-btn> </v-btn>
<v-btn <v-btn
tile tile
icon icon
@click="markAsReadMultiple" :disabled="!selectedItems || !selectedItems.length" > :class="[ !selectedItems || !selectedItems.length ? 'hidden': '']"
>
<v-icon icon="mdi-email-open" /> <v-icon icon="mdi-email-open" />
</v-btn> </v-btn>
</div> </div>
</div> </div>
</div> </div>
@ -124,7 +130,7 @@
<Column selectionMode="multiple" style="width: 3rem" :exportable="false"></Column> <Column selectionMode="multiple" style="width: 3rem" :exportable="false"></Column>
<Column field="userSender" :header="$t('Sender')" :sortable="true"> <Column field="userSender" :header="$t('From')" :sortable="false">
<template #body="slotProps"> <template #body="slotProps">
<q-avatar size="40px"> <q-avatar size="40px">
<img :src="slotProps.data.userSender.illustrationUrl + '?w=80&h=80&fit=crop'" /> <img :src="slotProps.data.userSender.illustrationUrl + '?w=80&h=80&fit=crop'" />
@ -134,7 +140,7 @@
v-if="slotProps.data" v-if="slotProps.data"
@click="showHandler(slotProps.data)" @click="showHandler(slotProps.data)"
class="cursor-pointer" class="cursor-pointer"
v-bind:class="[ true === slotProps.data.read ? 'font-normal': 'font-semibold']" :class="[ true === slotProps.data.read ? 'font-normal': 'font-semibold']"
> >
{{ slotProps.data.userSender.username }} {{ slotProps.data.userSender.username }}
</a> </a>
@ -142,7 +148,7 @@
</Column> </Column>
<Column field="title" :header="$t('Title')" :sortable="true"> <Column field="title" :header="$t('Title')" :sortable="false">
<template #body="slotProps"> <template #body="slotProps">
<a <a
v-if="slotProps.data" v-if="slotProps.data"

@ -14,30 +14,6 @@ require_once __DIR__.'/../global.inc.php';
$action = $_GET['a']; $action = $_GET['a'];
switch ($action) { switch ($action) {
case 'get_notifications_inbox':
$userId = api_get_user_id();
$listInbox = [];
if ('true' === api_get_setting('allow_message_tool')) {
$list = MessageManager::getMessageData(
0,
10,
null,
null,
['actions' => ['read'], 'type' => Message::MESSAGE_TYPE_INBOX]
);
foreach ($list as $row) {
$user = api_get_user_info($row['0']);
$temp['title'] = $row['1'];
$temp['date'] = $row['2'];
$temp['fullname'] = $user['complete_name'];
$temp['email'] = $user['email'];
$temp['url'] = $row['1'];
$listInbox[] = $temp;
}
}
header('Content-type:application/json');
echo json_encode($listInbox);
break;
case 'get_notifications_friends': case 'get_notifications_friends':
$userId = api_get_user_id(); $userId = api_get_user_id();
$listInvitations = []; $listInvitations = [];

@ -72,8 +72,6 @@ final class CDocumentExtension implements QueryCollectionExtensionInterface //,
throw new AccessDeniedException('cid is required'); throw new AccessDeniedException('cid is required');
} }
error_log('addWhere');
error_log('here!');
$rootAlias = $queryBuilder->getRootAliases()[0]; $rootAlias = $queryBuilder->getRootAliases()[0];
$queryBuilder $queryBuilder

@ -25,9 +25,9 @@ final class MessageExtension implements QueryCollectionExtensionInterface //, Qu
public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null): void public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null): void
{ {
if ($this->security->isGranted('ROLE_ADMIN')) { /*if ($this->security->isGranted('ROLE_ADMIN')) {
return; return;
} }*/
/* /*
if ('collection_query' === $operationName) { if ('collection_query' === $operationName) {
if (null === $user = $this->security->getUser()) { if (null === $user = $this->security->getUser()) {
@ -54,9 +54,9 @@ final class MessageExtension implements QueryCollectionExtensionInterface //, Qu
return; return;
} }
if ($this->security->isGranted('ROLE_ADMIN')) { /*if ($this->security->isGranted('ROLE_ADMIN')) {
return; return;
} }*/
$user = $this->security->getUser(); $user = $this->security->getUser();

@ -0,0 +1,52 @@
<?php
/* For licensing terms, see /license.txt */
declare(strict_types=1);
namespace Chamilo\CoreBundle\DataProvider\Extension;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryCollectionExtensionInterface;
//use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\QueryItemExtensionInterface;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use Chamilo\CoreBundle\Entity\Message;
use Chamilo\CoreBundle\Entity\MessageTag;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\Security;
final class MessageTagExtension implements QueryCollectionExtensionInterface //, QueryItemExtensionInterface
{
private Security $security;
public function __construct(Security $security)
{
$this->security = $security;
}
public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null): void
{
$this->addWhere($queryBuilder, $resourceClass);
}
/*public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, string $operationName = null, array $context = []): void
{
//error_log('applyToItem');
//$this->addWhere($queryBuilder, $resourceClass);
}*/
private function addWhere(QueryBuilder $queryBuilder, string $resourceClass): void
{
if (MessageTag::class !== $resourceClass) {
return;
}
$user = $this->security->getUser();
$alias = $queryBuilder->getRootAliases()[0];
$queryBuilder->andWhere(" $alias.user = :current ");
$queryBuilder->setParameters([
'current' => $user,
]);
}
}

@ -7,7 +7,6 @@ declare(strict_types=1);
namespace Chamilo\CoreBundle\Entity; namespace Chamilo\CoreBundle\Entity;
use ApiPlatform\Core\Annotation\ApiFilter; use ApiPlatform\Core\Annotation\ApiFilter;
use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiResource; use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\OrderFilter; use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\OrderFilter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter; use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;
@ -37,9 +36,11 @@ use Symfony\Component\Validator\Constraints as Assert;
*/ */
#[ApiResource( #[ApiResource(
collectionOperations: [ collectionOperations: [
'get' => [], 'get' => [
'security' => "is_granted('ROLE_USER')", // the get collection is also filtered by MessageExtension
],
'post' => [ 'post' => [
'security' => "is_granted('ROLE_USER')", 'security_post_denormalize' => "is_granted('CREATE', object)",
// 'deserialize' => false, // 'deserialize' => false,
// 'controller' => Create::class, // 'controller' => Create::class,
// 'openapi_context' => [ // 'openapi_context' => [

@ -39,21 +39,21 @@ use Symfony\Component\Validator\Constraints as Assert;
#[ApiResource( #[ApiResource(
collectionOperations: [ collectionOperations: [
'get' => [ 'get' => [
//'security' => "is_granted('ROLE_ADMIN')", 'security' => "is_granted('ROLE_USER')", // the get collection is also filtered by MessageTagExtension
], ],
'post' => [ 'post' => [
//'security' => "is_granted('ROLE_ADMIN') or object.user == user", 'security_post_denormalize' => "is_granted('CREATE', object)",
], ],
], ],
itemOperations: [ itemOperations: [
'get' => [ 'get' => [
//'security' => "is_granted('ROLE_ADMIN')", 'security' => "is_granted('VIEW', object)",
], ],
'put' => [ 'put' => [
//'security' => "is_granted('ROLE_ADMIN') or object.user == user", 'security' => "is_granted('EDIT', object)",
], ],
'delete' => [ 'delete' => [
//'security' => "is_granted('ROLE_ADMIN') or object.user == user", 'security' => "is_granted('DELETE', object)",
], ],
], ],
attributes: [ attributes: [

@ -93,23 +93,26 @@ use Symfony\Component\Validator\Constraints as Assert;
* }, * },
* }, * },
* ) * )
* @ApiFilter(SearchFilter::class, properties={"title":"partial", "resourceNode.parent":"exact"})
* @ApiFilter(PropertyFilter::class)
* @ApiFilter(
* OrderFilter::class,
* properties={
* "id",
* "resourceNode.title",
* "resourceNode.createdAt",
* "resourceNode.resourceFile.size",
* "resourceNode.updatedAt"
* }
* )
* *
* @ORM\EntityListeners({"Chamilo\CoreBundle\Entity\Listener\ResourceListener"}) * @ORM\EntityListeners({"Chamilo\CoreBundle\Entity\Listener\ResourceListener"})
* @ORM\Table(name="personal_file") * @ORM\Table(name="personal_file")
* @ORM\Entity * @ORM\Entity
*/ */
#[ApiFilter(SearchFilter::class, properties: [
'title' => 'partial',
'resourceNode.parent' => 'partial',
])]
#[ApiFilter(PropertyFilter::class)]
#[ApiFilter(OrderFilter::class, properties: [
'id',
'resourceNode.title',
'resourceNode.createdAt',
'resourceNode.resourceFile.size',
'resourceNode.updatedAt',
]
)]
class PersonalFile extends AbstractResource implements ResourceInterface class PersonalFile extends AbstractResource implements ResourceInterface
{ {
use TimestampableEntity; use TimestampableEntity;

@ -39,17 +39,17 @@ use UserManager;
* EquatableInterface is needed to check if the user needs to be refreshed. * EquatableInterface is needed to check if the user needs to be refreshed.
* *
* @ApiResource( * @ApiResource(
* attributes={"security"="is_granted('ROLE_ADMIN')"}, * attributes={"security"="is_granted('ROLE_USER')"},
* iri="http://schema.org/Person", * iri="http://schema.org/Person",
* normalizationContext={"groups"={"user:read"}}, * normalizationContext={"groups"={"user:read"}},
* denormalizationContext={"groups"={"user:write"}}, * denormalizationContext={"groups"={"user:write"}},
* collectionOperations={ * collectionOperations={
* "get"={"security"="is_granted('ROLE_ADMIN')"}, * "get"={"security"="is_granted('ROLE_USER')"},
* "post"={"security"="is_granted('ROLE_ADMIN')"} * "post"={"security"="is_granted('ROLE_USER')"}
* }, * },
* itemOperations={ * itemOperations={
* "get"={"security"="is_granted('ROLE_ADMIN')"}, * "get"={"security"="is_granted('ROLE_USER')"},
* "put"={"security"="is_granted('ROLE_ADMIN')"}, * "put"={"security"="is_granted('ROLE_USER')"},
* }, * },
* ) * )
* *
@ -1301,6 +1301,15 @@ class User implements UserInterface, EquatableInterface, ResourceInterface, Reso
return $this->id; return $this->id;
} }
public function getIri(): ?string
{
if (null === $this->id) {
return null;
}
return '/api/users/'.$this->getId();
}
public function getSlug(): string public function getSlug(): string
{ {
return $this->getUsername(); return $this->getUsername();

@ -6,12 +6,13 @@ declare(strict_types=1);
namespace Chamilo\CoreBundle\Entity; namespace Chamilo\CoreBundle\Entity;
use ApiPlatform\Core\Annotation\ApiResource;
use Chamilo\CoreBundle\Traits\UserTrait; use Chamilo\CoreBundle\Traits\UserTrait;
use DateTime; use DateTime;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
/** /**
* Associations between users (friends). * Associations between users.
* *
* @ORM\Table(name="user_rel_user", indexes={ * @ORM\Table(name="user_rel_user", indexes={
* @ORM\Index(name="idx_user_rel_user__user", columns={"user_id"}), * @ORM\Index(name="idx_user_rel_user__user", columns={"user_id"}),
@ -20,6 +21,36 @@ use Doctrine\ORM\Mapping as ORM;
* }) * })
* @ORM\Entity * @ORM\Entity
*/ */
#[ApiResource(
collectionOperations: [
'get' => [
//'security' => "is_granted('ROLE_ADMIN')",
],
'post' => [
//'security' => "is_granted('ROLE_ADMIN') or object.user == user",
],
],
itemOperations: [
'get' => [
//'security' => "is_granted('ROLE_ADMIN')",
],
'put' => [
//'security' => "is_granted('ROLE_ADMIN') or object.user == user",
],
'delete' => [
//'security' => "is_granted('ROLE_ADMIN') or object.user == user",
],
],
attributes: [
'security' => 'is_granted("ROLE_USER") and object.user == user',
],
denormalizationContext: [
'groups' => ['message_tag:write'],
],
normalizationContext: [
'groups' => ['message_tag:read'],
],
)]
class UserRelUser class UserRelUser
{ {
use UserTrait; use UserTrait;

@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
/* For licensing terms, see /license.txt */
namespace Chamilo\CoreBundle\Security\Authorization\Voter;
use Chamilo\CoreBundle\Entity\Message;
use Chamilo\CoreBundle\Entity\MessageTag;
use Chamilo\CoreBundle\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;
class MessageTagVoter extends Voter
{
public const CREATE = 'CREATE';
public const VIEW = 'VIEW';
public const EDIT = 'EDIT';
public const DELETE = 'DELETE';
private EntityManagerInterface $entityManager;
private Security $security;
public function __construct(
EntityManagerInterface $entityManager,
Security $security
) {
$this->entityManager = $entityManager;
$this->security = $security;
}
protected function supports(string $attribute, $subject): bool
{
$options = [
self::CREATE,
self::VIEW,
self::EDIT,
self::DELETE,
];
// if the attribute isn't one we support, return false
if (!\in_array($attribute, $options, true)) {
return false;
}
// only vote on Post objects inside this voter
return $subject instanceof MessageTag;
}
protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool
{
/** @var User $user */
$user = $token->getUser();
if (!$user instanceof UserInterface) {
return false;
}
if ($this->security->isGranted('ROLE_ADMIN')) {
return true;
}
/** @var MessageTag $message */
$message = $subject;
switch ($attribute) {
case self::CREATE:
case self::VIEW:
case self::EDIT:
case self::DELETE:
if ($message->getUser() === $user) {
return true;
}
break;
}
return false;
}
}

@ -16,6 +16,7 @@ use Symfony\Component\Security\Core\User\UserInterface;
class MessageVoter extends Voter class MessageVoter extends Voter
{ {
public const CREATE = 'CREATE';
public const VIEW = 'VIEW'; public const VIEW = 'VIEW';
public const EDIT = 'EDIT'; public const EDIT = 'EDIT';
public const DELETE = 'DELETE'; public const DELETE = 'DELETE';
@ -34,6 +35,7 @@ class MessageVoter extends Voter
protected function supports(string $attribute, $subject): bool protected function supports(string $attribute, $subject): bool
{ {
$options = [ $options = [
self::CREATE,
self::VIEW, self::VIEW,
self::EDIT, self::EDIT,
self::DELETE, self::DELETE,
@ -57,7 +59,7 @@ class MessageVoter extends Voter
return false; return false;
} }
// Admins have access to everything // Admins have access to everything.
if ($this->security->isGranted('ROLE_ADMIN')) { if ($this->security->isGranted('ROLE_ADMIN')) {
return true; return true;
} }
@ -66,6 +68,12 @@ class MessageVoter extends Voter
$message = $subject; $message = $subject;
switch ($attribute) { switch ($attribute) {
case self::CREATE:
if ($message->getUserSender() === $user) {
return true;
}
break;
case self::VIEW: case self::VIEW:
if ($message->getUserReceiver() === $user) { if ($message->getUserReceiver() === $user) {
return true; return true;

@ -52,9 +52,13 @@ abstract class AbstractApiTest extends ApiTestCase
/** /**
* Use credentials with token. * Use credentials with token.
*/ */
protected function getUserToken($body = []): string protected function getUserToken($body = [], $cleanToken = false): string
{ {
if ($this->token) { if ($cleanToken) {
$this->token = null;
}
if (null !== $this->token) {
return $this->token; return $this->token;
} }

@ -10,13 +10,13 @@ use Chamilo\CoreBundle\Entity\Message;
use Chamilo\CoreBundle\Entity\MessageTag; use Chamilo\CoreBundle\Entity\MessageTag;
use Chamilo\CoreBundle\Repository\MessageRepository; use Chamilo\CoreBundle\Repository\MessageRepository;
use Chamilo\CoreBundle\Repository\MessageTagRepository; use Chamilo\CoreBundle\Repository\MessageTagRepository;
use Chamilo\Tests\AbstractApiTest;
use Chamilo\Tests\ChamiloTestTrait; use Chamilo\Tests\ChamiloTestTrait;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
/** /**
* @covers \MessageRepository * @covers \MessageRepository
*/ */
class MessageRepositoryTest extends WebTestCase class MessageRepositoryTest extends AbstractApiTest
{ {
use ChamiloTestTrait; use ChamiloTestTrait;
@ -83,4 +83,72 @@ class MessageRepositoryTest extends WebTestCase
$this->assertSame(2, $message->getTags()->count()); $this->assertSame(2, $message->getTags()->count());
} }
public function testCreateMessageWithApi(): void
{
self::bootKernel();
$fromUser = $this->createUser('from');
$toUser = $this->createUser('to');
$tokenFrom = $this->getUserToken(
[
'username' => 'from',
'password' => 'from',
]
);
$this->createClientWithCredentials($tokenFrom)->request(
'POST',
'/api/messages',
[
'json' => [
'title' => 'hello',
'content' => 'content of hello',
'msgType' => Message::MESSAGE_TYPE_INBOX,
'userSender' => '/api/users/'.$fromUser->getId(),
'userReceiver' => '/api/users/'.$toUser->getId(),
],
]
);
$this->assertResponseIsSuccessful();
$this->assertResponseStatusCodeSame(201);
$this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8');
$this->assertJsonContains(
[
'@context' => '/api/contexts/Message',
'@type' => 'Message',
'title' => 'hello',
'read' => false,
'starred' => false,
]
);
// Try to send a message as another user
$this->createUser('bad');
$tokenFromBadUser = $this->getUserToken(
[
'username' => 'bad',
'password' => 'bad',
],
true
);
$this->createClientWithCredentials($tokenFromBadUser)->request(
'POST',
'/api/messages',
[
'json' => [
'title' => 'hello',
'content' => 'content of hello',
'msgType' => Message::MESSAGE_TYPE_INBOX,
'userSender' => '/api/users/'.$fromUser->getId(),
'userReceiver' => '/api/users/'.$toUser->getId(),
],
]
);
$this->assertResponseStatusCodeSame(403);
}
} }

@ -9,6 +9,7 @@ namespace Chamilo\Tests\CoreBundle\Repository;
use Chamilo\CoreBundle\Entity\MessageTag; use Chamilo\CoreBundle\Entity\MessageTag;
use Chamilo\CoreBundle\Repository\MessageTagRepository; use Chamilo\CoreBundle\Repository\MessageTagRepository;
use Chamilo\CoreBundle\Repository\Node\UserRepository; use Chamilo\CoreBundle\Repository\Node\UserRepository;
use Chamilo\Tests\AbstractApiTest;
use Chamilo\Tests\ChamiloTestTrait; use Chamilo\Tests\ChamiloTestTrait;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException; use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
@ -16,7 +17,7 @@ use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
/** /**
* @covers \MessageTagRepository * @covers \MessageTagRepository
*/ */
class MessageTagRepositoryTest extends WebTestCase class MessageTagRepositoryTest extends AbstractApiTest
{ {
use ChamiloTestTrait; use ChamiloTestTrait;
@ -48,7 +49,6 @@ class MessageTagRepositoryTest extends WebTestCase
$count = $tagRepo->count([]); $count = $tagRepo->count([]);
$this->assertSame(0, $count); $this->assertSame(0, $count);
$this->assertSame(0, $tag->getPosition()); $this->assertSame(0, $tag->getPosition());
} }
@ -74,6 +74,7 @@ class MessageTagRepositoryTest extends WebTestCase
->setTag('tag 2') ->setTag('tag 2')
->setUser($testUser) ->setUser($testUser)
; ;
$this->assertHasNoEntityViolations($tag2); $this->assertHasNoEntityViolations($tag2);
$tagRepo->update($tag2); $tagRepo->update($tag2);
@ -98,7 +99,7 @@ class MessageTagRepositoryTest extends WebTestCase
$this->assertHasNoEntityViolations($tag); $this->assertHasNoEntityViolations($tag);
$tagRepo->update($tag); $tagRepo->update($tag);
// Create second tag, with same name + same user // Create second tag, with same name + same user, should fail.
$tag = $tag =
(new MessageTag()) (new MessageTag())
->setTag('unique') ->setTag('unique')
@ -113,4 +114,110 @@ class MessageTagRepositoryTest extends WebTestCase
$count = $tagRepo->count([]); $count = $tagRepo->count([]);
$this->assertSame(1, $count); $this->assertSame(1, $count);
} }
public function testCreateTagWithApi(): void
{
self::bootKernel();
$testUser = $this->createUser('test');
$token = $this->getUserToken(
[
'username' => 'test',
'password' => 'test',
]
);
$response = $this->createClientWithCredentials($token)->request(
'POST',
'/api/message_tags',
[
'json' => [
'tag' => 'my tag',
'user' => $testUser->getIri(),
],
]
);
$this->assertResponseIsSuccessful();
$this->assertResponseStatusCodeSame(201);
// Check if the tag exists.
$this->createClientWithCredentials($token)->request(
'GET',
'/api/message_tags',
);
$this->assertResponseStatusCodeSame(200);
$this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8');
$this->assertJsonContains(
[
'@context' => '/api/contexts/MessageTag',
'@type' => 'hydra:Collection',
'hydra:totalItems' => 1,
]
);
// Fails to add the same tag twice.
$this->createClientWithCredentials($token)->request(
'POST',
'/api/message_tags',
[
'json' => [
'tag' => 'my tag',
'user' => $testUser->getIri(),
],
]
);
$this->assertResponseStatusCodeSame(422);
// Update tag.
$id = $response->toArray()['id'];
$this->createClientWithCredentials($token)->request(
'PUT',
'/api/message_tags/'.$id,
[
'json' => [
'tag' => 'my tag 2',
],
]
);
$this->assertResponseIsSuccessful();
$this->assertResponseStatusCodeSame(200);
// Add tag as another user
$this->createUser('bad');
$otherToken = $this->getUserToken(
[
'username' => 'bad',
'password' => 'bad',
],
true
);
// Fails to POST a tag to another user.
$this->createClientWithCredentials($otherToken)->request(
'POST',
'/api/message_tags',
[
'json' => [
'tag' => 'new tag',
'user' => $testUser->getIri(),
],
]
);
$this->assertResponseStatusCodeSame(403);
$this->createClientWithCredentials($otherToken)->request(
'GET',
'/api/message_tags',
);
$this->assertResponseStatusCodeSame(200);
$this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8');
$this->assertJsonContains(
[
'@context' => '/api/contexts/MessageTag',
'@type' => 'hydra:Collection',
'hydra:totalItems' => 0,
]
);
}
} }

@ -17,6 +17,29 @@ class PersonalFileRepositoryTest extends AbstractApiTest
{ {
use ChamiloTestTrait; use ChamiloTestTrait;
public function testAccessAsAnon(): void
{
$admin = $this->getUser('admin');
$client = static::createClient();
$client->request('GET', '/api/personal_files');
$this->assertResponseStatusCodeSame(401);
$client->request('POST', '/api/personal_files');
$this->assertResponseStatusCodeSame(401);
$client->request(
'GET',
'/api/personal_files',
[
'json' => [
'resourceNode.parent' => $admin->getResourceNode()->getId(),
],
]
);
$this->assertResponseStatusCodeSame(401);
}
public function testCreateFolder(): void public function testCreateFolder(): void
{ {
$username = 'test'; $username = 'test';
@ -24,10 +47,12 @@ class PersonalFileRepositoryTest extends AbstractApiTest
$user = $this->createUser($username, $password); $user = $this->createUser($username, $password);
$folderName = 'folder1'; $folderName = 'folder1';
$token = $this->getUserToken([ $token = $this->getUserToken(
[
'username' => $username, 'username' => $username,
'password' => $password, 'password' => $password,
]); ]
);
$resourceNodeId = $user->getResourceNode()->getId(); $resourceNodeId = $user->getResourceNode()->getId();
@ -46,15 +71,17 @@ class PersonalFileRepositoryTest extends AbstractApiTest
$this->assertResponseIsSuccessful(); $this->assertResponseIsSuccessful();
$this->assertResponseStatusCodeSame(201); $this->assertResponseStatusCodeSame(201);
$this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); $this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8');
$this->assertJsonContains([ $this->assertJsonContains(
[
'@context' => '/api/contexts/PersonalFile', '@context' => '/api/contexts/PersonalFile',
'@type' => 'PersonalFile', '@type' => 'PersonalFile',
'title' => $folderName, 'title' => $folderName,
'parentResourceNode' => $resourceNodeId, 'parentResourceNode' => $resourceNodeId,
]); ]
);
} }
public function testFileUpload(): void public function testFileUploadAndShare(): void
{ {
self::bootKernel(); self::bootKernel();
$username = 'sender'; $username = 'sender';
@ -63,20 +90,17 @@ class PersonalFileRepositoryTest extends AbstractApiTest
// Creates "sender" user. // Creates "sender" user.
$user = $this->createUser($username, $password); $user = $this->createUser($username, $password);
$token = $this->getUserToken([ $token = $this->getUserToken(
[
'username' => $username, 'username' => $username,
'password' => $password, 'password' => $password,
]); ]
);
// Creates "receiver" user. // Creates "receiver" user.
$receiverUsername = 'receiver'; $receiverUsername = 'receiver';
$receiverPassword = 'receiver'; $receiverPassword = 'receiver';
$receiverUser = $this->createUser($receiverUsername, $receiverPassword); $receiverUser = $this->createUser($receiverUsername, $receiverPassword);
$receiverToken = $this->getUserToken([
'username' => $receiverUsername,
'password' => $receiverPassword,
]);
$resourceNodeId = $user->getResourceNode()->getId(); $resourceNodeId = $user->getResourceNode()->getId();
$file = $this->getUploadedFile(); $file = $this->getUploadedFile();
@ -106,21 +130,25 @@ class PersonalFileRepositoryTest extends AbstractApiTest
$this->assertResponseIsSuccessful(); $this->assertResponseIsSuccessful();
$this->assertResponseStatusCodeSame(201); $this->assertResponseStatusCodeSame(201);
$this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); $this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8');
$this->assertJsonContains([ $this->assertJsonContains(
[
'@context' => '/api/contexts/PersonalFile', '@context' => '/api/contexts/PersonalFile',
'@type' => 'PersonalFile', '@type' => 'PersonalFile',
'title' => $fileName, 'title' => $fileName,
'parentResourceNode' => $resourceNodeId, 'parentResourceNode' => $resourceNodeId,
]); ]
);
// File URL. // File URL.
$url = $response->toArray()['contentUrl']; $url = $response->toArray()['contentUrl'];
$personalFileId = $response->toArray()['id']; $personalFileId = $response->toArray()['id'];
$resourceLinkList = [[ $resourceLinkList = [
[
'uid' => $receiverUser->getId(), 'uid' => $receiverUser->getId(),
'visibility' => $visibilityPublished, 'visibility' => $visibilityPublished,
]]; ],
];
// Share PersonalFile with user 'receiver'. // Share PersonalFile with user 'receiver'.
$this->createClientWithCredentials($token)->request( $this->createClientWithCredentials($token)->request(
@ -136,7 +164,8 @@ class PersonalFileRepositoryTest extends AbstractApiTest
$this->assertResponseIsSuccessful(); $this->assertResponseIsSuccessful();
$this->assertResponseStatusCodeSame(200); $this->assertResponseStatusCodeSame(200);
$this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); $this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8');
$this->assertJsonContains([ $this->assertJsonContains(
[
'@context' => '/api/contexts/PersonalFile', '@context' => '/api/contexts/PersonalFile',
'@type' => 'PersonalFile', '@type' => 'PersonalFile',
'title' => $fileName, 'title' => $fileName,
@ -153,7 +182,8 @@ class PersonalFileRepositoryTest extends AbstractApiTest
], ],
], ],
], ],
]); ]
);
// Access Checks. // Access Checks.
@ -197,4 +227,64 @@ class PersonalFileRepositoryTest extends AbstractApiTest
); );
$this->assertResponseIsSuccessful(); $this->assertResponseIsSuccessful();
} }
public function testUserUploadFileAsAnotherUser(): void
{
self::bootKernel();
$username = 'sender';
$password = 'sender';
// Creates "sender" user.
$user = $this->createUser($username, $password);
$this->createUser('bad', 'bad');
$badUserToken = $this->getUserToken(
[
'username' => 'bad',
'password' => 'bad',
],
true
);
// 1. This is the original user.
$resourceNodeId = $user->getResourceNode()->getId();
$file = $this->getUploadedFile();
// 2. "bad user" tries to upload file to the original user.
$this->createClientWithCredentials($badUserToken)->request(
'POST',
'/api/personal_files',
[
'headers' => [
'Content-Type' => 'multipart/form-data',
],
'extra' => [
'files' => [
'uploadFile' => $file,
],
],
'json' => [
'filetype' => 'file',
'size' => $file->getSize(),
'parentResourceNodeId' => $resourceNodeId,
],
]
);
$this->assertResponseStatusCodeSame(401);
// Bad user tries to get files from other user
$this->createClientWithCredentials($badUserToken)->request(
'GET',
'/api/personal_files',
[
'json' => [
'parentResourceNodeId' => $resourceNodeId,
],
]
);
$this->assertResponseStatusCodeSame(401);
}
} }

Loading…
Cancel
Save