Should Symfony2 ACL be used for checking ownership in SaaS? - symfony

In a multi tenant web application should Symfony2 ACL framework be used for checking the ownership of domain objects?
I can't get the point since (assuming each table has a back reference to the User object) i can simply check current user id against the entity owner id, like the following:
/*
* #Route("/edit/{slug}")
* #Method("GET|POST")
* #Secure(roles="ROLE_USER")
* #Template
*/
public function editAction($slug);
{
// Find the post given the slug
$repo = $this->getDoctrine()->getRepository('AcmeHelloBundle:Post');
$entity = $repo->findOneBySlug($slug);
$current = $this->get('security.contex')->getToken()->getUser();
// 404 if slug is invalid
if(!$entity) throw new $this->createNotFoundException();
// AccessDenied if current user is not the owner of the entity
if($current->getId() != $entity->getUser()->getId())
throw new AccessDeniedException();
}
Maybe ACL can help avoiding back referencing each entity to the user table? Any explanation or example would be helpful, thanks.

ACL is useful when you have a scenario that multiple people have access to same domain. The ACL documentation has a good example for that.
For example, lets say you have SaaS providing collaborative document editing for companies. A company may want to restrict access to a document, to only allow the executives of the company to edit it and not the employees. In that scenario, you can't use the User token alone, since multiple members needs to access to the domain. This is where the usefulness of ACL comes in.

Related

Checking that user has role according to roles hierarchy for not logged user

I faced with an issue of checking users permissions.
I know about AuthorizationChecker and other stuff for checking permissions for logged users, but I need to go through all the users and do some stuff to users that have specific permission(s) according to the roles hierarchy.
So what can help me replace isGranted() method for checking logged user that have token? Do I just need to make my own service to do this? I definitely won't even try to fetch that tokens for all users
Instead of creating a voter, another implementation of security token just I made a helper class that uses injected RoleHierarchy instance from to check is this role exists in given user's roles collection.
It’s definitely not the right way to do this, but I made it as much simple as I could:
public function isGranted(User $user, string ...$permissions): bool
{
$reachableRoles = $this->roleHierarchy->getReachableRoleNames($user->getRoles());
foreach ($permissions as $permission) {
if (\in_array($permission, $reachableRoles, true)) {
return true;
}
}
return false;
}

How to get current base url in register route class?

I'm developing plugin for nopcommerce shopping cart(ASP.NET Core 2.1).
Before registering routes in RouteProvider class,i need to access current domain name in order to some validation checks but when using IHttpContextAccessor to get Httpcontext and get current domain, Httpcontext is null.
Is there any solution to access current domain name in route provider class?
public void RegisterRoutes(IRouteBuilder routes)
{
var httpContextAccessor = EngineContext.Current.Resolve<IHttpContextAccessor>();
var domainName = httpContextAccessor.HttpContext.Request.Host.Value.ToString();
var pluginValidtyChecker = new PluginValidityChecker.ValidityChecker();
if (pluginValidtyChecker.CheckIsValid(domainName))
{
//Register routes
}
}
In the application developed by me users first enter the domain name the plugin should work on and then buy plugin.After purchasing we generate a license key that's mapped with domain name and users must be entered this license key in plugin settings.
Because plugin override some main route of nopcommerce i need first check that license key and domain name that's plugin currently work on, is valid or not.
This route must be registered on compile time.
It’s not easy to ensure, that the domain name is always available, because it’s up to the programmers to define it either in a configuration-file, or as Environment-Variable.
Furthermore, if the application runs in an AWS-container, the configuration is a bit more complicated, because the configuration-file has a different structure.
There is an Nuget-Package available the easy up things, but still not everybody uses it.
So what you want can’t be ensured in a bulletproof way.
If anybody can correct me. I would be quite happy, because I had a similar issue, which I could solve for my own application, but I‘m not glad with my solution yet.

Symfony multicompany application

I need to set up several parameters after user authentication.
We have a db with with oauth2 clients.
When one of them got access token and is trying to get access to protected API I need to identify the client (which is easy since access_token is bound to particular client) and define several application parameters (actually I need to load a specific file parameters.yml depends on the client).
My questions is:
How can I hook to event when user is authenticated?
How can I load a particular parameters.yml and make it relevant after user authenticate ?
Thank you!
When user is successfully authenticated, you can write a listener which listens to SecurityAuthenticationEvents::AUTHENTICATION_SUCCESS.
The public method of the listener should look like this:
public function onAuthenticationSuccess(AuthenticationEvent $event)
{
/**
* #var User $user
*/
$user = $event->getAuthenticationToken()->getUser();
// ...
return;
}
I believe you can use YamlFileLoader for that. Of course you can create a service class to read the contents from yaml files and provide them to different services in your app. I would not try to mix them with regular parameter / config files.

find user by username from multiple providers [symfony2]

I need to find a user object in symfony2 based on the username or emailadres. This is not for loggin in, but for other actions on a user.
I can simply request the (Doctrine2) repository and call the method loadByUsername from the UserProviderInterface that is on my repository-class.
But the code that needs to do this will be used in multiple projects and I need it to be a bit more generic. The user class/table might be different or the users might come from a completely different type of provider.
Is there a way to find a user by username just like Symfony2 itself uses when logging in?
This way it will work no matter how the user providers are configured in security.yml.
Is there some service in Symfony2 I can use for this?
Is there a "user provider service" where I can call a method something like "loadUserByUsername" that will try each configured provider?
After some poking into the SecurityBundle of Symfony itself, I figured out the following:
Given this is in your security.yml:
providers:
AdministrationUser:
entity:
class: AdministrationBundle\Entity\User
Symfony will create a service with the following name:
security.user.provider.concrete.administrationuser
This service uses the UserProviderInterface and when you fetch this service you can simply call the method loadUserByName and find your user.
So all you need to know is the name of the provider you configured yourself and you can determine the service-name and fetch it.
I'm in a more generic situation, so I added an alias to that service in the Extension-class of my bundle:
// alias the user_provider mentioned
$container->setAlias('my_security_bundle.user.provider', new Alias('security.user.provider.concrete.' . strtolower($config['user']['provider'])));
Where $config['user']['provider'] comes from config.yml (and needs to be configured in your Configuration class, but that is a different story.
Now I can simply use that new alias and I will get the correct service to find my user in a Controller like so:
/** #var UserProviderInterface $userProvider */
$userProvider = $this->get('my_security_bundle.user.provider');
$user = $userProvider->loadUserByUsername('someone#somewhere.tld');

Simple API Key Authentication in Symfony2 using FOSUserBundle (and HWIOauthBundle), filling in the gaps

Edit: See below for my own solution, which is, at the time of writing, functioning but imperfect. Would love some criticism and feedback, if I get something put together that I feel is really solid then I'll make a howto blog post for other people facing the same challenge.
I've been struggling with this for days, and I'm hoping someone can let me know if I'm on the right path.
I have a system with a FOSRestBundle webservice in which I'm currently using FOSUserBundle and HWIOAuthBundle to authenticate users.
I would like to set up stateless api key authentication for the webservice.
I've read through http://symfony.com/doc/current/cookbook/security/api_key_authentication.html and this seems simple enough to implement, I've also installed UecodeApiKeyBundle which seems to be mostly just an implementation of this book page.
My question is a n00b one...what now? The book page and bundle both cover authenticating a user by API key, but don't touch on the flow of logging users in, generating API keys, allowing users to register, etc. What I would really like is simple API endpoints for login, register, and logout that my app developers can use. Something like /api/v1/login, etc.
I think I can handle registration....login is confusing me though. Based upon some additional reading, it seems to me like what I need to do for login is this:
Create a controller at api/v1/login that accepts POST requests. The
request will either look like { _username: foo, _password: bar } or
something like { facebook_access_token: foo. Alternately, the facebook login could require a different action, like /user/login/facebook, and just redirect to the HWIOAuthBundle path }.
If the request contains _username and _password parameters, then I
need to forward the request to login-check (I'm not sure about this
one. Can I just process this form myself? Or, should I manually check
the username and password against the database?)
Add a login event listener, if user authenticated successfully,
generate an api key for the user (This is only necessary if I'm not checking it myself, of course)
Return the API Key in the response of the POST request (This breaks
the post-redirect-get strategy, but otherwise I don't see any issues
with it) I think this eliminates the redirect to login-check option I
listed above.
As you can probably see I'm confused. This is my first Symfony2 project, and the book pages on Security sound simple...but seem to gloss over some of the details and it's left me quite unsure of what way to proceed.
Thanks in advance!
=============================================================
Edit:
I've installed a API Key Authentication pretty much identically to the relevant cookbook article: http://symfony.com/doc/current/cookbook/security/api_key_authentication.html
To handle user's logging in, I've created a custom controller method. I doubt that this is perfect, I would love to hear some feedback on how it can be improved, but I do believe that I'm on the right path as my flow is now working. Here's the code (Please note, still early in development...I haven't looked at Facebook login yet, only simple username/password login):
class SecurityController extends FOSRestController
{
/**
* Create a security token for the user
*/
public function tokenCreateAction()
{
$request = $this->getRequest();
$username = $request->get('username',NULL);
$password = $request->get('password',NULL);
if (!isset($username) || !isset($password)){
throw new BadRequestHttpException("You must pass username and password fields");
}
$um = $this->get('fos_user.user_manager');
$user = $um->findUserByUsernameOrEmail($username);
if (!$user instanceof \Acme\UserBundle\Entity\User) {
throw new AccessDeniedHttpException("No matching user account found");
}
$encoder_service = $this->get('security.encoder_factory');
$encoder = $encoder_service->getEncoder($user);
$encoded_pass = $encoder->encodePassword($password, $user->getSalt());
if ($encoded_pass != $user->getPassword()) {
throw new AccessDeniedHttpException("Password does not match password on record");
}
//User checks out, generate an api key
$user->generateApiKey();
$em = $this->getDoctrine()->getEntityManager();
$em->persist($user);
$em->flush();
return array("apiKey" => $user->getApiKey());
}
}
This seems to work pretty well, and user registration will be handled similarly.
Interestingly to me, the api key authentication method I implemented from the cookbook appears to ignore the access_control settings in my security.yml file, in the cookbook they outline how to only generate the token for a specific path, but I didn't like that solution, so I've implemented my own (also somewhat poor) solution to not check the path I'm using to authenticate users
api_login:
pattern: ^/api/v1/user/authenticate$
security: false
api:
pattern: ^/api/*
stateless: true
anonymous: true
simple_preauth:
authenticator: apikey_authenticator
I'm sure there's a better way to do this too, but again...not sure what it is.
You are trying to implement stateless authentication with username and login. This is pretty much what the Oauth2 authentication passsword grant does. This is pretty standard, so instead of trying to implement it yourself i'd recommend you use a Bundle for that, for example the FOSOauthServerBundle. It can use FOSUserBundle as its user provider and would be cleaner, more secured and easier to use than a home-made solution.
To register user, your can create a register action in your API (e.g., in a REST API I'd use POST - api/v1/users), and in the controller method copy and past the code from the FOSUserBundle:RegistrationController (of course adapt it for your needs).
I did that in a REST API, it worked like a charm.
I don't think you actually really need a /login endpoint.
In the symfony doc, the api client is required to pass it's key (via apiKey http parameter) to every request to the API.
I am not sure it's in the best practice, but you could do this.
"The book page and bundle both cover authenticating a user by API key, but don't touch on the flow of logging users in, generating API keys, allowing users to register"
The best is to allow your users to register via a web form (for example with the route fos_user_register). User entity could have an apikey field, pre-populated with a key generated like this sha1("secret".time()) for example, and a button in their profile to regenerate the key.
Class GenearteToken extends FOSRestController
{
public getTokenAction(Request $request){
$apiKey = $request->query->get('apikey');
return $apiKey;
}
}

Resources