We launched new forums in March 2019—join us there. In a hurry for help with your website? Get Help Now!
  • Quote from: mt85 at May 25, 2010, 09:21 PM

    Is my implementation of findpolicy() correct or is there something wrong?
    Oh, sorry... I assumed you tested the query. wink

    Here is what your modTest class should look like (note the __construct() is not necessary if you are doing nothing but calling parent::__construct(); a leftover from xPDO 1.0 that supported PHP 4, but not E_STRICT compliance):
    <?php
    class modTest extends modAccessibleSimpleObject {
        public function findPolicy($context = '') {
            $policy = array();
            $context = !empty($context) ? $context : $this->xpdo->context->get('key');
            if (empty($this->_policies) || !isset($this->_policies[$context])) {
                $accessTable = $this->xpdo->getTableName('modAccessTest');
                $policyTable = $this->xpdo->getTableName('modAccessPolicy');
                $sql = "SELECT acl.target, acl.principal, acl.authority, acl.policy, p.data FROM {$accessTable} acl " .
                        "LEFT JOIN {$policyTable} p ON p.id = acl.policy " .
                        "WHERE acl.principal_class = 'modUserGroup' " .
                        "AND acl.target = :test " .
                        "GROUP BY acl.target, acl.principal, acl.authority, acl.policy";
                $bindings = array(
                    ':test' => $this->get('id')
                );
                $query = new xPDOCriteria($this->xpdo, $sql, $bindings);
                if ($query->stmt && $query->stmt->execute()) {
                    while ($row = $query->stmt->fetch(PDO::FETCH_ASSOC)) {
                        $policy['modAccessTest'][$row['target']][] = array(
                            'principal' => $row['principal'],
                            'authority' => $row['authority'],
                            'policy' => $row['data'] ? xPDO :: fromJSON($row['data'], true) : array(),
                        );
                    }
                }
                $this->_policies[$context] = $policy;
            } else {
                $policy = $this->_policies[$context];
            }
            return $policy;
        }
    }
    ?>

    Unless you wanted these policies to be attached to specific Contexts as well?
      • 20546
      • 74 Posts
      Great, one step further. It works with this implementation.
      But there are three more questions:

      – Should it be possible to use modPrincipal as the principal_class in the table? Because if I use modPrincipal it doesn’t consider the permissions that I set.

      - When I change the permissions from a database entry while I’m logged in i need to "flush all sessions" and log in again to see the changes. Is this normal?

      – When I’m logged out I get this: (Shouldn’t the modAccessTest-Array be empty in this state?)
       Array
      (
          [modx.user.contextTokens] => Array()
          [modx.user..attributes] => Array
              (
                  [web] => Array
                      (
                          [modAccessContext] => Array()
                          [modAccessResourceGroup] => Array()
                          [modAccessCategory] => Array()
                          [modAccessTest] => Array()
                              (
                                  [2] => Array
                                      (
                                          [0] => Array
                                              (
                                                  [principal] => 0
                                                  [authority] => 0
                                                  [policy] => Array
                                                      (
                                                      )
      ...
              )
      
          [webDocgroups] => Array()
      )
      • Quote from: mt85 at May 25, 2010, 11:25 PM

        – Should it be possible to use modPrincipal as the principal_class in the table? Because if I use modPrincipal it doesn’t consider the permissions that I set.
        No; modPrincipal is simply an abstract class that both modUser and modUserGroup extend. At the moment, all of the core only supports a principal_class of modUserGroup, but your findPolicy()/loadAttributes()/getAttributes() implementations can support either. This is why you should extend modUser rather than hacking on the modPrincipal/modUser implementations of loadAttributes()/getAttributes(); you can override their behavior in your modUser derivative however you choose.

        Quote from: mt85 at May 25, 2010, 11:25 PM

        - When I change the permissions from a database entry while I’m logged in i need to "flush all sessions" and log in again to see the changes. Is this normal?
        Yep; in order to be in any way efficient, the user policies are loaded into session and those policies must be explicitly reloaded or users must be logged out and back in if they change. In addition, Resources and Elements cache their policy data as well, so clearing the cache after a change to any ACL is also necessary.

        Quote from: mt85 at May 25, 2010, 11:25 PM

        – When I’m logged out I get this: (Shouldn’t the modAccessTest-Array be empty in this state?)
        Are you sure you don’t have an empty row in your modAccessTest table? The second switch statement in modUser::loadAttributes() looks for such rows to assign anonymous policies.
          • 20546
          • 74 Posts
          you can override their behavior in your modUser derivative however you choose
          Can I extend modUser in my main table class with loadAttributes()/getAttributes() like I did it with findpolicy()?

          the user policies are loaded into session and those policies must be explicitly reloaded or users must be logged out and back in if they change
          Say, I create a web-application that shares data to different users. When I add a new database record it should be immediately visible (withoud logout and re-login) for assigned users. Is it recommended to use $modx->user->getAttributes(’modTest’, ’web’, true); before each database-call? I tried it and it works, but I don’t know if it’s meant for this case...
          $modx->user->getAttributes('modTest', 'web', true);
          
          $tests = $modx->getCollection('modTest');
          foreach ($tests as $test) {
          echo $test->get('name')."<br />";
          }
          


          Is there a method to simply check if the user has access to a specific database record? Because if I use getObject(’modTest’,3) with an id that I don’t have access to, I get a System Error instead of a custom error. If the id is accessible everything works fine.
          • Quote from: mt85 at May 26, 2010, 12:03 PM

            you can override their behavior in your modUser derivative however you choose
            Can I extend modUser in my main table class with loadAttributes()/getAttributes() like I did it with findpolicy()?
            No, you can’t extend modAccessibleObject and modUser in the same class. It has to be a separate modUser derivative only.

            Quote from: mt85 at May 26, 2010, 12:03 PM

            the user policies are loaded into session and those policies must be explicitly reloaded or users must be logged out and back in if they change
            Say, I create a web-application that shares data to different users. When I add a new database record it should be immediately visible (withoud logout and re-login) for assigned users. Is it recommended to use $modx->user->getAttributes(’modTest’, ’web’, true); before each database-call? I tried it and it works, but I don’t know if it’s meant for this case...
            $modx->user->getAttributes('modTest', 'web', true);
            
            $tests = $modx->getCollection('modTest');
            foreach ($tests as $test) {
            echo $test->get('name')."<br />";
            }
            


            Is there a method to simply check if the user has access to a specific database record? Because if I use getObject(’modTest’,3) with an id that I don’t have access to, I get a System Error instead of a custom error. If the id is accessible everything works fine.
            A system error? What do you mean? If the record is not accessible, getObject() will return null.
            As for changing ACLs on the fly, well, it’s not designed for that. In fact, in that case, I would use some other object to provide the access control, either an organizational entity like Resource Groups for protecting Resources, or Categories for protecting Elements, or a custom checkPolicy() implementation on modTest that looks at a real-time table to determine access for every row you try to load. The former is better, the latter would not be scalable.
              • 20546
              • 74 Posts
              That’s the error I get: Fatal error: Call to a member function get() on a non-object in /home/httpd/vhosts/test.com/modx/httpdocs/core/cache/elements/modsnippet/23.include.cache.php on line 69

              As for changing ACLs on the fly, well, it’s not designed for that.
              Are there any disadvantages when I use: $modx->user->getAttributes(’modTest’, ’web’, true) before each database call?

              Is there already a tutorial about how to create something like the organizational entity like in Resource Groups? Is it easy to achieve?
              • Quote from: mt85 at May 26, 2010, 06:54 PM

                That’s the error I get: Fatal error: Call to a member function get() on a non-object in /home/httpd/vhosts/test.com/modx/httpdocs/core/cache/elements/modsnippet/23.include.cache.php on line 69
                <?php
                $object = $modx->getObject('ClassName', 3);
                if (!$object) {
                    // handle error here somehow
                    $modx->sendUnauthorizedPage();
                }
                // code that tries to use the object if it is accessible
                

                The other option is to design your policy so that all (including anonymous) users have permission to load the object; in that case you can $object->checkPolicy(’view’) or $object->checkPolicy(’list’) or any other permission you want to design for use in your application (IOW to be checked explicitly).

                modAccessibleObject automatically uses three permissions by default: load, save, remove. These determine if an object can be loaded (by getObject/getCollection/etc.), save()’d, or remove()’d. Any other permissions you put in your policy you must check in your code explicitly.



                Quote from: mt85 at May 26, 2010, 06:54 PM

                As for changing ACLs on the fly, well, it’s not designed for that.
                Are there any disadvantages when I use: $modx->user->getAttributes(’modTest’, ’web’, true) before each database call?
                Well, having to reload the user policies from the database is definitely slower than being able to just use the data loaded from the session.

                Quote from: mt85 at May 26, 2010, 06:54 PM

                Is there already a tutorial about how to create something like the organizational entity like in Resource Groups? Is it easy to achieve?
                It’s just a "related object" that serves as a container for your main object in a one-to-many relationship, i.e. one modTestGroup to many modTest instances. You can see how this is done in the modResourceGroup queries in loadAttributes() and the findPolicy() implementation in modResource. You just have to "find" your policies for modTest by modTestGroup and "load" them for your user the same way so they can be compared by the primary key of the modTestGroup (instead of modTest).

                Note that this is only going to help if you can identify the UserGroups and modTestGroups in advance. Then when you add a modTest record, access will be determined by what modTestGroup it is assigned to.
                  • 20546
                  • 74 Posts
                  I’ll try to create this container now. Is it right to just need the following tables (Hotels replaces Test)?

                  - modAccessHotels
                  - modHotelsGroup
                  - modHotels

                  <?xml version="1.0" encoding="UTF-8"?>
                  <model package="hotels" baseClass="xPDOObject" platform="mysql" defaultEngine="MyISAM" tablePrefix="tw_" phpdoc-package="hotels" phpdoc-subpackage="model">
                  
                      <object class="modAccessHotels" table="access_hotels" extends="modAccess">
                          <aggregate alias="Target" class="modHotels" local="target" foreign="id" owner="foreign" cardinality="one" />
                      </object>
                  
                      <object class="modHotelsGroup" table="hotel_groups" extends="xPDOSimpleObject">
                          <field key="hotels_group" dbtype="int" precision="10" phptype="integer" null="false" default="0" index="index" />
                          <field key="hotels" dbtype="int" precision="10" phptype="integer" null="false" default="0" index="index" />        
                          <aggregate alias="Hotels" class="modHotels" key="id" local="document" foreign="id" cardinality="one" owner="foreign" />
                      </object>
                  
                      <object class="modHotels" table="hotels" extends="modAccessibleSimpleObject">
                          <field key="name" dbtype="varchar" precision="100" phptype="string" null="false" default="" index="index" />
                          <field key="address" dbtype="varchar" precision="255" phptype="string" null="false" default="" />
                          <field key="city" dbtype="varchar" precision="255" phptype="string" null="false" default="" />
                          <field key="country" dbtype="varchar" precision="20" phptype="string" null="false" default="" />
                          <composite alias="HotelsGroup" class="modHotelsGroup" local="id" foreign="hotels" cardinality="many" owner="local" />
                          <composite alias="Acls" class="modAccessHotels" local="id" foreign="target" owner="local" cardinality="many" />
                      </object>
                  
                  </model>
                  • If you want a many-to-many relationship, i.e. each hotel can be in many groups and each group can have many hotels, then you would want one additional "cross-reference" table object between them, i.e.
                    <?xml version="1.0" encoding="UTF-8"?>
                    <model package="hotels" baseClass="xPDOObject" platform="mysql" defaultEngine="MyISAM" tablePrefix="tw_" phpdoc-package="hotels" phpdoc-subpackage="mysql">
                    
                        <object class="twAccessHotel" table="access_hotels" extends="modAccess">
                            <aggregate alias="Target" class="twHotel" local="target" foreign="id" owner="foreign" cardinality="one" />
                        </object>
                    
                        <object class="twHotelGroup" table="hotel_groups" extends="xPDOSimpleObject">
                            <field key="name" dbtype="varchar" precision="100" phptype="string" null="false" default="" index="unique" />
                            <composite alias="Hotels" class="twHotelGroups" local="id" foreign="group" cardinality="many" owner="local" />
                        </object>
                    
                        <object class="twHotelGroupHotel" table="hotel_group_hotels" extends="xPDOObject">
                            <field key="group" dbtype="int" precision="10" phptype="integer" null="false" default="0" index="pk" />
                            <field key="hotel" dbtype="int" precision="10" phptype="integer" null="false" default="0" index="pk" />
                            <aggregate alias="Group" class="twHotelGroup" local="group" foreign="id" cardinality="one" owner="foreign" />
                            <aggregate alias="Hotel" class="twHotel" local="hotel" foreign="id" cardinality="one" owner="foreign" />
                        </object>
                    
                        <object class="twHotel" table="hotels" extends="modAccessibleSimpleObject">
                            <field key="name" dbtype="varchar" precision="100" phptype="string" null="false" default="" index="index" />
                            <field key="address" dbtype="varchar" precision="255" phptype="string" null="false" default="" />
                            <field key="city" dbtype="varchar" precision="255" phptype="string" null="false" default="" />
                            <field key="country" dbtype="varchar" precision="20" phptype="string" null="false" default="" />
                            <composite alias="Groups" class="twHotelGroupHotel" local="id" foreign="hotel" cardinality="many" owner="local" />
                            <composite alias="Acls" class="twAccessHotel" local="id" foreign="target" owner="local" cardinality="many" />
                        </object>
                    
                    </model>

                    NOTE: I changed mod to tw just to demonstrate that the prefix used on our core MODx classes is meant to isolate core work from custom work. IOW, it will be a recommended best practice not to use mod as your class prefix to avoid any future conflict with the core classes. Not that we’d introduce a Hotel class, but... wink
                      • 28215
                      • 4,149 Posts
                      Just remember, if you use a custom table prefix for a 3PC, you then can’t have more than one MODx install in one database with that 3PC, since the table prefixes would conflict.
                        shaun mccormick | bigcommerce mgr of software engineering, former modx co-architect | github | splittingred.com