FOSUserBundle : handling different types of user - symfony

I'm starting a new project, using a symfony2 architercture and the usefull FosUserBundle to handle my users.
In my application, i have 2 different types of users, which are : User and Entreprise.
I created a Userform (with embedded form in it).
Now i want to create a EntrepriseForm (with a different embedded form) but i'm stuck, due to FosUserConfiguration allowing only 1 registration form type.
If it can helps, here is how my config.yml looks like :
fos_user:
db_driver: orm # other valid values are 'mongodb', 'couchdb' and 'propel'
firewall_name: main
user_class: Digin\UserBundle\Entity\User
registration:
confirmation:
from_email: # Use this node only if you don't want the global email address for the confirmation email
address: xxx#xxx.org
sender_name: xxx
enabled: true # change to true for required email confirmation
template: FOSUserBundle:Registration:email.txt.twig
form:
type: mybundle_user_registration
handler: fos_user.registration.form.handler.default
name: fos_user_registration_form
validation_groups: [Registration]
Any idea about how i should do that ?

On your place I will add a new Role in your security.yml(remember to start it with ROLE_ symfony ignores other roles). Then add the Listener InteractiveLoginListener to do some extra stuff redirections after login on diffrent roles. And then add JMSSecurityExtraBundle That will help you to validate the access in accions and controllers just by annotations:
use JMS\SecurityExtraBundle\Annotation\PreAuthorize;
class MyService
{
/** #PreAuthorize("hasRole('A') or (hasRole('B') and hasRole('C'))") */
public function secureMethod()
{
// ...
}
}
Look at the documentation: http://jmsyst.com/bundles/JMSSecurityExtraBundle/master/installation

Related

JWTRefreshTokenBundle change user_identity_field Symfony 5.4 + ApiPlatform

I am using : Symfony 5.4 + ApiPlatform + JWTRefreshTokenBundle 1.1
JWTRefreshTokenBundle => https://github.com/markitosgv/JWTRefreshTokenBundle
I need to change this parameter "user_identity_field" but there is no way to change this :
I tried to change the Yaml =>
gesdinet_jwt_refresh_token:
user_identity_field: email
user_provider: app_user_provider
I tried to modify this function in my user provider (app_user_provider) entity User.php :
public function getUserIdentifier(): string
{
return (string) $this->id;
}
Right now the better I can get is to have the ID instead of the E-mail in the user name column in my database, but as soon as I try to refresh the token, I get this message => " 401 "Invalid credentials".
I am tried to have "ID" instead of "E-MAIL" as user_identity_field.
Has anyone found a solution ?
Thanks.
Sf 5,4+
#gesdinet_jwt_refresh_token.yaml
gesdinet_jwt_refresh_token:
user_identity_field: email
ttl: 2592000
firewall: login
user_provider: security.user.provider.concrete.app_user_provider
i think you need to change the app_user_provider in the security.yaml file :
app_user_provider:
entity:
class: App\Entity\User
property: id
jwt:
lexik_jwt:
class: App\Entity\User
i think the problem is : symfony try to authneticate user with the app_user_provider and you have a conflict because id's are in your JWT but framework search for email.
For verify that you can check your tokens on : https://jwt.io/
The JWTRefreshTokenBundle (gesdinet/jwt-refresh-token-bundle) is build upon the JWTAuthenticationBundle (lexik/jwt-authentication-bundle), which is the bundle that defines the user_identity_field configuration:
# config/packages/lexik_jwt_authentication.yaml
lexik_jwt_authentication:
user_identity_field: 'email'

syntax for token_provider in symfony remember_me

I'm building remember_me functionality in symfony. Instead of tokens being stored in cookies I want to store them in database So, I'm trying to use an option called token_provider but there is not much information detailed on Symfony.com.
I am new to Symfony, can any one share the syntax of "token_provider" in security.yml->firewalls->remember_me?
any help will be appreciated.
Changes I have done
Created a custom service which extends Symfony\Bridge\Doctrine\Security\RememberMe\DoctrineTokenProvider and passed the db connection object from the constructor
class CustomTokenService extends DoctrineTokenService
{
public function __construct(EntityManagerInterface $em){
parent::__construct($em->getConnection());
}
}
Registered this service in app/config/services.yml
custom service in services.yml:
token_service:
alias:{App}\Bundle\Services\Utilities\CustomTokenService
public: true
In Security.yml:
remember_me:
secret: '%kernel.secret%'
lifetime: 604800 # 1 week in seconds
path: /
domain: ~
remember_me_parameter: _stay_signedin
token_provider: token_service

Symfony : How to access configuration in controller

This may be a silly question, but i can't see how to access this data :
In the main app/config/config.yml, i have the general configuration data for my application.
parameters: #accessible by getParameter()
locale: fr
# ...
fos_user: #accessible by ???
#...
registration:
form:
from_email:
address: mymail#mydomain.fr
sender_name: TheSenderName
In my custom bundle i can access the parameters from that config.yml file with :
$this->container->getParameter('locale'); //gives me "fr" as expected
But how can i access the non parameters configuration values ?
i would like here to get the adress defined in te FOS User bundle configuration :
I can't do
$admin_adress = $this->container->getParameter('fos_user.registration.confirmation.from_email.address');
What is the right way to access thoses ?
Edit :
Yes i wanted to access this config data from FOS User Bundle (here in the example, but it could be any), inside a controller or whatever in my bundle.
I think this question nailed it
class MyProjectExtension extends Extension
{
public function load( array $configs, ContainerBuilder $container )
{
// The next 2 lines are pretty common to all Extension templates.
$configuration = new Configuration();
$processedConfig = $this->processConfiguration( $configuration, $configs );
// This is the KEY TO YOUR ANSWER
$container->setParameter( 'from_email.address', $processedConfig[ 'registration.confirmation.from_email.address' ];
// Other stuff like loading services.yml
}
You can act that way
parameters:
locale: fr
fos_user:
registration:
form:
from_email:
address: mymail#mydomain.fr
# ...
fos_user:
#...
registration:
form:
from_email:
address: %fos_user.registration.form.from_email.address%
sender_name: TheSenderName
Of course I've choose this name just to fit your controller request for parameter: you can (and maybe should) choose other keys.
However with container->getParameter you can access only parameters section of your configuration file (plus parameters exposed from vendors)
One of the solutions can be passing your controller as a service, then inject your parameters like that :
# services.yml
my.amazing.controller:
class: CompanyFirst\LabBundle\Controller\PloufController
arguments:
- '#filesystem'
- '%dump_file_convention%' // your config parameter

Symfony2 multi-level dynamic router

I have a current project that has to displays both defined pages with specific entities, what is very easy to manage with Symfony2, and content pages on different layouts, what is - I guess - a bit less common.
I get in trouble trying to build the routing system.
For instance, if I have to display a page with some news,
I would like to update the router of my bundle with a new route like :
my_bundle_news_page:
pattern: /news
defaults:
_controller: MyBundle:NewsController:indexAction
But how to manage a dynamic router that could have a totally custom URL on many levels ?
Let's imagine I've got a "Page" Entity, that is self-references for an optionnal "parent-child" relation.
I don't think I can just use any config YAML file for this specific routing ?!
my_bundle_custom_page:
pattern: /{slug}
defaults:
_controller: MyBundle:PageController:showAction
This would bind all the first-level pages:
/projects
/about
/contact
/our-project
What about a page that would be displayed with, for instance, a slug like:
/our-project/health
In fact any URL...
/{slug-level1}/{slug-level2}/{slug-level3} etc.
Cause the pages are supposed to change and be updated from webmastering.
I guess the best way would be to have a router that compare the {slug} with a database field (entity property)
I read in the Symfony-CMF doc that it is possible to write a service based a route provider:
namespace MyBundle\Routing;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route as SymfonyRoute;
use MyBundle\Entity\PageRepository;
class RouteProvider extends PageRepository {
public function findPageBySlug($slug)
{
// Find a page by slug property
$page = $this->findOneBySlug($slug);
if (!$page) {
// Maybe any custom Exception
throw $this->createNotFoundException('The page you are looking for does not exists.');
}
$pattern = $page->getUrl(); // e.g. "/first-level/second-level/third-level"
$collection = new RouteCollection();
// create a new Route and set our page as a default
// (so that we can retrieve it from the request)
$route = new SymfonyRoute($pattern, array(
'page' => $page,
));
// add the route to the RouteCollection using a unique ID as the key.
$collection->add('page_'.uniqid(), $route);
return $collection;
}
}
But how to set it up as a service ? Are there some requirements ?
How could this kind of thing work, does it add a route to the RouteCollection when request is called ?
And will I be able to bind any route in this way ?
EDIT : services.yml of my bundle
parameters:
cmf_routing.matcher.dummy_collection.class: Symfony\Component\Routing\RouteCollection
cmf_routing.matcher.dummy_context.class: Symfony\Component\Routing\RequestContext
cmf_routing.generator.class: Symfony\Cmf\Bundle\RoutingBundle\Routing\ContentAwareGenerator
cmf_routing.nested_matcher.class: Symfony\Cmf\Component\Routing\NestedMatcher\NestedMatcher
cmf_routing.url_matcher.class: Symfony\Cmf\Component\Routing\NestedMatcher\NestedMatcher
fsbcms.chain_router.class: Symfony\Cmf\Component\Routing\ChainRouter
fsbcms.route_provider.class: FSB\CMSBundle\Routing\RouteProvider
fsbcms.dynamic_router.class: Symfony\Cmf\Component\Routing\DynamicRouter
fsbcms.route_entity.class: null
services:
fsbcms.router:
class: %fsbcms.chain_router.class%
arguments:
- "#logger"
calls:
- [setContext, ["router.request_context"]]
fsbcms.route_provider:
class: "%fsbcms.route_provider.class%"
arguments:
- "#doctrine"
cmf_routing.matcher.dummy_collection:
class: "%cmf_routing.matcher.dummy_collection.class%"
public: "false"
cmf_routing.matcher.dummy_context:
class: "%cmf_routing.matcher.dummy_context.class%"
public: false
cmf_routing.generator:
class: "%cmf_routing.generator.class%"
arguments:
- "#fsbcms.route_provider"
- "#logger"
calls:
- [setContainer, ["service_container"]]
- [setContentRepository, ["cmf_routing.content_repository"]]
cmf_routing.url_matcher:
class: "%cmf_routing.url_matcher.class%"
arguments: ["#cmf_routing.matcher.dummy_collection", "#cmf_routing.matcher.dummy_context"]
cmf_routing.nested_matcher:
class: "%cmf_routing.nested_matcher.class%"
arguments: ["#fsbcms.route_provider"]
calls:
- [setFinalMatcher, ["cmf_routing.url_matcher"]]
fsbcms.dynamic_router:
class: "%fsbcms.dynamic_router.class%"
arguments:
- "#router.request_context"
- "#cmf_routing.nested_matcher"
- "#cmf_routing.generator"
tags:
- { name: router, priority: 300 }
I suggest taking a look at the Symfony CMF routing component and the CmfRoutingBundle (to implement the component in symfony).
The Routing component uses a chain router, which is irrelevant for this question but it's good to know. The chain router chains over a queue of routers. The component provides a DynamicRouter that uses a NestedMatcher. That's exactly what you want.
The NestedMatcher uses a Route provider to get the routes from a dynamic source (e.g. a database). You are showing an example of a Route provider in your question.
Furthermore, it uses a FinalMatcher to match the route. You can just pass an instance of Symfony\Cmf\Component\Routing\NestedMatcher\UrlMatcher, as you are doing not too difficult things.
Take a look at the docs of the RoutingBundle to learn how to activate the chain router and then create a route provider which loads the routes, make a service:
acme_routing.route_provider:
class: Acme\RoutingBundle\Provider\DoctrineOrmProvider
arguments: ["#doctrine"]
Now, you can create a NestedMatcher service:
acme_routing.url_matcher:
class: Symfony\Cmf\Component\Routing\NestedMatcher\UrlMatcher
arguments: ["#cmf_routing.matcher.dummy_collection", "#cmf_routing.matcher.dummy_context"]
acme_routing.nested_matcher:
class: Symfony\Cmf\Component\Routing\NestedMatcher
arguments: ["#acme_routing.route_provider"]
calls:
- [setFinalMatcher, ["acme_routing.url_matcher"]]
Now, register the DynamicRouter and put it in the chain:
acme_routing.dynamic_router:
class: Symfony\Cmf\Component\Routing\DynamicRouter
arguments:
- "#router.request_context"
- "#acme_routing.nested_matcher"
- "#cmf_routing.generator"
tags:
- { name: router, priority: 300 }
Now, it should work and should load the routes from the database and match them against the request.

Symfony2 SonataAdmin: "Access Denied" Exception when trying to extend SonataUserAdmin

I need to extend SonataUser to set a field called isAdmin to true when a user is being created from the backend.
I have different User groups for ADMIN => (can create admin users and perform CRUD on other entities) and STAFF => (can perform CRUD on other entities).
Customers register from the frontend.
Both backend_users (STAFF) and customers are instances of the User entity, which extends SonataUser.
Till now I was using the default User and Group Admin classes. Here is how my app/config/config.yml looked
...app/config/config.yml...
users:
label: Users
items: [ sonata.user.admin.user ]
groups:
label: Groups
items: [sonata.user.admin.group]
...
It worked fine for me.
Now I needed to customize the default implementation so I copied the code from Sonata/UserBundle/User/BaseUser.php to <my namespace>/AdminBundle/Admin/BackendUser.php
I created the new service and mapped it in config.yml
...app/config/config.yml...
users:
label: Users
items: [ gd_admin.backend_user ]
groups:
label: Groups
items: [sonata.user.admin.group]
...
...GD/AdminBundle/Resources/services.yml...
parameters:
gd_admin.backend_user.class: GD\AdminBundle\Admin\BackendUserAdmin
..
services:
gd_admin.backend_user:
class: %gd_admin.backend_user.class%
tags:
- { name: sonata.admin, manager_type: orm, label: Backend User }
arguments: [null, GD\AdminBundle\Entity\User, null]
# NOTE: No group defined in tags
...
Earlier I had granted the following roles my ADMIN Group:
'ROLE_SONATA_USER_ADMIN_USER_EDIT',
'ROLE_SONATA_USER_ADMIN_USER_LIST',
'ROLE_SONATA_USER_ADMIN_ USER _CREATE',
'ROLE_SONATA_USER_ADMIN_ USER _VIEW',
'ROLE_SONATA_USER_ADMIN_ USER _DELETE',
'ROLE_SONATA_USER_ADMIN_ USER _OPERATOR',
'ROLE_SONATA_USER_ADMIN_ USER _MASTER',
Now they are:
'ROLE_GD_ADMIN_BACKEND_USER_EDIT',
'ROLE_GD_ADMIN_BACKEND_USER_LIST',
'ROLE_GD_ADMIN_BACKEND_USER_CREATE',
'ROLE_GD_ADMIN_BACKEND_USER_VIEW',
'ROLE_GD_ADMIN_BACKEND_USER_DELETE',
'ROLE_GD_ADMIN_BACKEND_USER_OPERATOR',
'ROLE_GD_ADMIN_BACKEND_USER_MASTER',
When I log into my admin/dashboard
I am able to see the BackendUser in Admin Dashboard widget.
But when I click on the "List" or "Add new" I get a 403: Access Denied Exception.
Where am I going wrong?
Thanks,
Amit
I don't think you have to mess around with the BaseUser class from the sonata user bundle.
Instead you could create a new admin crud in your own bundle based on the sonata user admin crud (Sonata\UserBundle\Admin\Document\UserAdmin) and extend it with a prePersist() method to set isAdmin to true:
public function prePersist($object)
{
$object->setIsAdmin(true);
}
prePersist is actually a hook that is called before persisting a new entity.

Resources