I need to find a way to set access to url by function.
For example, Can I set 'access control' in security.yml this way:
access_control:
- { path: ^/admin$, function: checkadmin() }
In the other words, symfony run 'checkadmin()' function, and that function return a boolean value , so check access the path.
Or exist other ways?
You have to provide a role when configuring an access control rule. That part of the security bundle (authorization) only cares about matching requests with required roles.
With that concrete rule, define a custom role like so:
access_control:
- { path: ^/admin$, role: CHECK_ADMIN }
Then the question becomes, how can I dynamically add a role to a user?
The answer is a Security Voter:
http://kriswallsmith.net/post/15994931191/symfony2-security-voters (best explination)
Dynamically adding roles to a user
http://symfony.com/doc/current/cookbook/security/voters.html
Related
Suppose I have two users, A and B.
A is allowed to access all the resources available, but B can access them only partially.
What is the proper way to prevent B from accessing resources to which B does not have permission?
Should I create some sort of whitelist that specifies only URLs B can access?
The following snippet is what I currently have.
It is a middleware which checks whether each request is allowed to access specific URLs.
const ALLOWED_URLS = ['api/resource1', 'api/resource2', 'api/resource3'];
const sessionCheck = (req, res, next) => {
const url = req.originalUrl;
// check whether accessing URL is allowed
}
Is there any better approach than this?
What you're asking about is called IAM (Identity & Access Management).
The roles, ownership, and permissions on specific resources is generally persisted in your database as business domain objects of their own. This is language agnostic and not specific to node or express. You should NOT build a white list of URLs. The resources you want to protect are in your database. You should map them to permission objects that in turn map to users. Your not protecting URLs, your protecting resources. Everyone is allowed to access any URL but the resources behind them are what you're protecting and those rules/permissions go in your database.
If your looking for industry standards then here are some common names/terms for those IAM objects that would be persisted in your DB:
Group
Role
User
Policy
User A's access would generally be determined by what role they have or what group they belong to. Whether you give user A a role, put them in an authorized group, or give them a direct permission doesn't really matter, these groupings exist to reduce duplication so you can pass around or take away multiple permissions at once. But the general idea is the same; your resources exist in a DB and you specify what is the required or allowed roles, groups, and users that can access those resources and you map users to groups, roles, etc as simple table entries. This means the real authorization logic is not in Node or Express or even in your webapp, its built into the resources themselves and tied to how the data is retrieved.
Resource Retrieval Code
When anyone makes a request for a given resource the query, regardless of your database type, should fail if the user is not authorized. This means the way you are retrieving data must be directly tied to how its authorized and not two separate steps; meaning you should not get the resource, then check if the user is authorized and you should not check if the user is authorized before getting the resource. The best practice is fuse/join the two so that you can not get the resource unless your authorized because we look for the resource using your role and if you don't have the right role we can't find the resource.
For example:
function getAccount(userId,accountId) {
makeSQLCall(userId,accountId)
}
SELECT *
FROM accounts a
WHERE a.accountId = accountId AND u.userId = userId
JOIN users u ON a.allowedRole = u.role
The SQL doesn't matter as the same can be done with other technologies but the last line is the most important (account.allowedRole = user.role). You literally pull the resource from the database using the users role so that if they are not authorized this fails and no data is returned. This is also your base/parent data retrieval function so other functions that don't know about authorization can use this function and authorization will be dealt with under the hood.
Express Pseudo Code
router.get('/api/resource1',function(req,res){
var user = utility.getUserFromRequest(req)
var resource = accountService.getAccount(user,req.body.accountId)
sendResponse(resource)
})
Looking at the code above the authorization is built into your domain model not your web application. If the user making the request above is not authorized they will get no data back. You have to figure out in your own business use case is it enough to return an empty result or do you need to return a 401 HTTP error code. If you need to inform your non-malicious users they are not authorized you can simply perform isAuthoriized(user,accountId) before running accountService.getAccount as a UI convenience. The strength of this approach is that if you or some other developer forgets to check isAuthorized 1st the call will still return no data since isAuthorized() is just for the users benefit and not security. The security is at the domain/db layer.
Typically you would build user resource routes and validate their ownership when you authenticate and their permissions on authorization.
E.g.
server.get('api/v0/products/:user', authMiddleware, (req, res, next) => {
// from the auth middleware comes a parsed token with payload
if (req.payload.user !== req.params.user) {
return res.status(401).send('not allowed')
}
// do action
})
An alternative would be to only access database objects that matches the payload of your authentication
The example would suggest using JWT middleware with a custom property user
I'm running the dropwizard example from:
https://github.com/dropwizard/dropwizard/tree/master/dropwizard-example
which includes an example how to protect a resource using http basic authentication.
In the example the user must belong to a specific role to access the resource:
#Path("/protected")
#Produces(MediaType.TEXT_PLAIN)
public class ProtectedResource {
#RolesAllowed("ADMIN")
#GET
public String showSecret(#Auth User user) {
return String.format("Hey there, %s. You know the secret! %d", user.getName(), user.getId());
}
}
I have a scenario where I want to enforce authentication, through the filter, but once authenticated I want all users to access the resource.
So my question is: how to indicate that all roles should be allowed to access the resource? I've tried replacing #RolesAllowed with #PermitAll but I'm getting:
java.lang.RuntimeException: Cannot inject Custom principal into unauthenticated request
! at io.dropwizard.auth.AuthValueFactoryProvider$AuthValueFactory.provide(AuthValueFactoryProvider.java:77) ~[dropwizard-auth-0.9.0-SNAPSHOT.jar:0.9.0-SNAPSHOT]
This was not supported in the first implementation of io.dropwizard.auth.AuthDynamicFeature. #PermitAll support has been introduced in v0.9.0-rc3.
I'm quite new to Sonata Admin Bundle and I'm trying to make the User bundle work, however what I really need is just to limit acces to the admin area to a single administrator so it may be a bit of an overhead. Is it feasibile with all the symfony dynamic routing to secure the access with something as simple as an .htaccess rule or sth similar?
I'd recommend restricting access to /admin path to a role (e.g. ROLE_ADMIN) and assigning the role only to the user, that should have said access:
# app/config/security.yml
security:
# ...
access_control:
# require ROLE_ADMIN for /admin*
- { path: ^/admin, roles: ROLE_ADMIN }
For more info on Access Controll see documentation.
I'm trying to understand something about Symfony and the "super admin".
When I use FOSUser to create a user with super admin privileges
php app/console fos:user:create adminuser --super-admin
I'd firstly like to know what means (from the doc)
[...]Specifying the --super-admin option will flag the user as a super admin[...]
I imagine it means granting ROLE_SUPER_ADMIN to the user because I don't see any super-admin field in the user table.
Secondly, while (still from the doc)
A super admin has access to any part of your application
security:
role_hierarchy:
ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH, ...]
Why do we still need to configure the access hierarchy for it ?
Looking at FOSUserBundle's code you will find that the CreateUserCommand if invoked with the --super-admin flag will call the UserManipulator with a boolean argument $superadmin=true.
Now the UserManipulator calls the UserManager who will create a User Object, call it's setSuperAdmin() method and persist the new user afterwards.
The method looks as follows:
public function setSuperAdmin($boolean)
{
if (true === $boolean) {
$this->addRole(static::ROLE_SUPER_ADMIN);
} else {
$this->removeRole(static::ROLE_SUPER_ADMIN);
}
return $this;
}
So answering your first question:
Yes, the --super-admin flag causes FOSUserBundle to create a new user with the ROLE_SUPER_ADMIN role.
You still have to include the role hierarchy in your security configuration because the ROLE_SUPER_ADMIN role basically doesn't differ from any other role.
It's just a convention provided by the Symfony standard edition that users with role ROLE_SUPER_ADMIN should not have any access restrictions.
If you want the ROLE_SUPER_ADMIN to bypass all security voters by default - have a look at JMSSecurityExtraBundle's IddqdVoter which implements this for the special role ROLE_IDDQD. But this has already been suggested in your other question here.
By defining the hierarchy, you explicitly grant it the ROLE_ADMIN and ROLE_ALLOWED_TO_SWITCH roles (or other custom roles you could have)
If you comment this line, and you try to access with your ROLE_SUPER_ADMIN user to an action with a ROLE_ADMIN check, you will get a not allowed error.
The ROLE_SUPER_ADMIN is just a convention for the name the super administrator role should have, but it does not have privileges by it's own, you have to explicitly grant them to it.
I am using a solution similar to http://blog.jmoz.co.uk/symfony2-fosuserbundle-role-entities
So I have a Role entity that implements RoleInterface and I have a modified User entity that is set up to have a ManyToMany relationship with the Roles.
This allows me to use code like this
$user = $this->get('security.context')->getToken()->getUser();
$role = new Role('ROLE_TEST');
$user->addRole($role);
$em = $this->getDoctrine()->getEntityManager();
$em->persist($role);
$em->persist($user);
$em->flush();
I can then check if a user has a role with
$user = $this->get('security.context')->getToken()->getUser();
if($user->hasRole('ROLE_TEST')){
//do stuff...
}
This solution is ok, but I need to have access to the security context and use code like this:
if($this->get('security.context')->isGranted('ROLE_TEST')){
//do stuff...
}
And in the security.yml cofig file I would like to use the access_control code like this:
access_control:
- { path: ^/test$, role: ROLE_TEST }
Do I need a custom user manager for this? The roles that are assigned to a user in the database are not being carried over to the built in Symfony security system.
In other words when I view the security section of the profiler it shows that the user is assigned to Roles [ROLE_USER], but I am hoping to get it so the system will also recognize the roles that I have set in the database for the logged in user such as ROLE_TEST.
The reason that this was not working is because I was still logged in with the same session. Logging out and then back in again to refresh the session with the new roles does the trick. DOH!