From e1f1aeee7c28b5640e497833e18dd6e7d56bb4d2 Mon Sep 17 00:00:00 2001 From: Davo Hynds Date: Mon, 20 Nov 2023 15:00:00 -0600 Subject: [PATCH] Add the ability to mask log data --- src/LtiServiceConnector.php | 37 ++++++++++++++++++++++++++----- src/ServiceRequest.php | 15 +++++++++++++ tests/LtiServiceConnectorTest.php | 30 +++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 5 deletions(-) diff --git a/src/LtiServiceConnector.php b/src/LtiServiceConnector.php index ab69986d..7485ef68 100644 --- a/src/LtiServiceConnector.php +++ b/src/LtiServiceConnector.php @@ -70,7 +70,8 @@ public function getAccessToken(ILtiRegistration $registration, array $scopes) $registration->getAuthTokenUrl(), ServiceRequest::TYPE_AUTH ); - $request->setPayload(['form_params' => $authRequest]); + $request->setPayload(['form_params' => $authRequest]) + ->setMaskResponseLogs(true); $response = $this->makeRequest($request); $tokenData = $this->getResponseBody($response); @@ -177,11 +178,16 @@ public function getAll( return $results; } - private function logRequest( + public static function getLogMessage( IServiceRequest $request, array $responseHeaders, ?array $responseBody - ): void { + ): string { + if ($request->getMaskResponseLogs()) { + $responseHeaders = static::maskValues($responseHeaders); + $responseBody = static::maskValues($responseBody); + } + $contextArray = [ 'request_method' => $request->getMethod(), 'request_url' => $request->getUrl(), @@ -195,11 +201,32 @@ private function logRequest( $contextArray['request_body'] = $requestBody; } - error_log(implode(' ', array_filter([ + return implode(' ', array_filter([ $request->getErrorPrefix(), json_decode($requestBody)->userId ?? null, print_r($contextArray, true), - ]))); + ])); + } + + private function logRequest( + IServiceRequest $request, + array $responseHeaders, + ?array $responseBody + ): void { + error_log(static::getLogMessage($request, $responseHeaders, $responseBody)); + } + + private static function maskValues(?array $payload) + { + if (!isset($payload) || empty($payload)) { + return $payload; + } + + foreach ($payload as $key => $value) { + $payload[$key] = '***'; + } + + return $payload; } private function getAccessTokenCacheKey(ILtiRegistration $registration, array $scopes) diff --git a/src/ServiceRequest.php b/src/ServiceRequest.php index bd36c3e8..7c1d53df 100644 --- a/src/ServiceRequest.php +++ b/src/ServiceRequest.php @@ -43,6 +43,9 @@ class ServiceRequest implements IServiceRequest private $contentType = 'application/json'; private $accept = 'application/json'; + // Other + private $maskResponseLogs = false; + public function __construct(string $method, string $url, $type = self::UNSUPPORTED) { $this->method = $method; @@ -120,6 +123,18 @@ public function setContentType(string $contentType): IServiceRequest return $this; } + public function getMaskResponseLogs(): bool + { + return $this->maskResponseLogs; + } + + public function setMaskResponseLogs(bool $shouldMask): IServiceRequest + { + $this->maskResponseLogs = $shouldMask; + + return $this; + } + public function getErrorPrefix(): string { $defaultMessage = 'Logging request data:'; diff --git a/tests/LtiServiceConnectorTest.php b/tests/LtiServiceConnectorTest.php index a7319d1b..ae3f24f0 100644 --- a/tests/LtiServiceConnectorTest.php +++ b/tests/LtiServiceConnectorTest.php @@ -330,6 +330,36 @@ public function testItGetsAll() $this->assertEquals($expected, $result); } + public function testItBuildsLogMessage() + { + $this->mockMakeRequest(); + $this->request->shouldReceive('getErrorPrefix') + ->once()->andReturn('Logging request data:'); + $this->request->shouldReceive('getMaskResponseLogs') + ->once()->andReturn(false); + + $result = $this->connector::getLogMessage($this->request, ['foo' => 'bar'], ['baz' => 'bat']); + + $expected = "Logging request data: id Array\n(\n [request_method] => POST\n [request_url] => https://example.com\n [response_headers] => Array\n (\n [foo] => bar\n )\n\n [response_body] => {\"baz\":\"bat\"}\n [request_body] => {\"userId\":\"id\"}\n)\n"; + + $this->assertEquals($expected, $result); + } + + public function testItMasksSensitiveDataInLogMessage() + { + $this->mockMakeRequest(); + $this->request->shouldReceive('getErrorPrefix') + ->once()->andReturn('Logging request data:'); + $this->request->shouldReceive('getMaskResponseLogs') + ->once()->andReturn(true); + + $result = $this->connector::getLogMessage($this->request, ['foo' => 'bar'], ['baz' => 'bat']); + + $expected = "Logging request data: id Array\n(\n [request_method] => POST\n [request_url] => https://example.com\n [response_headers] => Array\n (\n [foo] => ***\n )\n\n [response_body] => {\"baz\":\"***\"}\n [request_body] => {\"userId\":\"id\"}\n)\n"; + + $this->assertEquals($expected, $result); + } + private function mockMakeRequest() { // It makes another request