Giter VIP home page Giter VIP logo

footprint's Introduction

Footprint

Build Status Coverage Total Downloads License

This plugin allows you to pass the currently logged in user info to the model layer of a CakePHP application.

It comes bundled with the FootprintBehavior to allow you control over columns such as user_id, created_by, company_id similar to the core's TimestampBehavior.

Install

Using Composer:

composer require muffin/footprint

You then need to load the plugin by running console command:

bin/cake plugin load Muffin/Footprint

The Footprint plugin must be loaded before the Authentication plugin, so you should updated your config/plugins.php or Application::bootstrap() accordingly.

Usage

Middleware

Add the FootprintMiddleware to the middleware queue in your Application::middleware() method:

$middleware->add('Muffin/Footprint.Footprint');

It must be added after AuthenticationMiddleware to ensure that it can read the identify info after authentication is done.

If you don't have direct access to the place where AuthenticationMiddleware is added then check here.

Behavior

To use the included behavior to automatically update the created_by and modified_by fields of a record for example, add the following to your table's initialize() method:

$this->addBehavior('Muffin/Footprint.Footprint');

You can customize that like so:

$this->addBehavior('Muffin/Footprint.Footprint', [
    'events' => [
        'Model.beforeSave' => [
            'user_id' => 'new',
            'company_id' => 'new',
            'modified_by' => 'always'
        ]
    ],
    'propertiesMap' => [
        'company_id' => '_footprint.company.id',
    ],
]);

This will insert the currently logged in user's primary key in user_id and modified_by fields when creating a record, on the modified_by field again when updating the record and it will use the associated user record's company id in the company_id field when creating a record.

You can also provide a closure that accepts an EntityInterface and returns a bool:

$this->addBehavior('Muffin/Footprint.Footprint', [
    'events' => [
        'Model.beforeSave' => [
            'user_id' => 'new',
            'company_id' => 'new',
            'modified_by' => 'always',
            'deleted_by' => function ($entity): bool {
                return $entity->deleted !== null;
            },
        ]
    ],
]);

Adding middleware via event

In some cases you don't have direct access to the place where the AuthenticationMiddleware is added. Then you will have to add this to your src/Application.php

use Authentication\Middleware\AuthenticationMiddleware;
use Cake\Event\EventInterface;
use Cake\Http\MiddlewareQueue;
use Muffin\Footprint\Middleware\FootprintMiddleware;

// inside the bootstrap() method
$this->getEventManager()->on(
    'Server.buildMiddleware',
    function (EventInterface $event, MiddlewareQueue $middleware) {
        $middleware->insertAfter(AuthenticationMiddleware::class, FootprintMiddleware::class);
    }
);

Patches & Features

  • Fork
  • Mod, fix
  • Test - this is important, so it's not unintentionally broken
  • Commit - do not mess with license, todo, version, etc. (if you do change any, bump them into commits of their own that I can ignore when I pull)
  • Pull request - bonus point for topic branches

Bugs & Feedback

http://github.com/usemuffin/footprint/issues

License

Copyright (c) 2015-Present, Use Muffin and licensed under The MIT License.

footprint's People

Contributors

admad avatar altamira-sykora avatar challgren avatar dakota avatar eymen-elkum avatar inoas avatar jacobagtyler avatar jadb avatar japita-se avatar jeremyharris avatar jharder avatar justinatack avatar lordsimal avatar lorenzo avatar modpluz avatar mrothauer avatar waspinator avatar ypnos avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

footprint's Issues

CakePHP 3.6.12 Notice - Undefined property: UsersController::$_currentUserViewVar

Heya,

PHP Version 7.0.30-0+deb9u1
Apache/2.4.25 (Debian)
Linux 4.9.0-8-amd64 #1 SMP Debian 4.9.110-3+deb9u4 (2018-08-21) x86_64

just updated cakephp3 from 3.6.11 to 3.6.12 and now i get a notice after login submit.

Notice (1024): Undefined property: UsersController::$_currentUserViewVar in /vendor/muffin/footprint/src/Auth/FootprintAwareTrait.php on line 81 [CORE/src/Controller/Controller.php, line 387]

i think it comes from this change:
CakePHP 3.6.12 released
cakephp/cakephp#PR12593

Undefined controller properties now emit warnings when accessed. This prevents 'invalid method call on null' type errors.

Cuz $this is an object(App\Controller\UsersController) in that context.

Workaround:
In my UsersController i just added the property

class UsersController extends AppController
{
	protected $_currentUserViewVar; // Workaround 3.6.12 notice
	
	public function initialize()
	{

FIX:
Add this line in vendor/muffin/footprint/src/Auth/FootprintAwareTrait.php L81 to fix:

isset($this->_currentUserViewVar) && //FIX 3.6.12 notice

	protected function _getCurrentUser($user = NULL)
	{
		$this->_setCurrentUser($user);
				
		if (!$this->_currentUserInstance &&
			isset($this->_currentUserViewVar) && //FIX 3.6.12 notice
			!empty($this->viewVars[$this->_currentUserViewVar])
		)
		{
			$this->_currentUserInstance = $this->viewVars[$this->_currentUserViewVar];
		}
		
		return $this->_currentUserInstance;
	}

But i dont know where this "_currentUserViewVar" comes from or why it is not set.

EDIT: use isset and not empty in fix like ADmad did in the pr

Using _footprint in Custom Rules Checker

I am trying to use this plugin to pass logged in user details to a custom rule checker, however when I look at the $options array in the __invoke function, the key isn't present.

I am fairly certain the plugin has been installed and is operating correctly, as in the beforeSave function of my model (when I log the $options array to a file) I do see the _footprint key, just not when I log it from the __invoke function my custom rule checker.

I've been tying on my own to figure out why this hasn't worked. I've both read the different "Issues" already submitted here and tried to better understand how Traits and Events work in general (since these seem to be central to the functioning of this plugin). I think it has something to do with when adding the _footprint key is triggered. Is there a different event this needs to be listening on? I tried to figure this out, but I couldn't seem to wrap my head around it.

Any help would be appreciated and I can provide whatever details or code might be helpful. Thanks in advance.

Is the Authentication plugin required for this plugin to work?

I believe the application that I'm maintaining is using the Auth component (?)

$this->loadComponent('Auth', [
    'authorize' => 'Controller'
]);

I think I've gone through all the steps to install the Footprint plugin, but it's still not populating created_by and modified_by. I was wondering if the Authentication plugin was required for it to work?

Behaviour works in one model, does not in others

Hello,

I have a somehow strange situation with the plugin on CakePHP 3.4.4: my custom columns (createdby, modifiedby) have data in one model in before save but do not have in other(s).

To exclude code differences i have added in the same controller: UsersController, in the same function login save method for both models. Needles to say that it does not help if i swap the order in which the models are called.
I also need to add that i need to keep the trait in both AdminController and AppController because then in some places the trait is just not loaded.

################
AppController.php
################

<?php 
namespace App\Controller;

...
use Muffin\Footprint\Auth\FootprintAwareTrait;

class AppController extends Controller
{

    use FootprintAwareTrait;
    ...
?>

################
AdminController.php
################

<?php  
namespace App\Controller;

use Cake\Controller\Component\AuthComponent;
use Cake\Core\Plugin;
use Cake\Event\Event;
use Cake\Mailer\MailerAwareTrait;
use Muffin\Footprint\Auth\FootprintAwareTrait;//https://github.com/UseMuffin/Footprint

class AdminController extends AppController
{
    use MailerAwareTrait;
    use FootprintAwareTrait;
	...
	
	public function initialize()
    {
        parent::initialize();

        $this->_userModel = 'AuthAcl.Users';

        $this->loadComponent('Cookie');
        $this->loadComponent('Paginator');
        $this->loadComponent('Acl.Acl');
        $this->loadComponent('RequestHandler');
        $this->loadComponent('Flash');
        $this->loadComponent('Auth', [
            'authorize' => ['Controller'],
            'loginRedirect' => [
                'controller' => 'dashboard',
                'action' => 'index',
                'home'
            ],
            'logoutRedirect' => [
                'controller' => 'users',
                'action' => 'login'
            ],
            'loginAction' => [
                'controller' => 'users',
                'action' => 'login'
            ],
            'authenticate' => [
                AuthComponent::ALL => [
                    'contain' => [
                        'Groups',
                        'UsersPreferences']
                    ],
                    'passwordHasher' => [
                        'className' => 'Fallback',
                        'hashers' => [
                            'Default'
                        ]
                    ],
                    'userModel' => $this->_userModel
                ],
                'Form' => [
                    'finder' => 'IsUserActive',
                    'userModel' => $this->_userModel
                ]
            ],
            'authError' => __('You are not authorized to access.'),
        ]);
?>

################
AppTable.php
################

<?php
namespace App\Model\Table;

use App\Model\Entity\User;
use App\Model\Table\EntityInterface;
use ArrayObject;
use Cake\Event\Event;
use Cake\ORM\Table;

class AppTable extends Table
{

    public function initialize(array $config)
    {
        parent::initialize($config);
        $this->addBehavior('Muffin/Footprint.Footprint', [
            'events' => [
                'Model.beforeSave' => [
                    'createdby' => 'new',
                    'modifiedby' => 'always',
                ]
            ],
        ]);
    }
?>

################
UsersTable.php
################

<?php  
namespace AuthAcl\Model\Table;

use App\Model\Table\EntityInterface;
use Cake\Auth\DefaultPasswordHasher;
use Cake\ORM\Query;
use Cake\ORM\Table;
use Cake\Validation\Validator;


class UsersTable extends \App\Model\Table\AppTable
{
?>

################
OrdersTable.php
################

<?php
namespace App\Model\Table;

use ArrayObject;
use Cake\Chronos\ChronosInterface;
use Cake\Event\Event;
use Cake\I18n\Time;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\ORM\TableRegistry;
use Cake\Validation\Validator;

class OrdersTable extends \App\Model\Table\AppTable
{
	
?>	

################
UsersController.php
################

<?php
namespace App\Controller;

use App\Library\Util\Util;
use App\Model\Table\EntityInterface;
use AuthAcl\Model\Entity\User;
use Cake\Core\Configure;
use Cake\Event\Event;
use Cake\Network\Exception\BadRequestException;
use Cake\Network\Exception\NotFoundException;
use Cake\ORM\TableRegistry;
use Cake\Utility\Hash;
use Josegonzalez\CakeQueuesadilla\Queue\Queue;


class UsersController extends AdminController
{
	 ...
	 
	 /**
     *  login - used in front-end
     */
    public function login()
    {
        if ($this->request->is('post')) {
            $this->autoRender = false;
            //get banner session from banner session as the auth changes session od
            $user = $this->Auth->identify();
            if ($user) {
                $this->Auth->setUser($user);

                //Save last login
                $usersTable = $this->loadModel('AuthAcl.Users');
                $this->Users = $usersTable->get($user['id']);
                $this->Users->last_login = date('Y-m-d H:i:s');
                $usersTable->save($this->Users);

                //Dummy
                $this->Orders = $this->loadModel('Orders');
                $order = $this->Orders->get(1);
                $order->session = $this->session->id();
                $this->Orders->save($order);

                exit;
?>				

################
FootprintBehavior.php
################

<?php
  public function beforeSave(Event $event, Entity $entity, ArrayObject $options)
    {
        debug(get_class($entity));
        debug($options);
        $eventName = $event->name();
?>

################
Debug
################

 \vendor\muffin\footprint\src\Model\Behavior\FootprintBehavior.php (line 115)

'AuthAcl\Model\Entity\User'

\vendor\muffin\footprint\src\Model\Behavior\FootprintBehavior.php (line 116)

object(ArrayObject) {
	atomic => true
	associated => [
		'groups' => [],
		'companies' => [],
		'userspreferences' => [],
		'usersgroups' => []
	]
	checkRules => true
	checkExisting => true
	_primary => true
	_footprint => object(AuthAcl\Model\Entity\User) {

		'id' => (int) 7,
		'status' => (int) 1,
		'first_name' => 'Ionut',
		'last_name' => 'Irofte',
		'username' => 'juniorionut@
	...
	
 \vendor\muffin\footprint\src\Model\Behavior\FootprintBehavior.php (line 115)

'App\Model\Entity\Order'

\vendor\muffin\footprint\src\Model\Behavior\FootprintBehavior.php (line 116)

object(ArrayObject) {
	atomic => true
	associated => [
		'orders' => [],
		'adpromotions' => [],
		'orderslines' => [],
		'users' => [],
		'dfpreports' => []
	]
	checkRules => true
	checkExisting => true
	_primary => true
}

Causes Ambiguous Database Error

Hi All,

I'm getting an ambiguous database error issue. Here is my setup.

Users Behavior

$this->addBehavior('Muffin/Footprint.Footprint', [
            'events' => [
                'Model.beforeSave' => [
                    'group_id' => 'new',
                    'created_by' => 'new',
                    'modified_by' => 'always'
                ],
                'Model.beforeFind' => [
                    'group_id' => 'always'
                ],
            ],
            'propertiesMap' => [
                'group_id' => '_footprint.group_id',
            ],
        ]);

Exercises Behavior

$this->addBehavior('Muffin/Footprint.Footprint', [
            'events' => [
                'Model.beforeSave' => [
                    'group_id' => 'new',
                    'created_by' => 'new',
                    'modified_by' => 'always'
                ],
                'Model.beforeFind' => [
                    'group_id' => 'always'
                ],
            ],
            'propertiesMap' => [
                'group_id' => '_footprint.group_id',
            ],
        ]);

the SQL Query thats causing the issue

SELECT 
  Exercises.id AS `Exercises__id`, 
  Exercises.group_id AS `Exercises__group_id`, 
  Exercises.study_id AS `Exercises__study_id`, 
  Exercises.user_id AS `Exercises__user_id`, 
  Exercises.assigned AS `Exercises__assigned`, 
  Exercises.completed AS `Exercises__completed`, 
  Exercises.exercises AS `Exercises__exercises`, 
  Exercises.created AS `Exercises__created`, 
  Exercises.created_by AS `Exercises__created_by`, 
  Exercises.modified AS `Exercises__modified`, 
  Exercises.modified_by AS `Exercises__modified_by`, 
  Studies.id AS `Studies__id`, 
  Studies.number AS `Studies__number`, 
  Studies.name AS `Studies__name`, 
  Studies.page AS `Studies__page`, 
  Studies.url AS `Studies__url`, 
  Studies.what AS `Studies__what`, 
  Studies.why AS `Studies__why`, 
  Studies.how AS `Studies__how`, 
  Studies.exercise AS `Studies__exercise`, 
  Studies.created AS `Studies__created`, 
  Studies.created_by AS `Studies__created_by`, 
  Studies.modified AS `Studies__modified`, 
  Studies.modified_by AS `Studies__modified_by`, 
  Users.id AS `Users__id`, 
  Users.group_id AS `Users__group_id`, 
  Users.active AS `Users__active`, 
  Users.email AS `Users__email`, 
  Users.password AS `Users__password`, 
  Users.given_name AS `Users__given_name`, 
  Users.middle_name AS `Users__middle_name`, 
  Users.family_name AS `Users__family_name`, 
  Users.mobile_number AS `Users__mobile_number`, 
  Users.home_number AS `Users__home_number`, 
  Users.created AS `Users__created`, 
  Users.created_by AS `Users__created_by`, 
  Users.modified AS `Users__modified`, 
  Users.modified_by AS `Users__modified_by` 
FROM 
  exercises Exercises 
  INNER JOIN studies Studies ON Studies.id = (Exercises.study_id) 
  INNER JOIN users Users ON Users.id = (Exercises.user_id) 
WHERE 
  (
    Exercises.id = 1 
    AND group_id = 1
  ) 
ORDER BY 
  Exercises.assigned ASC 
LIMIT 
  1

If I take the above SQL and edit it by placing Exercises.group_id and inject it into SequelPro it fixes the issue. So I tried fixing things by added Exercises. etc in the behaviour settings but this produces another error.

this

$this->addBehavior('Muffin/Footprint.Footprint', [
            'events' => [
                'Model.beforeSave' => [
                    'Exercises.group_id' => 'new',
                    'Exercises.created_by' => 'new',
                    'Exercises.modified_by' => 'always'
                ],
                'Model.beforeFind' => [
                    'Exercises.group_id' => 'always'
                ],
            ],
            'propertiesMap' => [
                'Exercises.group_id' => '_footprint.group_id',
            ],
        ]);

causes this

Cannot convert value to integer

Is there a way around this problem, am I using the settings incorrectly or does this need to be need to be fixed in the code?

Using Footprint beforeFind Event

Hi,

Thanks for a great plugin, it works well for me.

I was wondering is it possible to use the beforeFind event to restrict the returned data by the Auth user_id?

I'm building a multi-tenant app and it would be great if Footprint could not only manage the save data but also restrict the find data.

I tried the following

UsersTable

        $this->addBehavior('Muffin/Footprint.Footprint', [
            'events' => [
                'Model.beforeSave' => [
                    'group_id' => 'new',
                    'created_by' => 'new',
                    'modified_by' => 'always'
                ],
                'Model.beforeFind' => [
                    'group_id'
                ]
            ],
            'propertiesMap' => [
                'group_id' => '_footprint.group_id',
            ],
        ]);

but I get

Error: SQLSTATE[23000]: Integrity constraint violation: 1052 Column 'group_id' in where clause is ambiguous

This error could be easily fixed with Users.group_id but when I add that to the Footprint behaviour I get the following.

Cannot convert value to integer

Is it possible to use the Footprint plugin this way or it it only for saving data and not retrieving?

API Prefix routing error

When using an API prefix routing the plugin doesn't seem to work:

SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'user_id' cannot be null

My table class:

$this->addBehavior('Muffin/Footprint.Footprint', [
            'events' => [
                'Model.beforeSave' => [
                    'user_id' => 'new',
                ]   
            ]
        ]);

My ApiAppController:

namespace App\Controller\Api;

use App\Controller\AppController;
use Cake\Event\Event;
use Muffin\Footprint\Auth\FootprintAwareTrait;
use Cake\Core\Exception\Exception;

class ApiAppController extends \App\Controller\AppController {

    use \Crud\Controller\ControllerTrait;
    use FootprintAwareTrait;

Without the API prefix routing the user_id is saved and the error does not fire.

Configuration problem

Using CakePHP4
Please, review the "Usage" readme secction. I'm unable to get it working correctly. Every fail (including Missing controller or action) end with: "You must have AuthenticationComponent or AuthComponent loaded to use Footprint"

`use Muffin\Footprint\Auth\FootprintAwareTrait;

class AppController extends Controller
{
use FootprintAwareTrait;

// Specify the user model if required. Defaults to "Users".
$this->_userModel = 'YourPlugin.Members';//<-- Cannot use $this out of function

}`

updated_by field

I need to 'updated_by' field when particular field value changed.. not always I want to update the updated field...

Is this plugin actually working?

I've installed and set it up, everything seems to be ok, the user is properly loaded into FootprintAwareTrait::_currentUserInstance but _footprint option key is empty when Model.beforeSave happens.

I've debugging it (with xdebug) and it seems that FootprintListener::handleEvent gets fired after Model.beforeSave, so then '_footprint' option key gets populated with current user, but it's useless as Model events already happened.

I don't know if I'm doing something wrong or what, there is no config issues, no error, just it doesn't work.
Does anybody get this plugin working?

integration with authentication plugin

I'm trying to use this plugin with the official authentication plugin, but I'm doing something wrong. I tried following the suggestion in the readme by adding:

use Muffin\Footprint\Auth\FootprintAwareTrait;

class AppController extends Controller
{
    use FootprintAwareTrait {
        _setCurrentUser as _footprintSetCurrentUser;
    }

    protected function _setCurrentUser($user = null)
    {
        if (!$user) {
            $user = $this->request->getAttribute('identity');
        }

        return $this->_footprintSetCurrentUser($user);
    }
}

to my AppController, but I get a TypeError Argument 1 passed to Cake\ORM\Marshaller::one() must be of the type array, object given, called in /app/vendor/cakephp/cakephp/src/ORM/Table.php on line 2630

calling $user->toArray() doesn't work, with a Call to undefined method Authentication\Identity::toArray() error.

wrapping the [$user] object in an array resolves errors, but doesn't actually save created_by/modified_by fields in the database.

Error on non existent element

<?= $this->Element('does_not_exist') ?>
Results in:

Argument 1 passed to Muffin\Footprint\Event\FootprintListener::__construct() must be an instance of Cake\ORM\Entity or null, string given, called in /home/vagrant/Apps/vtta.local/vendor/muffin/footprint/src/Auth/FootprintAwareTrait.php on line 57

Using same field in Model.beforeFind, bugs other where's clausules

In a model table, I configure Model.beforeSave for attributing the logged user in the new registry. And Model.beforeFind to retrieve just the registers that this user saved.
But if I have one where's clausule in a controller or other beforeFind, it causes an error "Error: SQLSTATE[HY093]: Invalid parameter number".

class ArticlesTable extends Table{
    public function initialize(array $config){
        parent::initialize($config);
        $this->table('articles');
        $this->displayField('title');
        $this->primaryKey('id');
        $this->addBehavior('Muffin/Footprint.Footprint', [
            'events' => [
                'Model.beforeSave' => [
                    'user_id' => 'always',
                ],
                'Model.beforeFind' => [
                    'user_id',
                ],
            ],
            'propertiesMap' => [
                'user_id' => '_footprint.user_id',
            ],
        ]);
...

And then in a Controller:

$articles = $this->Articles->find('all', [
    'fields' => ['id', 'title', 'slug']
 ])
->where(['user_id IN' => array_keys($users)], [], true);

Return:

object(Cake\ORM\Query) {
  '(help)' => 'This is a Query object, to get the results execute or iterate it.',
  'sql' => 'SELECT Articles.id AS "Articles__id", Articles.ano AS "Articles__ano", Articles.user_id AS "Articles__user_id" FROM articles Articles WHERE user_id in (:c0)',
  'params' => [
    ':c0' => [
      'value' => (int) 54769,
      'type' => 'biginteger',
      'placeholder' => ':c0'
    ],
    ':c1' => [
      'value' => (int) 54769,
      'type' => 'biginteger',
      'placeholder' => ':c1'
    ]
  ],
...

RFC - Store user record as array instead of entity

Is there any particular benefit of maintaining the user record as entity instead of just array provided by Auth? It just adds unnecessary overhead of type check and array to entity conversion imo. I would prefer the user record maintained by plugin to be in same format as Auth itself.

possible issue with reading config in FootprintBehavior's beforeFind()?

Hi there. I believe I have found a small issue from this feb 27 commit to normalize the options array early. I get the following errors on each find query:

Warning (2): array_keys() expects parameter 1 to be array, null given [ROOT/vendor/muffin/footprint/src/Model/Behavior/FootprintBehavior.php, line 82]
Warning (2): Invalid argument supplied for foreach() [ROOT/vendor/muffin/footprint/src/Model/Behavior/FootprintBehavior.php, line 82]

If i revert the two changes from this commit, my issue vanishes. I don't think this is a config issue on my end or anything like that; I'm relying on $_defaultConfig for each of the models that implement this Behavior.

It would seem to me that since i have no config set for beforeFind, $this->config('events.beforeFind') returns null, so the array_keys() call raises the warning. However, it's not clear to me why the commit referenced above would have created a new problem here where none apparently existed before. Could someone please take a look and help me see what I'm missing?

Thanks for your work on this wonderful plugin and for any help you might be able to provide here.

loadModel method, a belongsToMany relation and Footprint plugin doesn't work

I've found a very very particular (and somehow rare) bug, it took me some time to figure out what's the problem or at least how to reproduce it.

In short, if this 4 things happens together, you get a fatal error:

  • FootprintAwareTrait is used
  • User model has a belongsToMany relation
  • Make use of loadModel method to load User model inside Controller::initialize
  • Find Users containing things from the belongsToMany relation

If all that occurs, then you'll get:

Fatal error: Uncaught Error: Call to a member function getName() on null in 
.../src/ORM/Association/BelongsToMany.php on line 1371

While this is cakephp 3.4, I've tested with cakephp 3.3.x and the bug is also present.

I've made a cakephp test app with the minimun code needed to recreate this error, https://github.com/xavier83ar/cakephp-testapp

Cloning the repo, composer install, bin/cake migrations migrate, bin/cake server should be enough to get it up and running.

Go to /testing/not-working to see the bug, and go to /testing/working and /testing/working-too to see two other ways of achieve the same result that do work.

Also I've noticed that if Users model gets removed from TableRegistry right after it's used in \Muffin\Footprint\Auth\FootprintAwareTrait::_circumventEventManager (line 131) everything works fine; adding this line:

TableRegistry::remove($this->_userModel);

I don't have enough free time to go deeper and find out what's the real issue and where it cames from. Also it could be a plugin bug or a CakePHP bug, I can't be sure, but as I faced it when using the plugin, I decided to post here. I think that's all.

CakePHP 3.9.7 breaks muffin/footprint plugin #15377

This is a (multiple allowed):

  • bug

  • enhancement

  • feature-discussion (RFC)

  • CakePHP Version: 3.9.7

  • Platform and Target: nginx,mysql,debian buster

What you did

composer update
cakephp updating to 3.9.7

What happened

after updating cakephp, the muffin/footprint (v1.2.2) throws a 500.

What you expected to happen

geting the user from the muffin/footprint plugin

grafik

cakephp/cakephp#15377

FootprintBehavior's beforeSave doesn't seem to have the information it needs in it

Hi. This isn't really an 'issue' so much as a query. I have, I think, followed the instructions for getting this set up, and if I put breakpoints in the FootprintListener and FootprintAwareTrait I can see the User data being set where it seems to need to be, however when it then reaches the beforeSave in the FootprintBehavior, $options doesn't seem to contain any data pertaining to the user.

My Table's initialise:

$this->addBehavior('Muffin/Footprint.Footprint', [
    'events' => [
        'Model.beforeSave' => [
            'created_by' => 'new',
            'modified_by' => 'always',
        ]
    ]
]);

The $options variable in the FootprintBehavior's beforeSave():

ArrayObject::__set_state(array(
   'atomic' => true,
   'associated' => 
  array (
    'characters' => 
    array (
    ),
    'createduser' => 
    array (
    ),
    'modifyuser' => 
    array (
    ),
  ),
   'checkRules' => true,
   'checkExisting' => true,
   '_primary' => true,
))

My full AppController
My full XpTable

Inline assignment bug

if ($user === null && (empty($this->Auth) || !$user = $this->Auth->user())) {

I am pretty sure the last one - the inline assignment (dangerous!) - is not doing what it is supposed to do.
It will always evaluate to false because the missing braces before ! bool cast.
So better to avoid inline assignment completely.

Support Model.beforeRules

Right now rules cannot make use of the information provided by the FootprintBehavior, as it is injected after the rules checking step.

However rules might need to check on it.
Usage example:

Records can be locked for editing by a user. The rule ensures that the modifying user is the one holding the lock, if there is any (by comparing modifiedby in the entity with lockedby in the database).

I don't know the design decision to use beforeSave, but I would assume beforeRules is the better place in general. As the rules check the consistency of the entity and the user data might play part in it.

I volunteer to do a PR that enables Model.beforeRules next to Model.beforeSave.

(1.x) Add a configuration option for the Authentication event name to check

Hi, I'm running Cake 3.x and cannot upgrade to 4 (yet) but I am using the new-and-improved Authentication and Authorization plugins from Cake (ergo, not the AuthComponent anymore). I'm also using Footprint 1.x.

When I updated the Authz plugins, the post-identify event name changed from Auth.afterIdentify to Authentication.afterIdentify and now my footprinting code doesn't work anymore because it isn't setting the current user.

My suggestion/request is to make the event name configurable (at the app level, so I don't have to do it every time I load the behavior) so that Footprint listens for the correct identify event before setting the current user.

I can't use the Footprint Middleware because it's in the 2.x branch, which requires Cake 4.

If there is an alternative solution I am all ears. Thanks! ๐Ÿฐ

Changing the _footprint option key

Is there an easy way to set the option key (set by default to _footprint)?

By easy I mean without the need to rewrite a method (which is what I'm currently doing):

<?php
    use FootprintAwareTrait {
        footprint as realFootprint;
    }

    public function footprint(Event $event)
    {
        self::realFootprint($event);

        $this->_listener->setConfig('optionKey', '_customFootprintKey');
    }

Does this plugin only log one transaction and lose the data on the next one?

Seems to me that this plugin works similar to the Blame plugin. But I think we need to grow out these plugins to do more than just record a single user action on a record to having its own DB table that can LOG EVERY USER ACTION, thereby creating an opportunity for much deeper insight into user behavior and a records timeline/change log.

Breaks table association.

Following is in my composer.json for context:

 "require": {
   "php": ">=5.5.9",
   "cakephp/cakephp": "3.3.*",
   "mobiledetect/mobiledetectlib": "2.*",
   "cakephp/migrations": "~1.0",
   "cakephp/plugin-installer": "*",
   "friendsofcake/crud": "^4.3",
   "friendsofcake/search": "^2.3",
   "friendsofcake/crud-view": "^0.3.3",
   "friendsofcake/crud-users": "^0.2.0",
   "cakedc/users": "^3.2",
   "maiconpinto/cakephp-adminlte-theme": "^1.0",
   "muffin/footprint": "dev-master"
 },
   "require-dev": {
   "psy/psysh": "@stable",
   "cakephp/debug_kit": "~3.2",
   "cakephp/bake": "~1.1"
 }

When I enable the footprint plugin in my posts model:

$this->addBehavior('Muffin/Footprint.Footprint', [
  'events' => [
    'Model.beforeSave' => [
      'user_id' => 'new',
      'edit_user_id' => 'new',
      'username' => 'new',
    ]
  ],
]);

I have associations between PostsTable (belongsTo('Users')), UsersTable (hasMany('Posts')). When I enable this plugin it throws an error that posts is not associated with users. If I disable the plugin I am able to create a post with the logged in user. I have tried to identify the exact error but so far nothing specific has come up.

Thoughts? Thanks again, great plugin so far, been using it for a while now on a few projects this is definitely new.

Does not work with Cake 3.5.*

Hi guys,

Any success using this behaviour with Cake 3.5? I've tried to with 3.4 as well but no luck. It uses deprecated methods but even when these are fixed to new ones it does not move along.

When printing out in beforeSave the entity I get that user_id record is dirty but set to empty value.

Use footprint across plugins

I have split my application into plugins .

Im trying to use Footprint and I have the following setup:

  • I have FootprintAwareTrait called inside src/Controller/AppController
  • I have my Tables and Models in src and Controllers and Templates in plugins/Foo.

The above setup didn't work.

So I left FootprintAwareTrait; inside src/Controller/AppController and tried to move some stuff around:

  • Moved everything in src - WORKED
  • Moved everything in plugins/MyPluginName - WORKED

But I still want to have Tables and Models in src and Controllers and Templates in plugins/Foo, so any ideas if it's possible to use the Footprintplugin in that case ?

I have managed to reproduce that in a new project and made a repo.

UnitTesting a controller whose model has footprint, fails under certain condition

I am using the develop branch.

While unit testing a controller, the footprint behavior fails if get the model table before the post call. In other words, in the test here below, if the line 19 is before line 17, the user_id field will not be populated.

    1     public function testAdd(): void
    2     {
    3         $this->enableCsrfToken();
    4         $user = UserFactory::make(['status' => 5])->persist();
    5         $this->session(['Auth' => $user]);
    6         $data = [
    7             'date' => '2016-09-02',
    8             'name' => 'LST8 R1',
    9             'gene' => 'Lst8',
   10             'oligo_application' => [
   11               'name' => 'C-term tagging using pFA6a',
   12             ],
   13             'sequence' => 'TTTTTGTCACTTCGGTCCTTCAAGCTTGGTAGTTGATAAATTGTAAACTAGAATTCGAGCTCGTTTAAAC',
   14             'comments' => '',
   15             'concentration' => '100',
   16         ];
   17         $this->post('/oligos/add', $data);
   18         $this->assertResponseSuccess();
   19         $oligos = $this->getTableLocator()->get('Oligos');
   20         $query = $oligos->find()
   21             ->where(['date' => $data['date'], 'name' => $data['name']]);
   22         $this->assertEquals(1, $query->count(), 'Oligo saved');
   23         $oligo = $query->first();
   24         $this->assertEquals($user->id, $oligo->user_id, 'Id set');
   25     }

The action:

    1     public function add()
    2     {
    3         $oligo = $this->Oligos->newEmptyEntity();
    4         if ($this->request->is('post')) {
    5             $oligo = $this->Oligos->patchEntity($oligo, $this->request->getData());
    6             if ($this->Oligos->save($oligo)) {
    7                 debug('Gotcha');
    8  
    9                 return $this->redirect(['action' => 'index']);
   10             }
   11             debug($oligo);
   12             $this->Flash->error(__('The oligo could not be saved. Please, try again.'));
   13         }
   14         $this->set('oligo', $oligo);
   15     }

If $this->post('/oligos/add', $data);is before $oligos = $this->getTableLocator()->get('Oligos'); the entity is saved with user_id value set:

PHPUnit 9.5.10 by Sebastian Bergmann and contributors.

.                       1 / 1 (100%)APP/Controller/OligosController.php (line 102)
########## DEBUG ##########
'Gotcha'
###########################


Time: 00:00.142, Memory: 20.00 MB

OK (1 test, 3 assertions)

If $oligos = $this->getTableLocator()->get('Oligos'); is before $this->post('/oligos/add', $data); the save fails as user_idis null

 
F                      1 / 1 (100%)APP/Controller/OligosController.php (line 106)
########## DEBUG ##########
object(App\Model\Entity\Oligo) id:0 {
  'date' => object(Cake\I18n\FrozenDate) id:1 {
    'time' => '2016-09-02 00:00:00.000000+00:00'
    'timezone' => 'UTC'
    'fixedNowTime' => false
  }
  'name' => 'LST8 R1'
  'gene' => 'Lst8'
  'oligo_application' => object(App\Model\Entity\OligoApplication) id:2 {
    'name' => 'C-term tagging using pFA6a'
    '[new]' => true
    '[accessible]' => [
      'name' => true,
      'oligo_count' => true,
      'oligos' => true,
      'created' => true,
      'modified' => true
    ]
    '[dirty]' => [
      'name' => true
    ]
    '[original]' => []
    '[virtual]' => []
    '[hasErrors]' => false
    '[errors]' => []
    '[invalid]' => []
    '[repository]' => 'OligoApplications'
  }
  'sequence' => 'TTTTTGTCACTTCGGTCCTTCAAGCTTGGTAGTTGATAAATTGTAAACTAGAATTCGAGCTCGTTTAAAC'
  'comments' => ''
  'concentration' => '100'
  'melting_temp' => (float) 69.9
  'length' => (int) 70
  'oligo_application_id' => null
  'user_id' => null
  '[new]' => true
  '[accessible]' => [
    'date' => true,
    'name' => true,
    'gene' => true,
    'concentration' => true,
    'sequence' => true,
    'comments' => true,
    'created' => true,
    'modified' => true,
    'oligo_application' => true,
    'sgrnas' => true,
    '*' => false,
    'user_id' => true
  ]
  '[dirty]' => [
    'date' => true,
    'name' => true,
    'gene' => true,
    'oligo_application' => true,
    'sequence' => true,
    'comments' => true,
    'concentration' => true,
    'melting_temp' => true,
    'length' => true,
    'oligo_application_id' => true,
    'user_id' => true
  ]
  '[original]' => []
  '[virtual]' => []
  '[hasErrors]' => true
  '[errors]' => [
    'user_id' => [
      '_existsIn' => 'This value does not exist'
    ]
  ]
  '[invalid]' => []
  '[repository]' => 'Oligos'
}
###########################


Time: 00:00.149, Memory: 20.00 MB

There was 1 failure:

1) App\Test\TestCase\Controller\OligosControllerTest::testAdd
Oligo saved
Failed asserting that 0 matches expected 1.

Sorry for the verbosity

Integration tests fail with cake 3.3.4

With Cake 3.3.3 the following is included in our setUp() - and that puts content in the '_footprint' key in the table's $options array - but 3.3.4 and 3.3.5 this array is null :-/

use Muffin\Footprint\Event\FootprintListener;

//

    public function setUp()
    {
        parent::setUp();

        // put the login logic here instead in each function
        // Set session data
        $this->session([
            'Auth' => [
                'User' => [ /**/ ],
                    'language' => [
                        'short_code' => 'en_US',
                        'currency_id' => '83e0e78d-115e-423f-82dc-cf3f9da37b3d',
                    ],
                ],
            ],
        ]);
        $this->listener = new FootprintListener();
    }

Using Footprint in Unit Testing

Seems like the Footprint doesn't work when unit testing. The _footprint is not attached to the $options array in the beforeSave().

I am also aware of issue 21, so I have manually logged the user like:

   public function setUp()
    {
        parent::setUp();

        $this->session([
            'Auth' => [
                'User' => [
                    'id' => 'xxx',
                    'company_id' => 'xxx',
                    'Roles' => [
                        'company' => [
                            'name' => 'xxx',
                            'identifier' => 'xxx'
                        ],
                    ]
                ],
            ],
        ]);
    }

The above didn't help. Any suggestions ?

User saves as 0

Hi,

I'm trying to use this plugin, I've followed the instruction very carefully, installed via composer and manually enabled the plugin. When I save my user_id column always saves as 0. In my example substitute 'Bars' for 'Users'

https://thepasteb.in/p/oYhLc6GZ0wWB0c8

Thank you.

$options['_footprint'] is multi entities when using JwtAuth plugin

In the case we are more than one authentication method (in my case : Form and ADmad/JwtAuth.Jwt ).

then noticed that the $options['_footprint']['id'] is not accessible from and I have to use
$options['_footprint'][0]['id']

object(Cake\ORM\Entity)[228]
  protected '_properties' => 
    array (size=2)
      0 => 
        array (size=14)
          'id' => string 'e666e197-74d3-4d6c-9ae1-2be9eab76349' (length=36)
          'full_name' => string 'aaa' (length=5)
          'username' => string 'aaa' (length=10)
          'email' => string '[email protected]' (length=20)
          'email_token' => null
          'email_verified' => boolean true
          'email_token_expires' => null
          'active' => boolean true
          'password_token' => null
          'password_token_expires' => null
          'role' => string 'ADMIN' (length=5)
          'last_login' => null
          'created' => 
            object(Cake\I18n\Time)[216]
              ...
          'modified' => 
            object(Cake\I18n\Time)[219]
              ...
      1 => 
        object(ADmad\JwtAuth\Auth\JwtAuthenticate)[140]
          protected '_token' => string 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJlNjY2ZTE5Ny03NGQzLTRkNmMtOWFlMS0yYmU5ZWFiNzYzNDkiLCJleHAiOjE0NjM0ODY4NTl9.3OOTe8UHrLIjR-VzG8w8EI3CKoU1fMHXxC-nuwh5mi0' (length=165)
          protected '_payload' => 
            object(stdClass)[139]
              ...
          protected '_error' => null
          protected '_defaultConfig' => 
            array (size=6)
              ...
          protected '_registry' => 
            object(Cake\Controller\ComponentRegistry)[103]
              ...
          protected '_passwordHasher' => null
          protected '_needsPasswordRehash' => boolean false
          protected '_config' => 
            array (size=13)
              ...
          protected '_configInitialized' => boolean true
  protected '_original' => 
    array (size=0)
      empty
  protected '_hidden' => 
    array (size=0)
      empty
  protected '_virtual' => 
    array (size=0)
      empty
  protected '_className' => null
  protected '_dirty' => 
    array (size=2)
      0 => boolean true
      1 => boolean true
  protected '_new' => boolean true
  protected '_errors' => 
    array (size=0)
      empty
  protected '_invalid' => 
    array (size=0)
      empty
  protected '_accessible' => 
    array (size=2)
      '*' => boolean true
      'id' => boolean true
  protected '_registryAlias' => string 'Users' (length=5)

Testcase fail

I get this failure with a clean checkout:
composer installs cakephp 3.4.1

PHPUnit 5.3.2 by Sebastian Bergmann and contributors.

F.......                                                            8 / 8 (100%)

Time: 467 ms, Memory: 16.00Mb

There was 1 failure:

1) Muffin\Footprint\Test\TestCase\Model\Behavior\FootprintAwareTraitTest::testImplementedEvents
Failed asserting that Array &0 (
    'Model.initialize' => '2 listener(s)'
) is identical to Array &0 (
    'Model.initialize' => '1 listener(s)'
).

โ€ฆ/Footprint/tests/TestCase/Auth/FootprintAwareTraitTest.php:44

FAILURES!
Tests: 8, Assertions: 15, Failures: 1.

FootprintListener->handleEvent is never called

Expected Behavior

  • created_by is set with user_id
  • FootprintAwareTrait->footprint sets the listener for handleEvent
  • EventManager->dispatch() for event=beforeFilter finds FootprintListener
  • FootprintListener->handleEvent is called with the beforeFilter event, so that the user credentials in options will be set
  • FootprintBehavior->dispatch is called with the beforeSave event, to set created_by

Actual Behavior

  • created_by is empty
  • FootprintAwareTrait->footprint sets the listener for handleEvent, even User Information is loaded correctly
  • EventManager->dispatch() for event=beforeFilter DOES NOT find FootprintListener (but beforeSave Events)
  • FootprintListener->handleEvent is NEVER called
  • FootprintBehavior->dispatch IS ALWAYS called with the beforeSave event, but no user information found

Steps to Reproduce the Problem

I am using the deprecated AuthComponent with UsersModel und I want to fill created_by.
So far there is no Association btw. created_by and Users.

Actually, I am unsure about what you mean by this:
"FootprintBehavior attached to a model do not load the model inside Controller::initialize()"

I am using the Behavior in the ArticlesTable and access in the ArticlesController the by model $this->Articles->get() and $this->Articles->patchEntity() and $this->Articles->save(). Is that okay like this?

Anything that I might have missed?

Specifications

  • CakePHP: 4.2.7
  • muffin/footprint: 2.0.3

Problem with foreign key constraint

Its working fine with cakephp 3.5 till user is login. but how to set NULL (not 0) for not login users.

In simple words how to save fields (created_by, modified_by) with NULL not the 0.

This is the case where user can fill form with or without login and system need to track user if it is login else it should enter NULL (not zero).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.