Making the Auth Identity object available in Table classes

I’m using the Authentication and Authorization Middleware/Component tools in CakePHP4.x

I would find it convenient to have the Identity object in my Table classes since it can do the scope authorization processes. But getting foreign bodies into Tables has always been tricky for me.

The object is on the Request; unavailable in Tables.

Any suggestions? In the past I’ve written an override of the table factory that let me set my own properties of Tables on construction but this seems heavy handed.

Am I missing an easy solution?

UPDATE

I realized that just because the core table class restricts the config keys that can be used, there is no reason my Table class can’t expand the list.

    /**
     * Initialize method
     *
     * @param array $config The configuration for the Table.
     * @return void
     */
    public function initialize(array $config) : void {
        if (!empty($config['Identity'])) {
            $this->Identity = $config['Identity'];
        }
        if (!empty($config['authorization_scope'])) {
            $this->Identity = $config['authorization_scope'];
        }
        parent::initialize($config);
	$this->configureStackCache();
	$this->validateRoot();
    }

I can’t seem to find it documented anywhere, but Router::getRequest() should get you the request object from anywhere.

@Zuluru That is useful! I’d never turned that up. Life just became easier.

Well, not a panacea after all.

The request stored there is the original request without anything added later (like the Auth Identity). So, no joy there.

But the Router::getRequest() does provide the ->getSesion() method so that is a help.

The Session holds some Auth data, but not the Identity object made by the Middleware. However, it would be possible to correct this.

Turns out passing in configurations is not a simple fix in my case either. I immediately ran into an ‘can’t reconfigure’ error so I would have to start keeping a closer eye on the names of the registered tables and their configuration.

I’ve generally found that when I have to jump through so many hoops, it’s because I’m doing it wrong. Can you explain more about your “scope authorization processes” and why tables seem the best place to do them?

The requirement:

Pass the Auth Identity to a set of tables that share a common Parent Table class

does not seem like an exotic requirement.

But, here is my situation by way of a simple example (actual use is a warehousing/inventory system):

You have Articles, Authors, Comments, and Tags in the expected arrangement.

My system guarantees that when you get an article, you get this full set of context data whenever you get an article; you would never get only Article entities, or Articles and contained Author. All or nothing

This package comes as a kind of hper-entity with a standardized access system similar to the query system but designed to work on the set of returned Article objects. And the set of returned objects is in a Set object that allows aggregate access or the extraction of subsets of data.

This allows the objects to be used in a more random-access way rather the loop-loop style that tree structures impose.

Another key feature is that you can pass the Table class a set of any of the contained entity types and get back all the Article objects that are relevant in some way to the seeds you pass in. So, give it an Author and get all the Author’s Articles (or at least the current page of them).

This approach eliminates much of the complexity of the code by standardizing into a collection of large, standard objects. The trade off is the weight of the objects.

Now Authorization scope

There is obviously a fair amount going on in the Parent table class to build up these patterned objects. And it is all done out of site of the developer who simply has to provide seed data in need of the context/support objects.

The way this is built, the parent Table class templates the flow of processes and the sub-classes describe exact procedures for deducing the objects from the provided seeds and the details of the query to produce the objects.

The process is two stage, distill seeds to a set of ids for the object’s main subject (Articles in this example). Then marshal the objects from this id set.

At the moment the ids are ready, several useful tasks can be done. Pagination, the application of user run-time filters and… drum roll… the application of Authorization scope policies.

I checked my code, and Router::getRequest()->getAttribute('identity') is working fine in my table implementations in the few instances where I need it. You said you’re using the middleware version, AuthorizationMiddleware::__invoke should be adding the attribute to the request that gets passed on to the next level. Do you have custom config that changes the name attribute name from “identity”? Or trying to do some of your processing in middleware that’s run before this?

In other situations, I have custom finders which take some user-specific options that the controller generates (with its readily available full knowledge of the identity). And for still other scenarios, my initialization process writes the user ID and their basic permissions into the Configure singleton. Don’t know if either of those options would be useful to you.

You’re absolutely right! I think I was too tired and overlooked it.

Or possibly my debug to look at the value was in the very first table accessed, the one that was actually in the process of constructing the Identity :slight_smile:

Everything is rosy here on a fine spring day in the time of plague.

Its not good to explicit couple routing/requests with models.
Especially when making shells, there are no request in that context. (Eg. close articles after 30 days with no activity, setup a cronjob at everyday)

What you should do is to pass options in the save call

// In controllers
$this->Articles->save($article, [
    'user_id' => $this->Auth->user('id');
]);

// in shell, other contexts
$this->Articles->save($article, [
    'user_id' => null, // or an especial Id for system user, etc
]);

If you use Crud, you can setup this in the beforeFilter of the controller.

public function beforeFilter(Event $event)
{
    parent::beforeFilter($event);
    // assuming this is a add/edit action
    $this->Crud->action()->saveOptions([
        'user_id' => $this->Auth->user('id'),
   ]);
}
1 Like

Fair point.

My goal though is not to get a request in the model, but to get an Identity object and its interface into the model.

You can pass anything in the options, objects, arrays.

$this->Articles->save($article, [
    'user' => $this->request->->getAttribute('identity'),
    'something_else' => $this,
]);