Override service for specific user role - symfony

I have 3 services which should override the default services only if the user has a specific role.
Or even better. Inject the current user/security in the new services.
The service then performs the check for the user role and calls the original service.
I tried to inject security.context into it. But then $security->getToken() returns null.
In the controllers it works fine. How can i get the current user in my service? This is what i want to do:
class AlwaysVisibleNavigationQueryBuilder extends NavigationQueryBuilder
{
public function __construct(\Sulu\Component\Content\Compat\StructureManagerInterface $structureManager, $languageNamespace, SecurityContext $security)
{
if (in_array('ROLE_SULU_ADMINISTRATOR', $security->getToken()->getRoles())) {
// Show unpublished content, too
$this->published = false;
}
parent::__construct($structureManager, $languageNamespace);
}
}

At the moment of creation of the service, the securityContext was not aware of the current user. The Security is filles when the application runs and not on dependency-resolution.
The following Code works.
class AlwaysVisibleNavigationQueryBuilder extends NavigationQueryBuilder
{
protected $security;
public function __construct(\Sulu\Component\Content\Compat\StructureManagerInterface $structureManager, $languageNamespace, SecurityContext $security)
{
$this->security = $security;
parent::__construct($structureManager, $languageNamespace);
}
public function build($webspaceKey, $locales)
{
$roles = $this->security->getToken()->getRoles();
if (in_array('ROLE_SULU_ADMINISTRATOR', $roles)) {
// Show unpublished content, too
$this->published = false;
}
return parent::build($webspaceKey, $locales);
}
}
Thanks to Matteo!

Related

Symfony Voters on collection of objects

I am using Voters to manage permissions on my application and everything works fine for single objects.
What I don't seem to be able to do is apply a Voter on a collection of objects. For example, I have an end-point /persons that will return the complete list of people, but it should be filtered according to the rights of each user (a department manager should only see the people in their own department). Is there any way of doing this with Voters?
Well this can be done using an authorization checker:
// your controller
private $authorizationChecker;
public function __construct(AuthorizationCheckerInterface $authorizationChecker)
{
$this->authorizationChecker = $authorizationChecker;
}
// in your action
return array_filter($userCollection, function (User $user) {
return $this->authorizationChecker->isGranted('VOTER_NAME', $user);
});
or simply if you're controllers extend the Symfony's Controller class:
return array_filter($userCollection, function (User $user) {
return $this->isGranted('VOTER_NAME', $user);
});

Authenticated user on constructor

After research and watch over the docs and other links, i understand perfectly why this can't be done the "old way", but i really need a workaround/solution for this:
I have a base class, it's not called directly on the request (not declared on route):
class AdminPanelController extends Controller
{
protected $_authUser;
public function __construct() {
$this->middleware(function ($request, $next) {
$this->_authUser = Auth::guard('admin')->user();
dd($this->_authUser); // ok
return $next($request);
});
}
...
protected function yoo() {
dd($this->_authUser); // auth user is null, but i need this here
}
}
I need for the authenticated user to be available on the yoo() method. The controller called directly:
Route::get('users', 'UsersController#hey');
UsersController:
class UsersController extends AdminPanelController
{
protected params;
public function __construct(Request $request) {
parent::__construct();
$this->params = $this->yoo(); // auth user is null, but i need this here
}
...
}
Note that if i call the method $this->yoo() on another place instead of the constructor the user would be available
NOTE: i also tried $request->user() (Authentication user provider [] is not defined.) but since i have a multi auth system i have to provide a guard and tried $request->guard('admin')->user(), with the result being Method guard does not exist.

Dropwizard: BasicAuth

Using Dropwizard Authentication 0.9.0-SNAPSHOT
I want to check the credentials against database user (UserDAO).
I get the following exception
! org.hibernate.HibernateException: No session currently bound to
execution context
How to bind the session to the Authenticator?
Or are there better ways to check against the database user?
The Authenticator Class
package com.example.helloworld.auth;
import com.example.helloworld.core.User;
import com.example.helloworld.db.UserDAO;
import com.google.common.base.Optional;
import io.dropwizard.auth.AuthenticationException;
import io.dropwizard.auth.Authenticator;
import io.dropwizard.auth.basic.BasicCredentials;
public class ExampleAuthenticator implements Authenticator<BasicCredentials, User> {
UserDAO userDAO;
public ExampleAuthenticator(UserDAO userDAO) {
this.userDAO = userDAO;
}
#Override
public Optional<User> authenticate(BasicCredentials credentials) throws AuthenticationException {
Optional<User> user;
user = (Optional<User>) this.userDAO.findByEmail(credentials.getUsername());
if ("secret".equals(credentials.getPassword())) {
return Optional.of(new User(credentials.getUsername()));
}
return Optional.absent();
}
}
The Application Class
#Override
public void run(HelloWorldConfiguration configuration, Environment environment) throws Exception {
final UserDAO userDAO = new UserDAO(hibernate.getSessionFactory());
environment.jersey().register(new AuthDynamicFeature(
new BasicCredentialAuthFilter.Builder<User>()
.setAuthenticator(new ExampleAuthenticator(userDAO))
.setAuthorizer(new ExampleAuthorizer())
.setRealm("SUPER SECRET STUFF")
.buildAuthFilter()));
environment.jersey().register(RolesAllowedDynamicFeature.class);
//If you want to use #Auth to inject a custom Principal type into your resource
environment.jersey().register(new AuthValueFactoryProvider.Binder(User.class));
environment.jersey().register(new UserResource(userDAO));
To get auth to work with 0.9+ you need the following. You can refer to this particular changeset as an example.
Include the dependency.
<dependency>
<groupId>io.dropwizard</groupId>
<artifactId>dropwizard-auth</artifactId>
<version>${dropwizard.version}</version>
</dependency>
Register auth related stuff.
private void registerAuthRelated(Environment environment) {
UnauthorizedHandler unauthorizedHandler = new UnAuthorizedResourceHandler();
AuthFilter basicAuthFilter = new BasicCredentialAuthFilter.Builder<User>()
.setAuthenticator(new BasicAuthenticator())
.setAuthorizer(new UserAuthorizer())
.setRealm("shire")
.setUnauthorizedHandler(unauthorizedHandler)
.setPrefix("Basic")
.buildAuthFilter();
environment.jersey().register(new AuthDynamicFeature(basicAuthFilter));
environment.jersey().register(RolesAllowedDynamicFeature.class);
environment.jersey().register(new AuthValueFactoryProvider.Binder(User.class));
environment.jersey().register(unauthorizedHandler);
}
A basic authenticator
public class BasicAuthenticator<C, P> implements Authenticator<BasicCredentials, User> {
#Override
public Optional<User> authenticate(BasicCredentials credentials) throws AuthenticationException {
//do no authentication yet. Let all users through
return Optional.fromNullable(new User(credentials.getUsername(), credentials.getPassword()));
}
}
UnAuthorizedHandler
public class UnAuthorizedResourceHandler implements UnauthorizedHandler {
#Context
private HttpServletRequest request;
#Override
public Response buildResponse(String prefix, String realm) {
Response.Status unauthorized = Response.Status.UNAUTHORIZED;
return Response.status(unauthorized).type(MediaType.APPLICATION_JSON_TYPE).entity("Can't touch this...").build();
}
#Context
public void setRequest(HttpServletRequest request) {
this.request = request;
}
}
Authorizer
public class UserAuthorizer<P> implements Authorizer<User>{
/**
* Decides if access is granted for the given principal in the given role.
*
* #param principal a {#link Principal} object, representing a user
* #param role a user role
* #return {#code true}, if the access is granted, {#code false otherwise}
*/
#Override
public boolean authorize(User principal, String role) {
return true;
}
}
Finally use it in your resource
#GET
public Response hello(#Auth User user){
return Response.ok().entity("You got permission!").build();
}
You're going to need code in your Application class that looks like this
environment.jersey().register(AuthFactory.binder(new BasicAuthFactory<>(
new ExampleAuthenticator(userDAO), "AUTHENTICATION", User.class)));
Then you can use the #Auth tag on a User parameter for a method and any incoming authentication credentials will hit the authenticate method, allowing you to return the correct User object or absent if the credentials are not in your database.
EDIT: Works for Dropwizard v0.8.4
On Latest versions starting from 0.9 onward, you can use "#Context" annotation in resource class methods as shown below:
#RolesAllowed("EMPLOYEE")
#Path("/emp")
#GET
#Produces(MediaType.APPLICATION_JSON)
public Response getEmployeeResponse(#Context SecurityContext context) {
SimplePrincipal sp = (SimplePrincipal) context.getUserPrincipal();
return Response.ok("{\"Hello\": \"Mr. " + sp.getUsername() + "\"( Valuable emp )}").build();
}

symfony2: check isGranted for a route

supposed having certain route string like "/path/index.html" protected by firewall, how to chek whether current user is able to access it?
Thanks in advance!
I am sorry, I should have been more explicit: I have an array of route names and I construct a menu. A lot of users with different roles can access a page with this menu. The purpose is to show only accessible liks in this menu for a particular user.
Something like:
'security_context'->'user'->isGranted('/path/index.html')
This answer is based on your comments:
You should get the roles needed to access that route.to that you need access to the security.access_map service which is private.so it has to be injected directly.e.g: you can create a path_roles service like such that you can get the roles for a certain path:
namespace Acme\FooBundle;
class PathRoles
{
protected $accessMap;
public function __construct($accessMap)
{
$this->accessMap = $accessMap;
}
public function getRoles($path)
{ //$path is the path you want to check access to
//build a request based on path to check access
$request = Symfony\Component\HttpFoundation\Request::create($path, 'GET');
list($roles, $channel) = $this->accessMap->getPatterns($request);//get access_control for this request
return $roles;
}
}
now declare it as a service:
services:
path_roles:
class: 'Acme\FooBundle\PathRoles'
arguments: ['#security.access_map']
now use that service in your controller to get the roles for the path and construct your menu based on those roles and isGranted.i.e:
//code from controller
public function showAction(){
//do stuff and get the link path for the menu,store it in $paths
$finalPaths=array();
foreach($paths as $path){
$roles = $this->get('path_roles')->getRoles($path);
foreach($roles as $role){
$role = $role->getRole();//not sure if this is needed
if($this->get('security.context')->isGranted($role)){
$finalPaths[] = $path;
break;
}
}
//now construct your menu based on $finalPaths
}
}
You could use security.access_control configuration option:
securty:
access_control:
- { path: "^/path/index.html$", roles: ROLE_SOME_ROLE}
Or simply check that manually from within your controller:
class SomeController extends Controller {
public function indexAction() {
if (!$this->get('security.context')->isGranted(...)) {
throw new AccessDeniedException(...);
}
...
}
}

How to catch users with no username?

I'm migrating quite a large community to symfony2. The current user table contains a lot of users with non-alphanumeric chars in the username. In the new version I only allow [a-zA-Z0-9-] for benefits like semantic URLs for each user.
Is it possible to catch users who log in with email/pass and have no username set? I would like them to redirect to a page where they will be able to re-pick a username. The tricky part: they should not be able to touch anything on the site unless they have a correct username.
I thought about a event, from the fosuserbundle but I couldn't find a suitable one.
You could use events. See an example here: http://symfony.com/doc/2.0/cookbook/event_dispatcher/before_after_filters.html
Of course the action changing the username should be ignored by the event listener. Just like login and other anonymous actions.
You can return any response, including a redirect, by setting response on an event.
Just an idea. How about the AOP paradigm (JMSAopBundle)? Define a pointcut for you controllers (except for the login one):
class PrivateEntityInformationPointcut implements PointcutInterface
{
public function matchesClass(\ReflectionClass $class)
{
return $class->isSubclassOf('Your\Controller\Superclass')
&& $class->name !== 'Your\Controller\Access';
}
public function matchesMethod(\ReflectionMethod $method)
{
return true; // Any method
}
}
Then the interceptor should redirect to the page for setting the username:
class DenyEntityAccessInterceptor implements MethodInterceptorInterface
{
private $securityContext;
private $logger;
/**
* #DI\InjectParams({
* "securityContext" = #DI\Inject("security.context"),
* "logger" = #DI\Inject("logger"),
* })
*/
public function __construct(SecurityContext $securityContext,
Logger $logger)
{
$this->securityContext = $securityContext;
$this->logger = $logger;
}
public function intercept(MethodInvocation $invocation)
{
// Check username, redirect using the router, log what's happening
// It's OK
return $invocation->proceed();
}
}

Resources