RSS
 

Leave a Reply

Tip:
If you need to post a piece of code use {code type=php}...{/code}.
Allowed types are "php", "mysql", "html", "js", "css".

Please do not escape your post (leave all ", <, > and & as they are!). If you have encoded characters and need to reverse ("decode") it, you can do that here!
 

 
  1. Matt

    January 2, 2012 at 17:19

    This is really great and something sorely missing from the core Cake distro. My only suggestion is that you do not assume the reader has so much in place already. I suspect most people who are looking for something like this are doing so because they could not get the complicated aro/aco stuff working easily and would love a step-by-step of getting tinyAuth up and running.

    Also, the examples and tutorials in the cookbook 2.0 have users/groups tables instead of users/roles. You might want to specify the schema your plugin expects since it is different from those.

     
  2. Matt

    January 2, 2012 at 17:24

    There is one thing I am having trouble with. When an action is not allowed, the site just hangs and returns nothing. Any suggestions on how to have it return some kind of page, or return back to where it came from with a flash error message?

    Here is what I have in my AppController beforeFilter()

    //Configure AuthComponent
    $this->Auth->authorize = array('Tools.Tiny');
    $this->set('authUser', $this->Auth->user());
    //$this->Auth->authenticate = array('Form');
    $this->Auth->loginAction = array('controller' => 'users', 'action' => 'login');
    $this->Auth->logoutRedirect = array('controller' => 'users', 'action' => 'login');
    $this->Auth->loginRedirect = array('controller' => 'contacts', 'action' => 'index');
     
  3. Mark

    January 2, 2012 at 22:40

    Good points. I will have to refine it.

    Ok, so roles in 1.3 have changed to groups in 2.0 then? But they still seem to be 1:N only (multiple roles/groups per user not possible). The documentation doesn't reveal much.

    About your problem:
    Your Auth component is responsable for that. The Tiny (or any other Auth for that matter) can only return true/false or the right to access this page. It used to redirect to /. But I opened a ticket (http://cakephp.lighthouseapp.com/projects/42648/tickets/2390-auth-component-should-not-redirect-to-index-if-loginredirect-is-set) because it doesnt make any sense if you are a logged in user (you would want to be redirected to $this->Auth->loginRedirect).
    And thats exactly what the core Auth component does:

    $this->flash($this->authError);
    $controller->redirect($controller->referer('/'), null, true);
     
  4. Matt

    January 3, 2012 at 04:07

    OK, I think I figured out part of the hanging issue. When a page is not authorized, it is trying to redirect to /, but that is also not authorized. I have not figured out how to specify / in the acl.ini file. It is probably obvious, but I'm still a cake newbie.

     
  5. Mark

    January 3, 2012 at 11:30

    Well, that is a misconfiguration :) Your homepage (/) should always be public. Use $this->Auth->allow() in your controller for this action.

     
  6. Forbin

    January 7, 2012 at 15:37

    Does this work in 1.3?

     
  7. jetwes

    January 9, 2012 at 12:24

    I always get a failure after login:

    Notice (8): Undefined index: role_id [APP/Plugin/Tools/Controller/Component/Auth/TinyAuthorize.php, line 69]

    Mein Model ist so definiert:

    class User extends AppModel{
                public $name = 'User';
                public $belongsTo = array(
                    'Group' => array(
                        'className' => 'Group',
                        'foreignKey' => 'group_id',
                    )
                );
     
                public $hasAndBelongsToMany = array(
                    'Role' => array(
                        'className'                 => 'Role',
                        'joinTable'                 => 'roles_users',
                        'foreignKey'                => 'user_id',
                        'assosciationForeignKey'    => 'role_id',
                        'unique'                    => true,
                    ),
                    'Shop' => array(
                        'className'                 => 'Shop',
                        'joinTable'                 => 'shops_users',
                        'foreignKey'                => 'shop_user_id',
                        'assosciationForeignKey'    => 'shop_id',
                        'unique'                    => true,
                    )
                );

    Any Hints?
    Good work – keep on!

     
  8. Mark

    January 9, 2012 at 12:32

    You need to make sure the session contains the role information after logging the user in:

    HABTM (multiple roles per user):
    User.Role.id1,id2,…,idx,

    BT (one role per user):
    User.role_id

     
  9. jetwes

    January 9, 2012 at 17:32

    got it, thanks again for the quick support! Is there a easy way to build a dynamic menu with the acls? (It depends on the role if a user can see the menu item)
    Keep up the good work!

     
  10. Mark

    January 11, 2012 at 01:54

    I updated the class to allow other parent model relations besides "User<–>Role". It should be closer to the cookbook now, where they use "Groups".

    @jetwes
    There sure is a way to do that. I would like to use sth like that very much, as well.
    Basically we would need a helper to display certain "navigation" blocks and hide the ones you don't have access to. With caching this should be not too resource-eating…

     
  11. Nitish

    January 24, 2012 at 11:31

    Hey Mark,

    Thanks for the good work.

    I am saying its good, because it looks like that &amp; you won't claim it being the fastest &amp; easiest..

    I am a newbie in Cake, I have setup cake2.0 &amp; I tried to follow your instructions but like "Matt" said, you can still make the tutorial more clear &amp; to the point &amp; step by step. I got confused at points like -

    "If you don’t want this, use Configure to store your keys like so:"

    I took some time to figure out that I need to do this in bootstrap file using Configure::write.. I am still not sure if this is the right way..

    Basically, steps starting from we downloading cake &amp; till the point where we are ready with a basic setup will be something that will help this a lot

    Thanks &amp; Hoping you help me out

     
  12. Nitish

    January 24, 2012 at 11:45

     
  13. kozima

    February 27, 2012 at 21:00

    Does this work in 1.3?[2]

     
  14. Mark

    February 27, 2012 at 22:18

    No. Pretty sure it won't work with < 2.0. Mainly because Auth greatly improved in 2.0 and works pretty different from what it used to.

    But with some effort you might be able to downgrade it.

     
  15. rihad

    May 16, 2012 at 17:42

    Hi, thanks for the code. But how do I use it? Where do I put tinyauthdb? https://github.com/justinledwards/tinyauthdb/blob/2.1/app/Controller/Component/Auth/TinyAuthorize.php
    Then do I need to configure the Model if I'm not going to use a HABTM table? I'll be using a static array of roles instead. Could you add a few lines how to set this up and get running? Many of us are new to Cake, but have stringent job time limits :) Thanks.

     
  16. rihad

    May 17, 2012 at 13:03

    I cannot authorize, although I pass the password authentication step.

    authorize() in TinyAuthorize.php receives $user that lacks any joins to roles table:

    array(
    'id' =&gt; (int) 6,
    'username' =&gt; 'rihad',
    'created' =&gt; '2012-05-15 16:30:10',
    'modified' =&gt; '2012-05-17 16:36:24',
    )

    so of course it can't find $user['Role']
    Here's my user class:
    class Milli extends AppModel {
    public $hasAndBelongsToMany = array(
    'Role' =&gt; array(
    'className' =&gt; 'Role',
    'joinTable' =&gt; 'roles_users',
    'foreignKey' =&gt; 'user_id',
    'assosciationForeignKey' =&gt; 'role_id',
    'unique' =&gt; 'keepExisting'));

    }

    class Role is empty.

    class MilliController extends AppController
    public $components = array(
    'Session',
    'Auth' =&gt; array(
    'loginRedirect' =&gt; array('controller' =&gt; 'milli', 'action' =&gt; 'index'),
    'logoutRedirect' =&gt; array('controller' =&gt; 'milli', 'action' =&gt; 'index'),
    'authenticate' =&gt; array('Form' =&gt; array('userModel' =&gt; 'Milli')),
    'authorize' =&gt; array('Tiny' =&gt; array('aclModel' =&gt; 'Role')),
    'loginAction' =&gt; array('controller' =&gt; 'milli', 'action' =&gt; 'login')));

    public function beforeFilter() {
    $this-&gt;Auth-&gt;allow('login', 'logout');
    }

    Please help…

     
  17. rihad

    May 17, 2012 at 13:05

    sorry here's the code again:

    array(
            'id' => (int) 6,
            'username' => 'rihad',
            'created' => '2012-05-15 16:30:10',
            'modified' => '2012-05-17 16:36:24',
    )
     
    so of course it can't find $user['Role']
    Here's my user class:
    class Milli extends AppModel {
            public $hasAndBelongsToMany = array(
                            'Role' => array(
                                'className'                 => 'Role',
                                'joinTable'                 =>
    'roles_users',
                                'foreignKey'                => 'user_id',
                                'assosciationForeignKey'    => 'role_id',
                                'unique'                    =>
    'keepExisting'));
     
    }
     
    class Role is empty.
     
    class MilliController extends AppController
           public $components = array(
                    'Session',
                    'Auth' => array(
                            'loginRedirect' => array('controller' =>
    'milli', 'action' => 'index'),
                            'logoutRedirect' => array('controller' =>
    'milli', 'action' => 'index'),
                            'authenticate' => array('Form' =>
    array('userModel' => 'Milli')),
                            'authorize' => array('Tiny' =>
    array('aclModel' => 'Role')),
                            'loginAction' => array('controller' =>
    'milli', 'action' => 'login')));
     
            public function beforeFilter() {
                    $this->Auth->allow('login', 'logout');
            }

    Please help…

     
  18. Mark

    May 17, 2012 at 13:08

    tinyauthdb is not my project. so i am not familiar with that.
    regarding your second post – did you place it in the plugin or in the app?

    make sure recursive is high enough.
    I use my own AuthExt class which does that all for me.
    You might want to do this, as well.

    PS: with cake2.2 you can now set containable for this data, as well.

     
  19. rihad

    May 17, 2012 at 16:18

    Thanks for replying, Mark. I put it in my app's Controllers/Components/Auth/
    recursive is 1 by default and should be enough"
    "1 Cake fetches a Group, its domain and its associated Users"
    I'll still try setting it to 2 or 3 tomorrow and see.

    Other than that should the retrieval of user data and joins happen automagically? All I have is a user model with HABTM to roles through roles_users. No reverse relationships of any kind.

     
  20. Mark

    May 17, 2012 at 16:21

    The core itself usually only supports belongsTo (single role) – therefore the user itself should have sufficed so far.
    That's why I recommend the AuthExt component to modify it accordingly.

     
  21. rihad

    May 17, 2012 at 17:32

    You mean a class that subclasses TinyAuthorize? Then how would I change "recursive" from there? Isn't it in the model's config?

     
  22. Mark

    May 17, 2012 at 17:33

    No, subclassing AuthComponent and overriding the part where it only fetches the User record itself.
    Or – as mentioned before – ugprade to 2.2 and use contain

     
  23. rihad

    May 18, 2012 at 06:15

    Hi, Mark. As you suggested I've added recursive =&gt; 1

    public $components = array(
                    'Session',
                    'Auth' => array(
                            'authenticate' => array('Form' => array('userModel' => 'Milli', 'recursive' => 1)),
                            'authorize' => array('Tiny' => array('aclModel' => 'Role')),

    And now _findUser() in BaseAuthenticate.php indeed fetches the user deeply with its Role. But TinyAuth still is getting the shallow user in its authorize(). Would you happen to know what's going on? The session (/tmp/sess_* files) is lacking the Role stuff.

     
  24. rihad

    May 18, 2012 at 06:56

    Maybe I should be doing manual joins, as described in the docs and as is required for HABTM tables? See "Joining Tables" towards the end of http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html

    AFAIK joins can only be specified in the find() calls. But how do I affect the joins if they're only called internally from within the Auth subsystem? Should I be doing that anyway?

     
  25. rihad

    May 18, 2012 at 07:53

    I've tried adding joins in my User model beforeFind():

    public function beforeFind(array $query) {
                    $query['joins'] = array(
                            array(
                                    'table' => 'roles_users',
                                    'alias' => 'RolesUser',
                                    'type' => 'INNER',
                                    'conditions' =>
    array('Milli.id=RolesUser.user_id')),
                            array(
                                    'table' => 'roles',
                                    'alias' => 'Role',
                                    'type' => 'INNER',
                                    'conditions' =>
    array('RolesUser.role_id=Role.id')));
     
                    return $query;
            }

    Now Model::find() correctly does receives the join info:
    /lib/Cake/Model/Model.php (line 2676)

    array(
            'conditions' => array(
                    'Milli.username' => 'rihad',
                    'Milli.password' => '17bce4ac9c39019189b7bba280af55de9fe1d9a7'
            ),
            'fields' => null,
            'joins' => array(
                    (int) 0 => array(
                            'table' => 'roles_users',
                            'alias' => 'RolesUser',
                            'type' => 'INNER',
                            'conditions' => array(
                                    (int) 0 => 'Milli.id=RolesUser.user_id'
                            )
                    ),
                    (int) 1 => array(
                            'table' => 'roles',
                            'alias' => 'Role',
                            'type' => 'INNER',
                            'conditions' => array(
                                    (int) 0 => 'RolesUser.role_id=Role.id'
                            )
                    )
            ),
            'limit' => (int) 1,
            'offset' => null,
            'order' => array(
                    (int) 0 => null
            ),
            'page' => (int) 1,
            'group' => null,
            'callbacks' => true,
            'recursive' => (int) 2

    But the User is still fetched with Role side by side, so
    BaseAuthenticate ignores Role.

    array(
            (int) 0 => array(
                    'Milli' => array(
                            'password' => '*****',
                            'id' => (int) 6,
                            'username' => 'rihad',
                            'password_expiration' => null,
                            'created' => '2012-05-15 16:30:10',
                            'modified' => '2012-05-18 10:14:07',
                            'last_accessed' => null
                    ),
                    'Role' => array(
                            (int) 0 => array(
                                    'id' => (int) 1,
                                    'alias' => 'operations',
                                    'RolesUser' => array(
                                            'id' => (int) 6,
                                            'role_id' => (int) 1,
                                            'user_id' => (int) 6
                                    )
                            )
                    )
            )
    )

    I'm expecting Role to be nested inside Milli, or something like that.

    Please help, it's a SNAFU, I've invested too much of my limited time
    to get the job done, it's too late to go looking for a working
    framework :( I admit that it's probably a misconfiguration from my
    part. But I can't fix it.

     
  26. rihad

    May 18, 2012 at 10:17

    OK, I've worked around this deficiency by doing 2 things:
    (1) added 'recursive' =&gt; 1 to AuthI

    public $components = array(
                    'Auth' => array(
                            'authenticate' => array('Form' =>
    array('userModel' => 'Milli', 'recursive' => 1)),

    (2) writing afterFind() callback in my model:

    public function afterFind(array $query) {
                    if (count($query) == 1) {
                            # single result fetched
                            $record = &$query[0];
                            if (isset($record['Role']) &&
    is_array($record['Role'])) {
                                    $roles = array();
                                    foreach ($record['Role'] as $role)
                                            $roles[] = $role['RolesUser']
    ['role_id'];
                                    $record[$this->alias]['Role'] =
    $roles;
                                    unset($record['Role']);
                            }
                    }
     
                    return $query;
            }

    You bet this is ugly! But works.
    There's one small problem, though. When I remove currently logged in user's role to access a specific resource, he can still do so. Looks like authorize() accesses Session data, and Session has active user roles cached.

     
  27. Augusto

    August 3, 2012 at 06:27

    I can't get to work the part to allow superadmin access everything. Altough superadmin is logged in, it does not allow that role to access everything. Therefore I must setup all actions in acl.ini to make it work. Any ideas?

     
  28. Mark

    August 3, 2012 at 09:37

    Did you apply the quicktip from above? Make sure that the Role array in your Session is filled. If you use a single role based approach, you would have to check User.role_id in the session instead (not User.Role)

     
  29. Augusto

    August 3, 2012 at 16:07

    Sent last post by mistake. What I changed was $this->Auth->allow('*') to $this->Auth>allow() (I'm using 2.2.1). Now it's working perfectly!

     
  30. Mark

    August 3, 2012 at 16:21

    exactly, as noted in "NOTE 2012-02-25 ms" at the bottom ;)

     
  31. Augusto

    August 3, 2012 at 18:09

    *facepalm*
    Anyways, you state that "You must not declare your public actions this way! All those must be declared using

    $this->Auth->allow()

    ."
    But here I am not trying to allow access to anyone to public actions, but to allow superadmin (after he has logged in as so) access non-public actions (as showed in the

    $this->Auth->allow('*');

    snippet, that I had to modify). What am I getting wrong? :s

     
  32. Mark

    August 3, 2012 at 18:37

    The difference is HOW you do it. If you do it inside the beforeFilter for the superadmin role only it will also only affect users with this role. makes sense, doesn't it? Just include the check from above:

    if (in_array(Configure::read('Role.superadmin'), $userRoles)) {
        //only then allow all
    }
     
  33. Augusto

    August 3, 2012 at 19:59

    That's understood. I think I didn't express myself well. What I modified was:

    public function beforeFilter() {
        parent::beforeFilter();
        if (/* checks logged user is admin */) {
            $this->Auth->allow('*');
        }
    }

    To this to make it work:

    public function beforeFilter() {
        parent::beforeFilter();
        if (/* checks logged user is admin */) {
            $this->Auth->allow();
        }
    }
     
  34. Mark

    August 3, 2012 at 20:12

    that's true. it depends on what cake version you are using.
    in earlier versions it was '*' now it is just no arguments.
    I updated the tutorial to make it clearer :) thank you for pointing that out.

     
  35. Augusto

    August 3, 2012 at 20:20

    That's great! Keep up the good work…

    Thanks again

     
  36. Chyrus

    August 20, 2012 at 13:01

    Hi. How can i use TinyAuth without user table? I created user model

    App::uses('AppModel', 'Model');
    /**
     * User Model
     *
     */
    class User extends AppModel {
    	public $useTable = false;
    }

    andi get error:
    Error: Call to a member function find() on a non-object
    File: /home/chyrus/projekty/seotool/www/app/Plugin/Tools/Controller/Component/Auth/TinyAuthorize.php
    Line: 162

     
  37. Mark

    August 22, 2012 at 12:22

    You need to make Configure::read($this->settings['aclModel']); work

    So provide them in Configure as described above in your bootstrap/config etc:

    $config['Role']  = ...
     
  38. frzfrz

    November 9, 2012 at 16:03

    I see the code of TinyAuth and I want to use the public function validate() to do individual checks for certain roles to show edit buttons etc. But I cant find a way to access validate(), is there any?

     
  39. Mark

    March 1, 2013 at 20:35

    @frzfrz: See my updated note at the bottom about Auth class

     
  40. Bacon

    March 10, 2013 at 18:26

    Hello, thanks for great plugin, but I am stucked at the moment… When I try access for example /users/add, I am redirected to /.

    My AppController:

    class AppController extends Controller {
     
        public $components = array(
          'Auth' => array(
            'authenticate' => array(
              'Blowfish' => array(
                'contain' => array(
                  'Group' => array(
                    'fields' => array(
                      'id'
                  )
                )
              ) 
              )
            )
          )
        );
     
        public function beforeFilter() {
     
          $this->Auth->authorize = array(
            'Tools.Tiny' => array(
              'aclModel' => 'Group' 
            )
          );
     
        }
     
      }

    My acl.ini:

    [Users]
    add = Administrator

    Session array:

    [Auth] => Array
            (
                [User] => Array
                    (
                        [id] => 1
                        [username] => Bacon
                        [email] => 
                        [Group] => Array
                            (
                                [0] => Array
                                    (
                                        [id] => 1
                                        [GroupsUser] => Array
                                            (
                                                [id] => 1
                                                [user_id] => 1
                                                [group_id] => 1
                                            )
     
                                    )
     
                                [1] => Array
                                    (
                                        [id] => 2
                                        [GroupsUser] => Array
                                            (
                                                [id] => 2
                                                [user_id] => 1
                                                [group_id] => 2
                                            )
     
                                    )
     
                            )
     
                    )
     
            )

    Any idea, please?

     
  41. Mark

    March 10, 2013 at 18:31

    It tries to look for the ids in User.Group directly – as find(list) would return them.
    You need to put them in as flat array of ids.

    User.Group => array(1, 2, 3, ...)

    .
    or enhance the component.

     
  42. Bacon

    March 10, 2013 at 18:52

    Wow, what an extremely quick reply! :) Thanks. I was afraid of that… Hope I will find solution to convert it to flat array.

     
  43. Mark

    March 13, 2013 at 14:19

    You will be happy to hear that the deeper "contained" Role array is now supported :)

     
  44. Bacon

    March 16, 2013 at 03:41

    Mark, many thanks for your work! :) I finally got it working.

    But I would like to say, that maybe you should mention, that Group.alias (or Role.alias) have to be in lowercase in order to work. Took me few hours to realize, why am I not authorized to action, even if I have it set properly.

    Well, now it works, that in my acl.ini I can have:
    [Users]
    info = admin
    OR
    info = Admin

    and it would work because every role from .ini will be converted to lowercase (because $newRole = Configure::read($this-&gt;settings['aclModel'] . '.' . strtolower($role));).

    But if you have $this-&gt;settings['aclModel']['Admin'], it won't work, because $newRole is looking for ['admin']. I don't know if it is bug or feature, but I think it should be mentioned for other users to realize. Or maybe you can convert roles to lowercase also when you write them to config (Configure::write($this-&gt;settings['aclModel'], $availableRoles);).

    I am wondering that I am the only one with this issue… :) Keep your great work!

     
  45. Eric

    June 5, 2013 at 16:07

    I was wondering if anyone would know where the best place would be to put a restriction for something like 'RolesUser.site_id =&gt; 2' I am working on a SaaS app so I have 1 User to many roles but I need to select only roles pertaining to the domain(site_id) they are on

    $this-&gt;Auth-&gt;authenticate = array(
    'Form' =&gt; array(
    'scope' =&gt; array('RolesUser.site_id' =&gt; $result['Sites']['site_id']),
    'recursive' =&gt; 1,
    )
    );

    But it doesnt work

     
  46. Eric

    June 7, 2013 at 16:55

    Ok I got it sorted and Im posting what I did to make it work.

    class User extends AppModel {
     
        public $primaryKey = 'id';
        public $hasAndBelongsToMany = array(
    	'Role' => array(
    	    'className' => 'Role',
    	    'joinTable' => 'roles_users',
    	    'foreignKey' => 'user_id',
    	    'assosciationForeignKey' => 'role_id',
    	    'unique' => 'keepExisting'
    	)
        );
     
        function __construct($id = false, $table = null, $ds = null) { 
            $this->hasAndBelongsToMany['Role']['conditions'] = array('RolesUser.site_id' => Configure::read('Settings.site_id')); 
            parent::__construct($id, $table, $ds); 
        }

    The only issue Im still having and I dont thinkits tied to Tiny is I cant get the fields to work so I can use email instead of username