Get security token for non-logged user with Symfony - php

How can I get a security token for any user, not only the one currently logged in ?
I would like to be able to call isGranted() on a user fetched from the database

isGranted() comes from the Security service, so it would be hard/unnecessary to use that to get Roles without adjusting the state of the session.
Don't get me wrong, it's definitely possible... This would work, for example:
public function strangeAction()
{
// Get your User, however you normally get it
$user = $userRepository->find($id);
// Save the current token so you can put it back later
$previousToken = $this->get("security.context")->getToken();
// Create a new token
$token = new UsernamePasswordToken($user, null, "main", $user->getRoles());
// Update the security context with the new token
$this->get("security.context")->setToken($token);
// Now you have access to isGranted()
if ($this->get("security.context")->isGranted("ROLE_SOMETHING"))
{ /* Do something here */ }
// Don't forget to reset the token!
$this->get("security.context")->setToken($previousToken);
}
...but that really makes no sense.
In reality, you don't need the token. A much better way of doing this would be to add an isGranted() method into your User entity:
// Namespace\YourBundle\Entity\User.php
class User
{
...
public function isGranted($role)
{
return in_array($role, $this->getRoles());
}
...
}
Now you can get those roles in your controllers:
public function notSoStrangeAction()
{
// Get your User, however you normally get it
$user = $userRepository->find($id);
// Find out if that User has a Role associated to it
if ($user->isGranted("ROLE_SOMETHING"))
{ /* Do something here */ }
}

I had the same requirements a while ago. So I implemented it myself. Since you require the hierarchy information from the container it is not possible advised to extend the user entity with this functionality though.
// first check if the role is inside the user roles of the user
// if not then check for each user role if it is a master role of the check role
public function isGranted($user, $checkrole){
$userroles = $user->getRoles();
if (in_array($checkrole, $userroles)){return true;}
foreach ($userroles as $userrole){
if ($this->roleOwnsRole($userrole, $checkrole)){return true;}
}
return false;
}
// recursively loop over the subroles of the master to check if any of them are
// the suggested slave role. If yes then the masterrole is a master and has
// the same grants as the slave.
private function roleOwnsRole($masterRole, $slaveRole, $checkvalidityroles=true, $hierarchy=null)
{
if ($hierarchy===null){$hierarchy = $this->container->getParameter('security.role_hierarchy.roles');}
if ($masterRole === $slaveRole){ return false; }
if($checkvalidityroles && (!array_key_exists($masterRole, $hierarchy) || !array_key_exists($slaveRole, $hierarchy))){ return false; }
$masterroles = $hierarchy[$masterRole];
if(in_array($slaveRole, $masterroles)){
return true;
}else{
foreach($masterroles as $masterrolerec){
if ($this->roleOwnsRole($masterrolerec, $slaveRole, false, $hierarchy)){return true;}
}
return false;
}
}

I think the best way is to call AccessDecisionManager manually - like $securityContext->isGranted() does as well but for the currently logged in user. This is good too if you are using Symfony Voters to determine access.
$token = new UsernamePasswordToken($userObject, 'none', 'main', $userObject->getRoles());
$hasAccess = $this->get('security.access.decision_manager')->decide($token, array('voter'), $optionalObjectToCheckAccessTo);

Related

Call to a member function getId() on string after login

I am accessing a part of the site that requires the user to be logged in. But the system, instead of going to the login, and then to the site I want to access, shows me the error indicating that I am not logged in.
The controller code is as follows:
/**
* #Route("/estrategia/titulares", name="estrategia_titulares")
*/
public function estrategiaTitularesAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$logged = $this->get('security.token_storage')->getToken()->getUser();
$user = $em->getRepository('AppBundle:User')->find($logged->getId());
if(!$user)
{
$messageException = $this->get('translator')->trans('No se ha encontrado el usuario');
throw $this->createNotFoundException($messageException);
}
The error that appears is in the line of $user = $em->getRepository
Call to a member function getId() on string
How can I do so that the system first asks me to login, and then does redirect me to the page I want to access?
Since you are trying to detect if the user is logged in or not, it is entirely possible that you do not get a user entity at all.
If a user is not logged in, you won't get a "user object" from the token storage service, and you'll get the error you are getting when trying to access its properties.
Additionally, your user has already been refreshed from the DB by that point (if correctly authenticated), so getting it from Doctrine is redundant and you can avoid it.
The correct way of doing this, assuming that your user class implements Symfony\Component\Security\Core\User\UserInterface
$user = $this->get('security.token_storage')->getToken()->getUser();
if (!$user instanceof UserInterface) {
// failure mode. Throw exception, return RedirectResponse, or what you prefer.
// We are dealing with an anonymous, non-authenticated user
}
The token (getToken()), could be null as well, so that's something you may want to check.
But, considering your are in a controller, and that the route handled by the controller should be sitting behind one of the firewalls... normally it wouldn't be ever null.
If it were null, something else is amiss in your setup and you better catch it pronto.
Above answer is fine except that you have to verify if the getToken() method returns a valid token or NULL. Here the better version:
$user = NULL;
$token = $this->get('security.token_storage')->getToken();
if(NULL !== $token) {
$user = $token->getUser();
}
if($user instanceof UserInterface) {
// logged in user
}

Laravel User Policy Is Returning The Authenticated User's Information And Not The External User

I created a Laravel policy called "UserPolicy" which is supposed to filter out users that do not have the permission to edit, delete, and update other users. The problem is that I am trying to pass an external user's information to the policy. But instead, it just returns the authenticated users information.
My Policy:
public function edit(?User $user)
{
if(auth()->check()) {
dd($user);
$userpower = auth()->user()->roles()->min('power');
if($userpower <= $user->power) {
return true;
} else {
return false;
}
} else {
return false;
}
}
My Controller:
public function edit(User $user)
{
$this->authorize('edit', $user);
$roles = Role::all();
$user = User::where('steamid', $user->steamid)->with('roles')->first();
return view('manage.includes.users.edit')->with(compact('user', 'roles'));
}
For example, I am the user Bob. I am trying to edit the user, John. As a test, I included the dd() function to dump the $user information that is passing into the Policy. After seeing the results, instead of John's information being passed, it is Bob's. How can I make it where it is John's information and not Bob's.
Thank you for your help, if you need more information please let me know.
The first parameter is the authenticated user. The second parameter is the resource. Try defining your policy as:
/**
* Can a guest user, or an authenticated user (let's call this first user Bob)
* edit another user (let's call that second user John) ?
*/
public function edit(?User $bob, User $john)
{
//
}

Laravel logging System User fails

Im trying to log in a system user without sessions or cookies.
The following code in my middelware works unless the user id is from a system user:
$interface_token = $request->header('token');
if ($interface_token !== 'my_secret') {
return response('Unauthorized', 401);
}
$user_id = 1; // Just for testing. Here I want to login a system user.
$success = Auth::onceUsingId($user_id);
if (!$success)
{
throw new Exception('failed!');
}
I want to log in a system user since this specific routes are going to be call from an internal service not from a "real" user.
If I update the table users setting system_user = 0 of user id 1, it works.
If system_user = 1 then it doesn't authenticate.
This system_user column is just a tinyint added to the user's table that shouldn't affect but apparently it does.
The user model is using the SystemUserTrait.
Any ideas?
I'm using Laravel 5.4
Edit
The SystemUserTrait is adding a global scope which does the following:
public function apply(Builder $builder, Model $model)
{
$builder->where('system_user', '=', 0);
}
So that's why it was not working with the user system. But now the question is if is possible to disable this to authenticate the user.
I tried the following without success:
$user = User::withoutGlobalScope(SystemUser::class)->find(1);
$success = Auth::login($user);
The user is fetched but the login fails anyway.
Is there a way to avoid using the global scope for a function?
The solution was to fetch the user with User::withoutGlobalScope and then to use Auth::setUser function to avoid queries which used the scope.
$interface_token = $request->header('token');
if ($interface_token !== 'my_secret') {
return response('Unauthorized', 401);
}
$user = User::withoutGlobalScope(SystemUser::class)->find(1);
Auth::setUser($user);

Add Admin role in laravel 5.4

I have a database that in this: Admin has True isAdmin property, but other users have false isAdmin property.
I want to check if the user who logged in is an Admin or not by redirecting them to different pages in my app. My code in Controller is:
public function store(User $user)
{
if (auth()->attempt(request(['email', 'password']))) {
if ($user->isAdmin == 1) {
return redirect('/ShowUser');
}
{
return redirect('/lo');
}
}
return back()->withErrors(
[
'message' => 'Error'
]
);
}
But this code doesn't work; it sends the users to '/lo' all the time. How can I fix it?
You're missing an else keyword.
Right here:
if ($user->isAdmin == 1) {
return redirect('/ShowUser');
}
{ // <-- right here
return redirect('/lo');
}
add the else keyword.
if ($user->isAdmin == 1) {
return redirect('/ShowUser');
}
else { // <-- right here
return redirect('/lo');
}
anyway, your code will still run fine even after the edit above. But I have questions for you:
Is the user assumed to be in the database already?
What is the default value of isAdmin in the database?
Are you passing the isAdmin attribute as an input from a form or something?
And why is it a store request when you're just trying to log a user in?
It's a bit confusing. I can tell from your code that you're trying to log a user in, but you're doing it in a store method (nothing wrong with that, just convention), the store method is usually used in storing data (how coincidental!)

How to validate a parameter for a class within a method and prevent use if invalid?

I am building a small class to handle api requests and I am running into an issue with error handling (I am also a novice at OOP so please with me) I need to restrict or throw an error with any methods in my class that require the user parameter to be set and I also need to samething if token has not been retreived and I can't seem to wrap my head around it.
This is what I have so far...
$user array is set in a config file outside of class like so (empty by default):
$user = array(
'user_email' = '',
'user_pass' = ''
);
Class for handling API (simplified for question)
class eventAPI {
private $user
private $token
public function __construct($user) {
$this->user = $user;
// if possible assign token when instantiated
$this->retrieve_token($user);
}
private function retreive_token($user) {
// Check if user parameter has been set
if($this->validate_user_parameter()) {
// use credentials to make HTTP request for token
$token = 'somerandomtoken';
// assign token property retreived value
$this->token = $token;
} else {
echo 'User parameter has not been set.' // Not real message just for testing
return FALSE;
}
}
public function show_all_events() {
// Check if token has been retreived
if($this->validate_token_retreived()) {
// Use token to retreive events list via HTTP request
} else {
echo 'API not active. No valid token detected'; // for testing purposes
return FALSE
}
}
// reduntant code... Can't wrap my head around another way for checking for token.
public function show_single_event() {
// Check if token has been retreived
if($this->validate_token_retreived()) {
// Use token to retreive events list via HTTP request
} else {
echo 'API not active. No valid token detected'; // for testing purposes
return FALSE
}
}
// This is mostly where I am confused how to solve.
private function validate_user_parameter() {
foreach($this->user as $key => $value) {
// Remove whitespace from value
$value = trim($value);
if(empty($value)) {
echo 'User credentials have not been set'; // for testing purposes
return FALSE;
}
}
}
private function validate_token_retreived() {
$result = FALSE;
// Bool value not sure if this is the best way to do this
if(isset($this->$token)) {
$result = TRUE;
}
return $result;
}
}
First issue: I need to validate the user parameter which is in an array so I can use with a private method to retrieve the token. I chose to use a foreach loop to check each value but it seems a little archaic.
Second Issue: I have a redundant check in place for each public method to check if token is valid. Is there a better way to do this with OOP? I have many methods that require the token.
In short, how can I make sure that once the class is instantiated a public method that will be used by end user will not fire if any validation fails. The user info only needs to be valid once when instantiated and then the token needs to be valid for most remaining methods.
You don't need to pass $user parameter to retreive_token function. You got it in class scope. Just use $this->user in the function to access it. Also you didn't use it in that function, so why u passed it?
You didn't send true in any function.
There's nothing wrong with for-each but you want to check array_map too. Atleast you're applying a function to every item in array. It can be usefull. ps: seems for-each still faster then array_map
Also you would want to check empty function on which cases it returns false
You can use multiple returns in a function. You dont need to set a variable to do that.
Example
private function validate_token_retreived()
{
if(isset($this->token))
return true;
return false;
}
You couldn't use else in most cases.
Example
public function show_all_events()
{
if($this->validate_token_retreived()) {
// Use token to retreive events list via HTTP request
// and return here
}
echo 'API not active. No valid token detected'; // for testing purposes
return FALSE; // do you really return here? seems you are just generating an output
}

Categories