Symfony ACL => ObjectIdentity() is already associated with an ACL - symfony

I want to use ACL with Symfony 2 and load them in my fixtures.
The following code works perfectly but if I uncomment array('myprivateroom', 'user1', 'view'), I get the following error:
[Symfony\Component\Security\Acl\Exception\AclAlreadyExistsException] ObjectIdentity(1, Acme\RoomBundle\Entity\Room) is already associated with an ACL.
I think the problem is here $acl = $aclProvider-createAcl(ObjectIdentity::fromDomainObject($room)); but I don't know how to solve it.
public function load(ObjectManager $manager)
{
$datas = array(
array('myprivateroom', 'admin', 'owner'),
//array('myprivateroom', 'user1', 'view'),
array('mypublicroom', 'user1', 'owner'),
);
foreach ($datas as $data) {
// creating the ACL
$room = $this->getReference($data[0]);
$aclProvider = $this->container->get('security.acl.provider');
$objectIdentity = ObjectIdentity::fromDomainObject($room);
//problem here when I uncomment array('myprivateroom', 'user1', 'view'),
$acl = $aclProvider->createAcl(ObjectIdentity::fromDomainObject($room));
// retrieving the security identity of the user
$user = $this->getReference($data[1]);
$securityIdentity = UserSecurityIdentity::fromAccount($user);
//create mask
$builder = new MaskBuilder();
$builder->add($data[2]);
$mask = $builder->get();
// grant owner access
$acl->insertObjectAce($securityIdentity, $mask);
$aclProvider->updateAcl($acl);
}
}
Thanks
Update:
If I do 2 arrays like
$datas = array(
array('myprivateroom', 'admin', 'owner'),
array('mypublicroom', 'user1', 'owner'),
);
$datas2 = array(
array('myprivateroom', 'user1', 'view'),
);
And 2 foreach but, in the second, I replace $acl = $aclProvider->createAcl(ObjectIdentity::fromDomainObject($room)); by $acl = $aclProvider->findAcl($objectIdentity);, it works perfectly but I don't think it's the right way, and the following doesn't work.
try {
$acl = $aclProvider->findAcl($objectIdentity);
} catch (AclNotFoundException $e) {
$acl = $aclProvider->createAcl($objectIdentity);
}

I had exactly the same problem as you and I've solved it like this.
try {
$acl = $this->get('security.acl.provider')->findAcl($idObjeto);
} catch (\Symfony\Component\Security\Acl\Exception\AclNotFoundException $e) {
$acl = $this->get('security.acl.provider')->createAcl($idObjeto);
}
I do not explain very well why. The only different is "\Symfony\Component\Security\Acl\Exception\AclNotFoundException"

I'm coming a bit late but in case someone needs to understand what happened with your code. If you loop through this array :
$datas = array(
array('myprivateroom', 'admin', 'owner'),
array('myprivateroom', 'user1', 'view'),
array('mypublicroom', 'user1', 'owner'),
);
you'll be using :
$acl = $aclProvider->createAcl(ObjectIdentity::fromDomainObject($room));
on first and second loop. It will try to create an access control list (ACL) for 'myprivateroom' twice. That's why it throws an error saying you already have a list for this Room entity.
ObjectIdentity(1, Acme\RoomBundle\Entity\Room) is already associated with an ACL
A good way to solve it is to try if it has an ACL, and create it if it doesn't (answer provided by #JGrinon). Otherwise you can organize your data in a multidimensionnal array and loop through it :
// PHP array but you could use a JSON array to be clearer
$datas = array(
array('myprivateroom',
array(
array('admin', 'owner'),
array('user1', 'view1')
)
),
array('mypublicroom',
array(
array('user1', 'owner'),
)
),
);
// Call ACL Provider service before the loop
$aclProvider = $this->container->get('security.acl.provider');
// Loop through the array
foreach ($datas as $data) {
// Get the room name
$room = $this->getReference($data[0]);
$objectIdentity = ObjectIdentity::fromDomainObject($room);
// The list is created only once for each room
$acl = $aclProvider->createAcl(ObjectIdentity::fromDomainObject($room));
// Loop through the list of users and associated rights
$list = $this->getReference($data[1]);
foreach ($list as $row) {
// Retrieving the security identity of the user
$user = $row[0];
$securityIdentity = UserSecurityIdentity::fromAccount($user);
// Create Mask
$builder = new MaskBuilder();
$builder->add($row[1]);
$mask = $builder->get();
// Grant access
$acl->insertObjectAce($securityIdentity, $mask);
};
// Update Access Control List
$aclProvider->updateAcl($acl);
}
Hope this helps ;)

Related

Login WP - Connect single field to an external api

I made a plugin to allow wordpress login with external api.
Everything works, now what I have to do is that when a user logs in for the first time, the plugin checks to see if it is already present on wp, and where it was not already present, it creates a new user by taking behind username, email and password.
The new user is created but I would like it to bring with it also the id field from the external api saving it in an ACF field.
This is the code created so far:
function au_auth($user, $username, $password)
{
$options = get_option('au_options');
$endpoint = $options['au_apiurl'];
$user_email_key = 'email';
$password_key = 'password';
// Makes sure there is an endpoint set as well as username and password
if (!$endpoint || $user !== null || (empty($username) && empty($password))) {
return false;
}
// Check user exists locally
$user_exists = wp_authenticate_username_password(null, $username, $password);
if ($user_exists && $user_exists instanceof WP_User) {
$user = new WP_User($user_exists);
return $user;
}
// Build the POST request
$login_data = array(
$user_email_key => $username,
$password_key => $password
);
$auth_args = array(
'method' => 'POST',
'headers' => array(
'Content-type: application/x-www-form-urlencoded'
),
'sslverify' => false,
'body' => $login_data
);
$response = wp_remote_post($endpoint, $auth_args);
// Token if success; Not used right now
$response_token = json_decode($response['response']['token'], true);
$response_code = $response['response']['code'];
if ($response_code == 400) {
// User does not exist, send back an error message
$user = new WP_Error('denied', __("<strong>Error</strong>: Your username or password are incorrect."));
} else if ($response_code == 200) {
// External user exists, try to load the user info from the WordPress user table
$userobj = new WP_User();
// Does not return a WP_User object but a raw user object
$user = $userobj->get_data_by('email', $username);
if ($user && $user->ID) {
// Attempt to load the user with that ID
$user = new WP_User($user->ID);
}
} else {
// The user does not currently exist in the WordPress user table.
// Setup the minimum required user information
$userdata = array(
'user_email' => $username,
'user_login' => $username,
'user_pass' => $password
);
// A new user has been created
$new_user_id = wp_insert_user($userdata);
// Assign editor role to the new user (so he can access protected articles)
wp_update_user(
array(
'ID' => $new_user_id,
'role' => 'editor'
)
);
// Load the new user info
$user = new WP_User ($new_user_id);
}
}
// Useful for times when the external service is offline
remove_action('authenticate', 'wp_authenticate_username_password', 20);
return $user;
}
Anyone have any way how to help me?
Resolved! I hope this will help those who have found themselves in the same situation as me:
add_filter('authenticate', 'au_auth', 10, 3);
add_filter('register_new_user', 'au_registration', 10, 3);
// add_filter('profile_update', 'au_profile_update', 10, 3);
// add_filter('edit_user_profile_update', 'au_profile_edit', 10, 3);
function au_auth($user, $username, $password)
{
$options = get_option('au_options');
$endpoint = $options['au_apiurl'];
// Makes sure there is an endpoint set as well as username and password
if (!$endpoint || $user !== null || (empty($username) && empty($password))) {
return false;
}
$auth_args = [
'method' => 'POST',
'headers' => [
'Content-type: application/x-www-form-urlencoded',
],
'sslverify' => false,
'body' => [
'email' => $username,
'password' => $password,
],
];
$response = wp_remote_post($endpoint, $auth_args);
// Token if success; Not used right now
$response_token = json_decode($response['response']['token'], true);
$body = json_decode($response['body'], true);
$response_status_code = $response['response']['code'];
$success = $body !== 'KO';
if (!$success) {
// User does not exist, send back an error message
$user = new WP_Error('denied', __('<strong>Error</strong>: Your username
or password are incorrect.'));
} elseif ($success) {
$idExternal = $body['Id'];
$nome = $body['Name'];
$cognome = $body['Surname'];
$email = $body['Email'];
$userobj = new WP_User();
$user = $userobj->get_data_by('email', $email);
if ($user && $user->ID) {
$user = new WP_User($user->ID);
} else {
$userdata = [
'user_email' => $email,
'user_login' => join(' ', [$name, $surname]),
'user_pass' => '----',
];
$new_user_id = wp_insert_user($userdata);
$new_user_composite_id = 'user_' . $new_user_id;
update_field('field_60084ad3970a8', $idExternal, $new_user_composite_id);
update_field('field_5f22ca201c7b0', $name, $new_user_composite_id);
update_field('field_5f22ccd498f40', $surname, $new_user_composite_id);
update_field('field_5f22ce7b7c1db', $email, $new_user_composite_id);
$user = new WP_User($new_user_id);
}
}
remove_action('authenticate', 'wp_authenticate_username_password', 20);
return $user;
}

How to use Lock Component in Symfony3.4 with username

I am trying to use the lock component in symfony 3.4, like it is described on
https://symfony.com/doc/3.4/components/lock.html
I want to prevent multiple data changes from different users.
For example user1 is calling the same company form with data, then user2
How can I tell user2, that editing data is blocked by user1 (incl username) ?
UPDATE:
It is used in backend, where a lot of employees editing data of customers, order etc.
this form is just for editing. that means, if they want to update some data, they click "edit". They should be informed when another employee changes this record before the data is loaded into the form. It sometimes takes some time for the employee to change everything. If the employee receives a message when saving it, they have to go back,reload the data and start all over again.
an example out of my controller:
public function CompanyEdit(Request $request)
{
$error = null;
$company_id = $request->get('id');
// if (!preg_match('/^\d+$/', $company_id)){
// return $this->showError();
// }
$store = new SemaphoreStore();
$factory = new Factory($store);
$lock = $factory->createLock('company-edit-'.$company_id, 30);
if(!$lock->acquire()) {
//send output with username
// this data is locked by user xy
return 0;
}
$company = $this->getDoctrine()->getRepository(Company::class)->find($company_id);
$payment = $this->getDoctrine()->getRepository(Companypay::class)->findOneBy(array('company_id' => $company_id));
$form = $this->createFormBuilder()
->add('company', CompanyFormType::class, array(
'data_class' => 'AppBundle\Entity\Company',
'data' => $company
))
->add('payment', CompanyPayFormType::class, array(
'data_class' => 'AppBundle\Entity\CompanyPay',
'data' => $payment
))
->getForm();
$form->handleRequest($request);
$company = $form->get('company')->getData();
$payment = $form->get('payment')->getData();
if ($form->isSubmitted() && $form->isValid()) {
$event = new FormEvent($form, $request);
if ($payment->getCompanyId() == null) {
$payment->setCompanyId($company->getId());
}
try {
$this->getDoctrine()->getManager()->persist($company);
$this->getDoctrine()->getManager()->persist($payment);
$this->getDoctrine()->getManager()->flush();
$this->container->get('app.logging')->write('Kundendaten geƤndert', $company->getId());
} catch (PDOException $e) {
$error = $e->getMessage();
}
if (null === $response = $event->getResponse()) {
return $this->render('customer/edit.html.twig', [
'form' => $form->createView(),
'company' => $company,
'error' => $error,
'success' => true
]);
}
$lock->release();
return $response;
}
You can't (Locks can't have any metadata), but you probably don't want this in the first place.
In this case, you create a Lock when a user opens the edit page and release it when a user submits the form. But what if the users opens the page and doesn't submit the form? And why can't a user even view the form?
This looks like a XY-problem. I think you're trying to prevent users to overwrite data without knowing. Instead, you can add a timestamp or hash to the form that changes after changing the entity. For example:
<form type="hidden" name="updatedAt" value="{{ company.updatedAt()|date('U') }}" />
And in your form:
<?php
if ($company->getUpdatedAt()->format('U') !== $form->get('updatedAt')->getData()) {
throw new \LogicException('The entity has been changed after you opened the page');
}
Disclaimer: code is not tested and just as an example how this solution can look like.

How to edit sonata user with avatar outside admin bundle

Almost same ISSUES: link, link
UPDATE INFO: - $user = $this->getUser(); set the old image while edit(error form submit). The image replaced with the submitted one value(only value not displaying). WHILE ERROR FORM SUBMIT - I NEED TO DISPLAY THE OLD MEDIA.
NO RELATION WITH SONATA ADMIN.
I have Admin and User role. Both have seperate admin area. User admin area has more complex structure.
I added an Image(avatar) to SonataUser , it works good at admin. Its OneToOne - User and Media.
To edit profile at User Dashboard( Its not SonataAdmin - its i created seperately, Its a simple symfony style).
code:
public function editProfileAction() {
$user = $this->getUser();
if (!is_object($user) || !$user instanceof UserInterface) {
throw $this->createAccessDeniedException('This user does not have access to this section.');
}
// Check user has allready media?
$om = $this->getUser()->getImage();
$oldPath = $om ? $this->getMediaPath($om->getId()) : NULL;
$form = $this->creteForm();
$formHandler = $this->get('sonata.user.profile.form.handler');
$process = $formHandler->process($user);
if ($process) {
// if new file - delete old file
$this->deleteOldMedia($om, $oldPath);
$this->flashMSG(0, 'Profile updated!');
return $this->redirectToRoute('fz_user');
}
$x = ['cmf' => '', 'pTitle' => 'Profile'];
return $this->render(self::TEMPLATE, ['x' => $x, 'form' => $form->createView()]);
}
By the above code, works - with one problem. The reference image of old file is not deleting at server folder. New files are added and entity works fine (displaying at template - fine).
So I tried with my own code,
public function editProfileAction() {
$request = $this->get('request');
$user = $this->getUser();
if (!is_object($user) || !$user instanceof UserInterface) {
throw $this->createAccessDeniedException('This user does not have access to this section.');
}
// Check user has allready media?
$om = $this->getUser()->getImage();
$oldPath = $om ? $this->getMediaPath($om->getId(), 'reference') : NULL;
$oldTN = $om ? $this->getMediaPath($om->getId(), 'admin') : NULL;
$form = $this->createForm(ProfileType::class, $user);
$form->handleRequest($request);
$em = $this->getDoctrine()->getEntityManager();
$data = $form->getData();
if ($form->isSubmitted() && $form->isValid()) {
if (($oldPath != NULL) && ($data->getImage()->getBinaryContent() != NULL)) {
$this->deleteFile($oldPath);
$this->deleteFile($oldTN);
}
$em->persist($user);
$em->flush();
$this->flashMSG(0, 'Profile updated!');
return $this->redirectToRoute('fz_user');
}
// $$user->setImage($om);
$x = ['cmf' => '', 'pTitle' => 'Profile'];
return $this->render(self::TEMPLATE, ['x' => $x, 'form' => $form->createView()]);
}
My own code works - with one problem, If image validation is error - the all image at the template are disappeared. So to check i added $user->setImage(NULL); , the result is, the Null image is shown.(NULL image means at template i do if(null){ display my image }). The backend process - image upadate works good.
For now - I'm satisfied with my code. Here I need to make $user->setImage(xx); to the real image. while form submit with error on media. ONly at error on media.
If no media and error submit - works (displaying image).
UPDATE:
I used $em->refresh($user); from this answer , also it failed to update my image.
WHAT I FOUND ISSUE WITH USER: Its not using 'ApplicationSonataUserBundle:User' for SYMFONY app.user . Thats why, when i give $em->refresh($user); it not modifing username and other details. But it modifing the new details of ApplicationSonataUserBundle:User
Finally to solve I REDIRECTED with flash msg..
$em = $this->getDoctrine()->getManager();
$user = $this->get('security.token_storage')->getToken()->getUser();
$entity = $em->getRepository('ApplicationSonataUserBundle:User')->find($user->getId());
if (!$entity) {
throw $this->createNotFoundException('Unable to find User entity.');
}
$form = $this->createForm(ProfileType::class, $entity);
if ($request->getMethod() === 'POST') {
$form->handleRequest($request);
if ($form->isValid()) {
$em->flush();
return $this->redirectToRoute('fz_user');
}
$em->refresh($user);
$this->flashMSG(1, '' . $form->getErrors(true, false));
return $this->redirectToRoute('fz_user_profile_edit');
}

Creating database schema on the fly

I would like to create database for user when he register. The code for database creation looks like this
$connectionFactory = $this->container->get('doctrine.dbal.connection_factory');
$connection = $connectionFactory->createConnection(array(
'driver' => 'pdo_mysql',
'user' => 'root',
'password' => 'mysecretpassword',
'host' => 'localhost',
'dbname' => 'userdatabase',
));
$params = $connection->getParams();
$name = isset($params['path']) ? $params['path'] : $params['dbname'];
unset($params['dbname']);
$tmpConnection = DriverManager::getConnection($params);
// Only quote if we don't have a path
if (!isset($params['path'])) {
$name = $tmpConnection->getDatabasePlatform()->quoteSingleIdentifier($name);
}
$error = false;
try {
$tmpConnection->getSchemaManager()->createDatabase($name);
echo sprintf('<info>Created database for connection named <comment>%s</comment></info>', $name);
} catch (\Exception $e) {
echo sprintf('<error>Could not create database for connection named <comment>%s</comment></error>', $name);
echo sprintf('<error>%s</error>', $e->getMessage());
$error = true;
}
$tmpConnection->close();
I have entities created for that database in AccountBundle but do not know how to create database schema when user database is created.
It is often used in the tests. An example can be seen in LiipFunctionalTestBundle.
$metadatas = $om->getMetadataFactory()->getAllMetadata();
$schemaTool = new SchemaTool($om);
$schemaTool->dropDatabase($name);
if (!empty($metadatas)) {
$schemaTool->createSchema($metadatas);
}

get('security.context')->isGranted in functional test

I want do one functional test over on service Symfony2. The idea is call before to the controller and after that, load the service with the function. The function is this one:
function save($title,$description,$setId,$html,$validate,$articles){
$articles = explode(',', $articles);
if (false === $this->container->get('security.context')->isGranted('ROLE_USER')) {
throw new \Exception("Not allowed");
}else{
$profileId = $this->container->get('security.context')->getToken()->getUser()->getId();
$userName = $this->container->get('security.context')->getToken()->getUser()->getUserName();
}
}
and now my test code is :
$client = static::createClient();
$crawler = $client->request('GET','/sets/save',
array(
"title"=>"rtyui",
"description"=>"aksdjhashdkjahskjdh",
"set_id"=>"",
"html"=>"",
"validate"=>1,
"articels"=>"3,4"
)
);
but doesn't work already that I have this lines:
if (false === $this->container->get('security.context')->isGranted('ROLE_USER')) {
throw new \Exception("Not allowed");
Now, the question is, how i can do the validation process? I've tried to do this validation process as show the documentation:
$client = static::createClient(array(), array(
'PHP_AUTH_USER' => 'username',
'PHP_AUTH_PW' => 'pa$$word',
));
but I got the same error.
Also you can login user by Security Token:
$client = static::createClient();
$container = $client->getContainer();
$container->get('security.context')->setToken(
new UsernamePasswordToken(
$user, null, 'main', $user->getRoles()
)
);
Where:
$user - instance of User entity with role ROLE_USER,
main - your security provider name

Resources