Get the user in a RouteProviderInterface implementation - symfony

Is it possible to get the user in a RouteProviderInterface implementation?
My RouteProviderInterface implementation loads a number of new Routes, nothing special. But I want to customize the Routes based on a user setting, if a user is logged in.
If I inject the TokenStorage, the user is not loaded and null:
public function __construct(TokenStorage $tokenStorage) {
$this->user = $tokenStorage->getToken()->getUser(); // is null
}
Is there another way to get the user?
Some edit based on comments:
I am trying this with a authenticated user. I also dump the user in the actual controller being used and the user does exists there
All firewalls have "stateless: true" in the config

Related

Is there a better way to implement role based access in ASP.NET framework?

Basically I've spent the last few days trying to figure out how to add simple Admin and Member roles onto a website I'm developing for a friend. (I am using ASP.NET Framework 5.2.7.0). I know that Microsoft has a nice role based access feature built in which allows you to put something like [Authorize Role=("Admin") at the top of the controller; however I have not been able to get it to work at all and most of the resources I've found are for ASP.NET Core.
I've tried modifying my web.config file to enable the role based access (and hopefully migrate the roles and such to my database). But since I've been unable to figure any of this out, I've tried going a more hacky route. (**I am not an advanced programmer, I've been doing this for about a year now and am in no way a pro). This is what I've basically come up with in my attempt to verify if a user is an admin (which also didn't work).
[Authorize]
public class AdminController : Controller
{
private LDSXpressContext db = new LDSXpressContext();
public ActionResult AdminPortal()
{
IsAdmin();
return View();
}
private ActionResult IsAdmin()
{
string name = User.Identity.Name;
//The User.Identity.Name stores the user email when logged in
var currentUserObject = db.accounts.Where(x => x.clientEmail == name);
Account currentUser = new Account();
foreach (var user in currentUserObject)
{
//I loop through the results, even though only one user should
//be stored in the var CurrentUserObject because it's the only
//way I know how to assign it to an object and get its values.
currentUser = user;
}
if (currentUser.role == 2) //the number 2 indicates admin in my db
{
return null;
}
else
{
//Even when this is hit, it just goes back and returns the
//AdminPortal view
return RedirectToAction("Index", "Home");
}
}
}
Now I'm nearly positive that is is NOT a very secure way to check if a signed in user is an admin, but I was hoping that it would at least work. My idea was when someone attempted to access the AdminPortal, the IsAdmin method would run and check if the user is an admin in the database. If they are, then it returns null and the AdminPortal view is displayed, if they are not an Admin, then they are redirected to the Index view on the home page. However, the AdminPortal page is always displayed to any user and this doesn't seem to work either. I've even stepped into the code and watched it run over the return RedirectToAction("Index", "Home"); action, but then it jumps back to the AdminPortal method and just returns the AdminPortal view. So my question is:
1) If anyone happens to have experience with Role Based access in ASP.NET Framework, I would love some tips on how to get it set up
or,
2) If all else fails and I need to use my hacky method, why does it continue to return the AdminView even when the user is not an admin.
**Note: I know I could create a function that returns true or false if the user is an Admin or not, and then have an if/else statement in the AdminPortal controller that will return on view for true and another for false, however I don't want to have to implement that onto every ActionMethod, it'd be nice to keep it down to one line, or just the [Authorize Role="Admin] above the controller if possible.
Thank you guys so much for any help provided, I've been trying to research and fix this for days now and decided to reach out and ask the community!
At a minimum, you'll want to make some adjustments to what you're doing:
[Authorize]
public class AdminController : Controller
{
public ActionResult AdminPortal()
{
if(IsAdmin())
{
return View();
}
return RedirectToAction("Index", "Home");
}
private bool IsAdmin()
{
bool isAdmin = false;
using(LDSXpressContext db = new LDSXpressContext())
{
string name = User.Identity.Name;
//The User.Identity.Name stores the user email when logged in
// #see https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.singleordefault
var currentUser = db.accounts.SingleOrDefault(x => x.clientEmail.Equals(name, StringComparison.OrdinalIgnoreCase));
// If the email doesn't match a user, currentUser will be null
if (currentUser != null)
{
//the number 2 indicates admin in my db
isAdmin = currentUser.role == 2;
}
}
return isAdmin;
}
}
First off, DbContext instances are meant to used, at most, per the lifetime of an HTTP request. Moving it from the class / controller level and placing it within a using block makes sure that it's properly disposed.
Next, your IsAdmin function really just needs to return a true/false value based on your lookup, and then the AdminPortal action can decide what to do with that result.
Since email seems to be a unique field in your table, use the SingleOrDefault or FirstOrDefault LINQ extension to fetch a single matching record. Which one you use is up to you, but if it's truly a unique value, SingleOrDefault makes more sense (it will throw an exception if more than one row matches). Using the StringComparison flag with the String.Equals extension method makes your search case-insensitive. There are a few culture-specific versions of that, but ordinal matching is what I would normally use, here.
Implementing some version of the Identity framework is a bit too long for an answer here, but it's possible to implement a claims-based authentication scheme without too much work. That's something that probably needs a separate answer, though.

Handle multi level of roles

I have a multi applications which works with authenticated users.
All these applications can be deployed together for differents clients but with the same user database.
For example a chain of hotels.
User's roles informations are available in a header in each request.
For example a manager has full access in his own hotel but only read access in another hotel.
Ex:
{
["organization":"paris","roles":[ADMIN,ROOT]],
["organization":"london","roles":[READ]]
}
How can I handle many levels of roles by organizations?
I read some documentation in the symfony about voters and roles but nothing about roles in a kind of groups.
Voter is the way to go
// src/Security/PostVoter.php
namespace App\Security;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
class OrganisationVoter extends Voter
{
// these strings are just invented: you can use anything
const READ= 'READ';
const EDIT = 'EDIT ';
protected function supports($attribute, $subject); bool //todo
protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
{
// [...] check class like documentation
$organisation= $subject;
switch ($attribute) {
case self::READ:
return $this->canView($organisation, $user);
case self::EDIT:
return $this->canEdit($organisation, $user);
}
}
private function canView(Organisation $organisation, User $user)
{
//here your logic if your user has the same organisation
}
private function canEdit(Organisation $organisation, User $user)
{
//here your logic if your user has the same organisation than the one in parameter and the good level of right (admin, root)
}
}
Then in your controller (or twig or wherever)
if ($this->security->isGranted(OrganisationVoter::EDIT, $organisation)) {
return true;
}
What you describe has "attribute-based access control" written all over. abac helps you externalize / decouple authorization from the application / API you want to protect. This means you can develop functionality independently of the authorization logic.
There are a couple of standards out there - namely XACML and ALFA (abbreviated language for authorization).
This is what the architecture looks like:
the Policy Enforcement Point (PEP) intercepts the business flow and create an authorization request which it sends to the PDP
The Policy Decision Point (PDP) evaluates the incoming request against the policies it's been configured with. It eventually returns a decision to the PEP
The PDP may use Policy Information Points (PIP) to retrieve missing metadata (a user's department, role, location; a resource's department, owner...)
The previous answer forces you to implement a voter. That's very brittle and will require coding and updating it regularly as your requirements change. In ALFA, you don't need to do that. You simply write policies in plain old English that use the attributes you are interested in. For instance:
A user with role == "manager" can do action == "view" on object of type == "hotel"
A user with role == "manager" can do action == "edit" on object of type == "hotel" if hotel.owner == user.name

How to dynamically change user roles in Symfony2

I need to change user role on the fly based on specific data stored in the session. So on each request the authorization systems can check if the user can access some resource. I don't want store roles in a database. The role would be data dependent.
For example, a user that is admin of a real state company can manage his agents and properties, but an agent can manage only properties assigned to him. Also a buyer can view data of properties that he has purchased.
Take a look at This blog post, it shows how you can add user dependent roles. You can modify the part where he actually sets the role name.
I would make a few modifications to this method though
class User implements UserInterface
{
public function getRoles()
{
return new UserDependentRole($this);
}
}
And in the UserDependentRole class, have this instead:
public function getRole()
{
$roles[] = 'ROLE_' . strtoupper($this->user->getUsername());
//... make an array of all the roles you want the user to have
return $roles;
}
I'm sure if you read the article you will figure out how to do it.

Symfony/DRY - check if user is granted in every action

I'm using this code to check if a user is granted in my Symfony application :
$securityContext = $this->container->get('security.context');
if($securityContext->isGranted('IS_AUTHENTICATED_REMEMBERED') ){
$user = $this->get('security.context')->getToken()->getUser()->getId();
} else {
return $this->render('IelCategoryBundle:Category:home.html.twig');
}
I have to ckeck this in almost every CRUD action that I'm writing (edit, delete, ...).
I feel not DRY at all (no play on words ;-)). Is there a better way to check this in many actions ?
JMSSecurityExtraBundle provides the #Secure annotation which eases checking for a certain user-role before invoking a controller/service method.
use JMS\SecurityExtraBundle\Annotation as SecurityExtra;
/** #SecurityExtra\Secure(roles="IS_AUTHENTICATED_REMEMBERED") */
public function yourAction()
{
// ...
}
Your best bet would be to rely on Kernel event listeners: http://symfony.com/doc/current/cookbook/service_container/event_listener.html.
Implement your listener as a service and then you could set $this->user to desired value if isGranted results TRUE. Later on, you could easily retrieve the value within the controller using:
$myServiceListener->getUser();
On the other hand, if check fails you could easily redirect user to your home.html.twig.

How do you disable a User When Using ASP Forms Persistent Cookie/Ticket?

I'm using the Forms Auth and ASP Universal Membership Provider in an MVC 3 Site. We're persisting the cookie for user convenience.
FormsAuthentication.SetAuthCookie(model.UserName, true)
When we disable a user in the Membership provider like this:
memUser.IsApproved = model.IsActive;
provider.UpdateUser(memUser);
if they have the cookie they can still get in to the site. This is similar to what is described in this post:http://stackoverflow.com/questions/5825273/how-do-you-cancel-someones-persistent-cookie-if-their-membership-is-no-longer-v
We use Authorize attributes on our controllers, and I know that that is technically more Authorize than Authentication. But the certainly overloap so I'm trying to figure out what is the best MVC way to do a check that the user is not actually disabled? Custom AuthorizeAttribute that checks the user against the membership database? An obvious setting/method I'm missing with Forms auth to invalidate the ticket?
Update:
Here’s basically what I'm going with – we use a custom permission denied page which we we use to better inform user that they don’t have rights vs. they’re not logged in. And I added the IsApproved check. AuthorizeCore gets called when you put the attribute on a Controller or Action and if it returns false HandleUnauthorizedRequest is called.
public class CustomAuthorization : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (!filterContext.HttpContext.User.Identity.IsAuthenticated || !Membership.GetUser(filterContext.HttpContext.User.Identity.Name).IsApproved)
{
filterContext.Result = new HttpUnauthorizedResult();
// in the case that the user was authenticated, meaning he has a ticket,
// but is no longer approved we need to sign him out
FormsAuthentication.SignOut();
}
else
{
var permDeniedRouteVals = new System.Web.Routing.RouteValueDictionary() { { "controller", "MyErrorController" }, { "action", "MyPermissionDeniedAction" }, { "area", null } };
filterContext.Result = new RedirectToRouteResult(permDeniedRouteVals);
}
}
protected override bool AuthorizeCore(System.Web.HttpContextBase httpContext)
{
// since persisting ticket,
// adding this check for the case that the user is not active in the database any more
return base.AuthorizeCore(httpContext) && Membership.GetUser(httpContext.User.Identity.Name).IsApproved;
}
}
Usage:
[CustomAuthorization()]
public class MyController
Well, you're going to have to check the database regardless, the only question is how you want to do that. Yes, you could create a custom authorize attribute, or you could write some code for the OnAuthorize override in ControllerBase, or you could do it in Application_AuthenticateRequest.. lots of ways you could do it, depends on what works best for you.
The best way, of course, would be to not use a persistent ticket if this is an issue for you.
I pretty much always use Roles and a RolesProvider, even if there is just one role named "Login" - in part for this issue. This way, your Authorize attributes might look something like this:
[Authorize(Roles="Login")]
Where Login represents a basic 'Role' that all "active" accounts must have to be able to log in at all; Every protected action is protected by this, at minimum.
That way, simply removing the "Login" role effectively disables the user... because, in my Roles Provider, I am checking the logged-in user's roles against the database or server-local equivalent.
In your case, your "Login" role could simply resolve to a check on the IsApproved field on your user model.

Resources