Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions app/V1Module/presenters/EmailsPresenter.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

class EmailsPresenter extends BasePresenter
{

/**
* @var EmailLocalizationHelper
* @inject
Expand Down Expand Up @@ -57,7 +56,8 @@ public function checkDefault()
* Sends an email with provided subject and message to all ReCodEx users.
* @POST
* @Param(type="post", name="subject", validation="string:1..", description="Subject for the soon to be sent email")
* @Param(type="post", name="message", validation="string:1..", description="Message which will be sent, can be html code")
* @Param(type="post", name="message", validation="string:1..",
* description="Message which will be sent, can be html code")
*/
public function actionDefault()
{
Expand Down Expand Up @@ -87,7 +87,8 @@ public function checkSendToSupervisors()
* Sends an email with provided subject and message to all supervisors and superadmins.
* @POST
* @Param(type="post", name="subject", validation="string:1..", description="Subject for the soon to be sent email")
* @Param(type="post", name="message", validation="string:1..", description="Message which will be sent, can be html code")
* @Param(type="post", name="message", validation="string:1..",
* description="Message which will be sent, can be html code")
*/
public function actionSendToSupervisors()
{
Expand Down Expand Up @@ -123,7 +124,8 @@ public function checkSendToRegularUsers()
* Sends an email with provided subject and message to all regular users.
* @POST
* @Param(type="post", name="subject", validation="string:1..", description="Subject for the soon to be sent email")
* @Param(type="post", name="message", validation="string:1..", description="Message which will be sent, can be html code")
* @Param(type="post", name="message", validation="string:1..",
* description="Message which will be sent, can be html code")
*/
public function actionSendToRegularUsers()
{
Expand Down
138 changes: 138 additions & 0 deletions app/V1Module/presenters/ExtensionsPresenter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
<?php

namespace App\V1Module\Presenters;

use App\Exceptions\ForbiddenRequestException;
use App\Exceptions\BadRequestException;
use App\Model\Repository\Instances;
use App\Model\Repository\Users;
use App\Model\View\UserViewFactory;
use App\Helpers\Extensions;
use App\Security\AccessManager;
use App\Security\TokenScope;

/**
* Endpoints handling 3rd party extensions communication.
*/
class ExtensionsPresenter extends BasePresenter
{
/**
* @var Extensions
* @inject
*/
public $extensions;

/**
* @var Instances
* @inject
*/
public $instances;

/**
* @var Users
* @inject
*/
public $users;

/**
* @var AccessManager
* @inject
*/
public $accessManager;

/**
* @var UserViewFactory
* @inject
*/
public $userViewFactory;

public function checkUrl(string $extId, string $instanceId)
{
$user = $this->getCurrentUser();
$extension = $this->extensions->getExtension($extId);
$instance = $this->instances->findOrThrow($instanceId);
if (!$extension || !$extension->isAccessible($instance, $user)) {
throw new ForbiddenRequestException();
}
}

/**
* Return URL refering to the extension with properly injected temporary JWT token.
* @GET
* @Param(type="query", name="locale", required=false, validation="string:2")
* @Param(type="query", name="return", required=false, validation="string")
*/
public function actionUrl(string $extId, string $instanceId, ?string $locale, ?string $return)
{
$user = $this->getCurrentUser();
$extension = $this->extensions->getExtension($extId);

$token = $this->accessManager->issueToken(
$user,
null,
[TokenScope::EXTENSIONS],
$extension->getUrlTokenExpiration(),
["instance" => $instanceId, "extension" => $extId]
);

if (!$locale) {
$locale = $this->getCurrentUserLocale();
}

$this->sendSuccessResponse($extension->getUrl($token, $locale, $return ?? ''));
}

public function checkToken(string $extId)
{
/*
* This checker does not employ traditional ACLs for permission checks since it is trvial and it is better
* to keep everything here (in one place). However, this may change in the future should the presenter get
* more complex.
* This action expects to be authenticated by temporary token generated in 'url' action.
*/

// All users within this scope are allowed the operation...
if (!$this->isInScope(TokenScope::EXTENSIONS)) {
throw new ForbiddenRequestException();
}

// ...but the token must be also valid...
$token = $this->getAccessToken();
$instanceId = $token->getPayload('instance');
if ($token->getPayload('extension') !== $extId || !$instanceId) {
throw new BadRequestException();
}

// ...and the extension must be accessible by the user.
$user = $this->getCurrentUser();
$extension = $this->extensions->getExtension($extId);
$instance = $this->instances->findOrThrow($instanceId);
if (!$extension || !$extension->isAccessible($instance, $user)) {
throw new ForbiddenRequestException();
}
}

/**
* This endpoint is used by a backend of an extension to get a proper access token
* (from a temp token passed via URL). It also returns details about authenticated user.
* @POST
*/
public function actionToken(string $extId)
{
$user = $this->getCurrentUser();
$extension = $this->extensions->getExtension($extId);
$authUser = $extension->getTokenUserId() ? $this->users->findOrThrow($extension->getTokenUserId()) : $user;

$token = $this->accessManager->issueToken(
$authUser,
null,
$extension->getTokenScopes(),
$extension->getTokenExpiration(),
);

$this->sendSuccessResponse([
"accessToken" => $token,
"user" => $this->userViewFactory->getFullUser($user, false /* do not show really everything */),
]);
}
}
28 changes: 16 additions & 12 deletions app/V1Module/presenters/InstancesPresenter.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@

use App\Exceptions\ForbiddenRequestException;
use App\Exceptions\NotFoundException;
use App\Model\Entity\Group;
use App\Model\Entity\LocalizedGroup;
use App\Model\Entity\User;
use App\Model\View\GroupViewFactory;
use App\Model\View\InstanceViewFactory;
use App\Model\View\UserViewFactory;
Expand All @@ -25,7 +23,6 @@
*/
class InstancesPresenter extends BasePresenter
{

/**
* @var Instances
* @inject
Expand Down Expand Up @@ -94,7 +91,9 @@ function (Instance $instance) {
return $instance->isAllowed();
}
);
$this->sendSuccessResponse($this->instanceViewFactory->getInstances($instances));
$this->sendSuccessResponse(
$this->instanceViewFactory->getInstances($instances, $this->getCurrentUserOrNull())
);
}

public function checkCreateInstance()
Expand All @@ -109,7 +108,8 @@ public function checkCreateInstance()
* @POST
* @Param(type="post", name="name", validation="string:2..", description="Name of the instance")
* @Param(type="post", name="description", required=false, description="Description of the instance")
* @Param(type="post", name="isOpen", validation="bool", description="Should the instance be open for registration?")
* @Param(type="post", name="isOpen", validation="bool",
* description="Should the instance be open for registration?")
* @throws ForbiddenRequestException
*/
public function actionCreateInstance()
Expand All @@ -129,7 +129,7 @@ public function actionCreateInstance()
$this->instances->persist($instance->getRootGroup(), false);
$this->instances->persist($localizedRootGroup, false);
$this->instances->persist($instance);
$this->sendSuccessResponse($this->instanceViewFactory->getInstance($instance), IResponse::S201_CREATED);
$this->sendSuccessResponse($this->instanceViewFactory->getInstance($instance, $user), IResponse::S201_CREATED);
}

public function checkUpdateInstance(string $id)
Expand All @@ -144,7 +144,8 @@ public function checkUpdateInstance(string $id)
/**
* Update an instance
* @POST
* @Param(type="post", name="isOpen", validation="bool", required=false, description="Should the instance be open for registration?")
* @Param(type="post", name="isOpen", validation="bool", required=false,
* description="Should the instance be open for registration?")
* @param string $id An identifier of the updated instance
*/
public function actionUpdateInstance(string $id)
Expand All @@ -159,7 +160,7 @@ public function actionUpdateInstance(string $id)

$instance->setIsOpen($isOpen);
$this->instances->persist($instance);
$this->sendSuccessResponse($this->instanceViewFactory->getInstance($instance));
$this->sendSuccessResponse($this->instanceViewFactory->getInstance($instance, $this->getCurrentUser()));
}

public function checkDeleteInstance(string $id)
Expand Down Expand Up @@ -208,7 +209,7 @@ public function checkDetail(string $id)
public function actionDetail(string $id)
{
$instance = $this->instances->findOrThrow($id);
$this->sendSuccessResponse($this->instanceViewFactory->getInstance($instance));
$this->sendSuccessResponse($this->instanceViewFactory->getInstance($instance, $this->getCurrentUser()));
}

public function checkLicences(string $id)
Expand Down Expand Up @@ -268,9 +269,12 @@ public function checkUpdateLicence(string $licenceId)
/**
* Update an existing license for an instance
* @POST
* @Param(type="post", name="note", validation="string:2..255", required=false, description="A note for users or administrators")
* @Param(type="post", name="validUntil", validation="string", required=false, description="Expiration date of the license")
* @Param(type="post", name="isValid", validation="bool", required=false, description="Administrator switch to toggle licence validity")
* @Param(type="post", name="note", validation="string:2..255", required=false,
* description="A note for users or administrators")
* @Param(type="post", name="validUntil", validation="string", required=false,
* description="Expiration date of the license")
* @Param(type="post", name="isValid", validation="bool", required=false,
* description="Administrator switch to toggle licence validity")
* @param string $licenceId Identifier of the licence
* @throws NotFoundException
*/
Expand Down
3 changes: 1 addition & 2 deletions app/V1Module/presenters/LoginPresenter.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
use App\Model\Repository\SecurityEvents;
use App\Model\Repository\Users;
use App\Model\View\UserViewFactory;
use App\Security\AccessToken;
use App\Security\AccessManager;
use App\Security\ACL\IUserPermissions;
use App\Security\CredentialsAuthenticator;
Expand Down Expand Up @@ -257,7 +256,7 @@ public function actionIssueRestrictedToken()
$this->sendSuccessResponse(
[
"accessToken" => $this->accessManager->issueToken($user, $effectiveRole, $scopes, $expiration),
"user" => $this->userViewFactory->getFullUser($user)
"user" => $this->userViewFactory->getFullUser($user),
]
);
}
Expand Down
21 changes: 14 additions & 7 deletions app/V1Module/presenters/NotificationsPresenter.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,14 @@ public function checkCreate()

/**
* Create notification with given attributes
* @Param(type="post", name="groupsIds", validation="array", description="Identification of groups")
* @Param(type="post", name="visibleFrom", validation="timestamp", description="Date from which is notification visible")
* @Param(type="post", name="visibleTo", validation="timestamp", description="Date to which is notification visible")
* @Param(type="post", name="role", validation="string:1..", description="Users with this role and its children can see notification")
* @Param(type="post", name="groupsIds", validation="array",
* description="Identification of groups")
* @Param(type="post", name="visibleFrom", validation="timestamp",
* description="Date from which is notification visible")
* @Param(type="post", name="visibleTo", validation="timestamp",
* description="Date to which is notification visible")
* @Param(type="post", name="role", validation="string:1..",
* description="Users with this role and its children can see notification")
* @Param(type="post", name="type", validation="string", description="Type of the notification (custom)")
* @Param(type="post", name="localizedTexts", validation="array", description="Text of notification")
* @POST
Expand Down Expand Up @@ -218,9 +222,12 @@ public function checkUpdate(string $id)
* @POST
* @param string $id
* @Param(type="post", name="groupsIds", validation="array", description="Identification of groups")
* @Param(type="post", name="visibleFrom", validation="timestamp", description="Date from which is notification visible")
* @Param(type="post", name="visibleTo", validation="timestamp", description="Date to which is notification visible")
* @Param(type="post", name="role", validation="string:1..", description="Users with this role and its children can see notification")
* @Param(type="post", name="visibleFrom", validation="timestamp",
* description="Date from which is notification visible")
* @Param(type="post", name="visibleTo", validation="timestamp",
* description="Date to which is notification visible")
* @Param(type="post", name="role", validation="string:1..",
* description="Users with this role and its children can see notification")
* @Param(type="post", name="type", validation="string", description="Type of the notification (custom)")
* @Param(type="post", name="localizedTexts", validation="array", description="Text of notification")
* @throws NotFoundException
Expand Down
5 changes: 4 additions & 1 deletion app/V1Module/presenters/UsersPresenter.php
Original file line number Diff line number Diff line change
Expand Up @@ -655,7 +655,10 @@ public function actionInstances(string $id)
{
$user = $this->users->findOrThrow($id);

$this->sendSuccessResponse($this->instanceViewFactory->getInstances($user->getInstances()->toArray()));
$this->sendSuccessResponse($this->instanceViewFactory->getInstances(
$user->getInstances()->toArray(),
$this->getCurrentUser()
));
}

public function checkSetRole(string $id)
Expand Down
19 changes: 13 additions & 6 deletions app/V1Module/presenters/base/BasePresenter.php
Original file line number Diff line number Diff line change
Expand Up @@ -137,19 +137,26 @@ protected function isRequestJson(): bool
}

/**
* @return User
* @throws ForbiddenRequestException
* @return User|null (null if no user is authenticated)
*/
protected function getCurrentUser(): User
protected function getCurrentUserOrNull(): ?User
{
/** @var ?Identity $identity */
$identity = $this->getUser()->getIdentity();
return $identity?->getUserData();
}

if ($identity === null || $identity->getUserData() === null) {
/**
* @return User
* @throws ForbiddenRequestException
*/
protected function getCurrentUser(): User
{
$user = $this->getCurrentUserOrNull();
if ($user === null) {
throw new ForbiddenRequestException();
}

return $identity->getUserData();
return $user;
}

/**
Expand Down
9 changes: 9 additions & 0 deletions app/V1Module/router/RouterFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public static function createRouter()
$router[] = self::createWorkerFilesRoutes("$prefix/worker-files");
$router[] = self::createAsyncJobsRoutes("$prefix/async-jobs");
$router[] = self::createPlagiarismRoutes("$prefix/plagiarism");
$router[] = self::createExtensionsRoutes("$prefix/extensions");

return $router;
}
Expand Down Expand Up @@ -664,4 +665,12 @@ private static function createPlagiarismRoutes(string $prefix): RouteList
$router[] = new PostRoute("$prefix/<id>/<solutionId>", "Plagiarism:addSimilarities");
return $router;
}

private static function createExtensionsRoutes(string $prefix): RouteList
{
$router = new RouteList();
$router[] = new GetRoute("$prefix/<extId>/<instanceId>", "Extensions:url");
$router[] = new PostRoute("$prefix/<extId>", "Extensions:token");
return $router;
}
}
5 changes: 5 additions & 0 deletions app/V1Module/security/TokenScope.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,9 @@ class TokenScope
* Usually used in combination with other scopes. Allows refreshing the token.
*/
public const REFRESH = "refresh";

/**
* Scope for handling handshake and auth-exchange with 3rd party extensions. Temp tokens must use this scope.
*/
public const EXTENSIONS = "extensions";
}
Loading