diff --git a/.gitignore b/.gitignore index 4b726c75ae8..e7dc3ca7ac3 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,4 @@ docker-compose.override.yml tests/Sapi/Roadrunner/rr-* github-actions-helpers/_build.csproj.DotSettings .nuke +cookies.txt diff --git a/src/Integrations/Integrations/Laravel/LaravelIntegration.php b/src/Integrations/Integrations/Laravel/LaravelIntegration.php index 50a3a8a43d4..5949f410e73 100644 --- a/src/Integrations/Integrations/Laravel/LaravelIntegration.php +++ b/src/Integrations/Integrations/Laravel/LaravelIntegration.php @@ -461,6 +461,33 @@ function ($This, $scope, $args) use ($integration) { } ); + \DDTrace\hook_method( + 'Illuminate\Auth\Events\Authenticated', + '__construct', + null, + function ($This, $scope, $args) use ($integration) { + $authClass = 'Illuminate\Contracts\Auth\Authenticatable'; + if ( + !isset($args[1]) || + !$args[1] || + !($args[1] instanceof $authClass) + ) { + return; + } + + $meta = []; + $user = $args[1]; + if (isset($user->name)) { + $meta['name'] = $user->name; + } + if (isset($user->email)) { + $meta['email'] = $user->email; + } + + \DDTrace\set_user($user->getAuthIdentifier(), $meta); + } + ); + return Integration::LOADED; } diff --git a/tests/Common/WebFrameworkTestCase.php b/tests/Common/WebFrameworkTestCase.php index b4ad44920fc..aa6d23310e9 100644 --- a/tests/Common/WebFrameworkTestCase.php +++ b/tests/Common/WebFrameworkTestCase.php @@ -21,12 +21,25 @@ abstract class WebFrameworkTestCase extends IntegrationTestCase const PORT = 9999; const ERROR_LOG_NAME = 'phpunit_error.log'; + const COOKIE_JAR = 'cookies.txt'; /** * @var WebServer|null */ private static $appServer; protected $checkWebserverErrors = true; + protected $cookiesFile; + + protected function ddSetUp() + { + parent::ddSetUp(); + $this->cookiesFile = realpath(dirname(static::getAppIndexScript()) . '/' . static::COOKIE_JAR); + $f = @fopen($this->cookiesFile, "r+"); + if ($f !== false) { + ftruncate($f, 0); + fclose($f); + } + } public static function ddSetUpBeforeClass() { @@ -192,6 +205,10 @@ protected function sendRequest($method, $url, $headers = [], $body = [], $change curl_setopt($ch, CURLOPT_RETURNTRANSFER, $options[CURLOPT_RETURNTRANSFER]); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, $options[CURLOPT_FOLLOWLOCATION]); + curl_setopt($ch, CURLOPT_COOKIEJAR, $this->cookiesFile); + curl_setopt ($ch, CURLOPT_COOKIEFILE, $this->cookiesFile); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_HEADER, 1); if ($method === 'POST') { curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, is_array($body) ? json_encode($body) : $body); diff --git a/tests/Frameworks/Laravel/Version_8_x/app/Http/Controllers/LoginTestController.php b/tests/Frameworks/Laravel/Version_8_x/app/Http/Controllers/LoginTestController.php index 40647ecebb2..b5361660bff 100644 --- a/tests/Frameworks/Laravel/Version_8_x/app/Http/Controllers/LoginTestController.php +++ b/tests/Frameworks/Laravel/Version_8_x/app/Http/Controllers/LoginTestController.php @@ -50,4 +50,9 @@ public function register(Request $request): RedirectResponse return redirect(RouteServiceProvider::HOME); } + + public function behind_auth() + { + return "page behind auth"; + } } diff --git a/tests/Frameworks/Laravel/Version_8_x/routes/web.php b/tests/Frameworks/Laravel/Version_8_x/routes/web.php index 4ee7e5a1dd7..acc3dbdc113 100644 --- a/tests/Frameworks/Laravel/Version_8_x/routes/web.php +++ b/tests/Frameworks/Laravel/Version_8_x/routes/web.php @@ -39,6 +39,7 @@ Route::get('queue/workOn', [QueueTestController::class, 'workOn']); Route::get('login/auth', [LoginTestController::class, 'auth']); Route::get('login/signup', [LoginTestController::class, 'register']); +Route::get('/behind_auth', [LoginTestController::class, 'behind_auth'])->name('behind_auth')->middleware('auth'); // This route has to remain unnamed so we test both route cached and not cached. Route::get('/unnamed-route', [RouteCachingController::class, 'unnamed']); diff --git a/tests/Integrations/Laravel/V8_x/AutomatedLoginEventsTest.php b/tests/Integrations/Laravel/V8_x/LoginEventsTest.php similarity index 62% rename from tests/Integrations/Laravel/V8_x/AutomatedLoginEventsTest.php rename to tests/Integrations/Laravel/V8_x/LoginEventsTest.php index e87be3a6b16..d8dfd810b2c 100644 --- a/tests/Integrations/Laravel/V8_x/AutomatedLoginEventsTest.php +++ b/tests/Integrations/Laravel/V8_x/LoginEventsTest.php @@ -6,7 +6,7 @@ use DDTrace\Tests\Frameworks\Util\Request\GetSpec; use datadog\appsec\AppsecStatus; -class AutomatedLoginEventsTest extends WebFrameworkTestCase +class LoginEventsTest extends WebFrameworkTestCase { protected static function getAppIndexScript() { @@ -39,9 +39,16 @@ public static function ddTearDownAfterClass() protected function login($email) { - $this->call( - GetSpec::create('Login success event', '/login/auth?email='.$email) - ); + return $this->tracesFromWebRequest(function () use ($email) { + $this->call( + GetSpec::create('Login success event', '/login/auth?email='.$email) + ); + }); + } + + protected function createUser($id, $name, $email) { + //Password is password + $this->connection()->exec("insert into users (id, name, email, password) VALUES (".$id.", '".$name."', '".$email."', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi')"); } public function testUserLoginSuccessEvent() @@ -49,8 +56,7 @@ public function testUserLoginSuccessEvent() $id = 1234; $name = 'someName'; $email = 'test-user@email.com'; - //Password is password - $this->connection()->exec("insert into users (id, name, email, password) VALUES (".$id.", '".$name."', '".$email."', '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi')"); + $this->createUser($id, $name, $email); $this->login($email); @@ -63,6 +69,42 @@ public function testUserLoginSuccessEvent() $this->assertEquals('track_user_login_success_event', $events[0]['eventName']); } + public function testLoggedInCalls() + { + $id = 1234; + $name = 'someName'; + $email = 'test-user@email.com'; + $this->createUser($id, $name, $email); + + //First log in + $traces = $this->login($email); + + $meta = $traces[0][0]['meta']; + $this->assertEquals($id, $meta['usr.id']); + $this->assertEquals($name, $meta['usr.name']); + $this->assertEquals($email, $meta['usr.email']); + + $events = AppsecStatus::getInstance()->getEvents(); + $this->assertEquals(1, count($events)); + $this->assertEquals($id, $events[0]['userId']); + $this->assertEquals($name, $events[0]['metadata']['name']); + $this->assertEquals($email, $events[0]['metadata']['email']); + $this->assertTrue($events[0]['automated']); + $this->assertEquals('track_user_login_success_event', $events[0]['eventName']); + + //Now we are logged in lets do another call + AppsecStatus::getInstance()->setDefaults(); //Remove all events + $traces = $this->tracesFromWebRequest(function () { + $this->call(GetSpec::create('Behind auth', '/behind_auth')); + }); + + $meta = $traces[0][0]['meta']; + $this->assertEquals(0, count($events)); //Auth does not generate appsec events + $this->assertEquals($id, $meta['usr.id']); + $this->assertEquals($name, $meta['usr.name']); + $this->assertEquals($email, $meta['usr.email']); + } + public function testUserLoginFailureEvent() { $email = 'test-user-non-existing@email.com';