Multiple Contexts: One ReadOnly, One FullControl - modx

Multi-Context site has two (in reality more) contexts.
We need to have a user (group) where every user can edit in one context and only VIEW the other context.
The site is designed that there is no single root node in each context (because of friendly urls and logic that home is not parent of any other page).
Resource-Groups seems to be not working, because you have to add each resource manually in the group (there are tons of documents in).
Any Ideas how to handle this?

You have to use resource groups for this and a plugin, that sets the resource group on context base during saving the resource. The existing resources could be set by a one time running snippet.
The code for the plugin is quite simple, it has to run in onDocFormSave:
<?php
/**
* SetResourceGroup
*
* #package setresourcegroup
*/
/** #var $modx modX */
/** #var $scriptProperties array */
/* only operate on new resources */
if ($mode != modSystemEvent::MODE_NEW) {
$modx->log(xPDO::LOG_LEVEL_INFO, 'Old Resource ' . $resource->get('id'));
return;
} else {
$modx->log(xPDO::LOG_LEVEL_INFO, 'New Resource ' . $resource->get('id'));
switch($resource->get('context_key')) {
case 'en':
$group = 'Translator (en)';
if (!$resource->joinGroup($group)) {
$modx->log(xPDO::LOG_LEVEL_ERROR, 'Can\'t add the resource ' . $resource->get('id') . 'to the Resource Group "'. $group . '"');
}
break;
}
$group = 'Administrator';
if (!$resource->joinGroup($group)) {
$modx->log(xPDO::LOG_LEVEL_ERROR, 'Can\'t add the resource ' . $resource->get('id') . 'to the Resource Group "'. $group . '"');
}
}
return;

Related

Laravel Eloquent relationship query not working

I am attempting to build a query where a product is updated via an API. The application is built in Angular2 wrapped in a NodeJS layer.
A product has a many-to-many relationship with files. What I want to happen is that when data is sent to the API to update a file, a Eloquent query checks whether this relationship already exists and if not it adds a relationship into the 'file_product' table. I have this functionality working fine with a category many-to-many relationship but for some reason this is not working on the files relationship. here is my code:
Product Controller update function:
public function update(Request $request, $id)
{
if($id && is_numeric($id)) {
try {
$requestProductVars = $request->get('product');
$product = Product::find($id);
$product->title = $requestProductVars['title'];
$product->description = $requestProductVars['description'];
$product->images = json_encode($requestProductVars['images']);
$product->SKU = $requestProductVars['SKU'];
$product->manufacturer_id = $requestProductVars['manufacturer_id'];
$product->active = (($requestProductVars['active'] === true) ? '1' : '0');
if($request->get('categories')) {
$categories = $request->get('categories');
foreach($categories as $categoryId) {
$category = Category::find($categoryId);
if(!$product->categories->contains($category)) {
$product->categories()->save($category);
}
}
}
if(count($requestProductVars['files'])) {
$files = $requestProductVars['files'];
foreach($files as $file) {
$fileId = $file['id'];
$fileRecord = File::find($fileId);
if(!$product->files->contains($fileRecord)) {
$product->files()->save($fileRecord);
}
}
}
$product->save();
As you can see, I check to see if there is a 'files' property on the request and if there is I loop through each file, get the id and check if the relationship exists using:
if(!$product->files->contains($fileRecord)) {
The files property contains an array of File objects.
My code seems to stop here and doesn't even seem to execute if. The category functionality works fine in this code though which is odd and I am doing exactly the same thing here.
I checked the models for Product and File and the many-to-many relationships are defined here fine:
Product.php
/**
* Returns all files associated with a product
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function files() {
return $this->belongsToMany('App\File')->withTimestamps();
}
File.php
/**
* Returns all products related to a file
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function products() {
return $this->belongsToMany('App\Product')->withTimestamps();
}
Can anyone see why my code doesn't seem to be working?
Thanks
Ok I discovered what the problem was.
Before I abstracted out the files to products functionality I had a cell in the products table called 'files'. I had forgotten to delete this cell and this was causing a conflict when I was trying to query the files relationship. Hope this helps someone in the future.

Symfony2 - Adding controller security to limit the user to a specific blog access

I am attempting to implement security to restrict access to specific blogs by using USER_ROLE for each user. Each blog page has 1 owner that can post to it.
This is built on what I've learned from the fantastic tutorials from KNP University.
http://knpuniversity.com/screencast/symfony2-ep2
I've set this up using access_control in security.yml to limit access to each user based on their USER_ROLE. (user1 has access to /job1/new and /job1/create in order to create/edit/delete posts on the blog page---only 1 user has access to each blog page)
access_control:
- { path: ^/job1/new, roles: [ROLE_USER1, ROLE_ADMIN] }
- { path: ^/job2/new, roles: [ROLE_USER2, ROLE_ADMIN] }
- { path: ^/job3/new, roles: [ROLE_USER3, ROLE_ADMIN] }
- { path: ^/job1/create, roles: [ROLE_USER1, ROLE_ADMIN] }
- { path: ^/job2/create, roles: [ROLE_USER2, ROLE_ADMIN] }
- { path: ^/job3/create, roles: [ROLE_USER3, ROLE_ADMIN] }
Each /job1, /job2 etc. are separate blog pages. And I am using an if statement in Twig to determine which user has access to create/edit/delete posts.
{% if is_granted('ROLE_USER1') %}
<li>Add New Post</li>
{% endif %}
The problem is, as I add in more blog pages I will need to create more paths in the access control (e.g., /job4, /job5 etc.) which isn't an ideal solution although it does work.
I have detailed the code out here in the link below, as it was recommended to use security in the controller based on a Disqus conversation 'joe joe' with Ryan Weaver here --- http://knpuniversity.com/screencast/symfony2-ep2
My questions are:
1) Now that I have created a ManyToMany relationship with User and Category how do I setup security in the controller to prevent other users from accessing the create/edit/delete actions that they don't have the roles for?
2) How do I hide the option in Twig for create/edit/delete using this method / also what do I add in the access_control using this approach?
It is unrealistic to grow access control such that it needs to explicitly include each new user. Rather, one can use ROLE_USER to only allow access to editing/creating any entity (e.g., a blog) to an authenticated user. Once the user has authenticated, a controller can provide access to that user's blog entities.
This requires a one-to-many relationship between user and blogs. In the controller, it then becomes a simple matter of something like this:
...
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
/**
* ...
* #Security("has_role('ROLE_USER')")
*/
class BlogifyController extends Controller
{
public function blogAction
{
$user = $this->getUser();
$blogs = $user->getBlogs();
// do your form thing, etc.
...
}
}
I think you should use builtin Voter from Security Bundle. So, you create a Blog entity with 1:1 relationship to User Entity, then create a Voter service, build you logic in vote method, use that voter in controller, that's it. Here is sample example:
class SomeVoter implements VoterInterface
{
const CREATE = 'create';
const EDIT = 'edit';
const DELETE = 'delete';
/**
* #param string $attribute
* #return bool
*/
public function supportsAttribute($attribute)
{
return in_array($attribute, array(
self::CREATE,
self::EDIT,
self::DELETE
));
}
/**
* #param string $class
* #return bool
*/
public function supportsClass($class)
{
$supportedClass = 'Acme\DemoBundle\Entity\Blog';
return $supportedClass === $class || is_subclass_of($class, $supportedClass);
}
/**
* #param TokenInterface $token
* #param object $blog
* #param array $attributes
* #return int
*/
public function vote(TokenInterface $token, $blog, array $attributes)
{
....
$attribute = $attributes[0];
$user = $token->getUser();
switch($attribute) {
case 'edit':
if ($user->getId() === $blog->getUser()->getId()) {
return VoterInterface::ACCESS_GRANTED;
}
break;
....
}
...
}
}
controller action:
public function editAction($id)
{
$blog = ...;
if (false === $this->get('security.context')->isGranted('edit', $blog)) {
throw new AccessDeniedException('Unauthorised access!');
}
...
}

Change class_key permission in Modx Revo

I am looking for the permission to change the class_key of an resource but I can't find it. Any suggestions?
I need it so my client can change the resource-type of some resources by himself. He created some symlinks but they have to be weblinks.
Modx have permission "class_map" but it prevents any changes of this field. If you want to exclude certain types of resources you can use this plugin at event "OnManagerPageAfterRender".
<?php
// comma separated list of excluded from class list
$excluded = 'modSymLink,modStaticResource';
switch ($modx->event->name) {
case 'OnManagerPageAfterRender':
$controller_path = $controller->config['controller'];
if ($controller_path == 'resource/update' || $controller_path == 'resource/create') {
$my_js = "<script>Ext.onReady(function() {
Ext.getCmp('modx-resource-class-key').baseParams.skip = 'modXMLRPCResource,".$excluded."';
});</script>";
$controller->content = preg_replace('~(</head>)~i', $my_js . '\1', $controller->content);
}
break;
}

Symfony 2 ACL vs Voters

I want to use isGranted('EDIT', $userObject) for allow edit given user data by all administrators and managers and that one user.
Should I use ACL for control edit $userObject?
I have written extra Voter which check if logged user and given object are the same or user is manager or admin.
In acl I must add ACE for userObject for all administrators, managers and that one user.
Wchich way is recommended?
I am new in Symfony..
below is voter's code:
function vote(TokenInterface $token, $object, array $attributes)
{
$intersect=array_intersect(array('EDIT','VIEW' ), $attributes);
if (!empty($intersect))
{
//intersect is not empty, it seems to edit or view are in $attributes
//voter grants privileges for [user->granted object]
//manager->every customer, child-manager
//admin->every customer and manager
if ($token->getUser()->isAdmin())
{
return VoterInterface::ACCESS_GRANTED;
}
elseif ($token->getUser()->isCustomer())
{
//voter not want to think about customer grants, because customer grants currently are held in ACL
return VoterInterface::ACCESS_ABSTAIN;
}
/* #var $object \PSB\StoreBundle\Entity\Customer */
if (is_a($object, '\PSB\StoreBundle\Entity\Customer'))
{
if ($token->getUser()->isManager())
{
//managers also edit customers
return VoterInterface::ACCESS_GRANTED;
}
}
elseif (is_a($object, '\PSB\StoreBundle\Entity\Manager'))
{
/* #var $object \PSB\StoreBundle\Entity\Manager */
if ($token->getUser()->isManager())
{
//manager can edit own children
if ($token->getUser() == $object->getParent())
{
return VoterInterface::ACCESS_GRANTED;
}
}
}
}
return VoterInterface::ACCESS_ABSTAIN;
}
When your model already stores the data required to know if an action should be granted or not, it's really annoying to keep the ACL in sync with your real data.
So you should obviously implement your own voters for this.
PS: You should use $object instanceof Class instead of is_a($object, 'Class')

Symfony 2.1 - $this->get('security.context')->isGranted('ROLE_ADMIN') returns false even if Profiler says I have that role

I have a Controller action (the Controller has $this->securityContext set to $this->get('security.context') via JMSDiExtraBundle):
$user = $this->securityContext->getToken()->getUser();
$groupRepo = $this->getDoctrine()->getRepository('KekRozsakFrontBundle:Group');
if ($this->securityContext->isGranted('ROLE_ADMIN') === false) {
$myGroups = $groupRepo->findByLeader($user);
} else {
$myGroups = $groupRepo->findAll();
}
When I log in to the dev environment and check the profiler, I can see that I have the ROLE_ADMIN role granted, but I still get the filtered list of Groups.
I have put some debugging code in my Controller, and Symfony's RoleVoter.php. The string representation of the Token in my Controller ($this->securityContext->getToken()) and the one in RoleVoter.php are the same, but when I use $token->getRoles(), I get two different arrays.
My Users and Roles are stored in the database via the User and Role entities. Is this a bug that I found or am I doing something wrong?
Finally got it. A dim idea hit my mind a minute ago. The problem was caused my own RoleHierarchyInterface implementation. My original idea was to copy Symfony's own, but load it from the ORM instead of security.yml. But because of this, I had to totally rewrite the buildRoleMap() function. The diff is as follows:
private function buildRoleMap()
{
$this->map = array();
$roles = $this->roleRepo->findAll();
foreach ($roles as $mainRole) {
$main = $mainRole->getRole();
- $this->map[$main] = array();
+ $this->map[$main] = array($main);
foreach ($mainRole->getInheritedRoles() as $childRole) {
$this->map[$main][] = $childRole->getRole();
// TODO: This is one-level only. Get as deep as possible.
// BEWARE OF RECURSIVE NESTING!
foreach ($childRole->getInheritedRoles() as $grandchildRole) {
$this->map[$main][] = $grandchildRole->getRole();
}
}
}
}
This case - roles are set and are displayed in Symfony's profiler but isGranted returns false - can be happened when the role names does not start with prefix ROLE_.
Bad role name: USER_TYPE_ADMIN
Correct role name: ROLE_USER_TYPE_ADMIN

Resources