Logging impersonation data

Two Laravel packages I use often are packages to impersonate a user and packages to log activity (including model changes).

Both are useful, but there’s typically one piece missing. Activity log packages will log *who* initiated a change, but not who the person may have been impersonated by. If I log in as me, then impersonate user X, then make changes, the activity log will typically just show ‘user X made these changes’. However, it was really *me* doing it on behalf of person X.

There’s a few ways one could handle this, and I’d initially looked at request middleware (and still might move in that direction), but with the Spatie activitylog package, I added a ‘pipe’ during the call to initialize logging options.

Here’s a bit of code I use with the Spatie Activitylog package and the laravel-impersonate package.

<?php

namespace App\Services\ActivityLog\Pipes;

use Closure;
use Spatie\Activitylog\Contracts\LoggablePipe;
use Spatie\Activitylog\EventLogBag;

class AddImpersonatorPipe implements LoggablePipe
{
    public function __construct(protected string $field)
    {
    }

    public function handle(EventLogBag $event, Closure $next): EventLogBag
    {
        $manager = app('impersonate');
        if ($manager->isImpersonating()) {
            $impersonatorId = $manager->getImpersonatorId();
            $event->changes[$this->field] = $impersonatorId;
        }

        return $next($event);
    }
}

As you can see, I’m simply adding the originating impersonator ID to the ‘change’ list. It’s not a separated activity log property, nor a separate column in the database table where logged activity lives. That’s certainly possible, but I was trying to handle this without any database changes at this point.

On each model I log changes on, I have a ‘getActivitylogOptions’ method (via a trait) that adds the pipe code above to the logger’s pipeline.

    public function getActivitylogOptions(): LogOptions
    {
        self::addLogChange(new AddImpersonatorPipe('impersonator_id'));

        return LogOptions::defaults()
            ->logAll();
    }

What I’ve not tested yet is if non-model ‘activity’ log behaviour would still have the impersonation processing in place if it’s no model is invoked in the request. For example, just logging that an api call was made. However, pretty much every request has at least the User model invoked, which has the getActivitylogOptions() call in place, so I’m not sure there’s ever a time that would not be invoked.

Also… reviewing this out loud… the self::addLogChange() call may be being invoked once per object instantiation (or at least initial model instantiation) which may be redundant. I’ll need to investigate more.

There doesn’t seem to be a standard process for this, but it’s definitely useful information to log, even if you choose to log it a separate way.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *