Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to differentiate between Symfony user roles/groups in socket.io chat

I've been playing a bit with socket.io's chat, and I have a question: how can I differentiate between an admin user and a regular user in the chat room? I'd like the admin to have powers like kicking and banning people, but my users to not.

I'm using Symfony to develop my application, and I'd like to use its user database for the chat users. I'm using FOSUserBundle for my Symfony app's users. They are split into multiple groups, so I have the admin group, and others.

The admin group has ROLE_ADMIN which means that each user inside it has that role. That is the admins group and each user in this group should have permissions to ban, kick, mute etc. other users in the chat room.

For using my Symfony users in the chat, I've been reading into Redis to get their sessions, but I'm not exactly sure how to make the difference between my admin users and regular users. How can I prevent a regular user from making a request to the server that does something that the user doesn't have access to? Because anyone can do requests, but how can I validate those requests if they come from users stored in a MySQL database on the Apache server?

If not for Symfony, how this could be done in a regular PHP application? In the end, it doesn't matter how the admin is defined, but how to connect him to the Node server and how to make the Node server work with my user database.

I had an idea of simply encrypting and sending the user's data to the node server, then decrypt it there. Only the two servers know the private keys, so even if a client gets its hands on the encrypted data, he can't make a request with it for another client. I may do some IP check and a timestamp. The decrypted data on the node server could then be used to say if the user is an admin or not and allow him to send certain requests. Is this a good idea or is there a better way?

like image 763
George Irimiciuc Avatar asked Oct 18 '15 20:10

George Irimiciuc


2 Answers

I had an idea of simply encrypting and sending the user's data to the node server, then decrypt it there. Only the two servers know the private keys, so even if a client gets its hands on the encrypted data, he can't make a request with it for another client.

That's the basic idea.

How I would do it? I would use something like JWT to just send the userId to the node application. Doesn't have to be encrypted, because I only care about the jwt signature to make sure the request was indeed issued by the real user.

After that, using the userId I would make a server side call to the php application to check the roles of the user.

To elaborate:

  • The node app and the php app will use a shared secret to sign the JWT token.
  • The PHP application will expose the generated token to the frontend.
  • The socket.io client will send the token as part of the authentication to the node app.

How to handle banning

  • keep a list of opened sockets with their user id
  • create a webservice endpoint in the nodejs application which can hanlde the "ban" requests from the php application.
  • when such a request is received by the nodejs application, lookup the socket based on the userid and close the connection.
like image 52
Bogdan Avatar answered Oct 16 '22 09:10

Bogdan


I usually create a SecurityAccessManager service to check user's roles with voters calls (I can provide an exemple if needed) to check specific rights like "is this user can update this specific post?"

Config

company.navigation.security_access:
    class: Company\NavigationBundle\Services\SecurityAccessManager
    arguments: 
        - @security.authorization_checker            
        - @security.token_storage

Service code

namespace Company\NavigationBundle\Services;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;

class SecurityAccessManager
{
    private $authorizationChecker;
    private $tokenStorage;
    private $debug;

    public function __construct(
            AuthorizationCheckerInterface $authorizationChecker,
            TokenStorage $tokenStorage)
    {      
        $this->authorizationChecker = $authorizationChecker;
        $this->tokenStorage = $tokenStorage;   
        $this->debug = true;
    }

    // *************************************************************************
    // User
    // *************************************************************************

    public function getUser()
    {
        return $this->tokenStorage->getToken()->getUser();        
    }

    public function getUserId()
    {
        return $this->tokenStorage->getToken()->getUser()->getId();        
    }

    public function isAuthenticatedUser()
    {    
       return $this->authorizationChecker->isGranted('IS_AUTHENTICATED_REMEMBERED');
    }     

    // *************************************************************************
    // Roles checker
    // *************************************************************************

    public function isAdmin()
    {
        if($this->authorizationChecker->isGranted('ROLE_ADMIN') !== true) {
            return false;
        } else {
            return true;           
        }
    }    

    public function checkRightAdmin()
    {
        if($this->authorizationChecker->isGranted('ROLE_ADMIN') !== true) {
            throw new AccessDeniedException('Unauthorised access! '.($this->debug ? __FUNCTION__ : null));
        }

        return true;           
    }   

    public function checkUserHasRightToEditPost($postId)
    {
        // Check if user has right to modify the post
        if ($this->authorizationChecker->isGranted('is_user_has_right_to_edit_post', $postId) === false) {
            throw new AccessDeniedException('Unauthorised access! '.($this->debug ? __FUNCTION__ : null));
        }

        return true;
    }  
}

Then, in your controllers actions, you can check the user's rights

namespace Company\YourBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class YourBunbleController extends Controller
{   
    /**
     * Get the service
     * @return \Company\NavigationBundle\Services\SecurityAccessManager
     */
    private function getService()
    {        
        return $this->get('company.navigation.security_access');
    }  

    public function updatePostAction(Request $request, $postId)
    {   
        // Throw 403 if user has no admin rights
        $this->getService()->checkRightAdmin();

        // Throw 403 if user has no rights to update the post
        $this->getService()->checkUserHasRightToEditPost();

        //OK, you can update database
        ...
    }
}
like image 42
sdespont Avatar answered Oct 16 '22 09:10

sdespont