diff --git a/config/laravel-auditing.php b/config/laravel-auditing.php index 258ba3a..55cf65e 100644 --- a/config/laravel-auditing.php +++ b/config/laravel-auditing.php @@ -37,7 +37,7 @@ | */ 'resolver' => [ - 'user' => Ensi\LaravelAuditing\Facades\Subject::class, + 'user' => Ensi\LaravelAuditing\Resolvers\UserResolver::class, 'ip_address' => Ensi\LaravelAuditing\Resolvers\IpAddressResolver::class, 'user_agent' => Ensi\LaravelAuditing\Resolvers\UserAgentResolver::class, 'url' => Ensi\LaravelAuditing\Resolvers\UrlResolver::class, diff --git a/database/migrations/audits.stub b/database/migrations/audits.stub index 1711e60..3c262bb 100644 --- a/database/migrations/audits.stub +++ b/database/migrations/audits.stub @@ -32,9 +32,11 @@ class CreateAuditsTable extends Migration $table->uuid('transaction_uid')->nullable(); $table->timestamp('transaction_time', 6)->nullable(); + $table->string('user_id')->nullable(); $table->index(['subject_id', 'subject_type']); $table->index(['transaction_uid', 'created_at']); + $table->index(['user_id']); }); } diff --git a/src/Contracts/Principal.php b/src/Contracts/Principal.php index 8bc8f01..cfcf087 100644 --- a/src/Contracts/Principal.php +++ b/src/Contracts/Principal.php @@ -29,10 +29,8 @@ public function getName(): string; /** * Возвращает идентификатор пользователя, если есть. - * Для самого пользователя getAuthIdentifier и getUserIdentifier должны вернуть - * одно и то же значение. * - * @return int|null + * @return mixed */ - public function getUserIdentifier(): ?int; + public function getUserIdentifier(); } \ No newline at end of file diff --git a/src/Contracts/UserResolver.php b/src/Contracts/UserResolver.php index 8728c68..5ac8c8f 100644 --- a/src/Contracts/UserResolver.php +++ b/src/Contracts/UserResolver.php @@ -7,7 +7,7 @@ interface UserResolver /** * Resolve the User. * - * @return mixed|null + * @return string|null */ public static function resolve(); } diff --git a/src/Facades/Subject.php b/src/Facades/Subject.php index e2e8cb4..fddd8a2 100644 --- a/src/Facades/Subject.php +++ b/src/Facades/Subject.php @@ -2,17 +2,16 @@ namespace Ensi\LaravelAuditing\Facades; -use Ensi\LaravelAuditing\Resolvers\SubjectManager; use Ensi\LaravelAuditing\Contracts\Principal; -use Illuminate\Support\Optional; +use Ensi\LaravelAuditing\Resolvers\SubjectManager; use Illuminate\Support\Facades\Facade; -use Ensi\LaravelAuditing\Contracts\UserResolver; +use Illuminate\Support\Optional; /** * @method static void attach(\Ensi\LaravelAuditing\Contracts\Principal $subject) * @method static void detach() */ -class Subject extends Facade implements UserResolver +class Subject extends Facade { protected static function getFacadeAccessor(): string { diff --git a/src/Models/Audit.php b/src/Models/Audit.php index ecfc456..7c1637b 100644 --- a/src/Models/Audit.php +++ b/src/Models/Audit.php @@ -22,6 +22,7 @@ * @property int $root_entity_id Корневая сущность * @property string $subject_type * @property int $subject_id Субъект доступа + * @property string|null $user_id Идентификатор пользователя * * @property \Carbon\CarbonInterface $created_at * @property \Carbon\CarbonInterface $updated_at diff --git a/src/Resolvers/UserResolver.php b/src/Resolvers/UserResolver.php index 08df456..90e93be 100644 --- a/src/Resolvers/UserResolver.php +++ b/src/Resolvers/UserResolver.php @@ -4,6 +4,7 @@ use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Config; +use Illuminate\Support\Optional; class UserResolver implements \Ensi\LaravelAuditing\Contracts\UserResolver { @@ -19,8 +20,10 @@ public static function resolve() foreach ($guards as $guard) { if (Auth::guard($guard)->check()) { - return Auth::guard($guard)->user(); + return (string)Auth::guard($guard)->user()->getAuthIdentifier(); } } + + return null; } } diff --git a/src/SupportsAudit.php b/src/SupportsAudit.php index f2eb0ff..27bf607 100644 --- a/src/SupportsAudit.php +++ b/src/SupportsAudit.php @@ -2,6 +2,8 @@ namespace Ensi\LaravelAuditing; +use Ensi\LaravelAuditing\Contracts\Principal; +use Ensi\LaravelAuditing\Facades\Subject; use Illuminate\Database\Eloquent\Relations\MorphMany; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Arr; @@ -290,7 +292,7 @@ public function toAudit(): array $tags = implode(',', $this->generateTags()); - $user = $this->resolveUser(); + $subject = $this->resolveSubject(); return $this->transformAudit([ 'old_values' => $old, @@ -299,11 +301,12 @@ public function toAudit(): array 'event' => $this->auditEvent, 'auditable_id' => $this->getKey(), 'auditable_type' => $this->getMorphClass(), - 'subject_id' => $user ? $user->getAuthIdentifier() : null, - 'subject_type' => $user ? $user->getMorphClass() : null, + 'subject_id' => $subject?->getAuthIdentifier(), + 'subject_type' => $subject?->getMorphClass(), 'url' => $this->resolveUrl(), 'ip_address' => $this->resolveIpAddress(), 'user_agent' => $this->resolveUserAgent(), + 'user_id' => $this->resolveUser(), 'tags' => empty($tags) ? null : $tags, ]); } @@ -325,6 +328,16 @@ public function transformAudit(array $data): array return $data; } + /** + * Resolve the Subject. + * + * @return Principal|null + */ + protected function resolveSubject() + { + return Subject::resolve(); + } + /** * Resolve the User. * @@ -336,11 +349,11 @@ protected function resolveUser() { $userResolver = Config::get('laravel-auditing.resolver.user'); - if (is_subclass_of($userResolver, UserResolver::class)) { - return call_user_func([$userResolver, 'resolve']); + if (!is_subclass_of($userResolver, UserResolver::class)) { + throw new AuditingException('Invalid UserResolver implementation'); } - throw new AuditingException('Invalid UserResolver implementation'); + return call_user_func([$userResolver, 'resolve']); } /** diff --git a/tests/AuditingTestCase.php b/tests/AuditingTestCase.php index 4cfdee6..8719143 100644 --- a/tests/AuditingTestCase.php +++ b/tests/AuditingTestCase.php @@ -7,6 +7,7 @@ use Ensi\LaravelAuditing\Resolvers\IpAddressResolver; use Ensi\LaravelAuditing\Resolvers\UrlResolver; use Ensi\LaravelAuditing\Resolvers\UserAgentResolver; +use Ensi\LaravelAuditing\Resolvers\UserResolver; use Orchestra\Testbench\TestCase; class AuditingTestCase extends TestCase @@ -23,6 +24,10 @@ protected function getEnvironmentSetUp($app) 'database' => ':memory:', 'prefix' => '', ]); + $app['config']->set('auth.guards.api', [ + 'driver' => 'session', + 'provider' => 'users', + ]); // Audit $app['config']->set('laravel-auditing.drivers.database.connection', 'testing'); @@ -31,7 +36,7 @@ protected function getEnvironmentSetUp($app) 'web', 'api', ]); - $app['config']->set('laravel-auditing.resolver.user', Subject::class); + $app['config']->set('laravel-auditing.resolver.user', UserResolver::class); $app['config']->set('laravel-auditing.resolver.url', UrlResolver::class); $app['config']->set('laravel-auditing.resolver.ip_address', IpAddressResolver::class); $app['config']->set('laravel-auditing.resolver.user_agent', UserAgentResolver::class); diff --git a/tests/Models/User.php b/tests/Models/User.php index dd650d7..1504248 100644 --- a/tests/Models/User.php +++ b/tests/Models/User.php @@ -6,11 +6,13 @@ use Ensi\LaravelAuditing\Contracts\Principal; use Ensi\LaravelAuditing\Database\Factories\UserFactory; use Ensi\LaravelAuditing\SupportsAudit; +use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Database\Eloquent\Model; -class User extends Model implements Auditable, Principal +class User extends Model implements Auditable, Principal, Authenticatable { use SupportsAudit; + use \Illuminate\Auth\Authenticatable; /** * {@inheritdoc} diff --git a/tests/Models/VirtualUser.php b/tests/Models/VirtualUser.php new file mode 100644 index 0000000..c95b188 --- /dev/null +++ b/tests/Models/VirtualUser.php @@ -0,0 +1,36 @@ +setAuditEvent('created'); - $this->assertCount(12, $auditData = $model->toAudit()); + $this->assertCount(self::AUDIT_FIELDS_COUNT, $auditData = $model->toAudit()); Assert::assertArraySubset([ 'old_values' => [], @@ -442,7 +445,7 @@ public function itReturnsTheAuditDataIncludingSubjectAttributes() { $model->setAuditEvent('created'); - $this->assertCount(12, $auditData = $model->toAudit()); + $this->assertCount(self::AUDIT_FIELDS_COUNT, $auditData = $model->toAudit()); Assert::assertArraySubset([ 'old_values' => [], @@ -460,6 +463,105 @@ public function itReturnsTheAuditDataIncludingSubjectAttributes() { 'url' => 'console', 'ip_address' => '127.0.0.1', 'user_agent' => 'Symfony', + 'user_id' => null, + 'tags' => null, + ], $auditData, true); + } + + /** + * @dataProvider userResolverProvider + * @test + */ + public function itReturnsTheAuditDataIncludingUserId( + string $guard, + string $driver, + ?string $id + ) { + $this->app['config']->set('laravel-auditing.user.guards', [$guard]); + + $user = User::factory()->create(); + $this->actingAs($user, $driver); + $now = Carbon::now(); + + $model = Article::factory()->make([ + 'title' => 'How To Audit Eloquent Models', + 'content' => 'First step: install the laravel-auditing package.', + 'reviewed' => 1, + 'published_at' => $now, + ]); + + $model->setAuditEvent('created'); + + $this->assertCount(self::AUDIT_FIELDS_COUNT, $auditData = $model->toAudit()); + + Assert::assertArraySubset([ + 'old_values' => [], + 'new_values' => [ + 'title' => 'How To Audit Eloquent Models', + 'content' => 'First step: install the laravel-auditing package.', + 'reviewed' => 1, + 'published_at' => $now->toDateTimeString(), + ], + 'event' => 'created', + 'auditable_id' => null, + 'auditable_type' => Article::class, + 'subject_id' => null, + 'subject_type' => null, + 'url' => 'console', + 'ip_address' => '127.0.0.1', + 'user_agent' => 'Symfony', + 'user_id' => $id, + 'tags' => null, + ], $auditData, true); + } + + public function userResolverProvider(): array + { + return [ + ['api', 'web', null], + ['web', 'api', null], + ['api', 'api', '1'], + ['web', 'web', '1'], + ]; + } + + /** + * @test + */ + public function itReturnsTheAuditDataIncludingVirtualUserId() + { + $this->actingAs(new VirtualUser(), 'api'); + + $now = Carbon::now(); + + $model = Article::factory()->make([ + 'title' => 'How To Audit Eloquent Models', + 'content' => 'First step: install the laravel-auditing package.', + 'reviewed' => 1, + 'published_at' => $now, + ]); + + $model->setAuditEvent('created'); + + $this->assertCount(self::AUDIT_FIELDS_COUNT, $auditData = $model->toAudit()); + + Assert::assertArraySubset([ + 'old_values' => [], + 'new_values' => [ + 'title' => 'How To Audit Eloquent Models', + 'content' => 'First step: install the laravel-auditing package.', + 'reviewed' => 1, + 'published_at' => $now->toDateTimeString(), + ], + 'event' => 'created', + 'auditable_id' => null, + 'auditable_type' => Article::class, + 'subject_id' => null, + 'subject_type' => null, + 'url' => 'console', + 'ip_address' => '127.0.0.1', + 'user_agent' => 'Symfony', + 'user_id' => VirtualUser::ID, 'tags' => null, ], $auditData, true); } @@ -491,7 +593,7 @@ public function itExcludesAttributesFromTheAuditDataWhenInStrictMode() $model->setAuditEvent('created'); - $this->assertCount(12, $auditData = $model->toAudit()); + $this->assertCount(self::AUDIT_FIELDS_COUNT, $auditData = $model->toAudit()); Assert::assertArraySubset([ 'old_values' => [], @@ -605,7 +707,7 @@ public function transformAudit(array $data): array $model->setAuditEvent('created'); - $this->assertCount(12, $auditData = $model->toAudit()); + $this->assertCount(self::AUDIT_FIELDS_COUNT, $auditData = $model->toAudit()); Assert::assertArraySubset([ 'new_values' => [ diff --git a/tests/database/factories/UserFactory.php b/tests/database/factories/UserFactory.php index 5e553d4..143d572 100644 --- a/tests/database/factories/UserFactory.php +++ b/tests/database/factories/UserFactory.php @@ -10,6 +10,10 @@ |-------------------------------------------------------------------------- | */ + +/** + * @method User create(array $extra = []) + */ class UserFactory extends Factory { protected $model = User::class; diff --git a/tests/database/migrations/0000_00_00_000001_create_audits_test_table.php b/tests/database/migrations/0000_00_00_000001_create_audits_test_table.php index 076c879..9aede24 100644 --- a/tests/database/migrations/0000_00_00_000001_create_audits_test_table.php +++ b/tests/database/migrations/0000_00_00_000001_create_audits_test_table.php @@ -32,9 +32,11 @@ public function up() $table->uuid('transaction_uid')->nullable(); $table->timestamp('transaction_time', 6)->nullable(); + $table->string('user_id')->nullable(); $table->index(['subject_id', 'subject_type']); $table->index(['transaction_uid', 'created_at']); + $table->index(['user_id']); }); }