From 518a2d3d757a4efae30dce6b61fd97e274547c8b Mon Sep 17 00:00:00 2001 From: sreenivasa-murty-lrn <160576407+sreenivasa-murty-lrn@users.noreply.github.com> Date: Thu, 10 Oct 2024 12:10:43 +0530 Subject: [PATCH] Use Json::encode() throughout LRN-43802 (#85) api-events should forward user_id LRN-44754 [TEST] Include user_id in api-events security packet LRN-44754 Assimilate user_id treatment to LP LRN-43802 LRN-44748 [DOC] Update change log [Bug] Add the action value to the signature array only we not empty LRN-44748 [Bug] Fix the empty request param by converting to object and array LRN-44748 [Feature] Implement the request to jsonstring in contructor LRN-44748 [Feature] Iteration-3 Implement for generate() method the request MUST be any array LRN-44748 [Feature] Iteration-3 Convert the string to json for telemetry enabled LRN-44748 Use a decodedRequestPacket variable for the doecded request, fix up function signatures Fix up function signatures Remove wrong comment [Feature] Remove the unwanted code LRN-44748 Re-use variable instead of calling function again Remove double empty check [Feature] Add test case for geneeratesignature() with empty array and empty object LRN-44748 [Feature] Add new testcase for request with empty lines LRN-44748 [Feature] Remove the un-used method LRN-44748 Co-authored-by: david-scarratt-lrn --- ChangeLog.md | 7 + src/Request/Init.php | 123 ++++++++++-------- .../PreHashStrings/LegacyPreHashString.php | 27 ++-- .../PreHashStrings/PreHashStringInterface.php | 5 +- tests/Fixtures/ParamsFixture.php | 32 ++++- tests/Request/InitTest.php | 35 ++++- .../LegacyPreHashStringTest.php | 48 ++++++- 7 files changed, 200 insertions(+), 77 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index f3b5c3c..0322aeb 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Fixed +- Fixed an inconsistency with encoding to JSON +- Fixed incorrect replacement of SERVICE_ITEMS_API by SERVICE_EVENTS_API + in services not requiring `user_id` in the security packet +- Fixed handling of `user_id` in security packet for services not + requiring it + ## [v1.0.4] - 2024-07-11 ### Added - Added composable services for signature generation. diff --git a/src/Request/Init.php b/src/Request/Init.php index a707a40..cbb6930 100644 --- a/src/Request/Init.php +++ b/src/Request/Init.php @@ -71,8 +71,15 @@ class Init */ private $requestPacket; + private $decodedRequestPacket; + /** - * Tracking if the request was passed as a string + * Tracking if the request was passed as a string. If the request is indeed passed as string, + * there is an attempt to not alter the string in any way by decoding and encoding. This will + * only work if telemetry is disabled. Otherwise the metadata has to be set in the request + * meaning that decoding is required. It also does not work for assess if it has questions + * api settings + * * @var bool */ private $requestPassedAsString = false; @@ -141,19 +148,17 @@ public function __construct( $this->preHashStringGenerator = $this->preHashStringFactory->getPreHashStringGenerator($service); - // First validate the arguments passed - list ($requestPacket, $securityPacket) = $this->validate($service, $secret, $securityPacket, $requestPacket); - - if (self::$telemetryEnabled) { - $requestPacket = $this->addMeta($requestPacket); - } - // Set instance variables based off the arguments passed $this->service = $service; $this->securityPacket = $securityPacket; $this->secret = $secret; $this->requestPacket = $requestPacket; $this->action = $action; + $this->validate(); + + if (self::$telemetryEnabled) { + $this->addMeta(); + } // Set any service specific options $this->setServiceOptions(); @@ -177,11 +182,8 @@ public function __construct( * } * } * - * @param array $requestPacket - * - * @return array */ - private function addMeta(array $requestPacket): array + private function addMeta() { $sdkMetricsMeta = [ 'version' => $this->getSDKVersion(), @@ -191,15 +193,14 @@ private function addMeta(array $requestPacket): array 'platform_version' => php_uname('r') ]; - if (isset($requestPacket['meta'])) { - $requestPacket['meta']['sdk'] = $sdkMetricsMeta; + if (isset($this->decodedRequestPacket['meta'])) { + $this->decodedRequestPacket['meta']['sdk'] = $sdkMetricsMeta; } else { - $requestPacket['meta'] = [ + $this->decodedRequestPacket['meta'] = [ 'sdk' => $sdkMetricsMeta ]; } - - return $requestPacket; + $this->requestPacket = Json::encode($this->decodedRequestPacket); } /** @@ -230,7 +231,7 @@ public function generate(bool $encode = true) // Add the security packet (with signature) to the output $output['security'] = Json::encode($this->securityPacket); - $output['request'] = Json::encode($this->requestPacket); + $output['request'] = Json::encode($this->decodedRequestPacket); if (!empty($this->action)) { $output['action'] = $this->action; @@ -241,8 +242,8 @@ public function generate(bool $encode = true) case 'assess': // Stringify the request packet if necessary $output = $this->requestPassedAsString ? - Json::encode($this->requestPacket) : - $this->requestPacket; + $this->requestPacket : + $this->decodedRequestPacket; break; case 'author': case 'authoraide': @@ -253,8 +254,8 @@ public function generate(bool $encode = true) // Stringify the request packet if necessary $output['request'] = $this->requestPassedAsString ? - Json::encode($this->requestPacket) : - $this->requestPacket; + $this->requestPacket : + $this->decodedRequestPacket; break; case 'questions': // Add the security packet (with signature) to the root of output @@ -263,14 +264,14 @@ public function generate(bool $encode = true) // Remove the `domain` key from the security packet unset($output['domain']); - if (!empty($this->requestPacket)) { - $output = array_merge($output, $this->requestPacket); + if (!empty($this->decodedRequestPacket)) { + $output = array_merge($output, $this->decodedRequestPacket); } break; case 'events': // Add the security packet (with signature) to the output $output['security'] = $this->securityPacket; - $output['config'] = $this->requestPacket; + $output['config'] = $this->decodedRequestPacket; break; default: // no default @@ -319,14 +320,14 @@ private function setServiceOptions() // security information and a signature. Retrieve the security // information from $this and generate a signature for the // Questions API - if (array_key_exists('questionsApiActivity', $this->requestPacket)) { + if (array_key_exists('questionsApiActivity', $this->decodedRequestPacket)) { // prepare signature parts $signatureParts = []; $signatureParts['consumer_key'] = $this->securityPacket['consumer_key']; if (isset($this->securityPacket['domain'])) { $signatureParts['domain'] = $this->securityPacket['domain']; - } elseif (isset($this->requestPacket['questionsApiActivity']['domain'])) { - $signatureParts['domain'] = $this->requestPacket['questionsApiActivity']['domain']; + } elseif (isset($this->decodedRequestPacket['questionsApiActivity']['domain'])) { + $signatureParts['domain'] = $this->decodedRequestPacket['questionsApiActivity']['domain']; } else { $signatureParts['domain'] = 'assess.learnosity.com'; } @@ -338,7 +339,7 @@ private function setServiceOptions() $signatureParts['secret'] = $this->secret; // override security parameters in questionsApiActivity - $questionsApi = $this->requestPacket['questionsApiActivity']; + $questionsApi = $this->decodedRequestPacket['questionsApiActivity']; $questionsApi['consumer_key'] = $signatureParts['consumer_key']; unset($questionsApi['domain']); $questionsApi['timestamp'] = $signatureParts['timestamp']; @@ -351,7 +352,8 @@ private function setServiceOptions() $this->securityPacket = $signatureParts; $questionsApi['signature'] = $this->generateSignature(); - $this->requestPacket['questionsApiActivity'] = $questionsApi; + $this->decodedRequestPacket['questionsApiActivity'] = $questionsApi; + $this->requestPacket = Json::encode($this->decodedRequestPacket); } break; case 'questions': @@ -362,15 +364,15 @@ private function setServiceOptions() // The Events API requires a user_id, so we make sure it's a part // of the security packet as we share the signature in some cases if ( - array_key_exists('user_id', $this->requestPacket) - && !array_key_exists('user_id', $this->securityPacket) + array_key_exists('user_id', $this->decodedRequestPacket) && + !array_key_exists('user_id', $this->securityPacket) ) { - $this->securityPacket['user_id'] = $this->requestPacket['user_id']; + $this->securityPacket['user_id'] = $this->decodedRequestPacket['user_id']; } break; case 'events': $this->signRequestData = false; - $users = $this->requestPacket['users']; + $users = $this->decodedRequestPacket['users']; $hashedUsers = []; if (!$this->isAssocArray($users)) { throw new ValidationException('Passing an array of user IDs is deprecated,' . @@ -385,7 +387,8 @@ private function setServiceOptions() ); } if (count($hashedUsers)) { - $this->requestPacket['users'] = $hashedUsers; + $this->decodedRequestPacket['users'] = $hashedUsers; + $this->requestPacket = Json::encode($this->decodedRequestPacket); } break; default: @@ -397,42 +400,52 @@ private function setServiceOptions() /** * Validate the arguments passed to the constructor * - * @param string $service - * @param string $secret - * @param array|string $securityPacket - * @param array|string $requestPacket * @return array * @throws ValidationException */ - public function validate(string $service, string $secret, $securityPacket, $requestPacket): array + public function validate() { - if (is_string($requestPacket)) { - $requestPacket = json_decode($requestPacket, true); - $this->requestPassedAsString = true; - } - - if (is_null($requestPacket)) { - $requestPacket = []; + $this->validateAndSetRequestPacket(); + // In case the user gave us a JSON securityPacket, convert to an array + if (is_string($this->securityPacket)) { + $this->securityPacket = json_decode($this->securityPacket, true); } - if (empty($securityPacket) || !is_array($securityPacket)) { + if (empty($this->securityPacket) || !is_array($this->securityPacket)) { throw new ValidationException('The security packet must be an array or a valid JSON string'); } - // In case the user gave us a JSON securityPacket, convert to an array - if (!is_array($securityPacket) && is_string($securityPacket)) { - $securityPacket = json_decode($securityPacket, true); + if (empty($this->secret)) { + throw new ValidationException('The `secret` argument must be a valid string'); } - if (empty($secret)) { - throw new ValidationException('The `secret` argument must be a valid string'); + $this->securityPacket = $this->preHashStringGenerator->validate($this->securityPacket); + } + + private function validateAndSetRequestPacket() + { + $this->requestPassedAsString = $this->isRequestNonEmptyString(); + if ($this->requestPassedAsString) { + $this->decodedRequestPacket = json_decode($this->requestPacket, true); + if (!is_array($this->decodedRequestPacket)) { + throw new ValidationException('The request packet must be an array or a valid JSON string'); + } + return; } - if (!empty($requestPacket) && !is_array($requestPacket)) { + if (!empty($this->requestPacket) && !is_array($this->requestPacket)) { throw new ValidationException('The request packet must be an array or a valid JSON string'); } + if (empty($this->requestPacket)) { + $this->requestPacket = []; + } + $this->decodedRequestPacket = $this->requestPacket; + $this->requestPacket = Json::encode($this->decodedRequestPacket); + } - return $this->preHashStringGenerator->validate($securityPacket, $requestPacket); + private function isRequestNonEmptyString(): bool + { + return is_string($this->requestPacket) && !empty($this->requestPacket); } /** diff --git a/src/Services/PreHashStrings/LegacyPreHashString.php b/src/Services/PreHashStrings/LegacyPreHashString.php index c5d76bd..1f8623e 100644 --- a/src/Services/PreHashStrings/LegacyPreHashString.php +++ b/src/Services/PreHashStrings/LegacyPreHashString.php @@ -3,6 +3,7 @@ namespace LearnositySdk\Services\PreHashStrings; use LearnositySdk\Exceptions\ValidationException; +use LearnositySdk\Utils\Json; class LegacyPreHashString implements PreHashStringInterface { @@ -41,8 +42,8 @@ class LegacyPreHashString implements PreHashStringInterface self::SERVICE_ANNOTATIONS_API, self::SERVICE_AUTHOR_API, self::SERVICE_AUTHOR_AIDE_API, + self::SERVICE_ITEMS_API, self::SERVICE_DATA_API, - self::SERVICE_EVENTS_API, self::SERVICE_REPORTS_API, ]; @@ -112,7 +113,7 @@ public function __construct(string $service, bool $v1Compat = false) public function getPreHashString( array $security, - ?array $request, + $request, ?string $action = 'get', ?string $secret = null ): string { @@ -125,7 +126,11 @@ public function getPreHashString( if (isset($security['expires'])) { $signatureArray[] = $security['expires']; } - if (!in_array($this->service, static::SERVICES_NOT_REQUIRING_USER_ID)) { // || isset($security['user_id'])) { + + if (!in_array($this->service, static::SERVICES_NOT_REQUIRING_USER_ID) || isset($security['user_id'])) { + if (!isset($security['user_id'])) { + throw new ValidationException('User ID is required for this service'); + } $signatureArray[] = $security['user_id']; } @@ -139,17 +144,11 @@ public function getPreHashString( } if (in_array($this->service, static::SERVICES_REQUIRING_SIGNED_REQUEST)) { - $requestJson = $request; - if (is_array($request)) { - $requestJson = json_encode($request); - } - $signatureArray[] = $requestJson; + $signatureArray[] = is_array($request) ? Json::encode($request) : $request; } - if (in_array($this->service, static::SERVICES_REQUIRING_SIGNED_ACTION)) { - if (empty($action)) { - $action = 'get'; - } + // Add the action if necessary + if (!empty($action)) { $signatureArray[] = $action; } @@ -171,7 +170,7 @@ public static function supportsService(string $service): bool * nothing is done with the request packet, but future pre-hash schemes * may require it. */ - public function validate(array $security, array $request): array + public function validate(array $security): array { foreach (array_keys($security) as $key) { if (!in_array($key, static::VALID_SECURITY_KEYS)) { @@ -195,6 +194,6 @@ public function validate(array $security, array $request): array } /* end XXX */ - return [ $request, $security ]; + return $security; } } diff --git a/src/Services/PreHashStrings/PreHashStringInterface.php b/src/Services/PreHashStrings/PreHashStringInterface.php index 2a0c829..ced2dbf 100644 --- a/src/Services/PreHashStrings/PreHashStringInterface.php +++ b/src/Services/PreHashStrings/PreHashStringInterface.php @@ -11,7 +11,7 @@ interface PreHashStringInterface * @param string|null $secret only for legacy pre-hash string generation when $v1Compat is true * @return string the pre-hash string */ - public function getPreHashString(array $security, ?array $request, ?string $action, ?string $secret): string; + public function getPreHashString(array $security, $request, ?string $action, ?string $secret): string; /** Return a list of all supported services * @return string[] @@ -26,8 +26,7 @@ public static function supportsService(string $service): bool; /** Validate a request to be signed, and return a potentially modified request * @param array $security - * @param array $request * @return array <$updatedRequest, $updatedSecurity> * */ - public function validate(array $security, array $request): array; + public function validate(array $security): array; } diff --git a/tests/Fixtures/ParamsFixture.php b/tests/Fixtures/ParamsFixture.php index bfc2d62..32a2c84 100644 --- a/tests/Fixtures/ParamsFixture.php +++ b/tests/Fixtures/ParamsFixture.php @@ -203,6 +203,33 @@ public static function getWorkingAuthorApiParams(bool $assoc = false): array return $params; } + /** + * @param array/string $request + * @param bool $assoc If true, associative array will be returned + * @return array + */ + public static function getWorkingAuthorApiParamsWithRequest($request, bool $assoc = false): array + { + $service = 'author'; + $security = static::getSecurity(); + $secret = static::TEST_CONSUMER_SECRET; + $action = null; + + $params = [ + 'service' => $service, + 'security' => $security, + 'secret' => $secret, + 'request' => $request, + 'action' => $action, + ]; + + if (!$assoc) { + return array_values($params); + } + + return $params; + } + public static function getAuthorApiSignatureForVersion(string $version): string { switch ($version) { @@ -323,6 +350,7 @@ public static function getWorkingEventsApiParams(bool $assoc = false): array { $service = 'events'; $security = static::getSecurity(); + $security['user_id'] = 'events-proctor'; $secret = static::TEST_CONSUMER_SECRET; $request = [ 'users' => [ @@ -353,9 +381,9 @@ public static function getEventsApiSignatureForVersion(string $version): string { switch ($version) { case '01': - return '20739eed410d54a135e8cb3745628834886ab315bfc01693ce9acc0d14dc98bf'; + return 'fd236f3bc3e14bb291a222637fe86d71c3d4393ed7c51353a2f9a14b356fd605'; case '02': - return '$02$5c3160dbb9ab4d01774b5c2fc3b01a35ce4f9709c84571c27dfe333d1ca9d349'; + return '$02$e319a1c9c3e0118d1e81188a650b054a765e072a2d90e4b422ecca2ae9589449'; default: throw new \Exception(__FUNCTION__ . ' not re-implemented for signature version ' . $version); } diff --git a/tests/Request/InitTest.php b/tests/Request/InitTest.php index aaf496a..b6889d4 100644 --- a/tests/Request/InitTest.php +++ b/tests/Request/InitTest.php @@ -452,7 +452,7 @@ public function generateProvider(): array /* Events */ list($service, $security, $secret, $request, $action) = ParamsFixture::getWorkingEventsApiParams(); $eventsApiSig = ParamsFixture::getEventsApiSignatureForVersion(static::SDK_SIGNATURE_VERSION); - $eventsApiExpected = '{"security":{"consumer_key":"yis0TYCu7U9V4o7M","domain":"localhost","timestamp":"20140626-0528","signature":"' + $eventsApiExpected = '{"security":{"consumer_key":"yis0TYCu7U9V4o7M","domain":"localhost","timestamp":"20140626-0528","user_id":"events-proctor","signature":"' . $eventsApiSig . '"},"config":{"users":{"$ANONYMIZED_USER_ID_1":"64ccf06154cf4133624372459ebcccb8b2f8bd7458a73df681acef4e742e175c","$ANONYMIZED_USER_ID_2":"7fa4d6ef8926add8b6411123fce916367250a6a99f50ab8ec39c99d768377adb","$ANONYMIZED_USER_ID_3":"3d5b26843da9192319036b67f8c5cc26e1e1763811270ba164665d0027296952","$ANONYMIZED_USER_ID_4":"3b6ac78f60f3e3eb7a85cec8b48bdca0f590f959e0a87a9c4222898678bd50c8"}}}'; $eventsApi = [ $eventsApiExpected, @@ -586,6 +586,39 @@ public function generateSignatureProvider(): array ]; $testCases['api-reports'] = $reportsApi; + /* Author with empty request array */ + $requestAsEmptyArray = []; + list($service, $security, $secret, $request, $action) = ParamsFixture::getWorkingAuthorApiParamsWithRequest($requestAsEmptyArray); + $authorApi = [ + '$02$a863357d50c59921e8263cf49383ac2b02bf48ec4402dab183bf5d7160d721c9', + $service, $security, $secret, $request, $action, + ]; + + $testCases['api-author-empty-array'] = $authorApi; + + /* Author with empty request object */ + $requestAsEmptyJsonString = '{}'; + list($service, $security, $secret, $request, $action) = ParamsFixture::getWorkingAuthorApiParamsWithRequest($requestAsEmptyJsonString); + $authorApi = [ + '$02$7aaad82e101617545a04bdd2513f425b5bf82bc77f9d86c2f7e8972a18e8d6b5', + $service, $security, $secret, $request, $action, + ]; + + $testCases['api-author-empty-json'] = $authorApi; + + /* Author with line breaks in request object */ + $requestWithLineBreaks = '{ + "mode":"item_list", + "config":{"item_list":{"item":{}}},"user":{"id":"briammoser"} + }'; + list($service, $security, $secret, $request, $action) = ParamsFixture::getWorkingAuthorApiParamsWithRequest($requestWithLineBreaks); + $authorApi = [ + '$02$9f5541fe7ef9e74d9dd17f9df3c8d5d17534695f576ddb573635d9bb510d1ac0', + $service, $security, $secret, $request, $action, + ]; + + $testCases['api-author-with-line-breaks'] = $authorApi; + return $testCases; } diff --git a/tests/Services/PreHashStrings/LegacyPreHashStringTest.php b/tests/Services/PreHashStrings/LegacyPreHashStringTest.php index 4e00bd3..54fd59b 100644 --- a/tests/Services/PreHashStrings/LegacyPreHashStringTest.php +++ b/tests/Services/PreHashStrings/LegacyPreHashStringTest.php @@ -44,7 +44,7 @@ public function preHashStringProvider() 'author' => 'yis0TYCu7U9V4o7M_localhost_20140626-0528_{"mode":"item_list","config":{"item_list":{"item":{"status":true}}},"user":{"id":"walterwhite","firstname":"walter","lastname":"white"}}', 'authoraide' => 'yis0TYCu7U9V4o7M_localhost_20140626-0528_{"config":{"test-attribute":"test"},"user":{"id":"walterwhite","firstname":"walter","lastname":"white"}}', 'data' => 'yis0TYCu7U9V4o7M_localhost_20140626-0528_{"limit":100}_get', - 'events' => 'yis0TYCu7U9V4o7M_localhost_20140626-0528', + 'events' => 'yis0TYCu7U9V4o7M_localhost_20140626-0528_events-proctor', 'items' => 'yis0TYCu7U9V4o7M_localhost_20140626-0528_$ANONYMIZED_USER_ID_{"user_id":"$ANONYMIZED_USER_ID","rendering_type":"assess","name":"Items API demo - assess activity demo","state":"initial","activity_id":"items_assess_demo","session_id":"demo_session_uuid","type":"submit_practice","config":{"configuration":{"responsive_regions":true},"navigation":{"scrolling_indicator":true},"regions":"main","time":{"show_pause":true,"max_time":300},"title":"ItemsAPI Assess Isolation Demo","subtitle":"Testing Subtitle Text"},"items":["Demo3"]}', 'questions' => 'yis0TYCu7U9V4o7M_localhost_20140626-0528_$ANONYMIZED_USER_ID', 'reports' => 'yis0TYCu7U9V4o7M_localhost_20140626-0528_{"reports":[{"id":"report-1","type":"sessions-summary","user_id":"$ANONYMIZED_USER_ID","session_ids":["AC023456-2C73-44DC-82DA28894FCBC3BF"]}]}', @@ -55,7 +55,7 @@ public function preHashStringProvider() 'author' => 'yis0TYCu7U9V4o7M_localhost_20140626-0528_74c5fd430cf1242a527f6223aebd42d30464be22_{"mode":"item_list","config":{"item_list":{"item":{"status":true}}},"user":{"id":"walterwhite","firstname":"walter","lastname":"white"}}', 'authoraide' => null, /* no need for v1 compat, let's make this explicit */ 'data' => 'yis0TYCu7U9V4o7M_localhost_20140626-0528_74c5fd430cf1242a527f6223aebd42d30464be22_{"limit":100}_get', - 'events' => 'yis0TYCu7U9V4o7M_localhost_20140626-0528_74c5fd430cf1242a527f6223aebd42d30464be22', + 'events' => 'yis0TYCu7U9V4o7M_localhost_20140626-0528_events-proctor_74c5fd430cf1242a527f6223aebd42d30464be22', 'items' => 'yis0TYCu7U9V4o7M_localhost_20140626-0528_$ANONYMIZED_USER_ID_74c5fd430cf1242a527f6223aebd42d30464be22_{"user_id":"$ANONYMIZED_USER_ID","rendering_type":"assess","name":"Items API demo - assess activity demo","state":"initial","activity_id":"items_assess_demo","session_id":"demo_session_uuid","type":"submit_practice","config":{"configuration":{"responsive_regions":true},"navigation":{"scrolling_indicator":true},"regions":"main","time":{"show_pause":true,"max_time":300},"title":"ItemsAPI Assess Isolation Demo","subtitle":"Testing Subtitle Text"},"items":["Demo3"]}', 'questions' => 'yis0TYCu7U9V4o7M_localhost_20140626-0528_$ANONYMIZED_USER_ID_74c5fd430cf1242a527f6223aebd42d30464be22', 'reports' => 'yis0TYCu7U9V4o7M_localhost_20140626-0528_74c5fd430cf1242a527f6223aebd42d30464be22_{"reports":[{"id":"report-1","type":"sessions-summary","user_id":"$ANONYMIZED_USER_ID","session_ids":["AC023456-2C73-44DC-82DA28894FCBC3BF"]}]}', @@ -101,6 +101,8 @@ public function additionalTests(): array { return [ 'api-items-json_encode-bug' => $this->jsonEncodeBugParams(), + 'api-items-json_encode-url' => $this->jsonEncodeUrlParams(), + 'api-data-optional-user_id' => $this->unnecessaryUserId(), ]; } @@ -124,4 +126,46 @@ public function jsonEncodeBugParams(): array $params['expected'] = 'yis0TYCu7U9V4o7M_localhost_20140626-0528_$ANONYMIZED_USER_ID_{"user_id":"$ANONYMIZED_USER_ID","rendering_type":"assess","name":"' . $params['request']['name'] . '","state":"initial","activity_id":"items_assess_demo","session_id":"demo_session_uuid","type":"submit_practice","config":{"configuration":{"responsive_regions":true},"navigation":{"scrolling_indicator":true},"regions":"main","time":{"show_pause":true,"max_time":300},"title":"ItemsAPI Assess Isolation Demo","subtitle":"Testing Subtitle Text"},"items":["Demo3"]}'; return $params; } + + /** + * Test case for correct JSON encoding of URLs. + * @returns array < + * string $service + * array $security + * string $secret + * array $request + * ?string $action + * bool $v1Compat + * string $expected + * > + */ + public function jsonEncodeUrlParams(): array + { + $params = ParamsFixture::getWorkingItemsApiParams(true); + $params['request']['config']['configuration']['onsave_redirect_url'] = '/learning/assessments/'; + $params['v1Compat'] = false; + $params['expected'] = 'yis0TYCu7U9V4o7M_localhost_20140626-0528_$ANONYMIZED_USER_ID_{"user_id":"$ANONYMIZED_USER_ID","rendering_type":"assess","name":"Items API demo - assess activity demo","state":"initial","activity_id":"items_assess_demo","session_id":"demo_session_uuid","type":"submit_practice","config":{"configuration":{"responsive_regions":true,"onsave_redirect_url":"/learning/assessments/"},"navigation":{"scrolling_indicator":true},"regions":"main","time":{"show_pause":true,"max_time":300},"title":"ItemsAPI Assess Isolation Demo","subtitle":"Testing Subtitle Text"},"items":["Demo3"]}'; + return $params; + } + + /** + * Test case for optional presence of the user_id in the security packet. + * @returns array < + * string $service + * array $security + * string $secret + * array $request + * ?string $action + * bool $v1Compat + * string $expected + * > + */ + public function unnecessaryUserId(): array + { + $params = ParamsFixture::getWorkingDataApiParams(true); + $params['security']['user_id'] = 'a-user-id'; + $params['v1Compat'] = false; + $params['expected'] = 'yis0TYCu7U9V4o7M_localhost_20140626-0528_a-user-id_{"limit":100}_get'; + return $params; + } }