Laravel - getting data of the user from database by specific criterion - laravel-5.3

I have a users table which stores the names of my users and applications table which stores all applications made by the user...the user makes application for transport and the application is printed on the admins page so that the admin may know which user made the application.
How do I retrieve the name of the user who applied? How do I get all users that applied for a van?
One user has many admins as follows:
user model:
public function admin(){
return $this->hasMany('Martin\Models\admin');
}
admin model:
public function application()
{
return hasMany('Martin\Models\Application');
}
application table schema:
Schema::create('applications', function (Blueprint $table)
{
$table->increments('id');
$table->string('user_id')->nullable();
$table->string('destination');
$table->date('departure_date');
$table->integer('Number_of_days_hired');
$table->timestamp('departure_time')->nullable();
$table->boolean('approved')->default(0);
$table->timestamps();
});

$applications = Application::all(); //get all applications. Application is your Eloquent model of the Applications table
$userIds = $applications->pluck('user_id'); // get ids from all the users that applied
$names = User::whereIn($userIds)->pluck('name'); // name is equal to the name of the column in database which stores name of the User (you didn't specify that in your question)
foreach($names as $name) {
// do something with name
}
UPDATE
there are 2 ways to find the users who applied for specific thing:
1. blade file:
#foreach($users as $user)
#if($user->applied_for_van) // put your criteria for van application here
<td>{{$user->name}}</td>
#endif
#endforeach
2.1 file where you fetch all users:
$users_who_applied_for_van = [];
foreach($users as $user) {
if($user->applied_for_van) {
$users_who_applied_for_van[] = $user; // this equal to array_push. add user to the array basically
}
}
2.2 blade file:
#foreach($users_who_applied_for_van as $user)
<td>{{$user->name}}</td>
#endforeach

Related

Symfony Security - Checking User Role in constructor of service

I need to check if a user has admin rights in a service. The function that is being called in this service might be called a whole bunch of times for a single request. One could check for the role of the user once and save the result like this:
class myService
{
private $accessGranted;
public function __construct(Security $security)
{
// user might not be set up yet?
$accessGranted = $security->isGranted('ROLE_ADMIN');
}
public function someFunctionWithSecurity()
{
if( $accessGranted )
// do the admin stuff here
else
// do slightly different stuff here
}
}
This seems to work just fine when I test it locally.
I was wondering if there is anything wrong with this setup, or if this will lead to strange/unwanted results.

get users who have a specific role

I need to get the list of all my users having a specific role, is there any way to do it easily? The solution I figured out for now would be to retrive all users then apply a filter on each using is granted function (which is hardcore)
PS: I don't like using the db request that skims over data and if the user role equals the wanted role it returns it, else it doesn't. Which means that we don't take into account users with super roles.
Because of the role hierarchy, I don't see a way to avoid grabbing all the users and then filtering. You could make a user role table and add all possible user roles but that would get out of date if you changed the hierarchy.
However, once you have all the roles for a given user then you can test if a specific one is supported.
There is a role hierarchy object to help.
use Symfony\Component\Security\Core\Role\Role;
use Symfony\Component\Security\Core\Role\RoleHierarchy;
class RoleChecker
{
protected $roleHeirarchy;
public function __construct(RoleHierarchy $roleHierarchy)
{
$this->roleHierarchy = $roleHierarchy; // serviceId = security.role_hierarchy
}
protected function hasRole($roles,$targetRole)
{
$reachableRoles = $this->roleHierarchy->getReachableRoles($roles);
foreach($reachableRoles as $role)
{
if ($role->getRole() == $targetRole) return true;
}
return false;
}
}
# services.yml
# You need to alias the security.role_hierarchy service
cerad_core__role_hierarchy:
alias: security.role_hierarchy
You need to pass an array of role objects to hasRole. This is basically the same code that the security context object uses. I could not find another Symfony service just for this.
The is also a parameter value called '%security.role_hierarchy.roles%' that comes in handy at times as well.
Symfony 5 answer, it's a little bit easier:
namespace App\Controller;
...
use Symfony\Component\Security\Core\Role\RoleHierarchyInterface;
use Symfony\Component\Security\Core\Role\RoleHierarchy;
class UserController extends AbstractController
{
private $roleHierarchy;
/**
* #Route("/users", name="users")
*/
public function usersIndex(RoleHierarchyInterface $roleHierarchy)
{
$this->roleHierarchy = $roleHierarchy;
// your user service or your Doctrine code here
$users = ...
foreach ($users as $user) {
$roles = $roleHierarchy->getReachableRoleNames($user->getRoles());
\dump($roles);
if ($this->isGranted($user, 'ROLE_SUPER_ADMIN')) {
...
}
}
...
}
private function isGranted(User $user, string $role): bool
{
$reachableRoles = $this->roleHierarchy->getReachableRoleNames($user->getRoles());
foreach ($reachableRoles as $reachableRole) {
if ($reachableRole === $role) {
return true;
}
}
return false;
}
}
Note: I put everything in the controller for the sake of simplicity here, but of course I'd recommend to move the Role Management code into a separate service.

prevent some user from viewing some pages

I have a login form and after login I display some links:
I want to prevent non-admin users to click or forward to a specific page.
I don't want to use the symfony2 ROLES cause it is too complicated.
Is there something easier ?
Depending on how do you make the difference between admin and non-admin users in your User entity. If it's only a boolean flag (let's say admin attribute) :
User.php
private $admin;
// your attributes
public function isAdmin()
{
return $this->admin;
}
public function setAdmin($boolean)
{
$this->admin = $boolean;
}
// getters/setters
FooController.php
public function showAdminPanelAction()
{
if(!$this->getUser()->isAdmin()) {
throw new AccessDeniedHttpException('Forbidden Access');
}
else
{
// do your stuff
}
}
BUT Symfony2 roles are making things easier if you have more than 2 two different roles, a hierarchy, lots of users, etc...

Using object Helper Methods to implement authorization rules

I have the following:-
I am working on an asset management system using Asp.net MVC4 with windows authentication enabled.
The system allow to specify what actions a group of users can do(for example certain group can have the authority to add new physical asset , while they can only read certain logical asset, and so on).
So I found that using the build-in Asp.net role management, will not allow me to have the level of flexibility I want. So I decided to do the following:-
I have created a table named “group” representing the user groups. Where users are stored in active directory.
I have created a table named ”Security Role” which indicate what are the permission levels each group have on each asset type(edit, add, delete or view)per asset type.
Then on each action methods , I will use Helper methods to implement and check if certain users are within the related group that have the required permission ,, something such as
On the Car model object I will create a new helper method
Public bool HaveReadPermison(string userName) {
//check if this user is within a group than have Read permission on CARS, //OR is within a GROUP THAT HAVE HIGHER PERMISON SUCH AS EDIT OR ADD OR //DELETE.
}
Next, On the Action method, I will check if the user has the Read permission or not by calling the action method:-
public ActionResult ViewDetails(int id) { // to view transportation asset type
Car car = repository.GetCar(id);
if (!car.HaveReadPermision(User.Identity.Name)) {
if (car == null)
return View("NotFound");
else
return View(car);
}
else
return view (“Not Authorized”);
So can anyone advice if my approach will be valid or it will cause problem I am unaware about.
Regards
In my opinion, once you have decided to use the ASP membership and role providers you can keep leveraging them also for authorization, simply using the Authorize attribute. This will also allow to restrict access by user names and roles.
What the attribute won't do is Action-based authorization. In that case there are a few options but in my opinion this could be brilliantly resolved by a Custom Action Filter based loosely on the following code:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class CheckUserPermissionsAttribute : ActionFilterAttribute
{
public string Model { get; set; }
public string Action { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var user = filterContext.HttpContext.User.Identity.Name; // or get from DB
if (!Can(user, Action, Model)) // implement this method based on your tables and logic
{
filterContext.Result = new HttpUnauthorizedResult("You cannot access this page");
}
base.OnActionExecuting(filterContext);
}
}
Yes, it is vaguely inspired to CanCan, which is a nice Ruby gem for this kind of things.
Returning Unauthorized (401) will also instruct your server to redirect to the login page if one is specified. You may want to work on that logic if you want to redirect somewhere else. In that case you should do:
filterContext.Result = new RedirectToRouteResult(new System.Web.Routing.RouteValueDictionary { { "Controller", "Home" }, { "Action", "Index" } });
and choose the appropriate controller/action pair.
You can use the attribute like this:
[CheckUserPermissions(Action = "edit", Model = "car")]
public ActionResult Edit(int id = 0)
{
//..
}
Let me know if that works nicely for you.
The approach you took looks reasonable, but I would add few changes:
What if you forgot to call HaveReadPermision method? And checking authotization from Actions is not the cleanest solution either, that is not an Action reponsibility.
It is better to keep authorization logic separately. For instance you can create a decorator over you repository which will check the permissions of the current User:
public class AuthorizationDecorator: IRepository
{
public AuthorizationDecorator(IRepository realRepository, IUserProvider userProvider)
{
this.realRepository = realRepository;
this.userProvider = userProvider;
}
public Car GetCar(int id)
{
if(this.UserHaveReadPermission(this.userProvider.GetUserName(), Id))
{
return this.realRepository.GetCar(id);
}
else
{
throw new UserIsNotAuthorizedException();
}
}
private bool UserHaveReadPermission(string username, int id)
{
//do your authorization logic here
}
}
IUserProvider will return curent user name from httpRequest.
After doing the change you don't need to warry about authorization when writing Actions

Symfony2: creating a new SecurityIdentity

I'm using ACL in Symfony 2.1, and I need to create a new SecurityIdentity, so that my ACL can be set in function of some sort of groups.
Picture the following situation: there are groups with users (with different roles) that each have user information. In group 1, users with the ROLE_ADMIN can't edit other users from the same group's information, but in group 2, users with ROLE_ADMIN can edit others information.
So basically my ACL will vary in function of what group the user is in.
I thought I'd start solving this problem with the creation of a new "GroupSecurityIdentity". However the class itself doesn't suffice, as I get this exception when I use it:
$sid must either be an instance of UserSecurityIdentity, or RoleSecurityIdentity.
My question is: how do I "register" my new SecurityIdentity so I can use it as RoleSecurityIdentity and UserSecurityIdentity?
What better ways are there to implement a system similar to this I want to do?
2 years ago I went down that path, it turned out to be a bad decision. Modifying the ACL system is difficult and might cause problems when updating Symfony. There are at least 2 better solutions. I'll list them all so you can decide which best suits your needs.
New security identity
I'm using the GroupInterface from FOSUserBundle, but I guess you could use your own too. The following files need to be added:
AclProvider.php
The method to change is private - the whole file has to be copied, but the only change has to be made to hydrateObjectIdentities
GroupSecurityIdentity.php
MutableAclProvider.php
We have to duplicate the whole file as it must extend AclProvider, but we're using a custom one and can't therefore extend the stock MutableAclProvider. The methods changed are getInsertSecurityIdentitySql and getSelectSecurityIdentityIdSql.
SecurityIdentityRetrievalStrategy.php
Next up: rewire the dependency injection container by providing the following parameters:
<parameter key="security.acl.dbal.provider.class">
Acme\Bundle\DemoBundle\Security\Acl\Dbal\MutableAclProvider
</parameter>
<parameter key="security.acl.security_identity_retrieval_strategy.class">
Acme\Bundle\DemoBundle\Security\Acl\Domain\SecurityIdentityRetrievalStrategy
</parameter>
Time to cross fingers and see whether it works. Since this is old code I might have forgotten something.
Use roles for groups
The idea is to have group names correspond to roles.
A simple way is to have your User entity re-implement UserInterface::getRoles:
public function getRoles()
{
$roles = parent::getRoles();
// This can be cached should there be any performance issues
// which I highly doubt there would be.
foreach ($this->getGroups() as $group) {
// GroupInterface::getRole() would probably have to use its
// canonical name to get something like `ROLE_GROUP_NAME_OF_GROUP`
$roles[] = $group->getRole();
}
return $roles;
}
A possible implementation of GroupInterface::getRole():
public function getRole()
{
$name = $this->getNameCanonical();
return 'ROLE_GROUP_'.mb_convert_case($name, MB_CASE_UPPER, 'UTF-8');
}
It's now just a matter of creating the required ACE-s as written in the cookbook article.
Create a voter
Finally, you could use custom voters that check for the presence of specific groups and whether the user has access to said object. A possible implementation:
<?php
namespace Acme\Bundle\DemoBundle\Authorization\Voter;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
class MySecureObjectVoter implements VoterInterface
{
/**
* {#inheritDoc}
*/
public function supportsAttribute($attribute)
{
$supported = array('VIEW');
return in_array($attribute, $supported);
}
/**
* {#inheritDoc}
*/
public function supportsClass($class)
{
return $class instanceof GroupableInterface;
}
/**
* {#inheritDoc}
*/
public function vote(TokenInterface $token, $object, array $attributes)
{
$result = VoterInterface::ACCESS_ABSTAIN;
if (!$object instanceof MySecureObject) {
return VoterInterface::ACCESS_ABSTAIN;
}
foreach ($attributes as $attribute) {
if (!$this->supportsAttribute($attribute)) {
continue;
}
// Access is granted, if the user and object have at least 1
// group in common.
if ('VIEW' === $attribute) {
$objGroups = $object->getGroups();
$userGroups = $token->getUser()->getGroups();
foreach ($userGroups as $userGroup) {
foreach ($objGroups as $objGroup) {
if ($userGroup->equals($objGroup)) {
return VoterInterface::ACCESS_GRANTED;
}
}
}
return voterInterface::ACCESS_DENIED;
}
}
}
}
For more details on voters please refer to the cookbook example.
I would avoid creating a custom security identity. Use the two other methods provided. The second solution works best, if you will be having lots of records and each of them must have different access settings. Voters could be used for setting up simple access granting logic (which most smaller systems seem to fall under) or when flexibility is necessary.
I write my answer here to keep a track of this error message.
I implemented group support with ACL and i had to hack a bit the symfony core "MutableAclProvider.php"
protected function getSelectSecurityIdentityIdSql(SecurityIdentityInterface $sid)
{
if ($sid instanceof UserSecurityIdentity) {
$identifier = $sid->getClass().'-'.$sid->getUsername();
$username = true;
} elseif ($sid instanceof RoleSecurityIdentity) {
$identifier = $sid->getRole();
$username = false;
}else {
//throw new \InvalidArgumentException('$sid must either be an instance of UserSecurityIdentity, or RoleSecurityIdentity.');
$identifier = $sid->getClass().'-'.$sid->getGroupname();
$username = true;
}
return sprintf(
'SELECT id FROM %s WHERE identifier = %s AND username = %s',
$this->options['sid_table_name'],
$this->connection->quote($identifier),
$this->connection->getDatabasePlatform()->convertBooleans($username)
);
}
Even if the provided object is not an instance of UserSecurityIdentity or RoleSecurityIdentity it return a value. So now i can use a custom "GroupSecurityIdentity"
It's not easy to put in place but was much adapted to my system.

Resources