21/12/2015 - SYMFONY
This is an API that can be consumed with basic auth requests and there won't be login or logout features. Users are allowed to access URIs that they are permitted to otherwise "401 Unauthorised" error gets produced. The users, credentials, roles and URIs are all defined in "security.yml". For more info, click here.
http://webservice.local/app_dev.php/
: IS_AUTHENTICATED_ANONYMOUSLY, ROLE_STUDENT, ROLE_LECTURER, ROLE_ADMINhttp://webservice.local/app_dev.php/api/student
: ROLE_STUDENT, ROLE_LECTURER, ROLE_ADMINhttp://webservice.local/app_dev.php/api/lecturer
: ROLE_LECTURER, ROLE_ADMINhttp://webservice.local/app_dev.php/api/admin
: ROLE_ADMINTo assign multiple roles to a user, use roles: [ROLE_STUDENT, ROLE_LECTURER, ROLE_ADMIN]
.
# app/config/security.yml
security:
# http://symfony.com/doc/current/book/security.html#encoding-the-user-s-password
encoders:
Symfony\Component\Security\Core\User\User:
algorithm: bcrypt
cost: 12
# http://symfony.com/doc/current/book/security.html#hierarchical-roles
role_hierarchy:
ROLE_STUDENT: ROLE_USER
ROLE_LECTURER: ROLE_STUDENT
ROLE_ADMIN: [ROLE_LECTURER, ROLE_ALLOWED_TO_SWITCH]
# http://symfony.com/doc/current/book/security.html#where-do-users-come-from-user-providers
providers:
in_memory:
memory:
users:
student:
password: $2a$10$GaEjFjAlt4R5Sc3.rGUAXu087b/hN/mQqe0oDLGAzBRIxinYuCVq2 # student
roles: ROLE_STUDENT
lecturer:
password: $2a$10$GEOb1t0g5QusbzaJwtRfqOuVJhQAzH5jcJt1m487UO.DsnUmd6ul2 # lecturer
roles: ROLE_LECTURER
admin:
password: $2a$10$GogNFjqLFM8vlopZTdl3te2EoVsgX9EGDOilodUige/syc5vKFFwO # admin
roles: ROLE_ADMIN
# the main part of the security, where you can set up firewalls
# for specific sections of your app
firewalls:
# disables authentication for assets and the profiler, adapt it according to your needs
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
# the default controller has to be accessible for everybody
unsecured:
pattern: ^/$
security: false
# secures part of the application
api_secured:
pattern: ^/api/
stateless: true
http_basic:
realm: "Inanzzz Webservice API"
provider: in_memory
# with these settings you can restrict or allow access for different parts
# of your application based on roles, ip, host or methods
# http://symfony.com/doc/current/cookbook/security/access_control.html
access_control:
- { path: ^/, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api/, role: ROLE_STUDENT }
Install "jms/serializer-bundle" : "1.1.0"
with composer and add new JMS\SerializerBundle\JMSSerializerBundle(),
to AppKernel file.
# app/config/routing.yml
application_api:
resource: "@ApplicationApiBundle/Controller"
prefix: /
type: annotation
# app/config/services.yml
services:
doctrine_common_inflector:
class: Doctrine\Common\Inflector\Inflector
# src/Application/ApiBundle/Resources/config/controllers.yml
services:
application_api.controller.abstract:
class: Application\ApiBundle\Controller\AbstractController
abstract: true
arguments:
- @serializer
- @validator
- @doctrine_common_inflector
application_api.controller.default:
class: Application\ApiBundle\Controller\DefaultController
parent: application_api.controller.abstract
application_api.controller.api:
class: Application\ApiBundle\Controller\ApiController
parent: application_api.controller.abstract
namespace Application\ApiBundle\Controller;
use Doctrine\Common\Inflector\Inflector;
use JMS\Serializer\SerializationContext;
use JMS\Serializer\SerializerInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Validator\ConstraintViolationInterface;
use Symfony\Component\Validator\ConstraintViolationList;
use Symfony\Component\Validator\ConstraintViolationListInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
abstract class AbstractController
{
private $validContentTypes = ['json' => 'application/json', 'xml' => 'application/xml'];
protected $serializer;
protected $validator;
protected $inflector;
public function __construct(
SerializerInterface $serializer,
ValidatorInterface $validator,
Inflector $inflector
) {
$this->serializer = $serializer;
$this->validator = $validator;
$this->inflector = $inflector;
}
/**
* @param string $contentType
*
* @return string|Response
*/
protected function validateContentType($contentType)
{
if (!in_array($contentType, $this->validContentTypes)) {
return $this->createFailureResponse(
['content_type' => sprintf('Invalid content type [%s].', $contentType)],
'json',
Response::HTTP_UNSUPPORTED_MEDIA_TYPE
);
}
return array_search($contentType, $this->validContentTypes);
}
/**
* @param string $payload
* @param string $model
* @param string $format
*
* @return object|Response
*/
protected function validatePayload($payload, $model, $format)
{
$payload = $this->serializer->deserialize($payload, $model, $format);
$errors = $this->validator->validate($payload);
if (count($errors)) {
return $this->createFailureResponse($errors, $format);
}
return $payload;
}
/**
* @param array|object $content
* @param string $format
*
* @return Response
*/
protected function createSuccessResponse($content, $format = 'json')
{
return $this->getResponse($content, $format, Response::HTTP_OK);
}
/**
* @param array|ConstraintViolationListInterface $content
* @param string $format
*
* @return Response
*/
protected function createFailureResponse($content, $format = 'json')
{
$errorList = null;
if ($content instanceof ConstraintViolationList) {
foreach ($content as $error) {
$error = $this->getErrorFromValidation($error);
$errorList[$error['key']] = $error['value'];
}
} else {
$errorList = $content;
}
return $this->getResponse(['errors' => $errorList], $format, Response::HTTP_BAD_REQUEST);
}
/**
* @param array|object $content
* @param string $format
* @param int $status
*
* @return Response
*/
private function getResponse($content, $format, $status)
{
$context = new SerializationContext();
$context->setSerializeNull(false);
$response = $this->serializer->serialize($content, $format, $context);
return new Response($response, $status, ['Content-Type' => $this->validContentTypes[$format]]);
}
/**
* @param ConstraintViolationInterface $error
*
* @return array
*/
private function getErrorFromValidation($error)
{
$properties = $this->inflector->tableize($error->getPropertyPath());
return ['key' => $properties, 'value' => $error->getMessage()];
}
}
namespace Application\ApiBundle\Controller;
use Doctrine\Common\Inflector\Inflector;
use JMS\Serializer\SerializerInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Validator\Validator\ValidatorInterface;
/**
* @Route("", service="application_api.controller.default")
*/
class DefaultController extends AbstractController
{
public function __construct(
SerializerInterface $serializer,
ValidatorInterface $validator,
Inflector $inflector
) {
parent::__construct($serializer, $validator, $inflector);
}
/**
* @Method({"GET"})
* @Route("")
*
* @return Response
*/
public function indexAction()
{
return $this->createSuccessResponse('Welcome to Webservice API!');
}
}
As you can see below, unlike other two methods we didn't have to set @Security
for studentAction
method because, access control settings in security.yml dictates that logged in users (ROLE_STUDENT, ROLE_LECTURER, ROLE_ADMIN) can access to all /api/...
end-points by default. To override this behaviour, we use @Security
annotations.
namespace Application\ApiBundle\Controller;
use Doctrine\Common\Inflector\Inflector;
use JMS\Serializer\SerializerInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Validator\Validator\ValidatorInterface;
/**
* @Route("api", service="application_api.controller.api")
*/
class ApiController extends AbstractController
{
public function __construct(
SerializerInterface $serializer,
ValidatorInterface $validator,
Inflector $inflector
) {
parent::__construct($serializer, $validator, $inflector);
}
/**
* @Method({"GET"})
* @Route("/student")
*
* @return Response
*/
public function studentAction()
{
return $this->createSuccessResponse('Hello ROLE_STUDENT user');
}
/**
* @Method({"GET"})
* @Route("/lecturer")
* @Security("has_role('ROLE_LECTURER')")
*
* @return Response
*/
public function lecturerAction()
{
return $this->createSuccessResponse('Hello ROLE_LECTURER user');
}
/**
* @Method({"GET"})
* @Route("/admin")
* @Security("has_role('ROLE_ADMIN')")
*
* @return Response
*/
public function adminAction()
{
return $this->createSuccessResponse('Hello ROLE_ADMIN user');
}
}
All the user roles can access to /
so I won't demonstrate it here. Also, I won't show every single end-point tests here but you can trust what I say below.
Forbidden access to all /api/...
end-points.
Can access to /api/student
but not /api/lecturer
and /api/admin
end-points.
Can access to /api/student
and /api/lecturer
but not /api/admin
end-point.
Can access to all /api/...
end-points.