When I use the annotation #Security("is_granted('remove', user)"), I get a wrong user in Voter
/**
* Delete User by ID
*
* #Rest\Delete("/{id}", name="delete_user")
* #Security("is_granted('remove', user)")
*
* #ApiDoc(
* section="5. Users",
* resource=true,
* description="Delete User",
* headers={
* {
* "name"="Authorization: Bearer [ACCESS_TOKEN]",
* "description"="Authorization key",
* "required"=true
* }
* },
* requirements={
* {
* "name"="id",
* "dataType"="string",
* "requirement"="\[a-z\-]+",
* "description"="Id of the object to receive"
* }
* },
* output="Status"
* )
*
* #param User $user
* #return Response;
*/
public function deleteAction(User $user)
{
//$this->denyAccessUnlessGranted('remove', $user);
$em = $this->getDoctrine()->getManager();
$em->remove($user);
$em->flush();
$view = $this->view('Success deleted', Response::HTTP_NO_CONTENT);
return $this->handleView($view);
}
But, if I use functions in the body $this->denyAccessUnlessGranted('remove', $user);, it's all right. Help me to understand...
Settings
services:
user.user_voter:
class: OD\UserBundle\Security\UserVoter
arguments: ['#security.access.decision_manager']
public: false
tags:
- { name: security.voter }
Voter
namespace OD\UserBundle\Security;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use OD\UserBundle\Entity\User;
class UserVoter extends Voter
{
const VIEW = 'view';
const EDIT = 'edit';
const REMOVE = 'remove';
protected function supports($attribute, $subject)
{
# if the attribute isn't one we support, return false
if (!in_array($attribute, array(self::VIEW, self::EDIT, self::REMOVE))) {
return false;
}
# only vote on Booking objects inside this voter
if (!$subject instanceof User) {
return false;
}
return true;
}
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
{
$user = $token->getUser();
if (!$user instanceof User) {
# the user must be logged in; if not, deny access
return false;
}
switch ($attribute) {
case self::VIEW:
return $this->canView($subject, $user);
case self::EDIT:
return $this->canEdit($subject, $user);
case self::REMOVE:
return $this->canRemove($subject, $user);
}
throw new \LogicException('This code should not be reached!');
}
private function canView(User $subject, User $user)
{
if ($subject->getId() === $user->getId()) {
return true;
}
return false;
}
private function canEdit(User $subject, User $user)
{
if ($subject->getId() === $user->getId()) {
return true;
}
return false;
}
private function canRemove(User $subject, User $user)
{
if ($subject->getId() === $user->getId()) {
return true;
}
return false;
}
}
* #Security("is_granted('remove', removingUser)")
* #param User $removingUser
* #return Response;
*/
public function deleteAction(User $removingUser)
This code works well. This code works well. I did not find the confirmation, but it seems the user is reserved for the current user
Related
I'm taking over someone's code and I don't understand something about the voting.
Here is the PhotosController class:
class PhotosController extends Controller
{
/**
* #Route("/dashboard/photos/{id}/view", name="dashboard_photos_view")
* #Security("is_granted('view.photo', photo)")
* #param Photo $photo
* #param PhotoRepository $photoRepository
*/
public function index(Photo $photo, PhotoRepository $photoRepository)
{
$obj = $photoRepository->getFileObjectFromS3($photo);
header("Content-Type: {$obj['ContentType']}");
echo $obj['Body'];
exit;
}
Here is the voter class:
class PhotoVoter extends Voter
{
const VIEW = 'view.photo';
protected function supports($attribute, $subject)
{
if (!$subject instanceof Photo) {
return false;
}
if (!in_array($attribute, array(self::VIEW))) {
return false;
}
return true;
}
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
{
return $subject->getUser()->getId() === $token->getUser()->getId();
}
}
I don't understand what the
, photo
is for in the PhotosController class. And in PhpStorm I get "cannot find declaration" when I try to go to the "is_granted" declaration.
I code a simple app (Symfony 4.1.7) with a user and product system
A user can edit his product, but not another user's
My problem, I go on the edit route, it return access denied, even when it's my product
My ProductController :
/**
* #Route("seller/myproduct/{id}/edit", name="seller_edit_product")
* #param Product $product
* #return Response
* #Security("product.isAuthor(user)")
*/
public function edit(Product $product, Request $request): Response
{
$form = $this->createForm(ProductType::class, $product);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()){
$this->em->flush();
$this->addFlash('success', 'Modify Successfully');
return $this->redirectToRoute('seller_index_product');
}
return $this->render('seller/product/edit.html.twig', [
'product' => $product,
'form' => $form->createView()
]);
}
Product.php
/**
* #ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="product_id")
* #ORM\JoinColumn(nullable=false)
*/
private $user;
public function getUser(): User
{
return $this->user;
}
public function setUser(User $user): self
{
$this->user = $user;
return $this;
}
/**
* #return bool
*/
public function isAuthor(User $user = null)
{
return $user && $user->getProductId() === $this->getUser();
}
In my isAuhor function
== Access Denied
!== I can access the edition of product that Is not mine
User.php
/**
* #ORM\OneToMany(targetEntity="App\Entity\Product", mappedBy="user",orphanRemoval=true)
*/
private $product_id;
public function __construct()
{
$this->product_id = new ArrayCollection();
}
/**
* #return Collection|Product[]
*/
public function getProductId(): Collection
{
return $this->product_id;
}
public function addProductId(Product $productId): self
{
if (!$this->product_id->contains($productId)) {
$this->product_id[] = $productId;
$productId->setUser($this);
}
return $this;
}
}
Thank you
Your isAuthor function will always return false as you are comparing an ArrayCollection to a User
You could add a function in User Class definition that checks if a given user have a given product or no.
So in Product.php :
/**
* #return bool
*/
public function isAuthor(User $user = null)
{
return $user && $user->hasProduct($this);
}
And the hasProduction function could be something like this:
// this goes into User.php
/**
* #return bool
*/
public function hasProduct(Product $product)
{
return $this->product_id->contains($product)
}
It just my thought i just want to do like the same, I haven't try this because of i haven't any idea about this means how it will be done.
For Example:
I have - mysite.com/view?user_id=12 , I don't want to show url's parameters (?user_id=12) on url, On the place of this, I want to show like
mysite.com/view?_signature=encryptedparameter
(like : mysite.com/view?_signature=853ef7a0ff5aea6f24152b1d1ed4d771)
853ef7a0ff5aea6f24152b1d1ed4d771 will denotes user_id=12.
If i have multiple parameters it would be like the same and i will have a single encrypted string and i will get all parameters with that string.
Yes but i am using in Laravel 5.1
assume if i have "mysite.com/view?user_id=12"
And i want "mysite.com/view?_signature=encryptedparameter"
Please fallow some steps
laravel>packages>myfolder>core
+=>folder
-=>file
+packages
+Folder
+core
+src
+Faundation
-Application.php
+Routing
-RoutingServiceProvider.php
-UriGenerator.php
+Support
+Middleware
-UrlParamRevealerMiddleware.php
+ServiceProvider
-CoreServiceProvider.php
-UriParamProtector.php
<<IN APPLICATION.PHP>>
<?php
namespace Folder\Core\Foundation;
use Illuminate\Events\EventServiceProvider;
use Folder\Core\Routing\RoutingServiceProvider;
use Illuminate\Foundation\Application as BaseApplication;
use Folder\Core\Support\ServiceProvider\CoreServiceProvider;
class Application extends BaseApplication
{
/**
* Register all of the base service providers.
*
* #return void
*/
protected function registerBaseServiceProviders()
{
$this->register(new EventServiceProvider($this));
$this->register(new CoreServiceProvider($this));
$this->register(new RoutingServiceProvider($this));
}
}
<<//IN APPLICATION.PHP>>
<<-RoutingServiceProvider>>
<?php
namespace Folder\Core\Routing;
use Folder\Core\Routing\UrlGenerator;
use Illuminate\Routing\RoutingServiceProvider as BaseServiceProvider;
class RoutingServiceProvider extends BaseServiceProvider
{
/**
* Register the URL generator service.
*
* #return void
*/
protected function registerUrlGenerator()
{
$this->app['url'] = $this->app->share(function ($app) {
$routes = $app['router']->getRoutes();
// The URL generator needs the route collection that exists on the router.
// Keep in mind this is an object, so we're passing by references here
// and all the registered routes will be available to the generator.
$app->instance('routes', $routes);
$url = $this->getUrlGenerator($routes);
$url->setSessionResolver(function () {
return $this->app['session'];
});
// If the route collection is "rebound", for example, when the routes stay
// cached for the application, we will need to rebind the routes on the
// URL generator instance so it has the latest version of the routes.
$app->rebinding('routes', function ($app, $routes) {
$app['url']->setRoutes($routes);
});
return $url;
});
}
/**
* Get the url generator instance.
*
* #param \Illuminate\Routing\RouteCollection $routes
* #return \Folder\Core\Routing\UrlGenerator
*/
protected function getUrlGenerator($routes)
{
$url = new UrlGenerator(
$routes,
$this->app->rebinding(
'request',
$this->requestRebinder()
)
);
return $url;
}
}
<</-RoutingServiceProvider>>
<<UriGenerator.php>>
<?php
namespace Folder\Core\Routing;
use ErrorException;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Auth;
use Illuminate\Routing\UrlGenerator as BaseUrlGenerator;
class UrlGenerator extends BaseUrlGenerator
{
/**
* Get the URL to a named route.
*
* #param string $name
* #param mixed $parameters
* #param bool $absolute
* #return string
*
* #throws \InvalidArgumentException
*/
public function route($name, $parameters = [], $absolute = true)
{
$encryptedParameters = $parameters;
if (Auth::guest() === false) {
$encryptedParameters = $this->encrypt($name, $parameters);
}
return parent::route($name, $encryptedParameters, $absolute);
}
/**
* Get the cryptic engine.
*
* #return \Folder\Core\Support\UrlParamEncrypter
*
* #throws \ErrorException
*/
protected function getCrypt()
{
$app = App::getInstance();
if (isset($app['urlencryptor'])) {
return $app['urlencryptor'];
}
throw new ErrorException('URL Encryptor was not found.');
}
/**
* Get the protector engine.
*
* #return #return \Folder\Core\Support\UrlParamProtector
*
* #throws \ErrorException
*/
protected function getProtector()
{
$app = App::getInstance();
if (isset($app['urlprotector'])) {
return $app['urlprotector'];
}
throw new ErrorException('URL Protector was not found.');
}
/**
* Encrypts the parameter passed as querystring in URL.
*
* #param array $parameters
* #return array
*/
protected function encrypt($routeName, $parameters = [])
{
if (! is_array($parameters)) {
return $parameters;
}
if (count($parameters) === 0) {
return [];
}
if (Auth::guest() === true) {
return $parameters;
}
$protected = $this->getProtector()->protect($routeName, $parameters);
return ['__signature' => $protected];
}
}
<<//UriGenerator.php>>
<<UrlParamRevealerMiddleware.php>>
<?php
namespace Folder\Core\Support\Middleware;
use Closure;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Auth;
use Folder\Core\Support\UrlParamProtector;
class UrlParamRevealerMiddleware
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
(! Auth::guest()) && App::make(UrlParamProtector::class)->reveal($request);
return $next($request);
}
}
<<//UrlParamRevealerMiddleware.php>>
<<CoreServiceProvider.php>>
<?php
namespace Folder\Core\Support\ServiceProvider;
use Illuminate\Support\ServiceProvider;
use Folder\Core\Support\UrlParamProtector;
class CoreServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
$this->app->make('Illuminate\Contracts\Http\Kernel')
->pushMiddleware('Folder\Core\Support\Middleware\UrlParamRevealerMiddleware');
}
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
$this->registerUrlProtector();
}
/**
* Register UrlProtector class.
*/
protected function registerUrlProtector()
{
$this->app->singleton('urlprotector', function () {
return new UrlParamProtector();
});
}
}
<<//CoreServiceProvider.php>>
<<-UriParamProtector.php>>
<?php
namespace Folder\Core\Support;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Session;
class UrlParamProtector
{
/**
* Session key.
*
* #var string
*/
protected $sessionKey = '__url_protector__';
/**
* Request class.
*
* #var \Illuminate\Http\Request
*/
protected $request;
/**
* Values those needs to be merged in request object.
*
* #var array
*/
protected $valuesToBeMerged;
/**
* Create and returns VALID RFC 4211 COMPLIANT
* Universally Unique IDentifiers (UUID) version 4.
*
* #return string
*/
protected function getNewGuid()
{
if (function_exists('com_create_guid') === true) {
return trim(com_create_guid(), '{}');
}
return sprintf(
'%04X%04X-%04X-%04X-%04X-%04X%04X%04X',
mt_rand(0, 65535),
mt_rand(0, 65535),
mt_rand(0, 65535),
mt_rand(16384, 20479),
mt_rand(32768, 49151),
mt_rand(0, 65535),
mt_rand(0, 65535),
mt_rand(0, 65535)
);
}
/**
* Create the route key.
*
* #param string $routeName
* #param array $parameters
* #return string
*/
protected function getRouteKey($routeName, array $parameters = [])
{
if (count($parameters) <= 0) {
$paramToString = '';
} else {
$paramToString = implode('-', array_map(
function ($k, $v) {
return $k . '-' . $v;
},
array_keys($parameters),
array_values($parameters)
));
}
$routeKey = 'route__' . $routeName . (empty($paramToString) ? '' : '-' . $paramToString);
return $routeKey;
}
/**
* Returns a GUID for a URL parameter.
*
* #param string $routeName
* #param array $parameters
* #return string
*/
public function protect($routeName, array $parameters)
{
$routeKey = $this->getRouteKey($routeName, $parameters);
if (Session::has($this->sessionKey . '.' . $routeKey) === false) {
$guid = Str::lower($this->getNewGuid());
Session::set($this->sessionKey . '.' . $routeKey, [
'guid' => $guid,
'loggedin_user_id' => (Auth::guest() ? 0 : Auth::user()->id),
'params' => $parameters,
]);
} else {
$guid = Session::get($this->sessionKey . '.' . $routeKey . '.guid');
}
return $guid;
}
/**
* Check whether guid passed is a valid one or not.
*
* #param string $guid
* #return boolean
*/
protected function isValidGuid($guid)
{
foreach (Session::get($this->sessionKey) as $key => $value) {
if (!isset($value['guid'])) {
list($innerKey, $val) = each($value);
} else {
$val = $value;
}
if ($val['guid'] === $guid) {
$this->valuesToBeMerged = $val['params'];
return true;
}
}
return false;
}
/**
* Check whether guid passed is a valid one or not.
*
* #param string $guid
* #return boolean
*/
public function isValidGuidForPost($guid)
{
foreach (Session::get($this->sessionKey) as $key => $value) {
if ($value['guid'] === $guid && Auth::user()->id === $value['loggedin_user_id']) {
$this->valuesToBeMerged = $value['params'];
return true;
}
}
return false;
}
/**
* Merge the request with our revealed values.
*/
protected function mergeRequest()
{
$this->request->merge($this->valuesToBeMerged);
}
/**
* Check whether a "__signature" is correct or not.
*
* #param \Illuminate\Http\Request $request
* #return boolean
*/
public function reveal(Request &$request)
{
$this->request = $request;
$guid = ($this->request->query('__signature') ? : false);
if ($guid === false) {
return false;
}
if ($this->isValidGuid($guid) === false) {
App::abort(400);
}
$this->mergeRequest();
}
}
<<//-UriParamProtector.php>>
==============================================
after that in "bootstrap>App.php"
use this=>
$app = new Folder\Core\Foundation\Application(
realpath(__DIR__.'/../')
);
instad of =>
$app = new Illuminate\Foundation\Application(
realpath(__DIR__.'/../')
);
in any link use "data-href"
<a data-href="route('modify_lead_status',['user_id' => $users->id])" href="javascript:void(0)"><i class=\"fa fa-random\"></i></a>"
and after that any controller,Routing,middleware will be same.....
>>composer auto-dump
<<Please check spealing be sure use same>>
i hope this is help full....thanks
I have an OAuth API that requires an username and a password to get the user object (resource owner password credentials flow). I'm trying to get this end result :
User enters username/password
Symfony exchanges username/password for access and refresh tokens, then fetches the User object and populates a token with the fetched object
User is now authenticated on the website
The issue that I'm having is that I cannot seem to figure out how to do it the best way I can see : with an User provider. The UserProviderInterface asks to implement loadUserByUsername(), however I cannot do that, as I need the username AND the password to fetch the user object.
I tried to implement the SimplePreAuthenticatorInterface, but I still run into the same issue: after creating the PreAuthenticated token in createToken(), I need to authenticate it using authenticateToken(), and I still cannot fetch the user through the UserProvider, since I first have to use the username/password to get an access token that'd allow me to fetch the User object. I thought about adding a method to login in my UserProvider that'd login through the API using username/password and store the logged in tokens for any username in an array, and then fetch the tokens by username in that array, but that doesn't feel right.
Am I looking at it from the wrong angle ? Should I not be using PreAuthenticated tokens at all ?
A while ago i needed to implement a way to authenticate users through a webservice. This is what i end up doing based on this doc and the form login implementation of the symfony core.
First create a Token that represents the User authentication data present in the request:
use Symfony\Component\Security\Core\Authentication\Token\AbstractToken;
class WebserviceAuthToken extends AbstractToken
{
/**
* The password of the user.
*
* #var string
*/
private $password;
/**
* Authenticated Session ID.
*
* #var string
*/
private $authSessionID;
public function __construct($user, $password, array $roles = array())
{
parent::__construct($roles);
$this->setUser($user);
$this->password = $password;
parent::setAuthenticated(count($roles) > 0);
}
/**
* {#inheritDoc}
*/
public function getCredentials()
{
return '';
}
/**
* Returns the Authenticated Session ID.
*
* #return string
*/
public function getAuthSessionID()
{
return $this->authSessionID;
}
/**
* Sets the Authenticated Session ID.
*
* #param string $authSessionID
*/
public function setAuthSessionID($authSessionID)
{
$this->authSessionID = $authSessionID;
}
/**
* Returns the Password used to attempt login.
*
* #return string
*/
public function getPassword()
{
return $this->password;
}
/**
* {#inheritDoc}
*/
public function serialize()
{
return serialize(array(
$this->authSessionID,
parent::serialize()
));
}
/**
* {#inheritDoc}
*/
public function unserialize($serialized)
{
$data = unserialize($serialized);
list(
$this->authSessionID,
$parent,
) = $data;
parent::unserialize($parent);
}
}
The AuthSessionID that im storing is a token returned from the webservice that allows me to perform requests as an authenticated user.
Create a Webservice authentication listener which is responsible for fielding requests to the firewall and calling the authentication provider:
use RPanelBundle\Security\Authentication\Token\RPanelAuthToken;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Http\Firewall\AbstractAuthenticationListener;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface;
use Symfony\Component\Security\Http\HttpUtils;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class WebserviceAuthListener extends AbstractAuthenticationListener
{
private $csrfTokenManager;
/**
* {#inheritdoc}
*/
public function __construct(TokenStorageInterface $tokenStorage, AuthenticationManagerInterface $authenticationManager, SessionAuthenticationStrategyInterface $sessionStrategy, HttpUtils $httpUtils, $providerKey, AuthenticationSuccessHandlerInterface $successHandler, AuthenticationFailureHandlerInterface $failureHandler, array $options = array(), LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null, $csrfTokenManager = null)
{
if ($csrfTokenManager instanceof CsrfProviderInterface) {
$csrfTokenManager = new CsrfProviderAdapter($csrfTokenManager);
} elseif (null !== $csrfTokenManager && !$csrfTokenManager instanceof CsrfTokenManagerInterface) {
throw new InvalidArgumentException('The CSRF token manager should be an instance of CsrfProviderInterface or CsrfTokenManagerInterface.');
}
parent::__construct($tokenStorage, $authenticationManager, $sessionStrategy, $httpUtils, $providerKey, $successHandler, $failureHandler, array_merge(array(
'username_parameter' => '_username',
'password_parameter' => '_password',
'csrf_parameter' => '_csrf_token',
'intention' => 'authenticate',
'post_only' => true,
), $options), $logger, $dispatcher);
$this->csrfTokenManager = $csrfTokenManager;
}
/**
* {#inheritdoc}
*/
protected function requiresAuthentication(Request $request)
{
if ($this->options['post_only'] && !$request->isMethod('POST')) {
return false;
}
return parent::requiresAuthentication($request);
}
/**
* {#inheritdoc}
*/
protected function attemptAuthentication(Request $request)
{
if (null !== $this->csrfTokenManager) {
$csrfToken = $request->get($this->options['csrf_parameter'], null, true);
if (false === $this->csrfTokenManager->isTokenValid(new CsrfToken($this->options['intention'], $csrfToken))) {
throw new InvalidCsrfTokenException('Invalid CSRF token.');
}
}
if ($this->options['post_only']) {
$username = trim($request->request->get($this->options['username_parameter'], null, true));
$password = $request->request->get($this->options['password_parameter'], null, true);
} else {
$username = trim($request->get($this->options['username_parameter'], null, true));
$password = $request->get($this->options['password_parameter'], null, true);
}
$request->getSession()->set(Security::LAST_USERNAME, $username);
return $this->authenticationManager->authenticate(new WebserviceAuthToken($username, $password));
}
}
Create a Webservice login factory where we wook into the Security Component, and tell which is the User Provider and the available options:
class WebserviceFormLoginFactory extends FormLoginFactory
{
/**
* {#inheritDoc}
*/
public function getKey()
{
return 'webservice-form-login';
}
/**
* {#inheritDoc}
*/
protected function createAuthProvider(ContainerBuilder $container, $id, $config, $userProviderId)
{
$provider = 'app.security.authentication.provider.'.$id;
$container
->setDefinition($provider, new DefinitionDecorator('app.security.authentication.provider'))
->replaceArgument(1, new Reference($userProviderId))
->replaceArgument(2, $id);
return $provider;
}
/**
* {#inheritDoc}
*/
protected function getListenerId()
{
return 'app.security.authentication.listener';
}
}
Create an Authentication provider that will verify the validaty of the WebserviceAuthToken
class WebserviceAuthProvider implements AuthenticationProviderInterface
{
/**
* Service to handle DMApi account related calls.
*
* #var AccountRequest
*/
private $apiAccountRequest;
/**
* User provider service.
*
* #var UserProviderInterface
*/
private $userProvider;
/**
* Security provider key.
*
* #var string
*/
private $providerKey;
public function __construct(AccountRequest $apiAccountRequest, UserProviderInterface $userProvider, $providerKey)
{
$this->apiAccountRequest = $apiAccountRequest;
$this->userProvider = $userProvider;
$this->providerKey = $providerKey;
}
/**
* {#inheritdoc}
*/
public function authenticate(TokenInterface $token)
{
// Check if both username and password exist
if (!$username = $token->getUsername()) {
throw new AuthenticationException('Username is required to authenticate.');
}
if (!$password = $token->getPassword()) {
throw new AuthenticationException('Password is required to authenticate.');
}
// Authenticate the User against the webservice
$loginResult = $this->apiAccountRequest->login($username, $password);
if (!$loginResult) {
throw new BadCredentialsException();
}
try {
$user = $this->userProvider->loadUserByWebserviceResponse($loginResult);
// We dont need to store the user password
$authenticatedToken = new WebserviceAuthToken($user->getUsername(), "", $user->getRoles());
$authenticatedToken->setUser($user);
$authenticatedToken->setAuthSessionID($loginResult->getAuthSid());
$authenticatedToken->setAuthenticated(true);
return $authenticatedToken;
} catch (\Exception $e) {
throw $e;
}
}
/**
* {#inheritdoc}
*/
public function supports(TokenInterface $token)
{
return $token instanceof WebserviceAuthToken;
}
}
And finally create a User provider. In my case after i receive the response from the webservice, i check if the user is stored on redis, and if not i create it. After that the user is always loaded from redis.
class WebserviceUserProvider implements UserProviderInterface
{
/**
* Wrapper to Access the Redis.
*
* #var RedisDao
*/
private $redisDao;
public function __construct(RedisDao $redisDao)
{
$this->redisDao = $redisDao;
}
/**
* {#inheritdoc}
*/
public function loadUserByUsername($username)
{
// Get the UserId based on the username
$userId = $this->redisDao->getUserIdByUsername($username);
if (!$userId) {
throw new UsernameNotFoundException("Unable to find an UserId identified by Username = $username");
}
if (!$user = $this->redisDao->getUser($userId)) {
throw new UsernameNotFoundException("Unable to find an User identified by ID = $userId");
}
if (!$user instanceof User) {
throw new UnsupportedUserException();
}
return $user;
}
/**
* Loads an User based on the webservice response.
*
* #param \AppBundle\Service\Api\Account\LoginResult $loginResult
* #return User
*/
public function loadUserByWebserviceResponse(LoginResult $loginResult)
{
$userId = $loginResult->getUserId();
$username = $loginResult->getUsername();
// Checks if this user already exists, otherwise we need to create it
if (!$user = $this->redisDao->getUser($userId)) {
$user = new User($userId, $username);
if (!$this->redisDao->setUser($user) || !$this->redisDao->mapUsernameToId($username, $userId)) {
throw new \Exception("Couldnt create a new User for username = $username");
}
}
if (!$user instanceof User) {
throw new UsernameNotFoundException();
}
if (!$this->redisDao->setUser($user)) {
throw new \Exception("Couldnt Update Data for for username = $username");
}
return $this->loadUserByUsername($username);
}
/**
* {#inheritdoc}
*/
public function refreshUser(UserInterface $user)
{
if (!$user instanceof User) {
throw new UnsupportedUserException(
sprintf('Instances of "%s" are not supported.', get_class($user))
);
}
return $this->loadUserByUsername($user->getUsername());
}
/**
* {#inheritdoc}
*/
public function supportsClass($class)
{
return $class === 'AppBundle\Entities\User';
}
}
Required services :
app.security.user.provider:
class: AppBundle\Security\User\WebserviceUserProvider
arguments: ["#app.dao.redis"]
app.security.authentication.provider:
class: AppBundle\Security\Authentication\Provider\WebserviceAuthProvider
arguments: ["#api_caller", "", ""]
app.security.authentication.listener:
class: AppBundle\Security\Firewall\WebserviceAuthListener
abstract: true
parent: security.authentication.listener.abstract
Configured security:
security:
providers:
app_user_provider:
id: app.security.user.provider
firewalls:
default:
pattern: ^/
anonymous: ~
provider: app_user_provider
webservice_form_login: # Configure just like form_login from the Symfony core
If you have any question please let me know.
I call a service in a controller:
$this->getContainer()->get('pro_convocation')->sendDelegationEmails();
Here it is the method executed with the service:
function sendDelegationEmails()
{
foreach ($this->getRepository()->findWithPendingDelegationsEmail(1) as $convocation) {
if (! $convocation->delegationsEmailCanBeSent()) continue;
$this->sendDelegationsEmails($convocation);
$convocation->_setDelegationsEmailIsSent(); //<--- it is not saved
$this->save($convocation);
}
}
and the other used methods in the same class:
/**
* #return \Pro\ConvocationBundle\Entity\ConvocationRepository
*/
private function getRepository()
{
return $this->get('doctrine')->getRepository('ProConvocationBundle:Convocation');
}
function save(ConvocationEntity $convocation)
{
$this->getEntityManager()->persist($convocation);
$this->getEntityManager()->flush();
}
function sendDefinitiveMinute(ConvocationEntity $convocation)
{
foreach ($convocation->getCommunity()->getMembers() as $user) {
$this->get('pro.notifications')->send(Notification::create()
...
->setBody($this->get('templating')->render(
'ProConvocationBundle:Convocation:definitive-minute.html.twig',
array(
'convocation' => $convocation,
'convocationIsOrdinary' => $this->get('pro_convocation.ordinariness')->isOrdinary($convocation),
'user' => $user
)
))
);
}
}
EDIT: The send method:
function send(Notification $notification)
{
if (! $notification->getRecipient()) throw new \Exception("Recipient not set");
if (! $notification->getRecipient()->getEmail()) {
$this->logger->info(sprintf(
"Notification \"%s\" ignored as recipient %s has no email",
$notification->getSubject(), $notification->getRecipient()));
return;
}
// Ignore notifications to the own author
if ($notification->getAuthor() === $notification->getRecipient()) {
$this->logger->info(sprintf(
"Notification ignored as recipient is the author (%s)",
$notification->getRecipient()));
return;
}
$em = $this->doctrine->getManager();
$em->persist($notification);
$em->flush();
if ($this->notificationHasToBeMailed($notification)) {
$this->mail($notification);
$this->logger->info("Notification mailed to {$notification->getRecipient()}");
}
}
END EDIT
The _setDelegationsEmailIsSent method and delegationsEmailIsSent property in the Convocation entity:
/**
* #ORM\Column(type="boolean", name="delegations_email_is_sent")
* #Constraints\NotNull
*/
private $delegationsEmailIsSent = false;
/**
* #internal
* #see \Pro\ConvocationBundle\Convocation::sendDelegationsEmails()
*/
function _setDelegationsEmailIsSent()
{
$this->delegationsEmailIsSent = true;
}
The problem is that delegations_email_is_sent in the database is not changing to true when executing the sendDelegationEmails method in a controller. I've tried several changes without success. It seems to be related to flush method, but I don't find the issue.