Commit ed671c57 authored by François Agneray's avatar François Agneray
Browse files

Add admin middleware (protect metamodel routes)

parent f932b53a
......@@ -76,14 +76,6 @@ $container->set('App\Action\ProjectAction', function (ContainerInterface $c) {
return new App\Action\ProjectAction($c->get('em'));
});
$container->set('App\Action\UserListAction', function (ContainerInterface $c) {
return new App\Action\UserListAction($c->get('em'));
});
$container->set('App\Action\UserAction', function (ContainerInterface $c) {
return new App\Action\UserAction($c->get('em'));
});
$container->set('App\Action\GroupListAction', function (ContainerInterface $c) {
return new App\Action\GroupListAction($c->get('em'));
});
......
......@@ -13,31 +13,41 @@ declare(strict_types=1);
use Slim\Routing\RouteCollectorProxy;
$app->get('/', App\Action\RootAction::class);
$app->map([OPTIONS, GET, POST], '/database', App\Action\DatabaseListAction::class);
$app->map([OPTIONS, GET, PUT, DELETE], '/database/{id}', App\Action\DatabaseAction::class);
$app->map([OPTIONS, GET], '/database/{id}/table', App\Action\TableListAction::class);
$app->map([OPTIONS, GET, POST], '/project', App\Action\ProjectListAction::class);
$app->map([OPTIONS, GET, PUT, DELETE], '/project/{name}', App\Action\ProjectAction::class);
$app->map([OPTIONS, GET, POST], '/user', App\Action\UserListAction::class);
$app->map([OPTIONS, GET, PUT, DELETE], '/user/{email}', App\Action\UserAction::class);
$app->map([OPTIONS, GET, POST], '/group', App\Action\GroupListAction::class);
$app->map([OPTIONS, GET, PUT, DELETE], '/group/{id}', App\Action\GroupAction::class);
$app->map([OPTIONS, GET, POST], '/instance', App\Action\InstanceListAction::class);
$app->map([OPTIONS, GET, PUT, DELETE], '/instance/{name}', App\Action\InstanceAction::class);
$app->map([OPTIONS, GET, POST], '/instance/{name}/dataset-family', App\Action\DatasetFamilyListAction::class);
$app->map([OPTIONS, GET], '/instance/{name}/dataset', App\Action\DatasetListByInstanceAction::class);
$app->map([OPTIONS, GET, PUT, DELETE], '/dataset-family/{id}', App\Action\DatasetFamilyAction::class);
$app->map([OPTIONS, GET, POST], '/dataset-family/{id}/dataset', App\Action\DatasetListAction::class);
$app->map([OPTIONS, GET, PUT, DELETE], '/dataset/{name}', App\Action\DatasetAction::class);
$app->map([OPTIONS, GET, POST], '/dataset/{name}/criteria-family', App\Action\CriteriaFamilyListAction::class);
$app->map([OPTIONS, GET, PUT, DELETE], '/criteria-family/{id}', App\Action\CriteriaFamilyAction::class);
$app->map([OPTIONS, GET, POST], '/dataset/{name}/output-family', App\Action\OutputFamilyListAction::class);
$app->map([OPTIONS, GET, PUT, DELETE], '/output-family/{id}', App\Action\OutputFamilyAction::class);
$app->map([OPTIONS, GET], '/dataset/{name}/output-category', App\Action\OutputCategoryListByDatasetAction::class);
$app->map([OPTIONS, GET, POST], '/output-family/{id}/output-category', App\Action\OutputCategoryListAction::class);
$app->map([OPTIONS, GET, PUT, DELETE], '/output-category/{id}', App\Action\OutputCategoryAction::class);
$app->map([OPTIONS, GET], '/dataset/{name}/attribute', App\Action\AttributeListAction::class);
$app->map([OPTIONS, GET, PUT], '/dataset/{name}/attribute/{id}', App\Action\AttributeAction::class);
$app->map([OPTIONS, GET, PUT], '/dataset/{name}/attribute/{id}/distinct', App\Action\AttributeDistinctAction::class);
$app->group('', function (RouteCollectorProxy $group) {
$group->map([OPTIONS, GET, POST], '/database', App\Action\DatabaseListAction::class);
$group->map([OPTIONS, GET, PUT, DELETE], '/database/{id}', App\Action\DatabaseAction::class);
$group->map([OPTIONS, GET], '/database/{id}/table', App\Action\TableListAction::class);
$group->map([OPTIONS, GET, POST], '/project', App\Action\ProjectListAction::class);
$group->map([OPTIONS, GET, PUT, DELETE], '/project/{name}', App\Action\ProjectAction::class);
$group->map([OPTIONS, GET, POST], '/group', App\Action\GroupListAction::class);
$group->map([OPTIONS, GET, PUT, DELETE], '/group/{id}', App\Action\GroupAction::class);
$group->map([OPTIONS, GET, POST], '/instance', App\Action\InstanceListAction::class);
$group->map([OPTIONS, GET, PUT, DELETE], '/instance/{name}', App\Action\InstanceAction::class);
$group->map([OPTIONS, GET, POST], '/instance/{name}/dataset-family', App\Action\DatasetFamilyListAction::class);
$group->map([OPTIONS, GET], '/instance/{name}/dataset', App\Action\DatasetListByInstanceAction::class);
$group->map([OPTIONS, GET, PUT, DELETE], '/dataset-family/{id}', App\Action\DatasetFamilyAction::class);
$group->map([OPTIONS, GET, POST], '/dataset-family/{id}/dataset', App\Action\DatasetListAction::class);
$group->map([OPTIONS, GET, PUT, DELETE], '/dataset/{name}', App\Action\DatasetAction::class);
$group->map([OPTIONS, GET, POST], '/dataset/{name}/criteria-family', App\Action\CriteriaFamilyListAction::class);
$group->map([OPTIONS, GET, PUT, DELETE], '/criteria-family/{id}', App\Action\CriteriaFamilyAction::class);
$group->map([OPTIONS, GET, POST], '/dataset/{name}/output-family', App\Action\OutputFamilyListAction::class);
$group->map([OPTIONS, GET, PUT, DELETE], '/output-family/{id}', App\Action\OutputFamilyAction::class);
$group->map([OPTIONS, GET], '/dataset/{name}/output-category', App\Action\OutputCategoryListByDatasetAction::class);
$group->map(
[OPTIONS, GET, POST],
'/output-family/{id}/output-category',
App\Action\OutputCategoryListAction::class
);
$group->map([OPTIONS, GET, PUT, DELETE], '/output-category/{id}', App\Action\OutputCategoryAction::class);
$group->map([OPTIONS, GET], '/dataset/{name}/attribute', App\Action\AttributeListAction::class);
$group->map([OPTIONS, GET, PUT], '/dataset/{name}/attribute/{id}', App\Action\AttributeAction::class);
$group->map(
[OPTIONS, GET, PUT],
'/dataset/{name}/attribute/{id}/distinct',
App\Action\AttributeDistinctAction::class
);
})->add(new App\Middleware\AdminMiddleware());
$app->get('/search/{dname}', App\Action\SearchAction::class);
$app->get('/download-file/{dname}/[{fpath:.*}]', App\Action\DownloadFileAction::class);
<?php
/*
* This file is part of Anis Server.
*
* (c) Laboratoire d'Astrophysique de Marseille / CNRS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace App\Action;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use Slim\Exception\HttpBadRequestException;
use Slim\Exception\HttpNotFoundException;
use App\Entity\User;
final class UserAction extends AbstractAction
{
/**
* `GET` Returns the user found
* `PUT` Full update the user and returns the new version
* `DELETE` Delete the user found and return a confirmation message
*
* @param ServerRequestInterface $request PSR-7 This object represents the HTTP request
* @param ResponseInterface $response PSR-7 This object represents the HTTP response
* @param string[] $args This table contains information transmitted in the URL (see routes.php)
*
* @return ResponseInterface
*/
public function __invoke(Request $request, Response $response, array $args): Response
{
if ($request->getMethod() === OPTIONS) {
return $response->withHeader('Access-Control-Allow-Methods', 'GET, PUT, DELETE, OPTIONS');
}
// Search the correct user with primary key
$user = $this->em->find('App\Entity\User', $args['email']);
// If user is not found 404
if (is_null($user)) {
throw new HttpNotFoundException(
$request,
'User with email ' . $args['email'] . ' is not found'
);
}
if ($request->getMethod() === GET) {
$payload = json_encode($user);
}
if ($request->getMethod() === PUT) {
$parsedBody = $request->getParsedBody();
// If mandatories empty fields 400
foreach (array('role') as $a) {
if ($this->isEmptyField($a, $parsedBody)) {
throw new HttpBadRequestException(
$request,
'Param ' . $a . ' needed to edit the user'
);
}
}
$this->editUser($user, $parsedBody);
$payload = json_encode($user);
}
if ($request->getMethod() === DELETE) {
$email = $user->getEmail();
$this->em->remove($user);
$this->em->flush();
$payload = json_encode(array('message' => 'User with email ' . $email . ' is removed!'));
}
$response->getBody()->write($payload);
return $response;
}
/**
* Update user object with setters
*
* @param User $user The user to update
* @param array $parsedBody Contains the new values ​​of the user
*/
private function editUser(User $user, array $parsedBody): void
{
$user->setRole($parsedBody['role']);
$this->em->flush();
}
}
<?php
/*
* This file is part of Anis Server.
*
* (c) Laboratoire d'Astrophysique de Marseille / CNRS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace App\Action;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use Slim\Exception\HttpBadRequestException;
use App\Entity\User;
final class UserListAction extends AbstractAction
{
/**
* `GET` Returns a list of all users listed in the metamodel
* `POST` Add a new user
*
* @param ServerRequestInterface $request PSR-7 This object represents the HTTP request
* @param ResponseInterface $response PSR-7 This object represents the HTTP response
* @param string[] $args This table contains information transmitted in the URL (see routes.php)
*
* @return ResponseInterface
*/
public function __invoke(Request $request, Response $response, array $args): Response
{
if ($request->getMethod() === OPTIONS) {
return $response->withHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
}
if ($request->getMethod() === GET) {
$users = $this->em->getRepository('App\Entity\User')->findAll();
$payload = json_encode($users);
}
if ($request->getMethod() === POST) {
$parsedBody = $request->getParsedBody();
// To work this action needs user information
foreach (array('email') as $a) {
if ($this->isEmptyField($a, $parsedBody)) {
throw new HttpBadRequestException(
$request,
'Param ' . $a . ' needed to add a new user'
);
}
}
$user = $this->postUser($parsedBody);
$payload = json_encode($user);
$response = $response->withStatus(201);
}
$response->getBody()->write($payload);
return $response;
}
/**
* Add a new user into the metamodel
*
* @param array $parsedBody Contains the values ​​of the new user
*/
private function postUser(array $parsedBody): User
{
$user = new User($parsedBody['email']);
$user->setRole('user');
$this->em->persist($user);
$this->em->flush();
return $user;
}
}
......@@ -37,18 +37,6 @@ class Group implements \JsonSerializable
*/
protected $label;
/**
* Many Groups have Many Users.
*
* @ManyToMany(targetEntity="User", inversedBy="groups")
* @JoinTable(
* name="groups_users",
* joinColumns={@JoinColumn(name="group_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="user_email", referencedColumnName="email")}
* )
*/
protected $users;
/**
* Many Groups have Many Datasets privileges.
*
......@@ -61,9 +49,8 @@ class Group implements \JsonSerializable
*/
protected $datasets;
public function __construct(array $users, array $datasets)
public function __construct(array $datasets)
{
$this->users = new ArrayCollection($users);
$this->datasets = new ArrayCollection($datasets);
}
......@@ -82,16 +69,6 @@ class Group implements \JsonSerializable
$this->label = $label;
}
public function getUsers()
{
return $this->users;
}
public function setUsers($users)
{
$this->users = $users;
}
public function getDatasets()
{
return $this->datasets;
......@@ -104,11 +81,6 @@ class Group implements \JsonSerializable
public function jsonSerialize()
{
$users = array();
foreach ($this->getUsers() as $user) {
$users[] = $user->getEmail();
}
$datasets = array();
foreach ($this->getDatasets() as $dataset) {
$datasets[] = $dataset->getName();
......@@ -117,7 +89,6 @@ class Group implements \JsonSerializable
return [
'id' => $this->getId(),
'label' => $this->getLabel(),
'users' => $users,
'datasets' => $datasets
];
}
......
<?php
/*
* This file is part of Anis Server.
*
* (c) Laboratoire d'Astrophysique de Marseille / CNRS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace App\Entity;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;
/**
* @Entity
* @Table(name="anis_user")
*/
class User implements \JsonSerializable
{
/**
* @var string
*
* @Id
* @Column(type="string", nullable=false)
*/
protected $email;
/**
* @var string
*
* @Column(type="string", nullable=false)
*/
protected $role;
/**
* Many Users have Many Groups.
*
* @ManyToMany(targetEntity="Group", mappedBy="users")
*/
protected $groups;
public function __construct($email)
{
$this->email = $email;
$this->groups = new ArrayCollection();
}
public function getEmail()
{
return $this->email;
}
public function getRole()
{
return $this->role;
}
public function setRole($role)
{
$this->role = $role;
}
public function getGroups()
{
return $this->groups;
}
public function jsonSerialize()
{
$groups = array();
foreach ($this->getGroups() as $group) {
$groups[] = $group->getId();
}
return [
'email' => $this->getEmail(),
'role' => $this->getRole(),
'groups' => $groups
];
}
}
<?php
/*
* This file is part of Anis Server.
*
* (c) Laboratoire d'Astrophysique de Marseille / CNRS
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace App\Middleware;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Psr\Http\Server\MiddlewareInterface;
use Nyholm\Psr7\Response as NyholmResponse;
final class AdminMiddleware implements MiddlewareInterface
{
public function process(Request $request, RequestHandler $handler): Response
{
if ($request->getMethod() === GET) {
return $handler->handle($request);
}
$token = $request->getAttribute('token');
if (!$token) {
return (new NyholmResponse())->withStatus(401);
}
if (!in_array('anis_admin', $token->getClaim('realm_access')->roles)) {
return (new NyholmResponse())->withStatus(403);
}
return $handler->handle($request);
}
}
......@@ -85,6 +85,6 @@ final class AuthorizationMiddleware implements MiddlewareInterface
return (new NyholmResponse())->withStatus(401);
}
return $handler->handle($request->withAttribute('email', $token->getClaim('email')));
return $handler->handle($request->withAttribute('token', $token));
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment