diff --git a/.php-cs-fixer.cache b/.php-cs-fixer.cache
new file mode 100644
index 00000000..c814b4a8
--- /dev/null
+++ b/.php-cs-fixer.cache
@@ -0,0 +1 @@
+{"php":"8.1.29","version":"3.59.3:v3.59.3#30ba9ecc2b0e5205e578fe29973c15653d9bfd29","indent":" ","lineEnding":"\n","rules":{"array_syntax":{"syntax":"short"},"no_unused_imports":true,"ordered_imports":{"imports_order":["const","class","function"]},"php_unit_fqcn_annotation":true,"phpdoc_return_self_reference":true,"phpdoc_scalar":true},"hashes":{"\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder32718\/tests\/Util\/JsonTest.php":"229e164156257a33bc40ec45910f1561","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder30753\/tests\/Util\/JsonTest.php":"229e164156257a33bc40ec45910f1561","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder24222\/tests\/Util\/JsonTest.php":"229e164156257a33bc40ec45910f1561","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder25907\/tests\/DigitalIdentityClientTest.php":"7afddf255900f83a72e0a37a93a6f6fe","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder35317\/src\/DigitalIdentityClient.php":"9fba09c315d5bba5bd7b90ae9a717003","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder3167\/src\/Identity\/DigitalIdentityService.php":"d1ca6e501e690798ca13ec028cbf505e","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder667\/src\/Identity\/DigitalIdentityService.php":"d1ca6e501e690798ca13ec028cbf505e","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder32289\/src\/Identity\/DigitalIdentityService.php":"d1ca6e501e690798ca13ec028cbf505e","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder821\/tests\/DigitalIdentityClientTest.php":"7afddf255900f83a72e0a37a93a6f6fe","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder8036\/tests\/DigitalIdentityClientTest.php":"7afddf255900f83a72e0a37a93a6f6fe","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder3553\/tests\/DigitalIdentityClientTest.php":"7afddf255900f83a72e0a37a93a6f6fe","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder9961\/tests\/DigitalIdentityClientTest.php":"7afddf255900f83a72e0a37a93a6f6fe","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder34449\/tests\/DigitalIdentityClientTest.php":"7afddf255900f83a72e0a37a93a6f6fe","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder32249\/src\/Profile\/Util\/Attribute\/AnchorConverter.php":"c348f7a24bc5c13dc8b7362319cbfc9a","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder37291\/src\/Profile\/Util\/Attribute\/AnchorConverter.php":"c348f7a24bc5c13dc8b7362319cbfc9a","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder3093\/src\/Profile\/Util\/Attribute\/AnchorConverter.php":"c348f7a24bc5c13dc8b7362319cbfc9a","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder28127\/src\/Profile\/Util\/Attribute\/AnchorConverter.php":"c348f7a24bc5c13dc8b7362319cbfc9a","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder33456\/src\/Profile\/Util\/Attribute\/AnchorConverter.php":"c348f7a24bc5c13dc8b7362319cbfc9a","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder37936\/tests\/DigitalIdentityClientTest.php":"7afddf255900f83a72e0a37a93a6f6fe","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder20323\/tests\/Identity\/DigitalIdentityServiceTest.php":"a2830bae77f9853790b730ab1c52a9c0","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder135\/src\/DocScan\/Session\/Retrieve\/IdDocumentResourceResponse.php":"7bf4d09cc0a58470af431046cfc7687e","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder452\/src\/DocScan\/Session\/Retrieve\/IdDocumentResourceResponse.php":"7bf4d09cc0a58470af431046cfc7687e","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder1835\/src\/DocScan\/Session\/Retrieve\/IdDocumentResourceResponse.php":"f39668a92c3059e9d8298359ad6eedc9","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder11703\/src\/DocScan\/Session\/Create\/SdkConfig.php":"e570742d72b41967f9cc7e20495b7100","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder6945\/src\/DocScan\/Session\/Create\/SdkConfig.php":"defa00a16760c0f07cb4f16484770ddc","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder10761\/src\/DocScan\/Session\/Create\/SdkConfig.php":"61b63d7cb686a25b1e664868fb8a5460","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder25491\/src\/DocScan\/Session\/Create\/SdkConfig.php":"52a187cef6aa401b2c24a6af50f6d1b3","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder25245\/src\/DocScan\/Session\/Create\/SdkConfig.php":"e97a84e32a50146e14db930c88f07dec","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder6169\/src\/DocScan\/Session\/Create\/Task\/RequestedTextExtractionTaskBuilder.php":"994c482f94c986253a3da93140575f26","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder14820\/src\/DocScan\/Session\/Create\/Task\/RequestedTextExtractionTaskBuilder.php":"994c482f94c986253a3da93140575f26","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder8237\/src\/DocScan\/Session\/Create\/Task\/RequestedTextExtractionTaskBuilder.php":"07bb664508b1053fde7518c3e5c9e0d2","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder30396\/src\/DocScan\/Session\/Create\/Task\/RequestedTextExtractionTaskBuilder.php":"994c482f94c986253a3da93140575f26","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder26547\/src\/DocScan\/Session\/Create\/Task\/RequestedTextExtractionTaskBuilder.php":"575c5c8750d0429c00d78f86f8b15d71","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder26103\/src\/DocScan\/Session\/Create\/Task\/RequestedTextExtractionTaskBuilder.php":"756a15f92fed8a36b7645e3400543738","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder32665\/src\/DocScan\/Session\/Create\/Task\/RequestedTextExtractionTaskBuilder.php":"bbcf30388bf6ab6423a58d6c8cf20b2a","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder12231\/src\/DocScan\/Session\/Create\/Task\/RequestedTextExtractionTaskBuilder.php":"d4c1e5cd33fc66ed0e9016ef83067f13","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder15671\/src\/DocScan\/Session\/Create\/Task\/RequestedTextExtractionTaskBuilder.php":"1baf3d3d5e54d0120769f58a50b2d6a4","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder16639\/src\/DocScan\/Session\/Create\/Task\/RequestedTextExtractionTaskBuilder.php":"790f204c58bb5c755878db5467155929","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder29864\/src\/DocScan\/Session\/Create\/Task\/RequestedTextExtractionTaskBuilder.php":"1baf3d3d5e54d0120769f58a50b2d6a4","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder32797\/src\/DocScan\/Session\/Create\/Task\/RequestedTextExtractionTaskBuilder.php":"9d3a05bc0553ae4fb61fad16444794f1","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder17371\/src\/DocScan\/Session\/Create\/Task\/RequestedTextExtractionTaskBuilder.php":"1ab0b067273a82103be9978f9090d751","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder2626\/src\/DocScan\/Session\/Create\/Task\/RequestedTextExtractionTaskBuilder.php":"b262cf859858d0bfc7d493b82cfaf386","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder36694\/src\/DocScan\/Session\/Create\/Task\/RequestedTextExtractionTaskConfig.php":"b217af9f0e6d73ee26e5037494076717","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder24413\/src\/DocScan\/Session\/Create\/Task\/RequestedTextExtractionTaskConfig.php":"3a913bedb33741c25993d6f54631aab5","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder27718\/src\/DocScan\/Session\/Create\/Task\/RequestedTextExtractionTaskConfig.php":"639289121ce1709eeb7da41ad648d3f7","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder21698\/src\/DocScan\/Session\/Create\/Task\/RequestedTextExtractionTaskConfig.php":"c7d6178d1b7e1734a2315145e477d61a","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder22514\/src\/DocScan\/Session\/Create\/Task\/RequestedTextExtractionTaskConfig.php":"e991f862707f4967f66f52cf6e1ff4fc","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder37049\/src\/DocScan\/Session\/Create\/Task\/RequestedTextExtractionTaskConfig.php":"427142582bbc86bf6c5e80aa8c7b2945","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder24008\/src\/DocScan\/Session\/Create\/Task\/RequestedTextExtractionTaskConfig.php":"17c7713fa71fecefc01fc83ae0120850","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder19212\/src\/DocScan\/Session\/Create\/Task\/RequestedTextExtractionTaskConfig.php":"d45835ea3ec3e7889e1262ff766f22a8","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder6528\/src\/DocScan\/Session\/Create\/Task\/RequestedTextExtractionTaskConfig.php":"a8b7d00af84b1ecf11a3755ef4270a21","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder29603\/src\/DocScan\/Session\/Create\/Task\/RequestedTextExtractionTaskBuilder.php":"b262cf859858d0bfc7d493b82cfaf386","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder37059\/src\/DocScan\/Session\/Create\/Task\/RequestedTextExtractionTaskBuilder.php":"5ebeced1a30fcb9579e2c023fb6683f9","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder18315\/examples\/doc-scan\/routes\/web.php":"cb44e031de8731bfa516c2057adb3705","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder33073\/examples\/profile\/app\/Http\/Controllers\/IdentityController.php":"2f614a4174a0403ac48ef6dcd4796ebc","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder1171\/examples\/doc-scan\/routes\/web.php":"cb44e031de8731bfa516c2057adb3705","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder24221\/examples\/profile\/app\/Providers\/YotiDigitalIdentityServiceProvider.php":"ca9bdcd9f57192ded3421ec399c655d3","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder28861\/examples\/profile\/app\/Providers\/YotiDigitalIdentityServiceProvider.php":"ca9bdcd9f57192ded3421ec399c655d3","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder28556\/examples\/digitalidentity\/config\/app.php":"fd9dd9b2aa7f77bd4898eff4039a5399","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder14596\/examples\/profile\/config\/app.php":"fd9dd9b2aa7f77bd4898eff4039a5399","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder29786\/examples\/profile\/config\/app.php":"fd9dd9b2aa7f77bd4898eff4039a5399","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder18285\/examples\/profile\/config\/app.php":"dc2cea437306a860734271b629691f1b","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder6012\/examples\/profile\/config\/app.php":"dc2cea437306a860734271b629691f1b","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder9177\/examples\/digitalidentity\/storage\/framework\/views\/95c786522cd6c5bb1d0a69b2f33616f23bfd97c6.php":"fec431b44c0882fb0ff10121625ab7df","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder5081\/examples\/profile\/routes\/web.php":"4d6d95a1a884c50539b3ba82cb1cae6e","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder28691\/examples\/profile\/routes\/web.php":"b21cb257df9016e8e827652aecc620d6","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder16838\/examples\/digitalidentity\/storage\/framework\/views\/95c786522cd6c5bb1d0a69b2f33616f23bfd97c6.php":"fec431b44c0882fb0ff10121625ab7df","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder35858\/examples\/profile\/routes\/web.php":"b21cb257df9016e8e827652aecc620d6","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder18443\/examples\/digitalidentity\/storage\/framework\/views\/95c786522cd6c5bb1d0a69b2f33616f23bfd97c6.php":"fec431b44c0882fb0ff10121625ab7df","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder1673\/examples\/profile\/routes\/web.php":"b21cb257df9016e8e827652aecc620d6","\/private\/var\/folders\/hs\/kw0d0_0d2kj8xl5yd5_t2rz40000gn\/T\/PHP CS Fixertemp_folder4880\/examples\/digitalidentity\/storage\/framework\/views\/95c786522cd6c5bb1d0a69b2f33616f23bfd97c6.php":"fec431b44c0882fb0ff10121625ab7df"}}
\ No newline at end of file
diff --git a/examples/digitalidentity/.env.example b/examples/digitalidentity/.env.example
new file mode 100644
index 00000000..72022df2
--- /dev/null
+++ b/examples/digitalidentity/.env.example
@@ -0,0 +1,14 @@
+# This file is a template for defining the environment variables
+# Set the application config values here
+
+YOTI_SDK_ID=xxxxxxxxxxxxxxxxxxxxx
+
+# Below is the private key (in .pem format) associated with the Yoti Application you created on Yoti Hub
+YOTI_KEY_FILE_PATH=./keys/php-sdk-access-security.pem
+
+# Laravel config:
+APP_NAME=yoti.sdk.digitalidentity.demo
+APP_ENV=local
+APP_KEY=
+APP_DEBUG=true
+APP_URL=http://localhost
diff --git a/examples/digitalidentity/.gitignore b/examples/digitalidentity/.gitignore
new file mode 100644
index 00000000..4bb28b97
--- /dev/null
+++ b/examples/digitalidentity/.gitignore
@@ -0,0 +1,16 @@
+/node_modules
+/public/hot
+/public/storage
+/storage/*.key
+/vendor
+.env
+.env.backup
+.phpunit.result.cache
+Homestead.json
+Homestead.yaml
+npm-debug.log
+yarn-error.log
+
+*.pem
+keys/*.pem
+sdk
diff --git a/examples/digitalidentity/README.md b/examples/digitalidentity/README.md
new file mode 100644
index 00000000..8faa9b32
--- /dev/null
+++ b/examples/digitalidentity/README.md
@@ -0,0 +1,24 @@
+# Digital Identity Example
+
+## Requirements
+
+This example requires [Docker](https://docs.docker.com/)
+
+## Setup
+
+* Create your application in the [Yoti Hub](https://hub.yoti.com) (this requires having a Yoti account)
+ * Set the application domain of your app to `localhost:4002`
+* Do the steps below inside the [examples/digitalidentity](./) folder
+* Put `your-application-pem-file.pem` file inside the [keys](keys) folder, as Docker requires the `.pem` file to reside within the same location where it's run from.
+* Copy `.env.example` to `.env`
+* Open `.env` file and fill in the environment variable `YOTI_SDK_ID`
+ * Set `YOTI_KEY_FILE_PATH` to `./keys/your-application-pem-file.pem`
+* Install dependencies `docker-compose up composer`
+* Run the `docker-compose up --build` command
+* Visit [https://localhost:4002](https://localhost:4002)
+* Run the `docker-compose stop` command to stop the containers.
+
+> To see how to retrieve activity details using the one time use token, refer to the [digitalidentity controller](app/Http/Controllers/IdentityController.php)
+
+## Digital Identity Example
+* Visit [/generate-share](https://localhost:4002/generate-share)
diff --git a/examples/digitalidentity/app/Console/Kernel.php b/examples/digitalidentity/app/Console/Kernel.php
new file mode 100644
index 00000000..69914e99
--- /dev/null
+++ b/examples/digitalidentity/app/Console/Kernel.php
@@ -0,0 +1,41 @@
+command('inspire')->hourly();
+ }
+
+ /**
+ * Register the commands for the application.
+ *
+ * @return void
+ */
+ protected function commands()
+ {
+ $this->load(__DIR__.'/Commands');
+
+ require base_path('routes/console.php');
+ }
+}
diff --git a/examples/digitalidentity/app/Exceptions/Handler.php b/examples/digitalidentity/app/Exceptions/Handler.php
new file mode 100644
index 00000000..59c585dc
--- /dev/null
+++ b/examples/digitalidentity/app/Exceptions/Handler.php
@@ -0,0 +1,55 @@
+withFamilyName()
+ ->withGivenNames()
+ ->withFullName()
+ ->withDateOfBirth()
+ ->withGender()
+ ->withNationality()
+ ->withPhoneNumber()
+ ->withSelfie()
+ ->withEmail()
+ ->withDocumentDetails()
+ ->withDocumentImages()
+ ->build();
+
+ $redirectUri = 'https://host/redirect/';
+
+ $shareSessionRequest = (new ShareSessionRequestBuilder())
+ ->withPolicy($policy)
+ ->withRedirectUri($redirectUri)
+ ->build();
+ $session = $client->createShareSession($shareSessionRequest);
+ return $session->getId();
+ }
+ catch (\Throwable $e) {
+ Log::error($e->getTraceAsString());
+ throw new BadRequestHttpException($e->getMessage());
+ }
+ }
+ public function show(DigitalIdentityClient $client)
+ {
+ try {
+ return view('identity', [
+ 'title' => 'Digital Identity Complete Example',
+ 'sdkId' => $client->id
+ ]);
+ } catch (\Throwable $e) {
+ Log::error($e->getTraceAsString());
+ throw new BadRequestHttpException($e->getMessage());
+ }
+ }
+}
diff --git a/examples/digitalidentity/app/Http/Controllers/ReceiptController.php b/examples/digitalidentity/app/Http/Controllers/ReceiptController.php
new file mode 100644
index 00000000..39fe75a6
--- /dev/null
+++ b/examples/digitalidentity/app/Http/Controllers/ReceiptController.php
@@ -0,0 +1,124 @@
+warning("Unknown Content Type parsing as a String");
+ $shareReceipt = $client->fetchShareReceipt($request->query('ReceiptID'));
+
+ $profile = $shareReceipt->getProfile();
+
+ return view('receipt', [
+ 'fullName' => $profile->getFullName(),
+ 'selfie' => $profile->getSelfie(),
+ 'profileAttributes' => $this->createAttributesDisplayList($profile),
+ ]);
+ }
+
+ /**
+ * Create attributes display list.
+ *
+ * @param UserProfile $profile
+ *
+ * @return array
+ */
+ private function createAttributesDisplayList(UserProfile $profile): array
+ {
+ $profileAttributes = [];
+ foreach ($profile->getAttributesList() as $attribute) {
+ switch ($attribute->getName()) {
+ case UserProfile::ATTR_SELFIE:
+ case UserProfile::ATTR_FULL_NAME:
+ // Selfie and full name are handled separately.
+ break;
+ case UserProfile::ATTR_GIVEN_NAMES:
+ $profileAttributes[] = $this->createAttributeDisplayItem($attribute, 'Given names', 'yoti-icon-profile');
+ break;
+ case UserProfile::ATTR_FAMILY_NAME:
+ $profileAttributes[] = $this->createAttributeDisplayItem($attribute, 'Family names', 'yoti-icon-profile');
+ break;
+ case UserProfile::ATTR_DATE_OF_BIRTH:
+ $profileAttributes[] = $this->createAttributeDisplayItem($attribute, 'Date of Birth', 'yoti-icon-calendar');
+ break;
+ case UserProfile::ATTR_GENDER:
+ $profileAttributes[] = $this->createAttributeDisplayItem($attribute, 'Gender', 'yoti-icon-gender');
+ break;
+ case UserProfile::ATTR_STRUCTURED_POSTAL_ADDRESS:
+ $profileAttributes[] = $this->createAttributeDisplayItem($attribute, 'Structured Postal Address', 'yoti-icon-address');
+ break;
+ case UserProfile::ATTR_POSTAL_ADDRESS:
+ $profileAttributes[] = $this->createAttributeDisplayItem($attribute, 'Address', 'yoti-icon-address');
+ break;
+ case UserProfile::ATTR_PHONE_NUMBER:
+ $profileAttributes[] = $this->createAttributeDisplayItem($attribute, 'Mobile number', 'yoti-icon-phone');
+ break;
+ case UserProfile::ATTR_NATIONALITY:
+ $profileAttributes[] = $this->createAttributeDisplayItem($attribute, 'Nationality', 'yoti-icon-nationality');
+ break;
+ case UserProfile::ATTR_EMAIL_ADDRESS:
+ $profileAttributes[] = $this->createAttributeDisplayItem($attribute, 'Email address', 'yoti-icon-email');
+ break;
+ case UserProfile::ATTR_DOCUMENT_DETAILS:
+ $profileAttributes[] = $this->createAttributeDisplayItem($attribute, 'Document Details', 'yoti-icon-profile');
+ break;
+ case UserProfile::ATTR_DOCUMENT_IMAGES:
+ $profileAttributes[] = $this->createAttributeDisplayItem($attribute, 'Document Images', 'yoti-icon-profile');
+ break;
+ default:
+ // Skip age verifications (name containing ":").
+ if (strpos($attribute->getName(), ':') === false) {
+ $profileAttributes[] = $this->createAttributeDisplayItem(
+ $attribute,
+ ucwords(str_replace('_', ' ', $attribute->getName())),
+ 'yoti-icon-profile'
+ );
+ }
+ }
+ }
+
+ // Add age verifications.
+ $ageVerifications = $profile->getAgeVerifications();
+ if ($ageVerifications) {
+ foreach ($ageVerifications as $ageVerification) {
+ $profileAttributes[] = [
+ 'name' => 'Age Verification',
+ 'obj' => $ageVerification->getAttribute(),
+ 'age_verification' => $ageVerification,
+ 'icon' => 'yoti-icon-profile',
+ ];
+ }
+ }
+
+ return $profileAttributes;
+ }
+
+ /**
+ * Create attribute display item.
+ *
+ * @param Attribute $attribute
+ * @param string $displayName
+ * @param string $iconClass
+ *
+ * @return array
+ */
+ private function createAttributeDisplayItem(Attribute $attribute, string $displayName, string $iconClass): array
+ {
+ return [
+ 'name' => $displayName,
+ 'obj' => $attribute,
+ 'icon' => $iconClass,
+ ];
+ }
+}
diff --git a/examples/digitalidentity/app/Http/Kernel.php b/examples/digitalidentity/app/Http/Kernel.php
new file mode 100644
index 00000000..c3640f30
--- /dev/null
+++ b/examples/digitalidentity/app/Http/Kernel.php
@@ -0,0 +1,66 @@
+ [
+ \App\Http\Middleware\EncryptCookies::class,
+ \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
+ \Illuminate\Session\Middleware\StartSession::class,
+ // \Illuminate\Session\Middleware\AuthenticateSession::class,
+ \Illuminate\View\Middleware\ShareErrorsFromSession::class,
+ \App\Http\Middleware\VerifyCsrfToken::class,
+ \Illuminate\Routing\Middleware\SubstituteBindings::class,
+ ],
+
+ 'api' => [
+ 'throttle:60,1',
+ \Illuminate\Routing\Middleware\SubstituteBindings::class,
+ ],
+ ];
+
+ /**
+ * The application's route middleware.
+ *
+ * These middleware may be assigned to groups or used individually.
+ *
+ * @var array
+ */
+ protected $routeMiddleware = [
+ 'auth' => \App\Http\Middleware\Authenticate::class,
+ 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
+ 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
+ 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
+ 'can' => \Illuminate\Auth\Middleware\Authorize::class,
+ 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
+ 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
+ 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
+ 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
+ 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
+ ];
+}
diff --git a/examples/digitalidentity/app/Http/Middleware/Authenticate.php b/examples/digitalidentity/app/Http/Middleware/Authenticate.php
new file mode 100644
index 00000000..704089a7
--- /dev/null
+++ b/examples/digitalidentity/app/Http/Middleware/Authenticate.php
@@ -0,0 +1,21 @@
+expectsJson()) {
+ return route('login');
+ }
+ }
+}
diff --git a/examples/digitalidentity/app/Http/Middleware/CheckForMaintenanceMode.php b/examples/digitalidentity/app/Http/Middleware/CheckForMaintenanceMode.php
new file mode 100644
index 00000000..35b9824b
--- /dev/null
+++ b/examples/digitalidentity/app/Http/Middleware/CheckForMaintenanceMode.php
@@ -0,0 +1,17 @@
+check()) {
+ return redirect(RouteServiceProvider::HOME);
+ }
+
+ return $next($request);
+ }
+}
diff --git a/examples/digitalidentity/app/Http/Middleware/TrimStrings.php b/examples/digitalidentity/app/Http/Middleware/TrimStrings.php
new file mode 100644
index 00000000..5a50e7b5
--- /dev/null
+++ b/examples/digitalidentity/app/Http/Middleware/TrimStrings.php
@@ -0,0 +1,18 @@
+mapApiRoutes();
+
+ $this->mapWebRoutes();
+
+ //
+ }
+
+ /**
+ * Define the "web" routes for the application.
+ *
+ * These routes all receive session state, CSRF protection, etc.
+ *
+ * @return void
+ */
+ protected function mapWebRoutes()
+ {
+ Route::middleware('web')
+ ->namespace($this->namespace)
+ ->group(base_path('routes/web.php'));
+ }
+
+ /**
+ * Define the "api" routes for the application.
+ *
+ * These routes are typically stateless.
+ *
+ * @return void
+ */
+ protected function mapApiRoutes()
+ {
+ Route::prefix('api')
+ ->middleware('api')
+ ->namespace($this->namespace)
+ ->group(base_path('routes/api.php'));
+ }
+}
diff --git a/examples/digitalidentity/app/Providers/YotiDigitalIdentityServiceProvider.php b/examples/digitalidentity/app/Providers/YotiDigitalIdentityServiceProvider.php
new file mode 100644
index 00000000..f0d3ffa2
--- /dev/null
+++ b/examples/digitalidentity/app/Providers/YotiDigitalIdentityServiceProvider.php
@@ -0,0 +1,29 @@
+app->singleton(DigitalIdentityClient::class, function ($app) {
+ $config = $app['config']['yoti'];
+ return new DigitalIdentityClient($config['client.sdk.id'], $config['pem.file.path']);
+ });
+ }
+
+ /**
+ * @return array
+ */
+ public function provides()
+ {
+ return [DigitalIdentityClient::class];
+ }
+}
diff --git a/examples/digitalidentity/app/Providers/YotiServiceProvider.php b/examples/digitalidentity/app/Providers/YotiServiceProvider.php
new file mode 100644
index 00000000..4c357610
--- /dev/null
+++ b/examples/digitalidentity/app/Providers/YotiServiceProvider.php
@@ -0,0 +1,29 @@
+app->singleton(YotiClient::class, function ($app) {
+ $config = $app['config']['yoti'];
+ return new YotiClient($config['client.sdk.id'], $config['pem.file.path']);
+ });
+ }
+
+ /**
+ * @return array
+ */
+ public function provides()
+ {
+ return [YotiClient::class];
+ }
+}
diff --git a/examples/digitalidentity/artisan b/examples/digitalidentity/artisan
new file mode 100644
index 00000000..5c23e2e2
--- /dev/null
+++ b/examples/digitalidentity/artisan
@@ -0,0 +1,53 @@
+#!/usr/bin/env php
+make(Illuminate\Contracts\Console\Kernel::class);
+
+$status = $kernel->handle(
+ $input = new Symfony\Component\Console\Input\ArgvInput,
+ new Symfony\Component\Console\Output\ConsoleOutput
+);
+
+/*
+|--------------------------------------------------------------------------
+| Shutdown The Application
+|--------------------------------------------------------------------------
+|
+| Once Artisan has finished running, we will fire off the shutdown events
+| so that any final work may be done by the application before we shut
+| down the process. This is the last thing to happen to the request.
+|
+*/
+
+$kernel->terminate($input, $status);
+
+exit($status);
diff --git a/examples/digitalidentity/bootstrap/app.php b/examples/digitalidentity/bootstrap/app.php
new file mode 100644
index 00000000..037e17df
--- /dev/null
+++ b/examples/digitalidentity/bootstrap/app.php
@@ -0,0 +1,55 @@
+singleton(
+ Illuminate\Contracts\Http\Kernel::class,
+ App\Http\Kernel::class
+);
+
+$app->singleton(
+ Illuminate\Contracts\Console\Kernel::class,
+ App\Console\Kernel::class
+);
+
+$app->singleton(
+ Illuminate\Contracts\Debug\ExceptionHandler::class,
+ App\Exceptions\Handler::class
+);
+
+/*
+|--------------------------------------------------------------------------
+| Return The Application
+|--------------------------------------------------------------------------
+|
+| This script returns the application instance. The instance is given to
+| the calling script so we can separate the building of the instances
+| from the actual running of the application and sending responses.
+|
+*/
+
+return $app;
diff --git a/examples/digitalidentity/bootstrap/cache/.gitignore b/examples/digitalidentity/bootstrap/cache/.gitignore
new file mode 100644
index 00000000..d6b7ef32
--- /dev/null
+++ b/examples/digitalidentity/bootstrap/cache/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/examples/digitalidentity/composer.json b/examples/digitalidentity/composer.json
new file mode 100644
index 00000000..f2b24247
--- /dev/null
+++ b/examples/digitalidentity/composer.json
@@ -0,0 +1,59 @@
+{
+ "name": "yoti/yoti-php-sdk-example-digital-identity",
+ "description": "Yoti SDK Digital Identity Demo",
+ "license": "MIT",
+ "require": {
+ "php": "^8.0",
+ "fideloper/proxy": "^4.2",
+ "fruitcake/laravel-cors": "^1.0",
+ "guzzlehttp/guzzle": "^6.4 || ^7.0",
+ "laravel/framework": "^8.0",
+ "laravel/tinker": "^2.3.0",
+ "yoti/yoti-php-sdk": "^4.0"
+ },
+ "require-dev": {
+ "facade/ignition": "^2.0"
+ },
+ "config": {
+ "optimize-autoloader": true,
+ "preferred-install": "dist",
+ "sort-packages": true
+ },
+ "extra": {
+ "laravel": {
+ "dont-discover": []
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "App\\": "app/"
+ }
+ },
+ "minimum-stability": "dev",
+ "prefer-stable": true,
+ "scripts": {
+ "post-autoload-dump": [
+ "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
+ "@php artisan package:discover --ansi"
+ ],
+ "post-update-cmd": "@php artisan key:generate --ansi",
+ "copy-sdk": "grep -q 'yoti-php-sdk' ../../composer.json && rm -fr ./sdk && cd ../../ && git archive --prefix=sdk/ --format=tar HEAD | (cd - && tar xf -) || echo 'Could not install SDK from parent directory'",
+ "install-local": [
+ "@copy-sdk",
+ "composer install"
+ ],
+ "update-local": [
+ "@copy-sdk",
+ "composer update"
+ ]
+ },
+ "repositories": [
+ {
+ "type": "path",
+ "url": "./sdk",
+ "options": {
+ "symlink": true
+ }
+ }
+ ]
+}
diff --git a/examples/digitalidentity/config/app.php b/examples/digitalidentity/config/app.php
new file mode 100644
index 00000000..1a57883e
--- /dev/null
+++ b/examples/digitalidentity/config/app.php
@@ -0,0 +1,229 @@
+ env('APP_NAME', 'Laravel'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Application Environment
+ |--------------------------------------------------------------------------
+ |
+ | This value determines the "environment" your application is currently
+ | running in. This may determine how you prefer to configure various
+ | services the application utilizes. Set this in your ".env" file.
+ |
+ */
+
+ 'env' => env('APP_ENV', 'production'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Application Debug Mode
+ |--------------------------------------------------------------------------
+ |
+ | When your application is in debug mode, detailed error messages with
+ | stack traces will be shown on every error that occurs within your
+ | application. If disabled, a simple generic error page is shown.
+ |
+ */
+
+ 'debug' => (bool) env('APP_DEBUG', false),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Application URL
+ |--------------------------------------------------------------------------
+ |
+ | This URL is used by the console to properly generate URLs when using
+ | the Artisan command line tool. You should set this to the root of
+ | your application so that it is used when running Artisan tasks.
+ |
+ */
+
+ 'url' => env('APP_URL', 'http://localhost'),
+
+ 'asset_url' => env('ASSET_URL', null),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Application Timezone
+ |--------------------------------------------------------------------------
+ |
+ | Here you may specify the default timezone for your application, which
+ | will be used by the PHP date and date-time functions. We have gone
+ | ahead and set this to a sensible default for you out of the box.
+ |
+ */
+
+ 'timezone' => 'UTC',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Application Locale Configuration
+ |--------------------------------------------------------------------------
+ |
+ | The application locale determines the default locale that will be used
+ | by the translation service provider. You are free to set this value
+ | to any of the locales which will be supported by the application.
+ |
+ */
+
+ 'locale' => 'en',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Application Fallback Locale
+ |--------------------------------------------------------------------------
+ |
+ | The fallback locale determines the locale to use when the current one
+ | is not available. You may change the value to correspond to any of
+ | the language folders that are provided through your application.
+ |
+ */
+
+ 'fallback_locale' => 'en',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Faker Locale
+ |--------------------------------------------------------------------------
+ |
+ | This locale will be used by the Faker PHP library when generating fake
+ | data for your database seeds. For example, this will be used to get
+ | localized telephone numbers, street address information and more.
+ |
+ */
+
+ 'faker_locale' => 'en_US',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Encryption Key
+ |--------------------------------------------------------------------------
+ |
+ | This key is used by the Illuminate encrypter service and should be set
+ | to a random, 32 character string, otherwise these encrypted strings
+ | will not be safe. Please do this before deploying an application!
+ |
+ */
+
+ 'key' => env('APP_KEY'),
+
+ 'cipher' => 'AES-256-CBC',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Autoloaded Service Providers
+ |--------------------------------------------------------------------------
+ |
+ | The service providers listed here will be automatically loaded on the
+ | request to your application. Feel free to add your own services to
+ | this array to grant expanded functionality to your applications.
+ |
+ */
+
+ 'providers' => [
+
+ /*
+ * Laravel Framework Service Providers...
+ */
+ Illuminate\Auth\AuthServiceProvider::class,
+ Illuminate\Broadcasting\BroadcastServiceProvider::class,
+ Illuminate\Bus\BusServiceProvider::class,
+ Illuminate\Cache\CacheServiceProvider::class,
+ Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
+ Illuminate\Cookie\CookieServiceProvider::class,
+ Illuminate\Database\DatabaseServiceProvider::class,
+ Illuminate\Encryption\EncryptionServiceProvider::class,
+ Illuminate\Filesystem\FilesystemServiceProvider::class,
+ Illuminate\Foundation\Providers\FoundationServiceProvider::class,
+ Illuminate\Hashing\HashServiceProvider::class,
+ Illuminate\Mail\MailServiceProvider::class,
+ Illuminate\Notifications\NotificationServiceProvider::class,
+ Illuminate\Pagination\PaginationServiceProvider::class,
+ Illuminate\Pipeline\PipelineServiceProvider::class,
+ Illuminate\Queue\QueueServiceProvider::class,
+ Illuminate\Redis\RedisServiceProvider::class,
+ Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
+ Illuminate\Session\SessionServiceProvider::class,
+ Illuminate\Translation\TranslationServiceProvider::class,
+ Illuminate\Validation\ValidationServiceProvider::class,
+ Illuminate\View\ViewServiceProvider::class,
+
+ /*
+ * Package Service Providers...
+ */
+
+ /*
+ * Application Service Providers...
+ */
+ App\Providers\YotiServiceProvider::class,
+ App\Providers\YotiDigitalIdentityServiceProvider::class,
+ App\Providers\RouteServiceProvider::class,
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Class Aliases
+ |--------------------------------------------------------------------------
+ |
+ | This array of class aliases will be registered when this application
+ | is started. However, feel free to register as many as you wish as
+ | the aliases are "lazy" loaded so they don't hinder performance.
+ |
+ */
+
+ 'aliases' => [
+
+ 'App' => Illuminate\Support\Facades\App::class,
+ 'Arr' => Illuminate\Support\Arr::class,
+ 'Artisan' => Illuminate\Support\Facades\Artisan::class,
+ 'Auth' => Illuminate\Support\Facades\Auth::class,
+ 'Blade' => Illuminate\Support\Facades\Blade::class,
+ 'Broadcast' => Illuminate\Support\Facades\Broadcast::class,
+ 'Bus' => Illuminate\Support\Facades\Bus::class,
+ 'Cache' => Illuminate\Support\Facades\Cache::class,
+ 'Config' => Illuminate\Support\Facades\Config::class,
+ 'Cookie' => Illuminate\Support\Facades\Cookie::class,
+ 'Crypt' => Illuminate\Support\Facades\Crypt::class,
+ 'DB' => Illuminate\Support\Facades\DB::class,
+ 'Eloquent' => Illuminate\Database\Eloquent\Model::class,
+ 'Event' => Illuminate\Support\Facades\Event::class,
+ 'File' => Illuminate\Support\Facades\File::class,
+ 'Gate' => Illuminate\Support\Facades\Gate::class,
+ 'Hash' => Illuminate\Support\Facades\Hash::class,
+ 'Http' => Illuminate\Support\Facades\Http::class,
+ 'Lang' => Illuminate\Support\Facades\Lang::class,
+ 'Log' => Illuminate\Support\Facades\Log::class,
+ 'Mail' => Illuminate\Support\Facades\Mail::class,
+ 'Notification' => Illuminate\Support\Facades\Notification::class,
+ 'Password' => Illuminate\Support\Facades\Password::class,
+ 'Queue' => Illuminate\Support\Facades\Queue::class,
+ 'Redirect' => Illuminate\Support\Facades\Redirect::class,
+ 'Redis' => Illuminate\Support\Facades\Redis::class,
+ 'Request' => Illuminate\Support\Facades\Request::class,
+ 'Response' => Illuminate\Support\Facades\Response::class,
+ 'Route' => Illuminate\Support\Facades\Route::class,
+ 'Schema' => Illuminate\Support\Facades\Schema::class,
+ 'Session' => Illuminate\Support\Facades\Session::class,
+ 'Storage' => Illuminate\Support\Facades\Storage::class,
+ 'Str' => Illuminate\Support\Str::class,
+ 'URL' => Illuminate\Support\Facades\URL::class,
+ 'Validator' => Illuminate\Support\Facades\Validator::class,
+ 'View' => Illuminate\Support\Facades\View::class,
+
+ ],
+
+];
diff --git a/examples/digitalidentity/config/auth.php b/examples/digitalidentity/config/auth.php
new file mode 100644
index 00000000..aaf982bc
--- /dev/null
+++ b/examples/digitalidentity/config/auth.php
@@ -0,0 +1,117 @@
+ [
+ 'guard' => 'web',
+ 'passwords' => 'users',
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Authentication Guards
+ |--------------------------------------------------------------------------
+ |
+ | Next, you may define every authentication guard for your application.
+ | Of course, a great default configuration has been defined for you
+ | here which uses session storage and the Eloquent user provider.
+ |
+ | All authentication drivers have a user provider. This defines how the
+ | users are actually retrieved out of your database or other storage
+ | mechanisms used by this application to persist your user's data.
+ |
+ | Supported: "session", "token"
+ |
+ */
+
+ 'guards' => [
+ 'web' => [
+ 'driver' => 'session',
+ 'provider' => 'users',
+ ],
+
+ 'api' => [
+ 'driver' => 'token',
+ 'provider' => 'users',
+ 'hash' => false,
+ ],
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | User Providers
+ |--------------------------------------------------------------------------
+ |
+ | All authentication drivers have a user provider. This defines how the
+ | users are actually retrieved out of your database or other storage
+ | mechanisms used by this application to persist your user's data.
+ |
+ | If you have multiple user tables or models you may configure multiple
+ | sources which represent each model / table. These sources may then
+ | be assigned to any extra authentication guards you have defined.
+ |
+ | Supported: "database", "eloquent"
+ |
+ */
+
+ 'providers' => [
+ 'users' => [
+ 'driver' => 'eloquent',
+ 'model' => App\User::class,
+ ],
+
+ // 'users' => [
+ // 'driver' => 'database',
+ // 'table' => 'users',
+ // ],
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Resetting Passwords
+ |--------------------------------------------------------------------------
+ |
+ | You may specify multiple password reset configurations if you have more
+ | than one user table or model in the application and you want to have
+ | separate password reset settings based on the specific user types.
+ |
+ | The expire time is the number of minutes that the reset token should be
+ | considered valid. This security feature keeps tokens short-lived so
+ | they have less time to be guessed. You may change this as needed.
+ |
+ */
+
+ 'passwords' => [
+ 'users' => [
+ 'provider' => 'users',
+ 'table' => 'password_resets',
+ 'expire' => 60,
+ 'throttle' => 60,
+ ],
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Password Confirmation Timeout
+ |--------------------------------------------------------------------------
+ |
+ | Here you may define the amount of seconds before a password confirmation
+ | times out and the user is prompted to re-enter their password via the
+ | confirmation screen. By default, the timeout lasts for three hours.
+ |
+ */
+
+ 'password_timeout' => 10800,
+
+];
diff --git a/examples/digitalidentity/config/broadcasting.php b/examples/digitalidentity/config/broadcasting.php
new file mode 100644
index 00000000..3bba1103
--- /dev/null
+++ b/examples/digitalidentity/config/broadcasting.php
@@ -0,0 +1,59 @@
+ env('BROADCAST_DRIVER', 'null'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Broadcast Connections
+ |--------------------------------------------------------------------------
+ |
+ | Here you may define all of the broadcast connections that will be used
+ | to broadcast events to other systems or over websockets. Samples of
+ | each available type of connection are provided inside this array.
+ |
+ */
+
+ 'connections' => [
+
+ 'pusher' => [
+ 'driver' => 'pusher',
+ 'key' => env('PUSHER_APP_KEY'),
+ 'secret' => env('PUSHER_APP_SECRET'),
+ 'app_id' => env('PUSHER_APP_ID'),
+ 'options' => [
+ 'cluster' => env('PUSHER_APP_CLUSTER'),
+ 'useTLS' => true,
+ ],
+ ],
+
+ 'redis' => [
+ 'driver' => 'redis',
+ 'connection' => 'default',
+ ],
+
+ 'log' => [
+ 'driver' => 'log',
+ ],
+
+ 'null' => [
+ 'driver' => 'null',
+ ],
+
+ ],
+
+];
diff --git a/examples/digitalidentity/config/cache.php b/examples/digitalidentity/config/cache.php
new file mode 100644
index 00000000..4f41fdf9
--- /dev/null
+++ b/examples/digitalidentity/config/cache.php
@@ -0,0 +1,104 @@
+ env('CACHE_DRIVER', 'file'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Cache Stores
+ |--------------------------------------------------------------------------
+ |
+ | Here you may define all of the cache "stores" for your application as
+ | well as their drivers. You may even define multiple stores for the
+ | same cache driver to group types of items stored in your caches.
+ |
+ */
+
+ 'stores' => [
+
+ 'apc' => [
+ 'driver' => 'apc',
+ ],
+
+ 'array' => [
+ 'driver' => 'array',
+ 'serialize' => false,
+ ],
+
+ 'database' => [
+ 'driver' => 'database',
+ 'table' => 'cache',
+ 'connection' => null,
+ ],
+
+ 'file' => [
+ 'driver' => 'file',
+ 'path' => storage_path('framework/cache/data'),
+ ],
+
+ 'memcached' => [
+ 'driver' => 'memcached',
+ 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
+ 'sasl' => [
+ env('MEMCACHED_USERNAME'),
+ env('MEMCACHED_PASSWORD'),
+ ],
+ 'options' => [
+ // Memcached::OPT_CONNECT_TIMEOUT => 2000,
+ ],
+ 'servers' => [
+ [
+ 'host' => env('MEMCACHED_HOST', '127.0.0.1'),
+ 'port' => env('MEMCACHED_PORT', 11211),
+ 'weight' => 100,
+ ],
+ ],
+ ],
+
+ 'redis' => [
+ 'driver' => 'redis',
+ 'connection' => 'cache',
+ ],
+
+ 'dynamodb' => [
+ 'driver' => 'dynamodb',
+ 'key' => env('AWS_ACCESS_KEY_ID'),
+ 'secret' => env('AWS_SECRET_ACCESS_KEY'),
+ 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
+ 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
+ 'endpoint' => env('DYNAMODB_ENDPOINT'),
+ ],
+
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Cache Key Prefix
+ |--------------------------------------------------------------------------
+ |
+ | When utilizing a RAM based store such as APC or Memcached, there might
+ | be other applications utilizing the same cache. So, we'll specify a
+ | value to get prefixed to all our keys so we can avoid collisions.
+ |
+ */
+
+ 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache'),
+
+];
diff --git a/examples/digitalidentity/config/cors.php b/examples/digitalidentity/config/cors.php
new file mode 100644
index 00000000..558369dc
--- /dev/null
+++ b/examples/digitalidentity/config/cors.php
@@ -0,0 +1,34 @@
+ ['api/*'],
+
+ 'allowed_methods' => ['*'],
+
+ 'allowed_origins' => ['*'],
+
+ 'allowed_origins_patterns' => [],
+
+ 'allowed_headers' => ['*'],
+
+ 'exposed_headers' => [],
+
+ 'max_age' => 0,
+
+ 'supports_credentials' => false,
+
+];
diff --git a/examples/digitalidentity/config/database.php b/examples/digitalidentity/config/database.php
new file mode 100644
index 00000000..b42d9b30
--- /dev/null
+++ b/examples/digitalidentity/config/database.php
@@ -0,0 +1,147 @@
+ env('DB_CONNECTION', 'mysql'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Database Connections
+ |--------------------------------------------------------------------------
+ |
+ | Here are each of the database connections setup for your application.
+ | Of course, examples of configuring each database platform that is
+ | supported by Laravel is shown below to make development simple.
+ |
+ |
+ | All database work in Laravel is done through the PHP PDO facilities
+ | so make sure you have the driver for your particular database of
+ | choice installed on your machine before you begin development.
+ |
+ */
+
+ 'connections' => [
+
+ 'sqlite' => [
+ 'driver' => 'sqlite',
+ 'url' => env('DATABASE_URL'),
+ 'database' => env('DB_DATABASE', database_path('database.sqlite')),
+ 'prefix' => '',
+ 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
+ ],
+
+ 'mysql' => [
+ 'driver' => 'mysql',
+ 'url' => env('DATABASE_URL'),
+ 'host' => env('DB_HOST', '127.0.0.1'),
+ 'port' => env('DB_PORT', '3306'),
+ 'database' => env('DB_DATABASE', 'forge'),
+ 'username' => env('DB_USERNAME', 'forge'),
+ 'password' => env('DB_PASSWORD', ''),
+ 'unix_socket' => env('DB_SOCKET', ''),
+ 'charset' => 'utf8mb4',
+ 'collation' => 'utf8mb4_unicode_ci',
+ 'prefix' => '',
+ 'prefix_indexes' => true,
+ 'strict' => true,
+ 'engine' => null,
+ 'options' => extension_loaded('pdo_mysql') ? array_filter([
+ PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
+ ]) : [],
+ ],
+
+ 'pgsql' => [
+ 'driver' => 'pgsql',
+ 'url' => env('DATABASE_URL'),
+ 'host' => env('DB_HOST', '127.0.0.1'),
+ 'port' => env('DB_PORT', '5432'),
+ 'database' => env('DB_DATABASE', 'forge'),
+ 'username' => env('DB_USERNAME', 'forge'),
+ 'password' => env('DB_PASSWORD', ''),
+ 'charset' => 'utf8',
+ 'prefix' => '',
+ 'prefix_indexes' => true,
+ 'schema' => 'public',
+ 'sslmode' => 'prefer',
+ ],
+
+ 'sqlsrv' => [
+ 'driver' => 'sqlsrv',
+ 'url' => env('DATABASE_URL'),
+ 'host' => env('DB_HOST', 'localhost'),
+ 'port' => env('DB_PORT', '1433'),
+ 'database' => env('DB_DATABASE', 'forge'),
+ 'username' => env('DB_USERNAME', 'forge'),
+ 'password' => env('DB_PASSWORD', ''),
+ 'charset' => 'utf8',
+ 'prefix' => '',
+ 'prefix_indexes' => true,
+ ],
+
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Migration Repository Table
+ |--------------------------------------------------------------------------
+ |
+ | This table keeps track of all the migrations that have already run for
+ | your application. Using this information, we can determine which of
+ | the migrations on disk haven't actually been run in the database.
+ |
+ */
+
+ 'migrations' => 'migrations',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Redis Databases
+ |--------------------------------------------------------------------------
+ |
+ | Redis is an open source, fast, and advanced key-value store that also
+ | provides a richer body of commands than a typical key-value system
+ | such as APC or Memcached. Laravel makes it easy to dig right in.
+ |
+ */
+
+ 'redis' => [
+
+ 'client' => env('REDIS_CLIENT', 'phpredis'),
+
+ 'options' => [
+ 'cluster' => env('REDIS_CLUSTER', 'redis'),
+ 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'),
+ ],
+
+ 'default' => [
+ 'url' => env('REDIS_URL'),
+ 'host' => env('REDIS_HOST', '127.0.0.1'),
+ 'password' => env('REDIS_PASSWORD', null),
+ 'port' => env('REDIS_PORT', '6379'),
+ 'database' => env('REDIS_DB', '0'),
+ ],
+
+ 'cache' => [
+ 'url' => env('REDIS_URL'),
+ 'host' => env('REDIS_HOST', '127.0.0.1'),
+ 'password' => env('REDIS_PASSWORD', null),
+ 'port' => env('REDIS_PORT', '6379'),
+ 'database' => env('REDIS_CACHE_DB', '1'),
+ ],
+
+ ],
+
+];
diff --git a/examples/digitalidentity/config/filesystems.php b/examples/digitalidentity/config/filesystems.php
new file mode 100644
index 00000000..cd9f0962
--- /dev/null
+++ b/examples/digitalidentity/config/filesystems.php
@@ -0,0 +1,84 @@
+ env('FILESYSTEM_DRIVER', 'local'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Default Cloud Filesystem Disk
+ |--------------------------------------------------------------------------
+ |
+ | Many applications store files both locally and in the cloud. For this
+ | reason, you may specify a default "cloud" driver here. This driver
+ | will be bound as the Cloud disk implementation in the container.
+ |
+ */
+
+ 'cloud' => env('FILESYSTEM_CLOUD', 's3'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Filesystem Disks
+ |--------------------------------------------------------------------------
+ |
+ | Here you may configure as many filesystem "disks" as you wish, and you
+ | may even configure multiple disks of the same driver. Defaults have
+ | been setup for each driver as an example of the required options.
+ |
+ | Supported Drivers: "local", "ftp", "sftp", "s3"
+ |
+ */
+
+ 'disks' => [
+
+ 'local' => [
+ 'driver' => 'local',
+ 'root' => storage_path('app'),
+ ],
+
+ 'public' => [
+ 'driver' => 'local',
+ 'root' => storage_path('app/public'),
+ 'url' => env('APP_URL').'/storage',
+ 'visibility' => 'public',
+ ],
+
+ 's3' => [
+ 'driver' => 's3',
+ 'key' => env('AWS_ACCESS_KEY_ID'),
+ 'secret' => env('AWS_SECRET_ACCESS_KEY'),
+ 'region' => env('AWS_DEFAULT_REGION'),
+ 'bucket' => env('AWS_BUCKET'),
+ 'url' => env('AWS_URL'),
+ ],
+
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Symbolic Links
+ |--------------------------------------------------------------------------
+ |
+ | Here you may configure the symbolic links that will be created when the
+ | `storage:link` Artisan command is executed. The array keys should be
+ | the locations of the links and the values should be their targets.
+ |
+ */
+
+ 'links' => [
+ public_path('storage') => storage_path('app/public'),
+ ],
+
+];
diff --git a/examples/digitalidentity/config/hashing.php b/examples/digitalidentity/config/hashing.php
new file mode 100644
index 00000000..84257708
--- /dev/null
+++ b/examples/digitalidentity/config/hashing.php
@@ -0,0 +1,52 @@
+ 'bcrypt',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Bcrypt Options
+ |--------------------------------------------------------------------------
+ |
+ | Here you may specify the configuration options that should be used when
+ | passwords are hashed using the Bcrypt algorithm. This will allow you
+ | to control the amount of time it takes to hash the given password.
+ |
+ */
+
+ 'bcrypt' => [
+ 'rounds' => env('BCRYPT_ROUNDS', 10),
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Argon Options
+ |--------------------------------------------------------------------------
+ |
+ | Here you may specify the configuration options that should be used when
+ | passwords are hashed using the Argon algorithm. These will allow you
+ | to control the amount of time it takes to hash the given password.
+ |
+ */
+
+ 'argon' => [
+ 'memory' => 1024,
+ 'threads' => 2,
+ 'time' => 2,
+ ],
+
+];
diff --git a/examples/digitalidentity/config/logging.php b/examples/digitalidentity/config/logging.php
new file mode 100644
index 00000000..088c204e
--- /dev/null
+++ b/examples/digitalidentity/config/logging.php
@@ -0,0 +1,104 @@
+ env('LOG_CHANNEL', 'stack'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Log Channels
+ |--------------------------------------------------------------------------
+ |
+ | Here you may configure the log channels for your application. Out of
+ | the box, Laravel uses the Monolog PHP logging library. This gives
+ | you a variety of powerful log handlers / formatters to utilize.
+ |
+ | Available Drivers: "single", "daily", "slack", "syslog",
+ | "errorlog", "monolog",
+ | "custom", "stack"
+ |
+ */
+
+ 'channels' => [
+ 'stack' => [
+ 'driver' => 'stack',
+ 'channels' => ['single'],
+ 'ignore_exceptions' => false,
+ ],
+
+ 'single' => [
+ 'driver' => 'single',
+ 'path' => storage_path('logs/laravel.log'),
+ 'level' => 'debug',
+ ],
+
+ 'daily' => [
+ 'driver' => 'daily',
+ 'path' => storage_path('logs/laravel.log'),
+ 'level' => 'debug',
+ 'days' => 14,
+ ],
+
+ 'slack' => [
+ 'driver' => 'slack',
+ 'url' => env('LOG_SLACK_WEBHOOK_URL'),
+ 'username' => 'Laravel Log',
+ 'emoji' => ':boom:',
+ 'level' => 'critical',
+ ],
+
+ 'papertrail' => [
+ 'driver' => 'monolog',
+ 'level' => 'debug',
+ 'handler' => SyslogUdpHandler::class,
+ 'handler_with' => [
+ 'host' => env('PAPERTRAIL_URL'),
+ 'port' => env('PAPERTRAIL_PORT'),
+ ],
+ ],
+
+ 'stderr' => [
+ 'driver' => 'monolog',
+ 'handler' => StreamHandler::class,
+ 'formatter' => env('LOG_STDERR_FORMATTER'),
+ 'with' => [
+ 'stream' => 'php://stderr',
+ ],
+ ],
+
+ 'syslog' => [
+ 'driver' => 'syslog',
+ 'level' => 'debug',
+ ],
+
+ 'errorlog' => [
+ 'driver' => 'errorlog',
+ 'level' => 'debug',
+ ],
+
+ 'null' => [
+ 'driver' => 'monolog',
+ 'handler' => NullHandler::class,
+ ],
+
+ 'emergency' => [
+ 'path' => storage_path('logs/laravel.log'),
+ ],
+ ],
+
+];
diff --git a/examples/digitalidentity/config/mail.php b/examples/digitalidentity/config/mail.php
new file mode 100644
index 00000000..cfef410f
--- /dev/null
+++ b/examples/digitalidentity/config/mail.php
@@ -0,0 +1,108 @@
+ env('MAIL_MAILER', 'smtp'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Mailer Configurations
+ |--------------------------------------------------------------------------
+ |
+ | Here you may configure all of the mailers used by your application plus
+ | their respective settings. Several examples have been configured for
+ | you and you are free to add your own as your application requires.
+ |
+ | Laravel supports a variety of mail "transport" drivers to be used while
+ | sending an e-mail. You will specify which one you are using for your
+ | mailers below. You are free to add additional mailers as required.
+ |
+ | Supported: "smtp", "sendmail", "mailgun", "ses",
+ | "postmark", "log", "array"
+ |
+ */
+
+ 'mailers' => [
+ 'smtp' => [
+ 'transport' => 'smtp',
+ 'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
+ 'port' => env('MAIL_PORT', 587),
+ 'encryption' => env('MAIL_ENCRYPTION', 'tls'),
+ 'username' => env('MAIL_USERNAME'),
+ 'password' => env('MAIL_PASSWORD'),
+ ],
+
+ 'ses' => [
+ 'transport' => 'ses',
+ ],
+
+ 'mailgun' => [
+ 'transport' => 'mailgun',
+ ],
+
+ 'postmark' => [
+ 'transport' => 'postmark',
+ ],
+
+ 'sendmail' => [
+ 'transport' => 'sendmail',
+ 'path' => '/usr/sbin/sendmail -bs',
+ ],
+
+ 'log' => [
+ 'transport' => 'log',
+ 'channel' => env('MAIL_LOG_CHANNEL'),
+ ],
+
+ 'array' => [
+ 'transport' => 'array',
+ ],
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Global "From" Address
+ |--------------------------------------------------------------------------
+ |
+ | You may wish for all e-mails sent by your application to be sent from
+ | the same address. Here, you may specify a name and address that is
+ | used globally for all e-mails that are sent by your application.
+ |
+ */
+
+ 'from' => [
+ 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
+ 'name' => env('MAIL_FROM_NAME', 'Example'),
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Markdown Mail Settings
+ |--------------------------------------------------------------------------
+ |
+ | If you are using Markdown based email rendering, you may configure your
+ | theme and component paths here, allowing you to customize the design
+ | of the emails. Or, you may simply stick with the Laravel defaults!
+ |
+ */
+
+ 'markdown' => [
+ 'theme' => 'default',
+
+ 'paths' => [
+ resource_path('views/vendor/mail'),
+ ],
+ ],
+
+];
diff --git a/examples/digitalidentity/config/queue.php b/examples/digitalidentity/config/queue.php
new file mode 100644
index 00000000..00b76d65
--- /dev/null
+++ b/examples/digitalidentity/config/queue.php
@@ -0,0 +1,89 @@
+ env('QUEUE_CONNECTION', 'sync'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Queue Connections
+ |--------------------------------------------------------------------------
+ |
+ | Here you may configure the connection information for each server that
+ | is used by your application. A default configuration has been added
+ | for each back-end shipped with Laravel. You are free to add more.
+ |
+ | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null"
+ |
+ */
+
+ 'connections' => [
+
+ 'sync' => [
+ 'driver' => 'sync',
+ ],
+
+ 'database' => [
+ 'driver' => 'database',
+ 'table' => 'jobs',
+ 'queue' => 'default',
+ 'retry_after' => 90,
+ ],
+
+ 'beanstalkd' => [
+ 'driver' => 'beanstalkd',
+ 'host' => 'localhost',
+ 'queue' => 'default',
+ 'retry_after' => 90,
+ 'block_for' => 0,
+ ],
+
+ 'sqs' => [
+ 'driver' => 'sqs',
+ 'key' => env('AWS_ACCESS_KEY_ID'),
+ 'secret' => env('AWS_SECRET_ACCESS_KEY'),
+ 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
+ 'queue' => env('SQS_QUEUE', 'your-queue-name'),
+ 'suffix' => env('SQS_SUFFIX'),
+ 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
+ ],
+
+ 'redis' => [
+ 'driver' => 'redis',
+ 'connection' => 'default',
+ 'queue' => env('REDIS_QUEUE', 'default'),
+ 'retry_after' => 90,
+ 'block_for' => null,
+ ],
+
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Failed Queue Jobs
+ |--------------------------------------------------------------------------
+ |
+ | These options configure the behavior of failed queue job logging so you
+ | can control which database and table are used to store the jobs that
+ | have failed. You may change them to any database / table you wish.
+ |
+ */
+
+ 'failed' => [
+ 'driver' => env('QUEUE_FAILED_DRIVER', 'database'),
+ 'database' => env('DB_CONNECTION', 'mysql'),
+ 'table' => 'failed_jobs',
+ ],
+
+];
diff --git a/examples/digitalidentity/config/services.php b/examples/digitalidentity/config/services.php
new file mode 100644
index 00000000..2a1d616c
--- /dev/null
+++ b/examples/digitalidentity/config/services.php
@@ -0,0 +1,33 @@
+ [
+ 'domain' => env('MAILGUN_DOMAIN'),
+ 'secret' => env('MAILGUN_SECRET'),
+ 'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
+ ],
+
+ 'postmark' => [
+ 'token' => env('POSTMARK_TOKEN'),
+ ],
+
+ 'ses' => [
+ 'key' => env('AWS_ACCESS_KEY_ID'),
+ 'secret' => env('AWS_SECRET_ACCESS_KEY'),
+ 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
+ ],
+
+];
diff --git a/examples/digitalidentity/config/session.php b/examples/digitalidentity/config/session.php
new file mode 100644
index 00000000..d0ccd5a8
--- /dev/null
+++ b/examples/digitalidentity/config/session.php
@@ -0,0 +1,199 @@
+ env('SESSION_DRIVER', 'file'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Session Lifetime
+ |--------------------------------------------------------------------------
+ |
+ | Here you may specify the number of minutes that you wish the session
+ | to be allowed to remain idle before it expires. If you want them
+ | to immediately expire on the browser closing, set that option.
+ |
+ */
+
+ 'lifetime' => env('SESSION_LIFETIME', 120),
+
+ 'expire_on_close' => false,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Session Encryption
+ |--------------------------------------------------------------------------
+ |
+ | This option allows you to easily specify that all of your session data
+ | should be encrypted before it is stored. All encryption will be run
+ | automatically by Laravel and you can use the Session like normal.
+ |
+ */
+
+ 'encrypt' => false,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Session File Location
+ |--------------------------------------------------------------------------
+ |
+ | When using the native session driver, we need a location where session
+ | files may be stored. A default has been set for you but a different
+ | location may be specified. This is only needed for file sessions.
+ |
+ */
+
+ 'files' => storage_path('framework/sessions'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Session Database Connection
+ |--------------------------------------------------------------------------
+ |
+ | When using the "database" or "redis" session drivers, you may specify a
+ | connection that should be used to manage these sessions. This should
+ | correspond to a connection in your database configuration options.
+ |
+ */
+
+ 'connection' => env('SESSION_CONNECTION', null),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Session Database Table
+ |--------------------------------------------------------------------------
+ |
+ | When using the "database" session driver, you may specify the table we
+ | should use to manage the sessions. Of course, a sensible default is
+ | provided for you; however, you are free to change this as needed.
+ |
+ */
+
+ 'table' => 'sessions',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Session Cache Store
+ |--------------------------------------------------------------------------
+ |
+ | When using the "apc", "memcached", or "dynamodb" session drivers you may
+ | list a cache store that should be used for these sessions. This value
+ | must match with one of the application's configured cache "stores".
+ |
+ */
+
+ 'store' => env('SESSION_STORE', null),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Session Sweeping Lottery
+ |--------------------------------------------------------------------------
+ |
+ | Some session drivers must manually sweep their storage location to get
+ | rid of old sessions from storage. Here are the chances that it will
+ | happen on a given request. By default, the odds are 2 out of 100.
+ |
+ */
+
+ 'lottery' => [2, 100],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Session Cookie Name
+ |--------------------------------------------------------------------------
+ |
+ | Here you may change the name of the cookie used to identify a session
+ | instance by ID. The name specified here will get used every time a
+ | new session cookie is created by the framework for every driver.
+ |
+ */
+
+ 'cookie' => env(
+ 'SESSION_COOKIE',
+ Str::slug(env('APP_NAME', 'laravel'), '_').'_session'
+ ),
+
+ /*
+ |--------------------------------------------------------------------------
+ | Session Cookie Path
+ |--------------------------------------------------------------------------
+ |
+ | The session cookie path determines the path for which the cookie will
+ | be regarded as available. Typically, this will be the root path of
+ | your application but you are free to change this when necessary.
+ |
+ */
+
+ 'path' => '/',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Session Cookie Domain
+ |--------------------------------------------------------------------------
+ |
+ | Here you may change the domain of the cookie used to identify a session
+ | in your application. This will determine which domains the cookie is
+ | available to in your application. A sensible default has been set.
+ |
+ */
+
+ 'domain' => env('SESSION_DOMAIN', null),
+
+ /*
+ |--------------------------------------------------------------------------
+ | HTTPS Only Cookies
+ |--------------------------------------------------------------------------
+ |
+ | By setting this option to true, session cookies will only be sent back
+ | to the server if the browser has a HTTPS connection. This will keep
+ | the cookie from being sent to you if it can not be done securely.
+ |
+ */
+
+ 'secure' => env('SESSION_SECURE_COOKIE'),
+
+ /*
+ |--------------------------------------------------------------------------
+ | HTTP Access Only
+ |--------------------------------------------------------------------------
+ |
+ | Setting this value to true will prevent JavaScript from accessing the
+ | value of the cookie and the cookie will only be accessible through
+ | the HTTP protocol. You are free to modify this option if needed.
+ |
+ */
+
+ 'http_only' => true,
+
+ /*
+ |--------------------------------------------------------------------------
+ | Same-Site Cookies
+ |--------------------------------------------------------------------------
+ |
+ | This option determines how your cookies behave when cross-site requests
+ | take place, and can be used to mitigate CSRF attacks. By default, we
+ | do not enable this as other CSRF protection services are in place.
+ |
+ | Supported: "lax", "strict", "none", null
+ |
+ */
+
+ 'same_site' => 'lax',
+
+];
diff --git a/examples/digitalidentity/config/view.php b/examples/digitalidentity/config/view.php
new file mode 100644
index 00000000..22b8a18d
--- /dev/null
+++ b/examples/digitalidentity/config/view.php
@@ -0,0 +1,36 @@
+ [
+ resource_path('views'),
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Compiled View Path
+ |--------------------------------------------------------------------------
+ |
+ | This option determines where all the compiled Blade templates will be
+ | stored for your application. Typically, this is within the storage
+ | directory. However, as usual, you are free to change this value.
+ |
+ */
+
+ 'compiled' => env(
+ 'VIEW_COMPILED_PATH',
+ realpath(storage_path('framework/views'))
+ ),
+
+];
diff --git a/examples/digitalidentity/config/yoti.php b/examples/digitalidentity/config/yoti.php
new file mode 100644
index 00000000..d5f6e761
--- /dev/null
+++ b/examples/digitalidentity/config/yoti.php
@@ -0,0 +1,8 @@
+ env('YOTI_SDK_ID'),
+ 'pem.file.path' => (function($filePath) {
+ return strpos($filePath, '/') === 0 ? $filePath : base_path($filePath);
+ })(env('YOTI_KEY_FILE_PATH')),
+];
diff --git a/examples/digitalidentity/docker-compose.yml b/examples/digitalidentity/docker-compose.yml
new file mode 100644
index 00000000..7b5d8840
--- /dev/null
+++ b/examples/digitalidentity/docker-compose.yml
@@ -0,0 +1,25 @@
+version: '3'
+
+services:
+ web:
+ build: ../docker
+ ports:
+ - "4002:443"
+ volumes:
+ - ./:/usr/share/nginx/html
+ links:
+ - php
+
+ php:
+ build:
+ context: ../docker
+ dockerfile: php.dockerfile
+ volumes:
+ - ./:/usr/share/nginx/html
+
+ composer:
+ image: composer
+ volumes:
+ - ../../:/usr/share/yoti-php-sdk
+ working_dir: /usr/share/yoti-php-sdk/examples/digitalidentity
+ command: update-local
diff --git a/examples/digitalidentity/keys/.gitignore b/examples/digitalidentity/keys/.gitignore
new file mode 100644
index 00000000..d6b7ef32
--- /dev/null
+++ b/examples/digitalidentity/keys/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore
diff --git a/examples/digitalidentity/public/.htaccess b/examples/digitalidentity/public/.htaccess
new file mode 100644
index 00000000..3aec5e27
--- /dev/null
+++ b/examples/digitalidentity/public/.htaccess
@@ -0,0 +1,21 @@
+
+
+ Options -MultiViews -Indexes
+
+
+ RewriteEngine On
+
+ # Handle Authorization Header
+ RewriteCond %{HTTP:Authorization} .
+ RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
+
+ # Redirect Trailing Slashes If Not A Folder...
+ RewriteCond %{REQUEST_FILENAME} !-d
+ RewriteCond %{REQUEST_URI} (.+)/$
+ RewriteRule ^ %1 [L,R=301]
+
+ # Send Requests To Front Controller...
+ RewriteCond %{REQUEST_FILENAME} !-d
+ RewriteCond %{REQUEST_FILENAME} !-f
+ RewriteRule ^ index.php [L]
+
diff --git a/examples/digitalidentity/public/assets/css/index.css b/examples/digitalidentity/public/assets/css/index.css
new file mode 100644
index 00000000..14a2bc8c
--- /dev/null
+++ b/examples/digitalidentity/public/assets/css/index.css
@@ -0,0 +1,173 @@
+.yoti-body {
+ margin: 0;
+}
+
+.yoti-top-section {
+ display: flex;
+ flex-direction: column;
+
+ padding: 38px 0;
+
+ background-color: #f7f8f9;
+
+ align-items: center;
+}
+
+.yoti-logo-section {
+ margin-bottom: 25px;
+}
+
+.yoti-logo-image {
+ display: block;
+}
+
+.yoti-top-header {
+ font-family: Roboto, sans-serif;
+ font-size: 40px;
+ font-weight: 700;
+ line-height: 1.2;
+ margin-top: 0;
+ margin-bottom: 80px;
+ text-align: center;
+
+ color: #000;
+}
+
+@media (min-width: 600px) {
+ .yoti-top-header {
+ line-height: 1.4;
+ }
+}
+
+.yoti-sdk-integration-section {
+ margin: 30px 0;
+}
+
+#yoti-share-button {
+ width: 250px;
+ height: 45px;
+}
+
+.yoti-login-or-separator {
+ text-transform: uppercase;
+ font-family: Roboto;
+ font-size: 16px;
+ font-weight: bold;
+ line-height: 1.5;
+ text-align: center;
+ margin-top: 30px;
+}
+
+.yoti-login-dialog {
+ display: grid;
+
+ box-sizing: border-box;
+ width: 100%;
+ padding: 35px 38px;
+
+ border-radius: 5px;
+ background: #fff;
+
+ grid-gap: 25px;
+}
+
+@media (min-width: 600px) {
+ .yoti-login-dialog {
+ width: 560px;
+ padding: 35px 88px;
+ }
+}
+
+.yoti-login-dialog-header {
+ font-family: Roboto, sans-serif;
+ font-size: 24px;
+ font-weight: 700;
+ line-height: 1.1;
+
+ margin: 0;
+
+ color: #000;
+}
+
+.yoti-input {
+ font-family: Roboto, sans-serif;
+ font-size: 16px;
+ line-height: 1.5;
+
+ box-sizing: border-box;
+ padding: 12px 15px;
+
+ color: #000;
+ border: solid 2px #000;
+ border-radius: 4px;
+ background-color: #fff;
+}
+
+.yoti-login-actions {
+ display: flex;
+
+ justify-content: space-between;
+ align-items: center;
+}
+
+.yoti-login-forgot-button {
+ font-family: Roboto, sans-serif;
+ font-size: 16px;
+
+ text-transform: capitalize;
+}
+
+.yoti-login-button {
+ font-family: Roboto, sans-serif;
+ font-size: 16px;
+
+ box-sizing: border-box;
+ width: 145px;
+ height: 50px;
+
+ text-transform: uppercase;
+
+ color: #fff;
+ border: 0;
+ background-color: #000;
+}
+
+.yoti-sponsor-app-section {
+ display: flex;
+ flex-direction: column;
+
+ padding: 70px 0;
+
+ align-items: center;
+}
+
+.yoti-sponsor-app-header {
+ font-family: Roboto, sans-serif;
+ font-size: 20px;
+ font-weight: 700;
+ line-height: 1.2;
+
+ margin: 0;
+
+ text-align: center;
+
+ color: #000;
+}
+
+.yoti-store-buttons-section {
+ margin-top: 40px;
+ display: grid;
+ grid-gap: 10px;
+ grid-template-columns: 1fr;
+}
+
+@media (min-width: 600px) {
+ .yoti-store-buttons-section {
+ grid-template-columns: 1fr 1fr;
+ grid-gap: 25px;
+ }
+}
+
+.yoti-app-button-link {
+ text-decoration: none;
+}
\ No newline at end of file
diff --git a/examples/digitalidentity/public/assets/css/profile.css b/examples/digitalidentity/public/assets/css/profile.css
new file mode 100644
index 00000000..9d43066d
--- /dev/null
+++ b/examples/digitalidentity/public/assets/css/profile.css
@@ -0,0 +1,425 @@
+.yoti-html {
+ height: 100%;
+}
+
+.yoti-body {
+ margin: 0;
+ height: 100%;
+}
+
+.yoti-icon-profile,
+.yoti-icon-phone,
+.yoti-icon-email,
+.yoti-icon-calendar,
+.yoti-icon-verified,
+.yoti-icon-address,
+.yoti-icon-gender,
+.yoti-icon-nationality {
+ display: inline-block;
+ height: 28px;
+ width: 28px;
+ flex-shrink: 0;
+}
+
+.yoti-icon-profile {
+ background: no-repeat url('/assets/images/icons/profile.svg');
+}
+
+.yoti-icon-phone {
+ background: no-repeat url('/assets/images/icons/phone.svg');
+}
+
+.yoti-icon-email {
+ background: no-repeat url('/assets/images/icons/email.svg');
+}
+
+.yoti-icon-calendar {
+ background: no-repeat url('/assets/images/icons/calendar.svg');
+}
+
+.yoti-icon-verified {
+ background: no-repeat url('/assets/images/icons/verified.svg');
+}
+
+.yoti-icon-address {
+ background: no-repeat url('/assets/images/icons/address.svg');
+}
+
+.yoti-icon-gender {
+ background: no-repeat url('/assets/images/icons/gender.svg');
+}
+
+.yoti-icon-nationality {
+ background: no-repeat url('/assets/images/icons/nationality.svg');
+}
+
+.yoti-profile-layout {
+ display: grid;
+ grid-template-columns: 1fr;
+}
+
+@media (min-width: 1100px) {
+ .yoti-profile-layout {
+ grid-template-columns: 360px 1fr;
+ height: 100%;
+ }
+}
+
+.yoti-profile-user-section {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ flex-direction: column;
+ padding: 40px 0;
+ background-color: #f7f8f9;
+}
+
+@media (min-width: 1100px) {
+ .yoti-profile-user-section {
+ display: grid;
+ grid-template-rows: repeat(3, min-content);
+ align-items: center;
+ justify-content: center;
+ position: relative;
+ }
+}
+
+.yoti-profile-picture-image {
+ width: 220px;
+ height: 220px;
+ border-radius: 50%;
+}
+
+.yoti-profile-picture-powered,
+.yoti-profile-picture-account-creation {
+ font-family: Roboto;
+ font-size: 14px;
+ color: #b6bfcb;
+}
+
+.yoti-profile-picture-powered-section {
+ display: flex;
+ flex-direction: column;
+ text-align: center;
+ align-items: center;
+}
+
+@media (min-width: 1100px) {
+ .yoti-profile-picture-powered-section {
+ align-self: start;
+ }
+}
+
+.yoti-profile-picture-powered {
+ margin-bottom: 20px;
+}
+
+.yoti-profile-picture-section {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+@media (min-width: 1100px) {
+ .yoti-profile-picture-section {
+ position: absolute;
+ top: 50%;
+ transform: translateY(-50%);
+ width: 100%;
+ }
+}
+
+.yoti-logo-image {
+ margin-bottom: 25px;
+}
+
+.yoti-profile-picture-area {
+ position: relative;
+ display: inline-block;
+}
+
+.yoti-profile-picture-verified-icon {
+ display: block;
+ background: no-repeat url("/assets/images/icons/verified.svg");
+ background-size: cover;
+ height: 40px;
+ width: 40px;
+ position: absolute;
+ top: 10px;
+ right: 10px;
+}
+
+.yoti-profile-name {
+ margin-top: 20px;
+ font-family: Roboto, sans-serif;
+ font-size: 24px;
+ text-align: center;
+ color: #333b40;
+}
+
+.yoti-attributes-section {
+ display: flex;
+ flex-direction: column;
+ justify-content: start;
+ align-items: center;
+
+ width: 100%;
+ padding: 40px 0;
+}
+
+.yoti-attributes-section.-condensed {
+ padding: 0;
+}
+
+@media (min-width: 1100px) {
+ .yoti-attributes-section {
+ padding: 60px 0;
+ align-items: start;
+ overflow-y: scroll;
+ }
+
+ .yoti-attributes-section.-condensed {
+ padding: 0;
+ }
+}
+
+.yoti-company-logo {
+ margin-bottom: 40px;
+}
+
+@media (min-width: 1100px) {
+ .yoti-company-logo {
+ margin-left: 130px;
+ }
+}
+
+/* extended layout list */
+.yoti-attribute-list-header,
+.yoti-attribute-list-subheader {
+ display: none;
+}
+
+@media (min-width: 1100px) {
+ .yoti-attribute-list-header,
+ .yoti-attribute-list-subheader {
+ width: 100%;
+
+ display: grid;
+ grid-template-columns: 200px 1fr 1fr;
+ grid-template-rows: 40px;
+
+ align-items: center;
+ text-align: center;
+
+ font-family: Roboto;
+ font-size: 14px;
+ color: #b6bfcb;
+ }
+}
+
+.yoti-attribute-list-header-attribute,
+.yoti-attribute-list-header-value {
+ justify-self: start;
+ padding: 0 20px;
+}
+
+.yoti-attribute-list-subheader {
+ grid-template-rows: 30px;
+}
+
+.yoti-attribute-list-subhead-layout {
+ grid-column: 3;
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr;
+}
+
+.yoti-attribute-list {
+ display: grid;
+ width: 100%;
+}
+
+.yoti-attribute-list-item:first-child {
+ border-top: 2px solid #f7f8f9;
+}
+
+.yoti-attribute-list-item {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ grid-template-rows: minmax(60px, auto);
+ border-bottom: 2px solid #f7f8f9;
+ border-right: none;
+ border-left: none;
+}
+
+.yoti-attribute-list-item.-condensed {
+ grid-template-columns: 50% 50%;
+ padding: 5px 35px;
+}
+
+@media (min-width: 1100px) {
+ .yoti-attribute-list-item {
+ display: grid;
+ grid-template-columns: 200px 1fr 1fr;
+ grid-template-rows: minmax(80px, auto);
+ }
+
+ .yoti-attribute-list-item.-condensed {
+ grid-template-columns: 200px 1fr;
+ padding: 0 75px;
+ }
+}
+
+.yoti-attribute-cell {
+ display: flex;
+ align-items: center;
+}
+
+.yoti-attribute-name {
+ grid-column: 1 / 2;
+
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ border-right: 2px solid #f7f8f9;
+
+ padding: 20px;
+}
+
+@media (min-width: 1100px) {
+ .yoti-attribute-name {
+ justify-content: start;
+ }
+}
+
+.yoti-attribute-name.-condensed {
+ justify-content: start;
+}
+
+.yoti-attribute-name-cell {
+ display: flex;
+ align-items: center;
+}
+
+.yoti-attribute-name-cell-text {
+ font-family: Roboto, sans-serif;
+ font-size: 16px;
+ color: #b6bfcb;
+ margin-left: 12px;
+}
+
+.yoti-attribute-value-text table {
+ font-size: 14px;
+ border-spacing: 0;
+}
+
+.yoti-attribute-value-text table td:first-child {
+ font-weight: bold;
+}
+
+.yoti-attribute-value-text table td {
+ border-bottom: 1px solid #f7f8f9;
+ padding: 5px;
+}
+
+.yoti-attribute-value-text img {
+ width: 100%;
+}
+
+.yoti-attribute-value {
+ grid-column: 2 / 3;
+
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ padding: 20px;
+}
+
+@media (min-width: 1100px) {
+ .yoti-attribute-value {
+ justify-content: start;
+ }
+}
+
+.yoti-attribute-value.-condensed {
+ justify-content: start;
+}
+
+.yoti-attribute-value-text {
+ font-family: Roboto, sans-serif;
+ font-size: 18px;
+ color: #333b40;
+ word-break: break-word;
+}
+
+.yoti-attribute-anchors-layout {
+ grid-column: 1 / 3;
+ grid-row: 2 / 2;
+
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr;
+ grid-auto-rows: minmax(40px, auto);
+ font-family: Roboto, sans-serif;
+ font-size: 14px;
+
+ background-color: #f7f8f9;
+}
+
+@media (min-width: 1100px) {
+ .yoti-attribute-anchors-layout {
+ grid-column: 3 / 4;
+ grid-row: 1 / 2;
+ }
+}
+
+.yoti-attribute-anchors-head {
+ border-bottom: 1px solid #dde2e5;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+@media (min-width: 1100px) {
+ .yoti-attribute-anchors-head {
+ display: none;
+ }
+}
+
+.yoti-attribute-anchors {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.yoti-attribute-anchors-head.-s-v {
+ grid-column-start: span 1 s-v;
+}
+.yoti-attribute-anchors-head.-value {
+ grid-column-start: span 1 value;
+}
+.yoti-attribute-anchors-head.-subtype {
+ grid-column-start: span 1 subtype;
+}
+
+.yoti-attribute-anchors.-s-v {
+ grid-column-start: span 1 s-v;
+}
+
+.yoti-attribute-anchors.-value {
+ grid-column-start: span 1 value;
+}
+
+.yoti-attribute-anchors.-subtype {
+ grid-column-start: span 1 subtype;
+}
+
+.yoti-edit-section {
+ padding: 50px 20px;
+}
+
+@media (min-width: 1100px) {
+ .yoti-edit-section {
+ padding: 75px 110px;
+ }
+}
diff --git a/examples/digitalidentity/public/assets/images/app-store-badge.png b/examples/digitalidentity/public/assets/images/app-store-badge.png
new file mode 100644
index 00000000..3ec996cc
Binary files /dev/null and b/examples/digitalidentity/public/assets/images/app-store-badge.png differ
diff --git a/examples/digitalidentity/public/assets/images/app-store-badge@2x.png b/examples/digitalidentity/public/assets/images/app-store-badge@2x.png
new file mode 100644
index 00000000..84b34068
Binary files /dev/null and b/examples/digitalidentity/public/assets/images/app-store-badge@2x.png differ
diff --git a/examples/digitalidentity/public/assets/images/company-logo.jpg b/examples/digitalidentity/public/assets/images/company-logo.jpg
new file mode 100644
index 00000000..551474bf
Binary files /dev/null and b/examples/digitalidentity/public/assets/images/company-logo.jpg differ
diff --git a/examples/digitalidentity/public/assets/images/google-play-badge.png b/examples/digitalidentity/public/assets/images/google-play-badge.png
new file mode 100644
index 00000000..761f237b
Binary files /dev/null and b/examples/digitalidentity/public/assets/images/google-play-badge.png differ
diff --git a/examples/digitalidentity/public/assets/images/google-play-badge@2x.png b/examples/digitalidentity/public/assets/images/google-play-badge@2x.png
new file mode 100644
index 00000000..46707cea
Binary files /dev/null and b/examples/digitalidentity/public/assets/images/google-play-badge@2x.png differ
diff --git a/examples/digitalidentity/public/assets/images/icons/address.svg b/examples/digitalidentity/public/assets/images/icons/address.svg
new file mode 100644
index 00000000..f7d9b2a7
--- /dev/null
+++ b/examples/digitalidentity/public/assets/images/icons/address.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/examples/digitalidentity/public/assets/images/icons/calendar.svg b/examples/digitalidentity/public/assets/images/icons/calendar.svg
new file mode 100644
index 00000000..4f6b9bb7
--- /dev/null
+++ b/examples/digitalidentity/public/assets/images/icons/calendar.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/examples/digitalidentity/public/assets/images/icons/chevron-down-grey.svg b/examples/digitalidentity/public/assets/images/icons/chevron-down-grey.svg
new file mode 100644
index 00000000..6753becb
--- /dev/null
+++ b/examples/digitalidentity/public/assets/images/icons/chevron-down-grey.svg
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/examples/digitalidentity/public/assets/images/icons/document.svg b/examples/digitalidentity/public/assets/images/icons/document.svg
new file mode 100644
index 00000000..4c41271e
--- /dev/null
+++ b/examples/digitalidentity/public/assets/images/icons/document.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/examples/digitalidentity/public/assets/images/icons/email.svg b/examples/digitalidentity/public/assets/images/icons/email.svg
new file mode 100644
index 00000000..c4582d6e
--- /dev/null
+++ b/examples/digitalidentity/public/assets/images/icons/email.svg
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/digitalidentity/public/assets/images/icons/gender.svg b/examples/digitalidentity/public/assets/images/icons/gender.svg
new file mode 100644
index 00000000..af5c5772
--- /dev/null
+++ b/examples/digitalidentity/public/assets/images/icons/gender.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/examples/digitalidentity/public/assets/images/icons/nationality.svg b/examples/digitalidentity/public/assets/images/icons/nationality.svg
new file mode 100644
index 00000000..e57d7522
--- /dev/null
+++ b/examples/digitalidentity/public/assets/images/icons/nationality.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/examples/digitalidentity/public/assets/images/icons/phone.svg b/examples/digitalidentity/public/assets/images/icons/phone.svg
new file mode 100644
index 00000000..b19cce04
--- /dev/null
+++ b/examples/digitalidentity/public/assets/images/icons/phone.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/examples/digitalidentity/public/assets/images/icons/profile.svg b/examples/digitalidentity/public/assets/images/icons/profile.svg
new file mode 100644
index 00000000..5c514fc1
--- /dev/null
+++ b/examples/digitalidentity/public/assets/images/icons/profile.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/examples/digitalidentity/public/assets/images/icons/verified.svg b/examples/digitalidentity/public/assets/images/icons/verified.svg
new file mode 100644
index 00000000..7ca4dbb3
--- /dev/null
+++ b/examples/digitalidentity/public/assets/images/icons/verified.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/examples/digitalidentity/public/assets/images/logo.png b/examples/digitalidentity/public/assets/images/logo.png
new file mode 100644
index 00000000..c60227fa
Binary files /dev/null and b/examples/digitalidentity/public/assets/images/logo.png differ
diff --git a/examples/digitalidentity/public/assets/images/logo@2x.png b/examples/digitalidentity/public/assets/images/logo@2x.png
new file mode 100644
index 00000000..9f29784d
Binary files /dev/null and b/examples/digitalidentity/public/assets/images/logo@2x.png differ
diff --git a/examples/digitalidentity/public/favicon.ico b/examples/digitalidentity/public/favicon.ico
new file mode 100644
index 00000000..e69de29b
diff --git a/examples/digitalidentity/public/index.php b/examples/digitalidentity/public/index.php
new file mode 100644
index 00000000..4584cbcd
--- /dev/null
+++ b/examples/digitalidentity/public/index.php
@@ -0,0 +1,60 @@
+
+ */
+
+define('LARAVEL_START', microtime(true));
+
+/*
+|--------------------------------------------------------------------------
+| Register The Auto Loader
+|--------------------------------------------------------------------------
+|
+| Composer provides a convenient, automatically generated class loader for
+| our application. We just need to utilize it! We'll simply require it
+| into the script here so that we don't have to worry about manual
+| loading any of our classes later on. It feels great to relax.
+|
+*/
+
+require __DIR__.'/../vendor/autoload.php';
+
+/*
+|--------------------------------------------------------------------------
+| Turn On The Lights
+|--------------------------------------------------------------------------
+|
+| We need to illuminate PHP development, so let us turn on the lights.
+| This bootstraps the framework and gets it ready for use, then it
+| will load up this application so that we can run it and send
+| the responses back to the browser and delight our users.
+|
+*/
+
+$app = require_once __DIR__.'/../bootstrap/app.php';
+
+/*
+|--------------------------------------------------------------------------
+| Run The Application
+|--------------------------------------------------------------------------
+|
+| Once we have the application, we can handle the incoming request
+| through the kernel, and send the associated response back to
+| the client's browser allowing them to enjoy the creative
+| and wonderful application we have prepared for them.
+|
+*/
+
+$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
+
+$response = $kernel->handle(
+ $request = Illuminate\Http\Request::capture()
+);
+
+$response->send();
+
+$kernel->terminate($request, $response);
diff --git a/examples/digitalidentity/public/robots.txt b/examples/digitalidentity/public/robots.txt
new file mode 100644
index 00000000..eb053628
--- /dev/null
+++ b/examples/digitalidentity/public/robots.txt
@@ -0,0 +1,2 @@
+User-agent: *
+Disallow:
diff --git a/examples/digitalidentity/resources/views/identity.blade.php b/examples/digitalidentity/resources/views/identity.blade.php
new file mode 100644
index 00000000..de3cd057
--- /dev/null
+++ b/examples/digitalidentity/resources/views/identity.blade.php
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+ {{ $title }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/digitalidentity/resources/views/partial/address.blade.php b/examples/digitalidentity/resources/views/partial/address.blade.php
new file mode 100644
index 00000000..8e0465c9
--- /dev/null
+++ b/examples/digitalidentity/resources/views/partial/address.blade.php
@@ -0,0 +1,8 @@
+
+ @foreach ($address as $key => $value)
+
+ {{ $key }}
+ {{ $value }}
+
+ @endforeach
+
\ No newline at end of file
diff --git a/examples/digitalidentity/resources/views/partial/ageverification.blade.php b/examples/digitalidentity/resources/views/partial/ageverification.blade.php
new file mode 100644
index 00000000..e53e1b31
--- /dev/null
+++ b/examples/digitalidentity/resources/views/partial/ageverification.blade.php
@@ -0,0 +1,14 @@
+
+
+ Check Type
+ {{ $ageVerification->getCheckType() }}
+
+
+ Age
+ {{ $ageVerification->getAge() }}
+
+
+ Result
+ {{ $ageVerification->getResult() ? 'true' : 'false' }}
+
+
\ No newline at end of file
diff --git a/examples/digitalidentity/resources/views/partial/attribute.blade.php b/examples/digitalidentity/resources/views/partial/attribute.blade.php
new file mode 100644
index 00000000..ecfedecd
--- /dev/null
+++ b/examples/digitalidentity/resources/views/partial/attribute.blade.php
@@ -0,0 +1,13 @@
+@if ($value instanceof Yoti\Profile\Attribute\MultiValue)
+ @foreach ($value as $multiValue)
+ @include('partial/attribute', ['value' => $multiValue])
+ @endforeach
+@elseif ($value instanceof \Yoti\Media\Image)
+
+@elseif ($value instanceof \Yoti\Profile\Attribute\DocumentDetails)
+ @include('partial/documentdetails', ['documentDetails' => $value])
+@elseif ($value instanceof \DateTime) {
+ {{ $value->format('d-m-Y') }}
+@else
+ {{ $value }}
+@endif
\ No newline at end of file
diff --git a/examples/digitalidentity/resources/views/partial/documentdetails.blade.php b/examples/digitalidentity/resources/views/partial/documentdetails.blade.php
new file mode 100644
index 00000000..6ad2f91f
--- /dev/null
+++ b/examples/digitalidentity/resources/views/partial/documentdetails.blade.php
@@ -0,0 +1,18 @@
+
+
+ Type
+ {{ $documentDetails->getType() }}
+
+
+ Issuing Country
+ {{ $documentDetails->getIssuingCountry() }}
+
+
+ Document Number
+ {{ $documentDetails->getDocumentNumber() }}
+
+
+ Expiration Date
+ {{ $documentDetails->getExpirationDate()->format('d-m-Y') }}
+
+
\ No newline at end of file
diff --git a/examples/digitalidentity/resources/views/partial/report.blade.php b/examples/digitalidentity/resources/views/partial/report.blade.php
new file mode 100644
index 00000000..28bdd3f0
--- /dev/null
+++ b/examples/digitalidentity/resources/views/partial/report.blade.php
@@ -0,0 +1,39 @@
+@foreach ($report as $key => $value)
+
+
+
+
+ {{ $key }}
+
+
+
+
+ @foreach ($value as $name => $result)
+ @if (is_array($result))
+ @foreach ($result as $data => $view)
+ @if (is_array($view))
+ @foreach ($view as $key2 => $value2)
+ @if (is_array($value2))
+ {{json_encode($value2)}}
+ @else
+
+ {{ $key2 }} {{ $value2 }}
+
+ @endif
+ @endforeach
+ @else
+
+ {{ $data }} {{ $view }}
+
+ @endif
+ @endforeach
+ @else
+
+ {{ $name }} {{ $result }}
+
+ @endif
+ @endforeach
+
+
+
+ @endforeach
diff --git a/examples/digitalidentity/resources/views/receipt.blade.php b/examples/digitalidentity/resources/views/receipt.blade.php
new file mode 100644
index 00000000..1fef5463
--- /dev/null
+++ b/examples/digitalidentity/resources/views/receipt.blade.php
@@ -0,0 +1,104 @@
+
+
+
+
+
+ Yoti client example
+
+
+
+
+
+
+
+
+
+
Powered by
+
+
+
+
+
+
+ @if ($selfie)
+
+
+
+
+ @endif
+
+ @if ($fullName)
+
+ {{ $fullName->getValue() }}
+
+ @endif
+
+
+
+
+
+
+
+
+
+
+
+
+
+ @foreach($profileAttributes as $item)
+ @if ($item['obj'])
+
+
+
+
+ {{ $item['name'] }}
+
+
+
+
+ @switch ($item['name'])
+ @case ('Age Verification')
+ @include('partial/ageverification', ['ageVerification' => $item['age_verification']])
+ @break
+ @case ('Structured Postal Address')
+ @include('partial/address', ['address' => $item['obj']->getValue()])
+ @break
+ @case ('Identity Profile Report')
+ @include('partial/report', ['report' => $item['obj']->getValue()])
+ @break
+ @default
+ @include('partial/attribute', ['value' => $item['obj']->getValue()])
+ @endswitch
+
+
+
+
S / V
+
Value
+
Sub type
+
+ @foreach($item['obj']->getAnchors() as $anchor)
+
{{ $anchor->getType() }}
+
{{ $anchor->getValue() }}
+
{{ $anchor->getSubType() }}
+ @endforeach
+
+
+
+ @endif
+ @endforeach
+
+
+
+
+
+
diff --git a/examples/digitalidentity/routes/api.php b/examples/digitalidentity/routes/api.php
new file mode 100644
index 00000000..bcb8b189
--- /dev/null
+++ b/examples/digitalidentity/routes/api.php
@@ -0,0 +1,19 @@
+get('/user', function (Request $request) {
+ return $request->user();
+});
diff --git a/examples/digitalidentity/routes/channels.php b/examples/digitalidentity/routes/channels.php
new file mode 100644
index 00000000..963b0d21
--- /dev/null
+++ b/examples/digitalidentity/routes/channels.php
@@ -0,0 +1,18 @@
+id === (int) $id;
+});
diff --git a/examples/digitalidentity/routes/console.php b/examples/digitalidentity/routes/console.php
new file mode 100644
index 00000000..da55196d
--- /dev/null
+++ b/examples/digitalidentity/routes/console.php
@@ -0,0 +1,19 @@
+comment(Inspiring::quote());
+})->describe('Display an inspiring quote');
diff --git a/examples/digitalidentity/routes/web.php b/examples/digitalidentity/routes/web.php
new file mode 100644
index 00000000..c592b91f
--- /dev/null
+++ b/examples/digitalidentity/routes/web.php
@@ -0,0 +1,18 @@
+
+ */
+class DigitalIdentityClient
+{
+ private DigitalIdentityService $digitalIdentityService;
+ public string $id = '';
+ /**
+ * DigitalIdentityClient constructor.
+ *
+ * @param string $sdkId
+ * The SDK identifier generated by Yoti Hub when you create your app.
+ * @param string $pem
+ * PEM file path or string
+ * @param array $options (optional)
+ * SDK configuration options - {@see \Yoti\Util\Config} for available options.
+ *
+ * @throws PemFileException
+ */
+ public function __construct(
+ string $sdkId,
+ string $pem,
+ array $options = []
+ ) {
+ Validation::notEmptyString($sdkId, 'SDK ID');
+ $pemFile = PemFile::resolveFromString($pem);
+
+ // Set API URL from environment variable.
+ $options[Config::API_URL] = $options[Config::API_URL] ?? Env::get(Constants::ENV_DIGITAL_IDENTITY_API_URL);
+
+ $config = new Config($options);
+
+ $this->digitalIdentityService = new DigitalIdentityService($sdkId, $pemFile, $config);
+ $this->id = $sdkId;
+ }
+
+ /**
+ * Create a sharing session to initiate a sharing process based on a policy
+ *
+ * @throws DigitalIdentityException
+ *
+ * Aggregate exception signalling issues during the call
+ */
+ public function createShareSession(ShareSessionRequest $request): Identity\ShareSessionCreated
+ {
+ return $this->digitalIdentityService->createShareSession($request);
+ }
+
+ /**
+ * Create a sharing session QR code to initiate a sharing process based on a policy
+ *
+ * @throws DigitalIdentityException
+ *
+ * Aggregate exception signalling issues during the call
+ */
+ public function createShareQrCode(string $sessionId): Identity\ShareSessionCreatedQrCode
+ {
+ return $this->digitalIdentityService->createShareQrCode($sessionId);
+ }
+
+ /**
+ * Retrieve the sharing session QR code
+ *
+ * @throws DigitalIdentityException
+ *
+ * Aggregate exception signalling issues during the call
+ */
+ public function fetchShareQrCode(string $qrCodeId): Identity\ShareSessionFetchedQrCode
+ {
+ return $this->digitalIdentityService->fetchShareQrCode($qrCodeId);
+ }
+
+ /**
+ * Retrieve the sharing session
+ *
+ * @throws DigitalIdentityException
+ *
+ * Aggregate exception signalling issues during the call
+ */
+ public function fetchShareSession(string $sessionId): Identity\ShareSessionFetched
+ {
+ return $this->digitalIdentityService->fetchShareSession($sessionId);
+ }
+
+ /**
+ * Retrieve the decrypted share receipt.
+ *
+ * @throws DigitalIdentityException
+ *
+ * Aggregate exception signalling issues during the call
+ */
+ public function fetchShareReceipt(string $receiptId): Identity\Receipt
+ {
+ return $this->digitalIdentityService->fetchShareReceipt($receiptId);
+ }
+
+ public function getSdkID(): string
+ {
+ return $this->id;
+ }
+}
diff --git a/src/DocScan/Session/Create/SdkConfig.php b/src/DocScan/Session/Create/SdkConfig.php
index 51b85112..a3c8086d 100644
--- a/src/DocScan/Session/Create/SdkConfig.php
+++ b/src/DocScan/Session/Create/SdkConfig.php
@@ -95,7 +95,6 @@ public function __construct(
?bool $allowHandoff = null,
?array $idDocumentTextDataExtractionRetriesConfig = null,
?string $biometricConsentFlow = null
-
) {
$this->allowedCaptureMethods = $allowedCaptureMethods;
$this->primaryColour = $primaryColour;
@@ -111,7 +110,6 @@ public function __construct(
$this->attemptsConfiguration = new AttemptsConfiguration($idDocumentTextDataExtractionRetriesConfig);
}
$this->biometricConsentFlow = $biometricConsentFlow;
-
}
/**
diff --git a/src/DocScan/Session/Create/Task/RequestedTextExtractionTaskBuilder.php b/src/DocScan/Session/Create/Task/RequestedTextExtractionTaskBuilder.php
index 174828f9..802e1a51 100644
--- a/src/DocScan/Session/Create/Task/RequestedTextExtractionTaskBuilder.php
+++ b/src/DocScan/Session/Create/Task/RequestedTextExtractionTaskBuilder.php
@@ -62,11 +62,8 @@ public function withManualCheck(string $manualCheck): self
* @var bool
*/
private $createExpandedDocumentFields;
-
/**
- *
- * @param string $createExpandedDocumentFields
- *
+ * @param bool $createExpandedDocumentFields
* @return $this
*/
public function withCreateExpandedDocumentFields(bool $createExpandedDocumentFields): self
@@ -82,8 +79,11 @@ public function build(): RequestedTextExtractionTask
{
Validation::notEmptyString($this->manualCheck, 'manualCheck');
- $config = new RequestedTextExtractionTaskConfig($this->manualCheck, $this->chipData,
- $this->createExpandedDocumentFields);
+ $config = new RequestedTextExtractionTaskConfig(
+ $this->manualCheck,
+ $this->chipData,
+ $this->createExpandedDocumentFields
+ );
return new RequestedTextExtractionTask($config);
}
}
diff --git a/src/DocScan/Session/Create/Task/RequestedTextExtractionTaskConfig.php b/src/DocScan/Session/Create/Task/RequestedTextExtractionTaskConfig.php
index 7e5716d1..b124b343 100644
--- a/src/DocScan/Session/Create/Task/RequestedTextExtractionTaskConfig.php
+++ b/src/DocScan/Session/Create/Task/RequestedTextExtractionTaskConfig.php
@@ -29,8 +29,11 @@ class RequestedTextExtractionTaskConfig implements RequestedTaskConfigInterface
* @param string|null $chipData
* @param bool|null $createExpandedDocumentFields
*/
- public function __construct(string $manualCheck, ?string $chipData = null, ?bool $createExpandedDocumentFields = false)
- {
+ public function __construct(
+ string $manualCheck,
+ ?string $chipData = null,
+ ?bool $createExpandedDocumentFields = false
+ ) {
$this->manualCheck = $manualCheck;
$this->chipData = $chipData;
$this->createExpandedDocumentFields = $createExpandedDocumentFields;
diff --git a/src/DocScan/Session/Retrieve/IdDocumentResourceResponse.php b/src/DocScan/Session/Retrieve/IdDocumentResourceResponse.php
index 6d9dc824..6a25f006 100644
--- a/src/DocScan/Session/Retrieve/IdDocumentResourceResponse.php
+++ b/src/DocScan/Session/Retrieve/IdDocumentResourceResponse.php
@@ -54,7 +54,6 @@ public function __construct(array $idDocument)
$this->documentFields = isset($idDocument['document_fields'])
? new DocumentFieldsResponse($idDocument['document_fields'])
: null;
-
$this->expandedDocumentFields = isset($idDocument['expanded_document_fields'])
? new ExpandedDocumentFieldsResponse($idDocument['expanded_document_fields'])
: null;
diff --git a/src/Exception/DigitalIdentityException.php b/src/Exception/DigitalIdentityException.php
new file mode 100644
index 00000000..5e457c54
--- /dev/null
+++ b/src/Exception/DigitalIdentityException.php
@@ -0,0 +1,9 @@
+wantedAnchors = $wantedAnchors;
+
+ Validation::isBoolean($softPreference, 'soft_preference');
+ $this->softPreference = $softPreference;
+ }
+
+ public function jsonSerialize(): stdClass
+ {
+ return (object)[
+ 'anchors' => $this->wantedAnchors,
+ 'soft_preference' => $this->softPreference,
+ ];
+ }
+
+ /**
+ * @return WantedAnchor[]
+ */
+ public function getWantedAnchors(): array
+ {
+ return $this->wantedAnchors;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isSoftPreference(): bool
+ {
+ return $this->softPreference;
+ }
+}
diff --git a/src/Identity/Constraint/SourceConstraint.php b/src/Identity/Constraint/SourceConstraint.php
new file mode 100644
index 00000000..cd903789
--- /dev/null
+++ b/src/Identity/Constraint/SourceConstraint.php
@@ -0,0 +1,43 @@
+type = 'SOURCE';
+
+ Validation::isArrayOfType($wantedAnchors, [WantedAnchor::class], 'anchors');
+ $this->preferredSources = new PreferredSources($wantedAnchors, $softPreference);
+ }
+
+ public function getType(): string
+ {
+ return $this->type;
+ }
+
+ public function getPreferredSources(): PreferredSources
+ {
+ return $this->preferredSources;
+ }
+
+ public function jsonSerialize(): object
+ {
+ return (object)[
+ 'type' => $this->getType(),
+ 'preferred_sources' => $this->getPreferredSources(),
+ ];
+ }
+}
diff --git a/src/Identity/Constraint/SourceConstraintBuilder.php b/src/Identity/Constraint/SourceConstraintBuilder.php
new file mode 100644
index 00000000..59b62e16
--- /dev/null
+++ b/src/Identity/Constraint/SourceConstraintBuilder.php
@@ -0,0 +1,46 @@
+wantedAnchors = $wantedAnchors;
+
+ return $this;
+ }
+
+ public function withWantedAnchor(WantedAnchor $wantedAnchor): self
+ {
+ $this->wantedAnchors[] = $wantedAnchor;
+
+ return $this;
+ }
+
+ public function withSoftPreference(bool $softPreference): self
+ {
+ $this->softPreference = $softPreference;
+
+ return $this;
+ }
+
+ public function build(): SourceConstraint
+ {
+ return new SourceConstraint($this->wantedAnchors, $this->softPreference);
+ }
+}
diff --git a/src/Identity/Content/ApplicationContent.php b/src/Identity/Content/ApplicationContent.php
new file mode 100644
index 00000000..8487fc4a
--- /dev/null
+++ b/src/Identity/Content/ApplicationContent.php
@@ -0,0 +1,28 @@
+profile = $profile;
+ $this->extraData = $extraData;
+ }
+
+ public function getProfile(): ?ApplicationProfile
+ {
+ return $this->profile;
+ }
+
+ public function getExtraData(): ?ExtraData
+ {
+ return $this->extraData;
+ }
+}
diff --git a/src/Identity/Content/Content.php b/src/Identity/Content/Content.php
new file mode 100644
index 00000000..9cf61c01
--- /dev/null
+++ b/src/Identity/Content/Content.php
@@ -0,0 +1,45 @@
+profile = $profile;
+ $this->extraData = $extraData;
+ }
+
+ public function getProfile(): ?string
+ {
+ if (null !== $this->profile) {
+ $decoded = base64_decode($this->profile, true);
+ if ($decoded === false) {
+ throw new EncryptedDataException('Could not decode data');
+ }
+
+ return $decoded;
+ }
+
+ return null;
+ }
+
+ public function getExtraData(): ?string
+ {
+ if (null !== $this->extraData) {
+ $decoded = base64_decode($this->extraData, true);
+ if ($decoded === false) {
+ throw new EncryptedDataException('Could not decode data');
+ }
+
+ return $decoded;
+ }
+
+ return null;
+ }
+}
diff --git a/src/Identity/Content/UserContent.php b/src/Identity/Content/UserContent.php
new file mode 100644
index 00000000..a32c2dfd
--- /dev/null
+++ b/src/Identity/Content/UserContent.php
@@ -0,0 +1,28 @@
+profile = $profile;
+ $this->extraData = $extraData;
+ }
+
+ public function getProfile(): ?UserProfile
+ {
+ return $this->profile;
+ }
+
+ public function getExtraData(): ?ExtraData
+ {
+ return $this->extraData;
+ }
+}
diff --git a/src/Identity/DigitalIdentityService.php b/src/Identity/DigitalIdentityService.php
new file mode 100644
index 00000000..344b18c7
--- /dev/null
+++ b/src/Identity/DigitalIdentityService.php
@@ -0,0 +1,170 @@
+sdkId = $sdkId;
+ $this->pemFile = $pemFile;
+ $this->config = $config;
+ }
+
+ public function createShareSession(ShareSessionRequest $shareSessionRequest): ShareSessionCreated
+ {
+ $response = (new RequestBuilder($this->config))
+ ->withBaseUrl($this->config->getApiUrl() ?? Constants::DIGITAL_IDENTITY_API_URL)
+ ->withEndpoint(self::IDENTITY_SESSION_CREATION)
+ ->withHeader('X-Yoti-Auth-Id', $this->sdkId)
+ ->withPost()
+ ->withPayload(Payload::fromJsonData($shareSessionRequest))
+ ->withPemFile($this->pemFile)
+ ->build()
+ ->execute();
+
+ $httpCode = $response->getStatusCode();
+ if ($httpCode < 200 || $httpCode > 299) {
+ throw new DigitalIdentityException("Server responded with {$httpCode}", $response);
+ }
+
+ return new ShareSessionCreated(Json::decode((string)$response->getBody()));
+ }
+
+ public function createShareQrCode(string $sessionId): ShareSessionCreatedQrCode
+ {
+ $response = (new RequestBuilder($this->config))
+ ->withBaseUrl($this->config->getApiUrl() ?? Constants::DIGITAL_IDENTITY_API_URL)
+ ->withEndpoint(sprintf(self::IDENTITY_SESSION_QR_CODE_CREATION, $sessionId))
+ ->withHeader('X-Yoti-Auth-Id', $this->sdkId)
+ ->withPost()
+ ->withPemFile($this->pemFile)
+ ->build()
+ ->execute();
+
+ $httpCode = $response->getStatusCode();
+ if ($httpCode < 200 || $httpCode > 299) {
+ throw new DigitalIdentityException("Server responded with {$httpCode}", $response);
+ }
+
+ return new ShareSessionCreatedQrCode(Json::decode((string)$response->getBody()));
+ }
+
+ public function fetchShareQrCode(string $qrCodeId): ShareSessionFetchedQrCode
+ {
+ $response = (new RequestBuilder($this->config))
+ ->withBaseUrl($this->config->getApiUrl() ?? Constants::DIGITAL_IDENTITY_API_URL)
+ ->withEndpoint(sprintf(self::IDENTITY_SESSION_QR_CODE_RETRIEVAL, $qrCodeId))
+ ->withHeader('X-Yoti-Auth-Id', $this->sdkId)
+ ->withGet()
+ ->withPemFile($this->pemFile)
+ ->build()
+ ->execute();
+
+ $httpCode = $response->getStatusCode();
+ if ($httpCode < 200 || $httpCode > 299) {
+ throw new DigitalIdentityException("Server responded with {$httpCode}", $response);
+ }
+
+ return new ShareSessionFetchedQrCode(Json::decode((string)$response->getBody()));
+ }
+
+ public function fetchShareSession(string $sessionId): ShareSessionFetched
+ {
+ $response = (new RequestBuilder($this->config))
+ ->withBaseUrl($this->config->getApiUrl() ?? Constants::DIGITAL_IDENTITY_API_URL)
+ ->withEndpoint(sprintf(self::IDENTITY_SESSION_RETRIEVAL, $sessionId))
+ ->withHeader('X-Yoti-Auth-Id', $this->sdkId)
+ ->withGet()
+ ->withPemFile($this->pemFile)
+ ->build()
+ ->execute();
+
+ $httpCode = $response->getStatusCode();
+ if ($httpCode < 200 || $httpCode > 299) {
+ throw new DigitalIdentityException("Server responded with {$httpCode}", $response);
+ }
+
+ return new ShareSessionFetched(Json::decode((string)$response->getBody()));
+ }
+
+ /**
+ * @throws DigitalIdentityException
+ */
+ public function fetchShareReceipt(string $receiptId): Receipt
+ {
+ $receiptParser = new ReceiptParser();
+ $wrappedReceipt = $this->doFetchShareReceipt($receiptId);
+
+ if (null === $wrappedReceipt->getError()) {
+ $receiptKey = $this->fetchShareReceiptKey($wrappedReceipt);
+
+ return $receiptParser->createSuccess($wrappedReceipt, $receiptKey, $this->pemFile);
+ }
+
+ return $receiptParser->createFailure($wrappedReceipt);
+ }
+
+ private function doFetchShareReceipt(string $receiptId): WrappedReceipt
+ {
+ $receiptIdUrl = strtr($receiptId, '+/', '-_');
+ $response = (new RequestBuilder($this->config))
+ ->withBaseUrl($this->config->getApiUrl() ?? Constants::DIGITAL_IDENTITY_API_URL)
+ ->withEndpoint(sprintf(self::IDENTITY_SESSION_RECEIPT_RETRIEVAL, $receiptIdUrl))
+ ->withHeader('X-Yoti-Auth-Id', $this->sdkId)
+ ->withGet()
+ ->withPemFile($this->pemFile)
+ ->build()
+ ->execute();
+
+ $httpCode = $response->getStatusCode();
+ if ($httpCode < 200 || $httpCode > 299) {
+ throw new DigitalIdentityException("Server responded with {$httpCode}", $response);
+ }
+
+ return new WrappedReceipt(Json::decode((string)$response->getBody()));
+ }
+
+ private function fetchShareReceiptKey(WrappedReceipt $wrappedReceipt): ReceiptItemKey
+ {
+ $response = (new RequestBuilder($this->config))
+ ->withBaseUrl($this->config->getApiUrl() ?? Constants::DIGITAL_IDENTITY_API_URL)
+ ->withEndpoint(sprintf(
+ self::IDENTITY_SESSION_RECEIPT_KEY_RETRIEVAL,
+ $wrappedReceipt->getWrappedItemKeyId()
+ ))
+ ->withHeader('X-Yoti-Auth-Id', $this->sdkId)
+ ->withGet()
+ ->withPemFile($this->pemFile)
+ ->build()
+ ->execute();
+
+ $httpCode = $response->getStatusCode();
+ if ($httpCode < 200 || $httpCode > 299) {
+ throw new DigitalIdentityException("Server responded with {$httpCode}", $response);
+ }
+
+ return new ReceiptItemKey(Json::decode((string)$response->getBody()));
+ }
+}
diff --git a/src/Identity/Extension/BasicExtensionBuilder.php b/src/Identity/Extension/BasicExtensionBuilder.php
new file mode 100644
index 00000000..97d913aa
--- /dev/null
+++ b/src/Identity/Extension/BasicExtensionBuilder.php
@@ -0,0 +1,40 @@
+type = $type;
+
+ return $this;
+ }
+
+ /**
+ * @param mixed $content
+ *
+ * @return $this
+ */
+ public function withContent($content): self
+ {
+ $this->content = $content;
+
+ return $this;
+ }
+
+ public function build(): Extension
+ {
+ return new Extension($this->type, $this->content);
+ }
+}
diff --git a/src/Identity/Extension/Extension.php b/src/Identity/Extension/Extension.php
new file mode 100644
index 00000000..d7e436b1
--- /dev/null
+++ b/src/Identity/Extension/Extension.php
@@ -0,0 +1,38 @@
+type = $type;
+
+ Validation::notNull($type, 'content');
+ $this->content = $content;
+ }
+
+ /**
+ * @return stdClass
+ */
+ public function jsonSerialize(): stdClass
+ {
+ return (object)[
+ 'type' => $this->type,
+ 'content' => $this->content,
+ ];
+ }
+}
diff --git a/src/Identity/Extension/ExtensionBuilderInterface.php b/src/Identity/Extension/ExtensionBuilderInterface.php
new file mode 100644
index 00000000..1ab39bd8
--- /dev/null
+++ b/src/Identity/Extension/ExtensionBuilderInterface.php
@@ -0,0 +1,8 @@
+latitude = $latitude;
+
+ Validation::withinRange($longitude, -180, 180, 'longitude');
+ $this->longitude = $longitude;
+
+ Validation::notLessThan($radius, 0, 'radius');
+ $this->radius = $radius;
+
+ Validation::notLessThan($maxUncertainty, 0, 'maxUncertainty');
+ $this->maxUncertainty = $maxUncertainty;
+ }
+
+ /**
+ * @inheritDoc
+ *
+ * @return stdClass
+ */
+ public function jsonSerialize(): stdClass
+ {
+ return (object)[
+ 'expected_device_location' => [
+ 'latitude' => $this->latitude,
+ 'longitude' => $this->longitude,
+ 'radius' => $this->radius,
+ 'max_uncertainty_radius' => $this->maxUncertainty,
+ ]
+ ];
+ }
+}
diff --git a/src/Identity/Extension/LocationConstraintExtensionBuilder.php b/src/Identity/Extension/LocationConstraintExtensionBuilder.php
new file mode 100644
index 00000000..556cd19d
--- /dev/null
+++ b/src/Identity/Extension/LocationConstraintExtensionBuilder.php
@@ -0,0 +1,82 @@
+latitude = $latitude;
+ return $this;
+ }
+
+ /**
+ * Allows you to specify the Longitude of the user's expected location
+ */
+ public function withLongitude(float $longitude): self
+ {
+ $this->longitude = $longitude;
+ return $this;
+ }
+
+ /**
+ * Radius of the circle, centred on the specified location coordinates, where the device is
+ * allowed to perform the share.
+ *
+ * If not provided, a default value of 150m will be used.
+ *
+ * @param float $radius
+ * The allowable distance, in metres, from the given lat/long location
+ */
+ public function withRadius(float $radius): self
+ {
+ $this->radius = $radius;
+ return $this;
+ }
+
+ /**
+ * Maximum acceptable distance, in metres, of the area of uncertainty associated with the device
+ * location coordinates.
+ *
+ * If not provided, a default value of 150m will be used.
+ *
+ * @param float $maxUncertainty
+ * Maximum allowed measurement uncertainty, in metres
+ *
+ * @return $this
+ */
+ public function withMaxUncertainty(float $maxUncertainty): self
+ {
+ $this->maxUncertainty = $maxUncertainty;
+
+ return $this;
+ }
+
+ public function build(): Extension
+ {
+ $content = new LocationConstraintContent(
+ $this->latitude,
+ $this->longitude,
+ $this->radius,
+ $this->maxUncertainty
+ );
+
+ return new Extension(self::LOCATION_CONSTRAINT, $content);
+ }
+}
diff --git a/src/Identity/Extension/ThirdPartyAttributeContent.php b/src/Identity/Extension/ThirdPartyAttributeContent.php
new file mode 100644
index 00000000..0eeec95a
--- /dev/null
+++ b/src/Identity/Extension/ThirdPartyAttributeContent.php
@@ -0,0 +1,38 @@
+expiryDate = $expiryDate;
+
+ Validation::isArrayOfType($definitions, [AttributeDefinition::class], 'definitions');
+ $this->definitions = $definitions;
+ }
+
+ public function jsonSerialize(): stdClass
+ {
+ return (object)[
+ 'expiry_date' => $this->expiryDate
+ ->setTimezone(new \DateTimeZone('UTC'))
+ ->format(\DateTime::RFC3339_EXTENDED),
+ 'definitions' => $this->definitions,
+ ];
+ }
+}
diff --git a/src/Identity/Extension/ThirdPartyAttributeExtensionBuilder.php b/src/Identity/Extension/ThirdPartyAttributeExtensionBuilder.php
new file mode 100644
index 00000000..132fa720
--- /dev/null
+++ b/src/Identity/Extension/ThirdPartyAttributeExtensionBuilder.php
@@ -0,0 +1,66 @@
+expiryDate = $expiryDate;
+
+ return $this;
+ }
+
+ public function withDefinition(string $definition): self
+ {
+ $this->definitions[] = new AttributeDefinition($definition);
+
+ return $this;
+ }
+
+ /**
+ * @param string[] $definitions
+ */
+ public function withDefinitions(array $definitions): self
+ {
+ Validation::isArrayOfStrings($definitions, 'definitions');
+ $this->definitions = array_map(
+ function ($definition): AttributeDefinition {
+ return new AttributeDefinition($definition);
+ },
+ $definitions
+ );
+
+ return $this;
+ }
+
+ public function build(): Extension
+ {
+ return new Extension(
+ self::THIRD_PARTY_ATTRIBUTE,
+ new ThirdPartyAttributeContent(
+ $this->expiryDate,
+ $this->definitions
+ )
+ );
+ }
+}
diff --git a/src/Identity/Extension/TransactionalFlowExtensionBuilder.php b/src/Identity/Extension/TransactionalFlowExtensionBuilder.php
new file mode 100644
index 00000000..22eb5821
--- /dev/null
+++ b/src/Identity/Extension/TransactionalFlowExtensionBuilder.php
@@ -0,0 +1,35 @@
+content = $content;
+
+ return $this;
+ }
+
+ /**
+ * @return Extension with TRANSACTIONAL_FLOW type
+ */
+ public function build(): Extension
+ {
+ return new Extension(static::TYPE, $this->content);
+ }
+}
diff --git a/src/Identity/Policy/Policy.php b/src/Identity/Policy/Policy.php
new file mode 100644
index 00000000..62ade060
--- /dev/null
+++ b/src/Identity/Policy/Policy.php
@@ -0,0 +1,75 @@
+wantedAttributes = $wantedAttributes;
+
+ Validation::isArrayOfIntegers($wantedAuthTypes, 'wantedAuthTypes');
+ $this->wantedAuthTypes = $wantedAuthTypes;
+
+ $this->wantedRememberMe = $wantedRememberMe;
+ $this->wantedRememberMeOptional = $wantedRememberMeOptional;
+ $this->identityProfileRequirements = $identityProfileRequirements;
+ }
+
+
+ public function jsonSerialize(): stdClass
+ {
+ return (object)[
+ 'wanted' => $this->wantedAttributes,
+ 'wanted_auth_types' => $this->wantedAuthTypes,
+ 'wanted_remember_me' => $this->wantedRememberMe,
+ 'wanted_remember_me_optional' => $this->wantedRememberMeOptional,
+ 'identity_profile_requirements' => $this->identityProfileRequirements,
+ ];
+ }
+
+ /**
+ * IdentityProfileRequirements requested in the policy
+ *
+ * @return object|null
+ */
+ public function getIdentityProfileRequirements()
+ {
+ return $this->identityProfileRequirements;
+ }
+}
diff --git a/src/Identity/Policy/PolicyBuilder.php b/src/Identity/Policy/PolicyBuilder.php
new file mode 100644
index 00000000..e8a8b190
--- /dev/null
+++ b/src/Identity/Policy/PolicyBuilder.php
@@ -0,0 +1,332 @@
+getName();
+
+ if (null !== $wantedAttribute->getDerivation()) {
+ $key = $wantedAttribute->getDerivation();
+ }
+
+ if (null !== $wantedAttribute->getConstraints()) {
+ $key .= '-' . hash('sha256', Json::encode($wantedAttribute->getConstraints()));
+ }
+
+ $this->wantedAttributes[$key] = $wantedAttribute;
+
+ return $this;
+ }
+
+ /**
+ * @param Constraint[]|null $constraints
+ */
+ public function withWantedAttributeByName(
+ string $name,
+ array $constraints = null,
+ bool $acceptSelfAsserted = null
+ ): self {
+ $wantedAttributeBuilder = (new WantedAttributeBuilder())
+ ->withName($name);
+
+ if ($constraints !== null) {
+ $wantedAttributeBuilder->withConstraints($constraints);
+ }
+
+ if ($acceptSelfAsserted !== null) {
+ $wantedAttributeBuilder->withAcceptSelfAsserted($acceptSelfAsserted);
+ }
+
+ return $this->withWantedAttribute($wantedAttributeBuilder->build());
+ }
+
+ /**
+ * @param Constraint[]|null $constraints
+ */
+ public function withFamilyName(array $constraints = null, bool $acceptSelfAsserted = null): self
+ {
+ return $this->withWantedAttributeByName(
+ UserProfile::ATTR_FAMILY_NAME,
+ $constraints,
+ $acceptSelfAsserted
+ );
+ }
+
+ /**
+ * @param Constraint[]|null $constraints
+ */
+ public function withGivenNames(array $constraints = null, bool $acceptSelfAsserted = null): self
+ {
+ return $this->withWantedAttributeByName(
+ UserProfile::ATTR_GIVEN_NAMES,
+ $constraints,
+ $acceptSelfAsserted
+ );
+ }
+
+ /**
+ * @param Constraint[]|null $constraints
+ */
+ public function withFullName(array $constraints = null, bool $acceptSelfAsserted = null): self
+ {
+ return $this->withWantedAttributeByName(
+ UserProfile::ATTR_FULL_NAME,
+ $constraints,
+ $acceptSelfAsserted
+ );
+ }
+
+ /**
+ * @param Constraint[]|null $constraints
+ */
+ public function withDateOfBirth(array $constraints = null, bool $acceptSelfAsserted = null): self
+ {
+ return $this->withWantedAttributeByName(
+ UserProfile::ATTR_DATE_OF_BIRTH,
+ $constraints,
+ $acceptSelfAsserted
+ );
+ }
+
+ /**
+ * @param Constraint[]|null $constraints
+ */
+ public function withAgeOver(int $age, array $constraints = null, bool $acceptSelfAsserted = null): self
+ {
+ return $this->withAgeDerivedAttribute(
+ UserProfile::AGE_OVER . $age,
+ $constraints,
+ $acceptSelfAsserted
+ );
+ }
+
+ /**
+ * @param Constraint[]|null $constraints
+ */
+ public function withAgeUnder(int $age, array $constraints = null, bool $acceptSelfAsserted = null): self
+ {
+ return $this->withAgeDerivedAttribute(
+ UserProfile::AGE_UNDER . $age,
+ $constraints,
+ $acceptSelfAsserted
+ );
+ }
+
+ /**
+ * @param Constraint[]|null $constraints
+ */
+ public function withAgeDerivedAttribute(
+ string $derivation,
+ array $constraints = null,
+ bool $acceptSelfAsserted = null
+ ): self {
+ $wantedAttributeBuilder = (new WantedAttributeBuilder())
+ ->withName(UserProfile::ATTR_DATE_OF_BIRTH)
+ ->withDerivation($derivation);
+
+ if ($constraints !== null) {
+ $wantedAttributeBuilder->withConstraints($constraints);
+ }
+
+ if ($acceptSelfAsserted !== null) {
+ $wantedAttributeBuilder->withAcceptSelfAsserted($acceptSelfAsserted);
+ }
+
+ return $this->withWantedAttribute($wantedAttributeBuilder->build());
+ }
+
+ /**
+ * @param Constraint[]|null $constraints
+ */
+ public function withGender(array $constraints = null, bool $acceptSelfAsserted = null): self
+ {
+ return $this->withWantedAttributeByName(
+ UserProfile::ATTR_GENDER,
+ $constraints,
+ $acceptSelfAsserted
+ );
+ }
+
+ /**
+ * @param Constraint[]|null $constraints
+ */
+ public function withPostalAddress(array $constraints = null, bool $acceptSelfAsserted = null): self
+ {
+ return $this->withWantedAttributeByName(
+ UserProfile::ATTR_POSTAL_ADDRESS,
+ $constraints,
+ $acceptSelfAsserted
+ );
+ }
+
+ /**
+ * @param Constraint[]|null $constraints
+ */
+ public function withStructuredPostalAddress(array $constraints = null, bool $acceptSelfAsserted = null): self
+ {
+ return $this->withWantedAttributeByName(
+ UserProfile::ATTR_STRUCTURED_POSTAL_ADDRESS,
+ $constraints,
+ $acceptSelfAsserted
+ );
+ }
+
+ /**
+ * @param Constraint[]|null $constraints
+ */
+ public function withNationality(array $constraints = null, bool $acceptSelfAsserted = null): self
+ {
+ return $this->withWantedAttributeByName(
+ UserProfile::ATTR_NATIONALITY,
+ $constraints,
+ $acceptSelfAsserted
+ );
+ }
+
+ /**
+ * @param Constraint[]|null $constraints
+ */
+ public function withPhoneNumber(array $constraints = null, bool $acceptSelfAsserted = null): self
+ {
+ return $this->withWantedAttributeByName(
+ UserProfile::ATTR_PHONE_NUMBER,
+ $constraints,
+ $acceptSelfAsserted
+ );
+ }
+
+ /**
+ * @param Constraint[]|null $constraints
+ */
+ public function withSelfie(array $constraints = null, bool $acceptSelfAsserted = null): self
+ {
+ return $this->withWantedAttributeByName(
+ UserProfile::ATTR_SELFIE,
+ $constraints,
+ $acceptSelfAsserted
+ );
+ }
+
+ /**
+ * @param Constraint[]|null $constraints
+ */
+ public function withDocumentDetails(array $constraints = null, bool $acceptSelfAsserted = null): self
+ {
+ return $this->withWantedAttributeByName(
+ UserProfile::ATTR_DOCUMENT_DETAILS,
+ $constraints,
+ $acceptSelfAsserted
+ );
+ }
+
+ /**
+ * @param Constraint[]|null $constraints
+ */
+ public function withDocumentImages(array $constraints = null, bool $acceptSelfAsserted = null): self
+ {
+ return $this->withWantedAttributeByName(
+ UserProfile::ATTR_DOCUMENT_IMAGES,
+ $constraints,
+ $acceptSelfAsserted
+ );
+ }
+
+ /**
+ * @param Constraint[]|null $constraints
+ */
+ public function withEmail(array $constraints = null, bool $acceptSelfAsserted = null): self
+ {
+ return $this->withWantedAttributeByName(
+ UserProfile::ATTR_EMAIL_ADDRESS,
+ $constraints,
+ $acceptSelfAsserted
+ );
+ }
+
+
+ public function withSelfieAuthentication(bool $enabled = true): self
+ {
+ return $this->withWantedAuthType(self::SELFIE_AUTH_TYPE, $enabled);
+ }
+
+
+ public function withPinAuthentication(bool $enabled = true): self
+ {
+ return $this->withWantedAuthType(self::PIN_AUTH_TYPE, $enabled);
+ }
+
+ public function withWantedAuthType(int $wantedAuthType, bool $enabled = true): self
+ {
+ if ($enabled) {
+ $this->wantedAuthTypes[$wantedAuthType] = $wantedAuthType;
+ } else {
+ unset($this->wantedAuthTypes[$wantedAuthType]);
+ }
+
+ return $this;
+ }
+
+
+ public function withWantedRememberMe(bool $wantedRememberMe): self
+ {
+ $this->wantedRememberMe = $wantedRememberMe;
+ return $this;
+ }
+
+ public function withWantedRememberMeOptional(bool $wantedRememberMeOptional): self
+ {
+ $this->wantedRememberMeOptional = $wantedRememberMeOptional;
+ return $this;
+ }
+
+ /**
+ * Use an Identity Profile Requirement object for the share
+ *
+ * @param object $identityProfileRequirements
+ * @return $this
+ */
+ public function withIdentityProfileRequirements($identityProfileRequirements): self
+ {
+ $this->identityProfileRequirements = $identityProfileRequirements;
+ return $this;
+ }
+
+
+ public function build(): Policy
+ {
+ return new Policy(
+ array_values($this->wantedAttributes),
+ array_values($this->wantedAuthTypes),
+ $this->wantedRememberMe,
+ $this->wantedRememberMeOptional,
+ $this->identityProfileRequirements
+ );
+ }
+}
diff --git a/src/Identity/Policy/WantedAnchor.php b/src/Identity/Policy/WantedAnchor.php
new file mode 100644
index 00000000..1714ca8a
--- /dev/null
+++ b/src/Identity/Policy/WantedAnchor.php
@@ -0,0 +1,29 @@
+value = $value;
+ $this->subType = $subType;
+ }
+
+ public function jsonSerialize(): stdClass
+ {
+ return (object)[
+ 'name' => $this->value,
+ 'sub_type' => $this->subType,
+ ];
+ }
+}
diff --git a/src/Identity/Policy/WantedAnchorBuilder.php b/src/Identity/Policy/WantedAnchorBuilder.php
new file mode 100644
index 00000000..9625445f
--- /dev/null
+++ b/src/Identity/Policy/WantedAnchorBuilder.php
@@ -0,0 +1,32 @@
+value = $value;
+ return $this;
+ }
+
+ public function withSubType(string $subType): self
+ {
+ $this->subType = $subType;
+ return $this;
+ }
+
+ public function build(): WantedAnchor
+ {
+ Validation::notNull($this->value, 'value');
+ Validation::notNull($this->subType, 'sub_type');
+
+ return new WantedAnchor($this->value, $this->subType);
+ }
+}
diff --git a/src/Identity/Policy/WantedAttribute.php b/src/Identity/Policy/WantedAttribute.php
new file mode 100644
index 00000000..dc34da79
--- /dev/null
+++ b/src/Identity/Policy/WantedAttribute.php
@@ -0,0 +1,123 @@
+name = $name;
+
+ $this->derivation = $derivation;
+ $this->optional = $optional;
+ $this->acceptSelfAsserted = $acceptSelfAsserted;
+
+ if (null !== $constraints) {
+ Validation::isArrayOfType($constraints, [Constraint::class], 'constraints');
+ $this->constraints = $constraints;
+ }
+ }
+
+ /**
+ * Name identifying the WantedAttribute
+ *
+ * @return string
+ */
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
+ /**
+ * Additional derived criteria.
+ *
+ * @return string
+ */
+ public function getDerivation(): ?string
+ {
+ return $this->derivation;
+ }
+
+ /**
+ * List of constraints to add to an attribute.
+ *
+ * If you do not provide any particular constraints, Yoti will provide you with the
+ * information from the most recently added source.
+ *
+ * @return Constraint[] $constraints
+ */
+ public function getConstraints(): ?array
+ {
+ return $this->constraints;
+ }
+
+ /**
+ * Accept self asserted attributes.
+ *
+ * These are attributes that have been self-declared, and not verified by Yoti.
+ *
+ * @return bool|null
+ */
+ public function getAcceptSelfAsserted(): ?bool
+ {
+ return $this->acceptSelfAsserted;
+ }
+
+ /**
+ * @return bool
+ */
+ public function getOptional(): bool
+ {
+ return $this->optional;
+ }
+
+ public function jsonSerialize(): stdClass
+ {
+ $data = new stdClass();
+ $data->name = $this->getName();
+ $data->optional = $this->getOptional();
+
+ if (null !== $this->getDerivation()) {
+ $data->derivation = $this->getDerivation();
+ }
+
+ if (null !== $this->getConstraints()) {
+ $data->constraints = $this->getConstraints();
+ }
+
+ if (null !== $this->getAcceptSelfAsserted()) {
+ $data->accept_self_asserted = $this->getAcceptSelfAsserted();
+ }
+
+ return $data;
+ }
+}
diff --git a/src/Identity/Policy/WantedAttributeBuilder.php b/src/Identity/Policy/WantedAttributeBuilder.php
new file mode 100644
index 00000000..c2a53f1f
--- /dev/null
+++ b/src/Identity/Policy/WantedAttributeBuilder.php
@@ -0,0 +1,77 @@
+name = $name;
+
+ return $this;
+ }
+
+ public function withDerivation(string $derivation): self
+ {
+ $this->derivation = $derivation;
+
+ return $this;
+ }
+
+ public function withOptional(bool $optional): self
+ {
+ $this->optional = $optional;
+
+ return $this;
+ }
+
+ public function withAcceptSelfAsserted(bool $acceptSelfAsserted): self
+ {
+ $this->acceptSelfAsserted = $acceptSelfAsserted;
+
+ return $this;
+ }
+
+ /**
+ * @param Constraint[] $constraints
+ */
+ public function withConstraints(array $constraints): self
+ {
+ $this->constraints = $constraints;
+
+ return $this;
+ }
+
+ public function withConstraint(Constraint $constraint): self
+ {
+ $this->constraints[] = $constraint;
+
+ return $this;
+ }
+
+ public function build(): WantedAttribute
+ {
+ return new WantedAttribute(
+ $this->name,
+ $this->derivation,
+ $this->optional,
+ $this->acceptSelfAsserted,
+ $this->constraints,
+ );
+ }
+}
diff --git a/src/Identity/Reader/AttributeListReader.php b/src/Identity/Reader/AttributeListReader.php
new file mode 100644
index 00000000..641029b2
--- /dev/null
+++ b/src/Identity/Reader/AttributeListReader.php
@@ -0,0 +1,7 @@
+id = $id;
+ $this->sessionId = $sessionId;
+ $this->timestamp = $timestamp;
+ $this->applicationContent = $applicationContent;
+ $this->userContent = $userContent;
+ $this->rememberMeId = $rememberMeId;
+ $this->parentRememberMeId = $parentRememberMeId;
+ $this->error = $error;
+ }
+
+ public function getId(): string
+ {
+ return $this->id;
+ }
+
+ public function getSessionId(): string
+ {
+ return $this->sessionId;
+ }
+
+ public function getTimestamp(): \DateTime
+ {
+ return $this->timestamp;
+ }
+
+ public function getProfile(): ?UserProfile
+ {
+ return $this->userContent->getProfile();
+ }
+
+ public function getExtraData(): ?ExtraData
+ {
+ return $this->userContent->getExtraData();
+ }
+
+ public function getApplicationContent(): ApplicationContent
+ {
+ return $this->applicationContent;
+ }
+
+ public function getUserContent(): UserContent
+ {
+ return $this->userContent;
+ }
+
+ public function getRememberMeId(): ?string
+ {
+ return $this->rememberMeId;
+ }
+
+ public function getParentRememberMeId(): ?string
+ {
+ return $this->parentRememberMeId;
+ }
+
+ public function getError(): ?string
+ {
+ return $this->error;
+ }
+}
diff --git a/src/Identity/ReceiptBuilder.php b/src/Identity/ReceiptBuilder.php
new file mode 100644
index 00000000..9bed933f
--- /dev/null
+++ b/src/Identity/ReceiptBuilder.php
@@ -0,0 +1,98 @@
+id = $id;
+
+ return $this;
+ }
+
+ public function withSessionId(string $sessionId): self
+ {
+ $this->sessionId = $sessionId;
+
+ return $this;
+ }
+
+ public function withRememberMeId(string $rememberMeId = null): self
+ {
+ $this->rememberMeId = $rememberMeId;
+
+ return $this;
+ }
+
+ public function withParentRememberMeId(string $parentRememberMeId = null): self
+ {
+ $this->parentRememberMeId = $parentRememberMeId;
+
+ return $this;
+ }
+
+ public function withTimestamp(\DateTime $timestamp): self
+ {
+ $this->timestamp = $timestamp;
+
+ return $this;
+ }
+
+ public function withApplicationContent(ApplicationProfile $profile, ExtraData $extraData = null): self
+ {
+ $this->applicationContent = new ApplicationContent($profile, $extraData);
+
+ return $this;
+ }
+
+ public function withUserContent(UserProfile $profile = null, ExtraData $extraData = null): self
+ {
+ $this->userContent = new UserContent($profile, $extraData);
+
+ return $this;
+ }
+
+ public function withError(string $error = null): self
+ {
+ $this->error = $error;
+
+ return $this;
+ }
+
+ public function build(): Receipt
+ {
+ return new Receipt(
+ $this->id,
+ $this->sessionId,
+ $this->timestamp,
+ $this->applicationContent,
+ $this->userContent,
+ $this->rememberMeId,
+ $this->parentRememberMeId,
+ $this->error,
+ );
+ }
+}
diff --git a/src/Identity/ReceiptItemKey.php b/src/Identity/ReceiptItemKey.php
new file mode 100644
index 00000000..1c8b049a
--- /dev/null
+++ b/src/Identity/ReceiptItemKey.php
@@ -0,0 +1,46 @@
+ $sessionData
+ */
+ public function __construct(array $sessionData)
+ {
+ $this->id = $sessionData['id'];
+ $this->iv = $sessionData['iv'];
+ $this->value = $sessionData['value'];
+ }
+
+ /**
+ * @return string
+ */
+ public function getId(): string
+ {
+ return $this->id;
+ }
+
+ /**
+ * @return string
+ */
+ public function getIv(): string
+ {
+ return $this->iv;
+ }
+
+ /**
+ * @return string
+ */
+ public function getValue(): string
+ {
+ return $this->value;
+ }
+}
diff --git a/src/Identity/ReceiptParser.php b/src/Identity/ReceiptParser.php
new file mode 100644
index 00000000..5847a438
--- /dev/null
+++ b/src/Identity/ReceiptParser.php
@@ -0,0 +1,165 @@
+logger = $logger ?? new Logger();
+ }
+
+ public function createSuccess(
+ WrappedReceipt $wrappedReceipt,
+ ReceiptItemKey $wrappedItemKey,
+ PemFile $pemFile
+ ): Receipt {
+ $receiptKey = $this->decryptReceiptKey($wrappedReceipt->getWrappedKey(), $wrappedItemKey, $pemFile);
+
+ $applicationProfile = new ApplicationProfile(
+ AttributeListConverter::convertToYotiAttributesList($this->parseProfileAttr(
+ $wrappedReceipt->getProfile(),
+ $receiptKey,
+ ))
+ );
+
+ $extraData = null !== $wrappedReceipt->getExtraData() ?
+ $this->parseExtraData($wrappedReceipt->getExtraData(), $receiptKey) :
+ null;
+
+ $userProfile = null !== $wrappedReceipt->getOtherPartyProfile() ? new UserProfile(
+ AttributeListConverter::convertToYotiAttributesList(
+ $this->parseProfileAttr(
+ $wrappedReceipt->getOtherPartyProfile(),
+ $receiptKey,
+ )
+ )
+ ) : null;
+
+ $otherExtraData = null !== $wrappedReceipt->getOtherPartyExtraData() ?
+ $this->parseExtraData($wrappedReceipt->getOtherPartyExtraData(), $receiptKey) :
+ null;
+
+
+ $receipt = (new ReceiptBuilder())
+ ->withId($wrappedReceipt->getId())
+ ->withSessionId($wrappedReceipt->getSessionId())
+ ->withTimestamp($wrappedReceipt->getTimestamp())
+ ->withApplicationContent(
+ $applicationProfile,
+ $extraData
+ )
+ ->withUserContent(
+ $userProfile,
+ $otherExtraData
+ );
+
+ if (null !== $wrappedReceipt->getRememberMeId()) {
+ $receipt->withRememberMeId($wrappedReceipt->getRememberMeId());
+ }
+
+ if (null !== $wrappedReceipt->getParentRememberMeId()) {
+ $receipt->withParentRememberMeId($wrappedReceipt->getParentRememberMeId());
+ }
+
+ return $receipt->build();
+ }
+
+ public function createFailure(WrappedReceipt $wrappedReceipt): Receipt
+ {
+ return (new ReceiptBuilder())
+ ->withId($wrappedReceipt->getId())
+ ->withSessionId($wrappedReceipt->getSessionId())
+ ->withTimestamp($wrappedReceipt->getTimestamp())
+ ->withError($wrappedReceipt->getError())
+ ->build();
+ }
+
+ private function decryptReceiptKey(string $wrappedKey, ReceiptItemKey $wrappedItemKey, PemFile $pemFile): string
+ {
+ // Convert 'iv' and 'value' from base64 to binary
+ $iv = (string)base64_decode($wrappedItemKey->getIv(), true);
+ $encryptedItemKey = (string)base64_decode($wrappedItemKey->getValue(), true);
+
+ // Decrypt the 'value' field (encrypted item key) using the private key
+ $unwrappedKey = '';
+ if (
+ !openssl_private_decrypt(
+ $encryptedItemKey,
+ $unwrappedKey,
+ (string)$pemFile
+ )
+ ) {
+ throw new EncryptedDataException('Could not decrypt the item key');
+ }
+
+ // Check that 'wrappedKey' is a base64-encoded string
+ $wrappedKey = base64_decode($wrappedKey, true);
+ if ($wrappedKey === false) {
+ throw new EncryptedDataException('wrappedKey is not a valid base64-encoded string');
+ }
+
+ // Decompose the 'wrappedKey' into 'cipherText' and 'tag'
+ $cipherText = substr($wrappedKey, 0, -16);
+ $tag = substr($wrappedKey, -16);
+
+ // Decrypt the 'cipherText' using the 'iv' and the decrypted item key
+ $receiptKey = openssl_decrypt(
+ $cipherText,
+ 'aes-256-gcm',
+ $unwrappedKey,
+ OPENSSL_RAW_DATA,
+ $iv,
+ $tag
+ );
+ if ($receiptKey === false) {
+ throw new EncryptedDataException('Could not decrypt the receipt key');
+ }
+
+ return $receiptKey;
+ }
+
+ private function parseProfileAttr(string $profile, string $wrappedKey): AttributeList
+ {
+ $attributeList = new AttributeList();
+
+ $decryptedData = IdentityEncryptedData::decrypt(
+ $profile,
+ $wrappedKey
+ );
+
+ $attributeList->mergeFromString($decryptedData);
+
+ return $attributeList;
+ }
+
+ private function parseExtraData(string $extraData, string $wrappedKey): ExtraData
+ {
+ $decryptAttribute = IdentityEncryptedData::decrypt(
+ $extraData,
+ $wrappedKey
+ );
+
+ return ExtraDataConverter::convertValue(
+ $decryptAttribute,
+ $this->logger
+ );
+ }
+}
diff --git a/src/Identity/ShareSessionCreated.php b/src/Identity/ShareSessionCreated.php
new file mode 100644
index 00000000..4bb0f924
--- /dev/null
+++ b/src/Identity/ShareSessionCreated.php
@@ -0,0 +1,71 @@
+id = $sessionData['id'];
+ }
+
+ if (isset($sessionData['status'])) {
+ Validation::isString($sessionData['status'], 'status');
+ $this->status = $sessionData['status'];
+ }
+
+ if (isset($sessionData['expiry'])) {
+ Validation::isString($sessionData['expiry'], 'expiry');
+ $this->expiry = $sessionData['expiry'];
+ }
+ }
+
+ /**
+ * @return string
+ */
+ public function getId(): string
+ {
+ return $this->id;
+ }
+
+ /**
+ * @return string
+ */
+ public function getStatus(): string
+ {
+ return $this->status;
+ }
+
+ /**
+ * @return string
+ */
+ public function getExpiry(): string
+ {
+ return $this->expiry;
+ }
+
+ public function jsonSerialize(): object
+ {
+ return (object)[
+ 'id' => $this->getId(),
+ 'status' => $this->getStatus(),
+ 'expiry' => $this->getExpiry(),
+ ];
+ }
+}
diff --git a/src/Identity/ShareSessionCreatedQrCode.php b/src/Identity/ShareSessionCreatedQrCode.php
new file mode 100644
index 00000000..901074b0
--- /dev/null
+++ b/src/Identity/ShareSessionCreatedQrCode.php
@@ -0,0 +1,48 @@
+id = $sessionData['id'];
+ }
+
+ if (isset($sessionData['uri'])) {
+ $this->uri = $sessionData['uri'];
+ }
+ }
+
+ public function jsonSerialize(): object
+ {
+ return (object)[
+ 'id' => $this->id,
+ 'uri' => $this->uri
+ ];
+ }
+
+ /**
+ * @return string
+ */
+ public function getId(): string
+ {
+ return $this->id;
+ }
+
+ /**
+ * @return string
+ */
+ public function getUri(): string
+ {
+ return $this->uri;
+ }
+}
diff --git a/src/Identity/ShareSessionFetched.php b/src/Identity/ShareSessionFetched.php
new file mode 100644
index 00000000..ac171a2f
--- /dev/null
+++ b/src/Identity/ShareSessionFetched.php
@@ -0,0 +1,117 @@
+ $sessionData
+ */
+ public function __construct(array $sessionData)
+ {
+ if (isset($sessionData['id'])) {
+ $this->id = $sessionData['id'];
+ }
+ if (isset($sessionData['status'])) {
+ $this->status = $sessionData['status'];
+ }
+ if (isset($sessionData['expiry'])) {
+ $this->expiry = $sessionData['expiry'];
+ }
+ if (isset($sessionData['created'])) {
+ $this->created = $sessionData['created'];
+ }
+ if (isset($sessionData['updated'])) {
+ $this->updated = $sessionData['updated'];
+ }
+ if (isset($sessionData['qrCode'])) {
+ $this->qrCodeId = $sessionData['qrCode']['id'];
+ }
+ if (isset($sessionData['receipt'])) {
+ $this->receiptId = $sessionData['receipt']['id'];
+ }
+ }
+
+ public function jsonSerialize(): object
+ {
+ return (object)[
+ 'id' => $this->id,
+ 'status' => $this->status,
+ 'expiry' => $this->expiry,
+ 'created' => $this->created,
+ 'updated' => $this->updated,
+ 'qrCodeId' => $this->qrCodeId,
+ 'receiptId' => $this->receiptId,
+ ];
+ }
+
+ /**
+ * @return string
+ */
+ public function getId(): string
+ {
+ return $this->id;
+ }
+
+ /**
+ * @return string
+ */
+ public function getStatus(): string
+ {
+ return $this->status;
+ }
+
+ /**
+ * @return string
+ */
+ public function getCreated(): string
+ {
+ return $this->created;
+ }
+
+ /**
+ * @return string
+ */
+ public function getUpdated(): string
+ {
+ return $this->updated;
+ }
+
+ /**
+ * @return string
+ */
+ public function getExpiry(): string
+ {
+ return $this->expiry;
+ }
+
+ /**
+ * @return string
+ */
+ public function getQrCodeId(): string
+ {
+ return $this->qrCodeId;
+ }
+
+ /**
+ * @return string
+ */
+ public function getReceiptId(): string
+ {
+ return $this->receiptId;
+ }
+}
diff --git a/src/Identity/ShareSessionFetchedQrCode.php b/src/Identity/ShareSessionFetchedQrCode.php
new file mode 100644
index 00000000..1f8aaabf
--- /dev/null
+++ b/src/Identity/ShareSessionFetchedQrCode.php
@@ -0,0 +1,110 @@
+ $sessionData
+ */
+ public function __construct(array $sessionData)
+ {
+ if (isset($sessionData['id'])) {
+ $this->id = $sessionData['id'];
+ }
+ if (isset($sessionData['expiry'])) {
+ $this->expiry = $sessionData['expiry'];
+ }
+ if (isset($sessionData['policy'])) {
+ $this->policy = $sessionData['policy'];
+ }
+ if (isset($sessionData['extensions'])) {
+ foreach ($sessionData['extensions'] as $extension) {
+ $this->extensions[] = new Extension($extension['type'], $extension['content']);
+ }
+ }
+ if (isset($sessionData['session'])) {
+ $this->session = new ShareSessionCreated($sessionData['session']);
+ }
+ if (isset($sessionData['redirectUri'])) {
+ $this->redirectUri = $sessionData['redirectUri'];
+ }
+ }
+
+ public function jsonSerialize(): object
+ {
+ return (object)[
+ 'id' => $this->id,
+ 'expiry' => $this->expiry,
+ 'policy' => $this->policy,
+ 'extensions' => $this->extensions,
+ 'session' => $this->session,
+ 'redirectUri' => $this->redirectUri,
+ ];
+ }
+
+ /**
+ * @return string
+ */
+ public function getId(): string
+ {
+ return $this->id;
+ }
+
+ /**
+ * @return string
+ */
+ public function getExpiry(): string
+ {
+ return $this->expiry;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPolicy(): string
+ {
+ return $this->policy;
+ }
+
+ /**
+ * @return Extension[]
+ */
+ public function getExtensions(): array
+ {
+ return $this->extensions;
+ }
+
+ /**
+ * @return ShareSessionCreated
+ */
+ public function getSession(): ShareSessionCreated
+ {
+ return $this->session;
+ }
+
+ /**
+ * @return string
+ */
+ public function getRedirectUri(): string
+ {
+ return $this->redirectUri;
+ }
+}
diff --git a/src/Identity/ShareSessionNotification.php b/src/Identity/ShareSessionNotification.php
new file mode 100644
index 00000000..5a6fc24f
--- /dev/null
+++ b/src/Identity/ShareSessionNotification.php
@@ -0,0 +1,70 @@
+
+ */
+ private array $headers;
+
+ /**
+ * @param string[] $headers
+ */
+ public function __construct(string $url, string $method, bool $verifyTls, array $headers)
+ {
+ $this->url = $url;
+ $this->method = $method;
+ $this->verifyTls = $verifyTls;
+ $this->headers = $headers;
+ }
+
+ public function jsonSerialize(): object
+ {
+ return (object)[
+ 'url' => $this->getUrl(),
+ 'method' => $this->getMethod(),
+ 'verifyTls' => $this->isVerifyTls(),
+ 'headers' => $this->getHeaders(),
+ ];
+ }
+
+ /**
+ * @return string
+ */
+ public function getUrl(): string
+ {
+ return $this->url;
+ }
+
+ /**
+ * @return string
+ */
+ public function getMethod(): string
+ {
+ return $this->method;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isVerifyTls(): bool
+ {
+ return $this->verifyTls;
+ }
+
+ /**
+ * @return string[]
+ */
+ public function getHeaders(): array
+ {
+ return $this->headers;
+ }
+}
diff --git a/src/Identity/ShareSessionNotificationBuilder.php b/src/Identity/ShareSessionNotificationBuilder.php
new file mode 100644
index 00000000..39d7aba3
--- /dev/null
+++ b/src/Identity/ShareSessionNotificationBuilder.php
@@ -0,0 +1,65 @@
+
+ */
+ private array $headers;
+
+ public function withUrl(string $url): self
+ {
+ $this->url = $url;
+
+ return $this;
+ }
+
+ public function withMethod(string $method = 'POST'): self
+ {
+ $this->method = $method;
+
+ return $this;
+ }
+
+ public function withVerifyTls(bool $verifyTls = true): self
+ {
+ $this->verifyTls = $verifyTls;
+
+ return $this;
+ }
+
+ /**
+ * @param string[] $headers
+ */
+ public function withHeaders(array $headers): self
+ {
+ $this->headers = $headers;
+
+ return $this;
+ }
+
+ public function withHeader(string $key, string $header): self
+ {
+ $this->headers[$key] = $header;
+
+ return $this;
+ }
+
+ public function build(): ShareSessionNotification
+ {
+ return new ShareSessionNotification(
+ $this->url,
+ $this->method,
+ $this->verifyTls,
+ $this->headers
+ );
+ }
+}
diff --git a/src/Identity/ShareSessionRequest.php b/src/Identity/ShareSessionRequest.php
new file mode 100644
index 00000000..94e9cadb
--- /dev/null
+++ b/src/Identity/ShareSessionRequest.php
@@ -0,0 +1,110 @@
+|null
+ */
+ private ?array $subject;
+
+ private Policy $policy;
+
+ /**
+ * @var Extension[]|null
+ */
+ private ?array $extensions = null;
+
+ private string $redirectUri;
+
+ private ?ShareSessionNotification $notification;
+
+ /**
+ * @param array|null $subject
+ * @param Policy $policy
+ * @param Extension[]|null $extensions
+ * @param string $redirectUri
+ * @param ShareSessionNotification|null $notification
+ */
+ public function __construct(
+ Policy $policy,
+ string $redirectUri,
+ ?array $extensions = null,
+ ?array $subject = null,
+ ?ShareSessionNotification $notification = null
+ ) {
+ $this->policy = $policy;
+ $this->redirectUri = $redirectUri;
+
+ if (null !== $extensions) {
+ Validation::isArrayOfType($extensions, [Extension::class], 'extensions');
+ $this->extensions = $extensions;
+ }
+
+ $this->subject = $subject;
+ $this->notification = $notification;
+ }
+
+ /**
+ * @return array|null
+ */
+ public function getSubject(): ?array
+ {
+ return $this->subject;
+ }
+
+ /**
+ * @return Policy
+ */
+ public function getPolicy(): Policy
+ {
+ return $this->policy;
+ }
+
+ /**
+ * @return Extension[]|null
+ */
+ public function getExtensions(): ?array
+ {
+ return $this->extensions;
+ }
+
+ /**
+ * @return string
+ */
+ public function getRedirectUri(): string
+ {
+ return $this->redirectUri;
+ }
+
+ /**
+ * @return ShareSessionNotification|null
+ */
+ public function getNotification(): ?ShareSessionNotification
+ {
+ return $this->notification;
+ }
+
+ public function jsonSerialize(): \stdClass
+ {
+ $data = new \stdClass();
+ $data->policy = $this->getPolicy();
+ $data->redirectUri = $this->getRedirectUri();
+ if (null !== $this->getSubject()) {
+ $data->subject = $this->getSubject();
+ }
+ if (null !== $this->getExtensions()) {
+ $data->extensions = $this->getExtensions();
+ }
+ if (null !== $this->getNotification()) {
+ $data->notification = $this->getNotification();
+ }
+
+ return $data;
+ }
+}
diff --git a/src/Identity/ShareSessionRequestBuilder.php b/src/Identity/ShareSessionRequestBuilder.php
new file mode 100644
index 00000000..583d06fe
--- /dev/null
+++ b/src/Identity/ShareSessionRequestBuilder.php
@@ -0,0 +1,84 @@
+
+ */
+ private ?array $subject = null;
+
+ private Policy $policy;
+
+ /**
+ * @var Extension[]
+ */
+ private ?array $extensions = null;
+
+ private string $redirectUri;
+
+ private ?ShareSessionNotification $notification = null;
+
+ /**
+ * @param array $subject
+ */
+ public function withSubject(array $subject): self
+ {
+ $this->subject = $subject;
+
+ return $this;
+ }
+
+ public function withPolicy(Policy $policy): self
+ {
+ $this->policy = $policy;
+
+ return $this;
+ }
+
+ /**
+ * @param Extension[] $extensions
+ */
+ public function withExtensions(array $extensions): self
+ {
+ $this->extensions = $extensions;
+
+ return $this;
+ }
+
+ public function withExtension(Extension $extension): self
+ {
+ $this->extensions[] = $extension;
+
+ return $this;
+ }
+
+ public function withRedirectUri(string $redirectUri): self
+ {
+ $this->redirectUri = $redirectUri;
+
+ return $this;
+ }
+
+ public function withNotification(ShareSessionNotification $notification): ShareSessionRequestBuilder
+ {
+ $this->notification = $notification;
+
+ return $this;
+ }
+
+ public function build(): ShareSessionRequest
+ {
+ return new ShareSessionRequest(
+ $this->policy,
+ $this->redirectUri,
+ $this->extensions,
+ $this->subject,
+ $this->notification
+ );
+ }
+}
diff --git a/src/Identity/Util/IdentityEncryptedData.php b/src/Identity/Util/IdentityEncryptedData.php
new file mode 100644
index 00000000..d960449b
--- /dev/null
+++ b/src/Identity/Util/IdentityEncryptedData.php
@@ -0,0 +1,41 @@
+mergeFromString($data);
+
+ $decrypted = openssl_decrypt(
+ $encryptedDataProto->getCipherText(),
+ 'aes-256-cbc',
+ $unwrappedKey,
+ OPENSSL_RAW_DATA,
+ $encryptedDataProto->getIv()
+ );
+
+ if ($decrypted !== false) {
+ return $decrypted;
+ }
+
+ throw new EncryptedDataException('Could not decrypt data');
+ }
+}
diff --git a/src/Identity/WrappedReceipt.php b/src/Identity/WrappedReceipt.php
new file mode 100644
index 00000000..8d7c04c4
--- /dev/null
+++ b/src/Identity/WrappedReceipt.php
@@ -0,0 +1,143 @@
+ $sessionData
+ */
+ public function __construct(array $sessionData)
+ {
+ $this->id = $sessionData['id'];
+ $this->sessionId = $sessionData['sessionId'];
+ $this->timestamp = DateTime::stringToDateTime($sessionData['timestamp']);
+ $this->wrappedItemKeyId = $sessionData['wrappedItemKeyId'];
+ $this->wrappedKey = $sessionData['wrappedKey'];
+
+ if (isset($sessionData['content'])) {
+ $this->content = new Content(
+ $sessionData['content']['profile'] ?? null,
+ $sessionData['content']['extraData'] ?? null
+ );
+ }
+ if (isset($sessionData['otherPartyContent'])) {
+ $this->otherPartyContent = new Content(
+ $sessionData['otherPartyContent']['profile'] ?? null,
+ $sessionData['otherPartyContent']['extraData'] ?? null
+ );
+ }
+
+ if (isset($sessionData['rememberMeId'])) {
+ $this->rememberMeId = $this->base64decode($sessionData['rememberMeId']);
+ }
+ if (isset($sessionData['parentRememberMeId'])) {
+ $this->parentRememberMeId = $this->base64decode($sessionData['parentRememberMeId']);
+ }
+ if (isset($sessionData['error'])) {
+ $this->error = $sessionData['error'];
+ }
+ }
+
+ public function getId(): string
+ {
+ return $this->id;
+ }
+
+ public function getSessionId(): string
+ {
+ return $this->sessionId;
+ }
+
+ public function getTimestamp(): \DateTime
+ {
+ return $this->timestamp;
+ }
+
+ /**
+ * @return string
+ * @throws DigitalIdentityException
+ */
+ public function getProfile(): string
+ {
+ if (null === $this->content->getProfile()) {
+ throw new DigitalIdentityException('Application profile should not be missing');
+ }
+
+ return $this->content->getProfile();
+ }
+
+ public function getExtraData(): ?string
+ {
+ return $this->content->getExtraData();
+ }
+
+ public function getOtherPartyProfile(): ?string
+ {
+ return $this->otherPartyContent->getProfile();
+ }
+
+ public function getOtherPartyExtraData(): ?string
+ {
+ return $this->otherPartyContent->getExtraData();
+ }
+
+ public function getWrappedItemKeyId(): string
+ {
+ return $this->wrappedItemKeyId;
+ }
+
+ public function getWrappedKey(): string
+ {
+ return $this->wrappedKey;
+ }
+
+ public function getRememberMeId(): ?string
+ {
+ return $this->rememberMeId;
+ }
+
+ public function getParentRememberMeId(): ?string
+ {
+ return $this->parentRememberMeId;
+ }
+
+ public function getError(): ?string
+ {
+ return $this->error;
+ }
+
+ private function base64decode(string $encoded): string
+ {
+ $decoded = base64_decode($encoded, true);
+ if ($decoded === false) {
+ throw new EncryptedDataException('Could not decode data');
+ }
+ return $decoded;
+ }
+}
diff --git a/src/Profile/BaseProfile.php b/src/Profile/BaseProfile.php
index 0b91a479..b09200e4 100644
--- a/src/Profile/BaseProfile.php
+++ b/src/Profile/BaseProfile.php
@@ -9,19 +9,19 @@
class BaseProfile
{
/**
- * @var \Yoti\Profile\Attribute[]
+ * @var Attribute[]
*/
private $attributesList;
/**
- * @var \Yoti\Profile\Attribute[][] keyed by attribute name.
+ * @var Attribute[][] keyed by attribute name.
*/
private $attributesMap;
/**
* Profile constructor.
*
- * @param \Yoti\Profile\Attribute[] $attributesList
+ * @param Attribute[] $attributesList
*/
public function __construct(array $attributesList)
{
@@ -48,7 +48,7 @@ function ($carry, Attribute $attr) {
/**
* @param string $attributeName.
*
- * @return \Yoti\Profile\Attribute[]
+ * @return Attribute[]
*/
public function getAttributesByName(string $attributeName): array
{
@@ -75,7 +75,7 @@ public function getAttributeById(string $attributeId): ?Attribute
/**
* @param string $attributeName.
*
- * @return \Yoti\Profile\Attribute|null
+ * @return Attribute|null
*/
public function getProfileAttribute(string $attributeName): ?Attribute
{
@@ -86,7 +86,7 @@ public function getProfileAttribute(string $attributeName): ?Attribute
/**
* Get all attributes.
*
- * @return \Yoti\Profile\Attribute[]
+ * @return Attribute[]
*/
public function getAttributesList(): array
{
diff --git a/src/Profile/Service.php b/src/Profile/Service.php
index 2e36b911..fbd215aa 100644
--- a/src/Profile/Service.php
+++ b/src/Profile/Service.php
@@ -63,7 +63,6 @@ public function getActivityDetails(string $encryptedConnectToken): ActivityDetai
{
// Decrypt connect token
$token = $this->decryptConnectToken($encryptedConnectToken);
-
// Request endpoint
$response = (new RequestBuilder($this->config))
->withBaseUrl($this->config->getApiUrl() ?? Constants::API_URL)
@@ -76,6 +75,7 @@ public function getActivityDetails(string $encryptedConnectToken): ActivityDetai
->execute();
$httpCode = $response->getStatusCode();
+
if ($httpCode < 200 || $httpCode > 299) {
throw new ActivityDetailsException("Server responded with {$httpCode}", $response);
}
diff --git a/src/Profile/Util/Attribute/AnchorConverter.php b/src/Profile/Util/Attribute/AnchorConverter.php
index 33e6fb57..a13d6147 100644
--- a/src/Profile/Util/Attribute/AnchorConverter.php
+++ b/src/Profile/Util/Attribute/AnchorConverter.php
@@ -115,8 +115,7 @@ private static function convertCertToX509(string $certificate): \stdClass
}
});
- $decodedX509Data = Json::decode(Json::encode(Json::convert_from_latin1_to_utf8_recursively($X509Data)), false);
-
+ $decodedX509Data = Json::decode(Json::encode(Json::convertFromLatin1ToUtf8Recursively($X509Data)), false);
// Ensure serial number is cast to string.
// @see \phpseclib\Math\BigInteger::__toString()
$decodedX509Data
diff --git a/src/Util/Json.php b/src/Util/Json.php
index e014e174..8be63f78 100644
--- a/src/Util/Json.php
+++ b/src/Util/Json.php
@@ -56,18 +56,26 @@ private static function validate(): void
}
}
- public static function convert_from_latin1_to_utf8_recursively($dat)
+ /**
+ * Recursively converts data from Latin1 to UTF-8 encoding.
+ *
+ * @param mixed $dat
+ * @return mixed
+ */
+ public static function convertFromLatin1ToUtf8Recursively($dat)
{
if (is_string($dat)) {
return utf8_encode($dat);
} elseif (is_array($dat)) {
$ret = [];
- foreach ($dat as $i => $d) $ret[ $i ] = self::convert_from_latin1_to_utf8_recursively($d);
-
+ foreach ($dat as $i => $d) {
+ $ret[$i] = self::convertFromLatin1ToUtf8Recursively($d);
+ }
return $ret;
} elseif (is_object($dat)) {
- foreach ($dat as $i => $d) $dat->$i = self::convert_from_latin1_to_utf8_recursively($d);
-
+ foreach (get_object_vars($dat) as $i => $d) {
+ $dat->$i = self::convertFromLatin1ToUtf8Recursively($d);
+ }
return $dat;
} else {
return $dat;
diff --git a/src/YotiClient.php b/src/YotiClient.php
index 54caf6bc..4ea7000f 100644
--- a/src/YotiClient.php
+++ b/src/YotiClient.php
@@ -28,20 +28,11 @@
*/
class YotiClient
{
- /**
- * @var AmlService
- */
- private $amlService;
+ private AmlService $amlService;
- /**
- * @var ProfileService
- */
- private $profileService;
+ private ProfileService $profileService;
- /**
- * @var ShareUrlService
- */
- private $shareUrlService;
+ private ShareUrlService $shareUrlService;
/**
* YotiClient constructor.
diff --git a/tests/DigitalIdentityClientTest.php b/tests/DigitalIdentityClientTest.php
new file mode 100644
index 00000000..267e8e01
--- /dev/null
+++ b/tests/DigitalIdentityClientTest.php
@@ -0,0 +1,174 @@
+createMock(Policy::class);
+ $redirectUri = 'https://host/redirect/';
+
+ $shareSessionRequest = (new ShareSessionRequestBuilder())
+ ->withPolicy($policy)
+ ->withRedirectUri($redirectUri)
+ ->build();
+
+ $response = $this->createMock(ResponseInterface::class);
+ $response->method('getBody')->willReturn(Psr7\Utils::streamFor(json_encode([
+ 'id' => 'some_id',
+ 'status' => 'some_status',
+ 'expiry' => 'some_time',
+ ])));
+
+ $response->method('getStatusCode')->willReturn(201);
+
+ $httpClient = $this->createMock(ClientInterface::class);
+ $httpClient
+ ->expects($this->once())
+ ->method('sendRequest')
+ ->willReturn($response);
+
+ $yotiClient = new DigitalIdentityClient(TestData::SDK_ID, TestData::PEM_FILE, [
+ Config::HTTP_CLIENT => $httpClient,
+ ]);
+
+ $result = $yotiClient->createShareSession($shareSessionRequest);
+
+ $this->assertInstanceOf(ShareSessionCreated::class, $result);
+ }
+
+ /**
+ * @covers ::createShareQrCode
+ * @covers ::__construct
+ */
+ public function testCreateShareQrCode()
+ {
+ $response = $this->createMock(ResponseInterface::class);
+ $response->method('getBody')->willReturn(Psr7\Utils::streamFor(json_encode([
+ 'id' => 'some_id',
+ 'uri' => 'some_uri',
+ ])));
+
+ $response->method('getStatusCode')->willReturn(201);
+
+ $httpClient = $this->createMock(ClientInterface::class);
+ $httpClient
+ ->expects($this->once())
+ ->method('sendRequest')
+ ->willReturn($response);
+
+ $yotiClient = new DigitalIdentityClient(TestData::SDK_ID, TestData::PEM_FILE, [
+ Config::HTTP_CLIENT => $httpClient,
+ ]);
+
+ $result = $yotiClient->createShareQrCode(TestData::SOME_ID);
+
+ $this->assertInstanceOf(ShareSessionCreatedQrCode::class, $result);
+ }
+
+ /**
+ * @covers ::fetchShareQrCode
+ * @covers ::__construct
+ */
+ public function testFetchShareQrCode()
+ {
+ $response = $this->createMock(ResponseInterface::class);
+ $response->method('getBody')->willReturn(Psr7\Utils::streamFor(json_encode([
+ 'id' => 'id',
+ 'expiry' => 'expiry',
+ 'policy' => 'policy',
+ 'extensions' => [['type' => 'type', 'content' => 'content']],
+ 'session' => ['id' => 'id', 'status' => 'status', 'expiry' => 'expiry'],
+ 'redirectUri' => 'redirectUri',
+ ])));
+
+ $response->method('getStatusCode')->willReturn(201);
+
+ $httpClient = $this->createMock(ClientInterface::class);
+ $httpClient
+ ->expects($this->once())
+ ->method('sendRequest')
+ ->willReturn($response);
+
+ $yotiClient = new DigitalIdentityClient(TestData::SDK_ID, TestData::PEM_FILE, [
+ Config::HTTP_CLIENT => $httpClient,
+ ]);
+
+ $result = $yotiClient->fetchShareQrCode(TestData::SOME_ID);
+
+ $this->assertInstanceOf(ShareSessionFetchedQrCode::class, $result);
+ }
+
+ /**
+ * @covers ::fetchShareSession
+ * @covers ::__construct
+ */
+ public function testFetchShareSession()
+ {
+ $response = $this->createMock(ResponseInterface::class);
+ $response->method('getBody')->willReturn(Psr7\Utils::streamFor(json_encode([
+ 'id' => 'SOME_ID',
+ 'status' => 'SOME_STATUS',
+ 'expiry' => 'SOME_EXPIRY',
+ 'created' => 'SOME_CREATED',
+ 'updated' => 'SOME_UPDATED',
+ 'qrCode' => ['id' => 'SOME_QRCODE_ID'],
+ 'receipt' => ['id' => 'SOME_RECEIPT_ID'],
+ ])));
+
+ $response->method('getStatusCode')->willReturn(201);
+
+ $httpClient = $this->createMock(ClientInterface::class);
+ $httpClient
+ ->expects($this->once())
+ ->method('sendRequest')
+ ->willReturn($response);
+
+ $yotiClient = new DigitalIdentityClient(TestData::SDK_ID, TestData::PEM_FILE, [
+ Config::HTTP_CLIENT => $httpClient,
+ ]);
+
+ $result = $yotiClient->fetchShareSession(TestData::SOME_ID);
+
+ $this->assertInstanceOf(ShareSessionFetched::class, $result);
+ }
+
+ /**
+ * @covers ::fetchShareReceipt
+ * @covers ::__construct
+ */
+ public function testFetchShareReceipt()
+ {
+ $response = $this->createMock(ResponseInterface::class);
+
+ $response->method('getStatusCode')->willReturn(201);
+
+ $identityService = $this->createMock(DigitalIdentityService::class);
+
+ $result = $identityService->fetchShareReceipt(TestData::SOME_ID);
+
+ $this->assertInstanceOf(Receipt::class, $result);
+ }
+}
diff --git a/tests/Identity/Constraint/PreferredSourcesTest.php b/tests/Identity/Constraint/PreferredSourcesTest.php
new file mode 100644
index 00000000..8b5b4ff1
--- /dev/null
+++ b/tests/Identity/Constraint/PreferredSourcesTest.php
@@ -0,0 +1,42 @@
+ $wantedAnchors,
+ 'soft_preference' => true
+ ];
+
+ $this->assertInstanceOf(PreferredSources::class, $preferredSource);
+ $this->assertEquals(json_encode($expected), json_encode($preferredSource));
+ $this->assertEquals($wantedAnchors, $preferredSource->getWantedAnchors());
+ $this->assertTrue($preferredSource->isSoftPreference());
+ }
+}
diff --git a/tests/Identity/Constraint/SourceConstraintsBuilderTest.php b/tests/Identity/Constraint/SourceConstraintsBuilderTest.php
new file mode 100644
index 00000000..ac6423c6
--- /dev/null
+++ b/tests/Identity/Constraint/SourceConstraintsBuilderTest.php
@@ -0,0 +1,66 @@
+withWantedAnchor(new WantedAnchor('SOME_VALUE'))
+ ->withSoftPreference(true)
+ ->build();
+
+ $this->assertInstanceOf(SourceConstraint::class, $sourceConstraint);
+ $this->assertInstanceOf(PreferredSources::class, $sourceConstraint->getPreferredSources());
+ $this->assertEquals('SOURCE', $sourceConstraint->getType());
+ }
+
+ /**
+ * @covers ::build
+ * @covers ::withWantedAnchors
+ * @covers \Yoti\Identity\Constraint\SourceConstraint::__construct
+ * @covers \Yoti\Identity\Constraint\SourceConstraint::jsonSerialize
+ */
+ public function testShouldBuildCorrectlyWithMultipleAnchors()
+ {
+ $wantedAnchors = [
+ new WantedAnchor('some'),
+ new WantedAnchor('some_2'),
+ ];
+
+ $sourceConstraint = (new SourceConstraintBuilder())
+ ->withWantedAnchors($wantedAnchors)
+ ->build();
+
+ $expectedConstraint = [
+ 'type' => 'SOURCE',
+ 'preferred_sources' => $sourceConstraint->getPreferredSources()
+ ];
+
+ $this->assertEquals($wantedAnchors, $sourceConstraint->getPreferredSources()->getWantedAnchors());
+ $this->assertEquals(
+ json_encode($wantedAnchors),
+ json_encode($sourceConstraint->getPreferredSources()->getWantedAnchors())
+ );
+ $this->assertEquals(json_encode($expectedConstraint), json_encode($sourceConstraint));
+ }
+}
diff --git a/tests/Identity/Content/ApplicationContentTest.php b/tests/Identity/Content/ApplicationContentTest.php
new file mode 100644
index 00000000..49acc689
--- /dev/null
+++ b/tests/Identity/Content/ApplicationContentTest.php
@@ -0,0 +1,35 @@
+createMock(ApplicationProfile::class);
+ $extraData = $this->createMock(ExtraData::class);
+
+ $applicationContent = new ApplicationContent($applicationProfile, $extraData);
+
+ $this->assertInstanceOf(ApplicationProfile::class, $applicationContent->getProfile());
+ $this->assertInstanceOf(ExtraData::class, $applicationContent->getExtraData());
+
+ $applicationContent2 = new ApplicationContent();
+
+ $this->assertNull($applicationContent2->getProfile());
+ $this->assertNull($applicationContent2->getExtraData());
+ }
+}
diff --git a/tests/Identity/Content/ContentTest.php b/tests/Identity/Content/ContentTest.php
new file mode 100644
index 00000000..f7bf4594
--- /dev/null
+++ b/tests/Identity/Content/ContentTest.php
@@ -0,0 +1,36 @@
+assertEquals($someString, $content->getProfile());
+ $this->assertEquals($someString2, $content->getExtraData());
+
+ $content = new Content($someString, $someString2);
+
+ $this->expectException(EncryptedDataException::class);
+
+ $content->getProfile();
+ $content->getExtraData();
+ }
+}
diff --git a/tests/Identity/Content/UserContentTest.php b/tests/Identity/Content/UserContentTest.php
new file mode 100644
index 00000000..283371cd
--- /dev/null
+++ b/tests/Identity/Content/UserContentTest.php
@@ -0,0 +1,35 @@
+createMock(UserProfile::class);
+ $extraData = $this->createMock(ExtraData::class);
+
+ $userContent = new UserContent($userProfile, $extraData);
+
+ $this->assertInstanceOf(UserProfile::class, $userContent->getProfile());
+ $this->assertInstanceOf(ExtraData::class, $userContent->getExtraData());
+
+ $userContent2 = new UserContent();
+
+ $this->assertNull($userContent2->getProfile());
+ $this->assertNull($userContent2->getExtraData());
+ }
+}
diff --git a/tests/Identity/DigitalIdentityServiceTest.php b/tests/Identity/DigitalIdentityServiceTest.php
new file mode 100644
index 00000000..c693ba26
--- /dev/null
+++ b/tests/Identity/DigitalIdentityServiceTest.php
@@ -0,0 +1,151 @@
+extensionMock = $this->createMock(Extension::class);
+ $this->policyMock = $this->createMock(Policy::class);
+ }
+
+ /**
+ * @covers ::createShareSession
+ * @covers ::__construct
+ */
+ public function testShouldCreateShareSession()
+ {
+ $shareSessionRequest = (new ShareSessionRequestBuilder())
+ ->withPolicy($this->policyMock)
+ ->withRedirectUri(self::URI)
+ ->withExtension($this->extensionMock)
+ ->build();
+
+ $response = $this->createMock(ResponseInterface::class);
+ $response->method('getBody')->willReturn(Psr7\Utils::streamFor(json_encode([
+ 'id' => 'some_id',
+ 'status' => 'some_status',
+ 'expiry' => 'some_time',
+ ])));
+
+ $response->method('getStatusCode')->willReturn(201);
+
+ $identityService = $this->createMock(DigitalIdentityService::class);
+
+ $result = $identityService->createShareSession($shareSessionRequest);
+
+ $this->assertInstanceOf(ShareSessionCreated::class, $result);
+ }
+
+ /**
+ * @covers ::createShareQrCode
+ * @covers ::__construct
+ */
+ public function testShouldCreateShareQrCode()
+ {
+ $response = $this->createMock(ResponseInterface::class);
+ $response->method('getBody')->willReturn(Psr7\Utils::streamFor(json_encode([
+ 'id' => 'some_id',
+ 'uri' => 'some_uri',
+ ])));
+
+ $response->method('getStatusCode')->willReturn(201);
+
+ $identityService = $this->createMock(DigitalIdentityService::class);
+
+ $result = $identityService->createShareQrCode(TestData::SOME_ID);
+
+ $this->assertInstanceOf(ShareSessionCreatedQrCode::class, $result);
+ }
+
+ /**
+ * @covers ::fetchShareQrCode
+ * @covers ::__construct
+ */
+ public function testShouldFetchShareQrCode()
+ {
+ $response = $this->createMock(ResponseInterface::class);
+ $response->method('getBody')->willReturn(Psr7\Utils::streamFor(json_encode([
+ 'id' => 'id',
+ 'expiry' => 'expiry',
+ 'policy' => 'policy',
+ 'extensions' => [['type' => 'type', 'content' => 'content']],
+ 'session' => ['id' => 'id', 'status' => 'status', 'expiry' => 'expiry'],
+ 'redirectUri' => 'redirectUri',
+ ])));
+
+ $response->method('getStatusCode')->willReturn(201);
+
+ $identityService = $this->createMock(DigitalIdentityService::class);
+
+ $result = $identityService->fetchShareQrCode(TestData::SOME_ID);
+
+ $this->assertInstanceOf(ShareSessionFetchedQrCode::class, $result);
+ }
+
+ /**
+ * @covers ::fetchShareSession
+ * @covers ::__construct
+ */
+ public function testShouldFetchShareSession()
+ {
+ $response = $this->createMock(ResponseInterface::class);
+ $response->method('getBody')->willReturn(Psr7\Utils::streamFor(json_encode([
+ 'id' => 'SOME_ID',
+ 'status' => 'SOME_STATUS',
+ 'expiry' => 'SOME_EXPIRY',
+ 'created' => 'SOME_CREATED',
+ 'updated' => 'SOME_UPDATED',
+ 'qrCode' => ['id' => 'SOME_QRCODE_ID'],
+ 'receipt' => ['id' => 'SOME_RECEIPT_ID'],
+ ])));
+
+ $response->method('getStatusCode')->willReturn(201);
+
+ $identityService = $this->createMock(DigitalIdentityService::class);
+
+ $result = $identityService->fetchShareSession(TestData::SOME_ID);
+
+ $this->assertInstanceOf(ShareSessionFetched::class, $result);
+ }
+
+ /**
+ * @covers ::fetchShareReceipt
+ * @covers ::__construct
+ */
+ public function testShouldFetchShareReceipt()
+ {
+ $response = $this->createMock(ResponseInterface::class);
+
+ $response->method('getStatusCode')->willReturn(201);
+
+ $identityService = $this->createMock(DigitalIdentityService::class);
+
+ $result = $identityService->fetchShareReceipt(TestData::SOME_ID);
+
+ $this->assertInstanceOf(Receipt::class, $result);
+ }
+}
diff --git a/tests/Identity/Extension/BasicExtensionBuilderTest.php b/tests/Identity/Extension/BasicExtensionBuilderTest.php
new file mode 100644
index 00000000..026a045f
--- /dev/null
+++ b/tests/Identity/Extension/BasicExtensionBuilderTest.php
@@ -0,0 +1,40 @@
+withType($someType)
+ ->withContent($someContent)
+ ->build();
+
+ $expectedJson = json_encode([
+ 'type' => $someType,
+ 'content' => $someContent,
+ ]);
+
+ $this->assertEquals($expectedJson, json_encode($constraints));
+ }
+}
diff --git a/tests/Identity/Extension/LocationConstraintContentTest.php b/tests/Identity/Extension/LocationConstraintContentTest.php
new file mode 100644
index 00000000..a0973db1
--- /dev/null
+++ b/tests/Identity/Extension/LocationConstraintContentTest.php
@@ -0,0 +1,44 @@
+ [
+ 'latitude' => $expectedLatitude,
+ 'longitude' => $expectedLongitude,
+ 'radius' => $expectedRadius,
+ 'max_uncertainty_radius' => $expectedMaxUncertainty,
+ ],
+ ]);
+
+ $this->assertEquals($expectedJson, json_encode($content));
+ }
+}
diff --git a/tests/Identity/Extension/LocationConstraintExtensionBuilderTest.php b/tests/Identity/Extension/LocationConstraintExtensionBuilderTest.php
new file mode 100644
index 00000000..875b8bb7
--- /dev/null
+++ b/tests/Identity/Extension/LocationConstraintExtensionBuilderTest.php
@@ -0,0 +1,164 @@
+expectException(\RangeException::class);
+ $this->expectExceptionMessage('\'latitude\' value \'-91\' is less than \'-90\'');
+
+ (new LocationConstraintExtensionBuilder())
+ ->withLatitude(-91)
+ ->withLongitude(0)
+ ->build();
+ }
+
+ /**
+ * @covers ::withLatitude
+ */
+ public function testLatitudeTooHigh()
+ {
+ $this->expectException(\RangeException::class);
+ $this->expectExceptionMessage('\'latitude\' value \'91\' is greater than \'90\'');
+
+ (new LocationConstraintExtensionBuilder())
+ ->withLatitude(91)
+ ->withLongitude(0)
+ ->build();
+ }
+
+ /**
+ * @covers ::withLongitude
+ */
+ public function testLongitudeTooLow()
+ {
+ $this->expectException(\RangeException::class);
+ $this->expectExceptionMessage('\'longitude\' value \'-181\' is less than \'-180\'');
+
+ (new LocationConstraintExtensionBuilder())
+ ->withLatitude(0)
+ ->withLongitude(-181)
+ ->build();
+ }
+
+ /**
+ * @covers ::withLongitude
+ */
+ public function testLongitudeTooHigh()
+ {
+ $this->expectException(\RangeException::class);
+ $this->expectExceptionMessage('\'longitude\' value \'181\' is greater than \'180\'');
+
+ (new LocationConstraintExtensionBuilder())
+ ->withLatitude(0)
+ ->withLongitude(181)
+ ->build();
+ }
+
+ /**
+ * @covers ::withRadius
+ */
+ public function testRadiusLessThanZero()
+ {
+ $this->expectException(\RangeException::class);
+ $this->expectExceptionMessage('\'radius\' value \'-1\' is less than \'0\'');
+
+ (new LocationConstraintExtensionBuilder())
+ ->withLatitude(0)
+ ->withLongitude(0)
+ ->withRadius(-1)
+ ->build();
+ }
+
+ /**
+ * @covers ::withMaxUncertainty
+ */
+ public function testMaxUncertaintyLessThanZero()
+ {
+ $this->expectException(\RangeException::class);
+ $this->expectExceptionMessage('\'maxUncertainty\' value \'-1\' is less than \'0\'');
+
+ (new LocationConstraintExtensionBuilder())
+ ->withLatitude(0)
+ ->withLongitude(0)
+ ->withMaxUncertainty(-1)
+ ->build();
+ }
+
+ /**
+ * @covers ::build
+ */
+ public function testBuild()
+ {
+ $expectedLatitude = 50.8169;
+ $expectedLongitude = -0.1367;
+ $expectedRadius = 30;
+ $expectedMaxUncertainty = 40;
+
+ $extension = (new LocationConstraintExtensionBuilder())
+ ->withLatitude($expectedLatitude)
+ ->withLongitude($expectedLongitude)
+ ->withRadius($expectedRadius)
+ ->withMaxUncertainty($expectedMaxUncertainty)
+ ->build();
+
+ $expectedJson = json_encode([
+ 'type' => self::TYPE_LOCATION_CONSTRAINT,
+ 'content' => [
+ 'expected_device_location' => [
+ 'latitude' => $expectedLatitude,
+ 'longitude' => $expectedLongitude,
+ 'radius' => $expectedRadius,
+ 'max_uncertainty_radius' => $expectedMaxUncertainty,
+ ],
+ ],
+ ]);
+
+ $this->assertEquals($expectedJson, json_encode($extension));
+ }
+
+ /**
+ * @covers ::build
+ */
+ public function testBuildDefaultValues()
+ {
+ $expectedLatitude = 50.8169;
+ $expectedLongitude = -0.1367;
+ $expectedDefaultRadius = 150;
+ $expectedDefaultMaxUncertainty = 150;
+
+ $extension = (new LocationConstraintExtensionBuilder())
+ ->withLatitude($expectedLatitude)
+ ->withLongitude($expectedLongitude)
+ ->build();
+
+ $expectedJson = json_encode([
+ 'type' => self::TYPE_LOCATION_CONSTRAINT,
+ 'content' => [
+ 'expected_device_location' => [
+ 'latitude' => $expectedLatitude,
+ 'longitude' => $expectedLongitude,
+ 'radius' => $expectedDefaultRadius,
+ 'max_uncertainty_radius' => $expectedDefaultMaxUncertainty,
+ ],
+ ],
+ ]);
+
+ $this->assertEquals($expectedJson, json_encode($extension));
+ }
+}
diff --git a/tests/Identity/Extension/ThirdPartyAttributeContentTest.php b/tests/Identity/Extension/ThirdPartyAttributeContentTest.php
new file mode 100644
index 00000000..4fe6b729
--- /dev/null
+++ b/tests/Identity/Extension/ThirdPartyAttributeContentTest.php
@@ -0,0 +1,42 @@
+ '2019-12-02T12:00:00.123+00:00',
+ 'definitions' => [
+ [
+ 'name' => $someDefinition,
+ ],
+ ],
+ ]);
+
+ $this->assertEquals($expectedJson, json_encode($thirdPartyAttributeContent));
+ }
+}
diff --git a/tests/Identity/Extension/ThirdPartyAttributeExtensionBuilderTest.php b/tests/Identity/Extension/ThirdPartyAttributeExtensionBuilderTest.php
new file mode 100644
index 00000000..43252040
--- /dev/null
+++ b/tests/Identity/Extension/ThirdPartyAttributeExtensionBuilderTest.php
@@ -0,0 +1,132 @@
+someDate = new \DateTime(self::SOME_DATE_STRING);
+ }
+
+ /**
+ * @covers ::withExpiryDate
+ * @covers ::withDefinition
+ * @covers ::build
+ */
+ public function testBuild()
+ {
+ $thirdPartyAttributeExtension = (new ThirdPartyAttributeExtensionBuilder())
+ ->withExpiryDate($this->someDate)
+ ->withDefinition(self::SOME_DEFINITION)
+ ->withDefinition(self::SOME_OTHER_DEFINITION)
+ ->build();
+
+ $expectedJson = $this->createExpectedJson(
+ $this->someDate->format(\DateTime::RFC3339_EXTENDED),
+ [
+ self::SOME_DEFINITION,
+ self::SOME_OTHER_DEFINITION,
+ ]
+ );
+
+ $this->assertJsonStringEqualsJsonString(
+ $expectedJson,
+ json_encode($thirdPartyAttributeExtension)
+ );
+ }
+
+ /**
+ * @covers ::withDefinitions
+ */
+ public function testWithDefinitionsOverwritesExistingDefinitions()
+ {
+ $thirdPartyAttributeExtension = (new ThirdPartyAttributeExtensionBuilder())
+ ->withExpiryDate($this->someDate)
+ ->withDefinition('initial definition')
+ ->withDefinitions([
+ self::SOME_DEFINITION,
+ self::SOME_OTHER_DEFINITION,
+ ])
+ ->build();
+
+ $this->assertJsonStringEqualsJsonString(
+ $this->createExpectedJson(
+ $this->someDate->format(\DateTime::RFC3339_EXTENDED),
+ [
+ self::SOME_DEFINITION,
+ self::SOME_OTHER_DEFINITION,
+ ]
+ ),
+ json_encode($thirdPartyAttributeExtension)
+ );
+ }
+
+ /**
+ * @covers ::withExpiryDate
+ *
+ * @dataProvider expiryDateDataProvider
+ */
+ public function testWithExpiryDateFormat($inputDate, $outputDate)
+ {
+ $thirdPartyAttributeExtension = (new ThirdPartyAttributeExtensionBuilder())
+ ->withExpiryDate(new \DateTime($inputDate))
+ ->build();
+
+ $this->assertJsonStringEqualsJsonString(
+ $this->createExpectedJson($outputDate, []),
+ json_encode($thirdPartyAttributeExtension)
+ );
+ }
+
+ /**
+ * Provides test expiry dates.
+ */
+ public function expiryDateDataProvider(): array
+ {
+ return [
+ ['2020-01-02T01:02:03.123456Z', '2020-01-02T01:02:03.123+00:00'],
+ ['2020-01-01T01:02:03.123+04:00', '2019-12-31T21:02:03.123+00:00'],
+ ['2020-01-02T01:02:03.123-02:00', '2020-01-02T03:02:03.123+00:00']
+ ];
+ }
+
+ /**
+ * Create expected third party extension JSON.
+ *
+ * @param string $expiryDate
+ * @param string[] $definitions
+ *
+ * @return string
+ */
+ private function createExpectedJson(string $expiryDate, array $definitions): string
+ {
+ return json_encode([
+ 'type' => self::THIRD_PARTY_ATTRIBUTE_TYPE,
+ 'content' => [
+ 'expiry_date' => $expiryDate,
+ 'definitions' => array_map(
+ function ($definition) {
+ return [ 'name' => $definition ];
+ },
+ $definitions
+ ),
+ ],
+ ]);
+ }
+}
diff --git a/tests/Identity/Extension/TransactionalFlowExtensionBuilderTest.php b/tests/Identity/Extension/TransactionalFlowExtensionBuilderTest.php
new file mode 100644
index 00000000..6ad50610
--- /dev/null
+++ b/tests/Identity/Extension/TransactionalFlowExtensionBuilderTest.php
@@ -0,0 +1,36 @@
+ 'content'];
+
+ $constraints = (new TransactionalFlowExtensionBuilder())
+ ->withContent($someContent)
+ ->build();
+
+ $expectedJson = json_encode([
+ 'type' => self::TYPE_TRANSACTIONAL_FLOW,
+ 'content' => $someContent,
+ ]);
+
+ $this->assertEquals($expectedJson, json_encode($constraints));
+ }
+}
diff --git a/tests/Identity/Policy/PolicyBuilderTest.php b/tests/Identity/Policy/PolicyBuilderTest.php
new file mode 100644
index 00000000..d223bd79
--- /dev/null
+++ b/tests/Identity/Policy/PolicyBuilderTest.php
@@ -0,0 +1,644 @@
+withFamilyName()
+ ->withGivenNames()
+ ->withFullName()
+ ->withDateOfBirth()
+ ->withGender()
+ ->withPostalAddress()
+ ->withStructuredPostalAddress()
+ ->withNationality()
+ ->withPhoneNumber()
+ ->withSelfie()
+ ->withEmail()
+ ->withDocumentDetails()
+ ->withDocumentImages()
+ ->build();
+
+ $expectedWantedAttributeData = [
+ 'wanted' => [
+ ['name' => 'family_name', 'optional' => false],
+ ['name' => 'given_names', 'optional' => false],
+ ['name' => 'full_name', 'optional' => false],
+ ['name' => 'date_of_birth', 'optional' => false],
+ ['name' => 'gender', 'optional' => false],
+ ['name' => 'postal_address', 'optional' => false],
+ ['name' => 'structured_postal_address', 'optional' => false],
+ ['name' => 'nationality', 'optional' => false],
+ ['name' => 'phone_number', 'optional' => false],
+ ['name' => 'selfie', 'optional' => false],
+ ['name' => 'email_address', 'optional' => false],
+ ['name' => 'document_details', 'optional' => false],
+ ['name' => 'document_images', 'optional' => false],
+ ],
+ 'wanted_auth_types' => [],
+ 'wanted_remember_me' => false,
+ 'wanted_remember_me_optional' => false,
+ 'identity_profile_requirements' => null,
+ ];
+
+ $this->assertEquals(json_encode($expectedWantedAttributeData), json_encode($policy));
+ }
+
+ /**
+ * @covers ::withWantedAttributeByName
+ */
+ public function testWithWantedAttributeByNameWithConstraints()
+ {
+ $someAttributeName = 'some_attribute_name';
+ $sourceConstraint = (new SourceConstraintBuilder())
+ ->withWantedAnchor(new WantedAnchor('SOME'))
+ ->build();
+
+ $constraints = [
+ $sourceConstraint,
+ ];
+
+ $policy = (new PolicyBuilder())
+ ->withWantedAttributeByName($someAttributeName, $constraints, true)
+ ->build();
+
+ $expectedWantedAttributeData = [
+ 'wanted' => [
+ [
+ 'name' => $someAttributeName,
+ 'optional' => false,
+ "constraints" => [
+ [
+ "type" => "SOURCE",
+ "preferred_sources" => [
+ "anchors" => [
+ [
+ "name" => "SOME",
+ "sub_type" => "",
+ ]
+ ],
+ "soft_preference" => false,
+ ],
+ ],
+ ],
+ "accept_self_asserted" => true,
+ ],
+ ],
+ 'wanted_auth_types' => [],
+ 'wanted_remember_me' => false,
+ 'wanted_remember_me_optional' => false,
+ 'identity_profile_requirements' => null,
+ ];
+
+ $this->assertJsonStringEqualsJsonString(
+ json_encode($expectedWantedAttributeData),
+ json_encode($policy)
+ );
+ }
+
+ /**
+ * @covers ::withWantedAttribute
+ * @covers ::withFamilyName
+ */
+ public function testWithDuplicateAttribute()
+ {
+ $policy = (new PolicyBuilder())
+ ->withFamilyName()
+ ->withFamilyName()
+ ->build();
+
+ $expectedWantedAttributeData = [
+ 'wanted' => [
+ ['name' => 'family_name', 'optional' => false],
+ ],
+ 'wanted_auth_types' => [],
+ 'wanted_remember_me' => false,
+ 'wanted_remember_me_optional' => false,
+ 'identity_profile_requirements' => null,
+ ];
+
+ $this->assertEquals(json_encode($expectedWantedAttributeData), json_encode($policy));
+ }
+
+ /**
+ * @covers ::withWantedAttribute
+ * @covers ::withFamilyName
+ */
+ public function testWithDuplicateAttributeDifferentConstraints()
+ {
+ $sourceConstraint = (new SourceConstraintBuilder())
+ ->withWantedAnchor(new WantedAnchor('SOME'))
+ ->build();
+
+ $sourceConstraint2 = (new SourceConstraintBuilder())
+ ->withWantedAnchor(new WantedAnchor('SOME_2'))
+ ->build();
+
+
+ $policy = (new PolicyBuilder())
+ ->withFamilyName()
+ ->withFamilyName([$sourceConstraint])
+ ->withFamilyName([$sourceConstraint2])
+ ->build();
+
+ $jsonData = $policy->jsonSerialize();
+
+ $this->assertCount(3, $jsonData->wanted);
+ foreach ($jsonData->wanted as $wantedAttribute) {
+ $this->assertEquals('family_name', $wantedAttribute->getName());
+ }
+ }
+
+ /**
+ * @covers ::build
+ * @covers ::withWantedAttributeByName
+ */
+ public function testWithWantedAttributeByName()
+ {
+ $policy = (new PolicyBuilder())
+ ->withWantedAttributeByName('family_name')
+ ->withWantedAttributeByName('given_names')
+ ->build();
+
+ $expectedWantedAttributeData = [
+ 'wanted' => [
+ ['name' => 'family_name', 'optional' => false],
+ ['name' => 'given_names', 'optional' => false],
+ ],
+ 'wanted_auth_types' => [],
+ 'wanted_remember_me' => false,
+ 'wanted_remember_me_optional' => false,
+ 'identity_profile_requirements' => null,
+ ];
+
+ $this->assertEquals(json_encode($expectedWantedAttributeData), json_encode($policy));
+ }
+
+ /**
+ * @covers ::build
+ * @covers ::withWantedAttribute
+ */
+ public function testWithAttributeObjects()
+ {
+ $wantedFamilyName = (new WantedAttributeBuilder())
+ ->withName('family_name')
+ ->build();
+
+ $wantedGivenNames = (new WantedAttributeBuilder())
+ ->withName('given_names')
+ ->build();
+
+ $policy = (new PolicyBuilder())
+ ->withWantedAttribute($wantedFamilyName)
+ ->withWantedAttribute($wantedGivenNames)
+ ->build();
+
+ $expectedWantedAttributeData = [
+ 'wanted' => [
+ ['name' => 'family_name', 'optional' => false],
+ ['name' => 'given_names', 'optional' => false],
+ ],
+ 'wanted_auth_types' => [],
+ 'wanted_remember_me' => false,
+ 'wanted_remember_me_optional' => false,
+ 'identity_profile_requirements' => null,
+ ];
+
+ $this->assertEquals(json_encode($expectedWantedAttributeData), json_encode($policy));
+ }
+
+ /**
+ * @covers ::withDateOfBirth
+ * @covers ::withAgeOver
+ * @covers ::withAgeUnder
+ * @covers ::withAgeDerivedAttribute
+ */
+ public function testWithAgeDerivedAttributes()
+ {
+ $policy = (new PolicyBuilder())
+ ->withDateOfBirth()
+ ->withAgeOver(18)
+ ->withAgeUnder(30)
+ ->withAgeUnder(40)
+ ->build();
+
+ $expectedWantedAttributeData = [
+ 'wanted' => [
+ ['name' => 'date_of_birth', 'optional' => false],
+ ['name' => 'date_of_birth', 'optional' => false, 'derivation' => 'age_over:18'],
+ ['name' => 'date_of_birth', 'optional' => false, 'derivation' => 'age_under:30'],
+ ['name' => 'date_of_birth', 'optional' => false, 'derivation' => 'age_under:40'],
+ ],
+ 'wanted_auth_types' => [],
+ 'wanted_remember_me' => false,
+ 'wanted_remember_me_optional' => false,
+ 'identity_profile_requirements' => null,
+ ];
+
+ $this->assertEquals(json_encode($expectedWantedAttributeData), json_encode($policy));
+ }
+
+ /**
+ * @covers ::withAgeDerivedAttribute
+ */
+ public function testWithAgeDerivedAttributesWithConstraints()
+ {
+ $sourceConstraint = (new SourceConstraintBuilder())
+ ->withWantedAnchor(new WantedAnchor('SOME'))
+ ->build();
+
+
+ $policy = (new PolicyBuilder())
+ ->withAgeDerivedAttribute(UserProfile::AGE_OVER . '18', [$sourceConstraint])
+ ->build();
+
+ $expectedWantedAttributeData = [
+ 'wanted' => [
+ [
+ 'name' => 'date_of_birth',
+ 'optional' => false,
+ 'derivation' => 'age_over:18',
+ "constraints" => [
+ [
+ "type" => "SOURCE",
+ "preferred_sources" => [
+ "anchors" => [
+ [
+ "name" => "SOME",
+ "sub_type" => "",
+ ]
+ ],
+ "soft_preference" => false,
+ ],
+ ],
+ ],
+ ],
+ ],
+ 'wanted_auth_types' => [],
+ 'wanted_remember_me' => false,
+ 'wanted_remember_me_optional' => false,
+ 'identity_profile_requirements' => null,
+ ];
+
+ $this->assertJsonStringEqualsJsonString(
+ json_encode($expectedWantedAttributeData),
+ json_encode($policy)
+ );
+ }
+
+
+ /**
+ * @covers ::withAgeUnder
+ * @covers ::withAgeDerivedAttribute
+ * @covers ::withWantedAttribute
+ */
+ public function testWithDuplicateAgeDerivedAttributes()
+ {
+ $policy = (new PolicyBuilder())
+ ->withAgeUnder(30)
+ ->withAgeUnder(30)
+ ->build();
+
+ $expectedWantedAttributeData = [
+ 'wanted' => [
+ ['name' => 'date_of_birth', 'optional' => false, 'derivation' => 'age_under:30'],
+ ],
+ 'wanted_auth_types' => [],
+ 'wanted_remember_me' => false,
+ 'wanted_remember_me_optional' => false,
+ 'identity_profile_requirements' => null,
+ ];
+
+ $this->assertEquals(json_encode($expectedWantedAttributeData), json_encode($policy));
+ }
+
+ /**
+ * @covers ::withSelfieAuthentication
+ * @covers ::withPinAuthentication
+ * @covers ::withWantedAuthType
+ */
+ public function testWithAuthTypes()
+ {
+ $policy = (new PolicyBuilder())
+ ->withSelfieAuthentication()
+ ->withPinAuthentication()
+ ->withWantedAuthType(99)
+ ->build();
+
+ $expectedWantedAttributeData = [
+ 'wanted' => [],
+ 'wanted_auth_types' => [self::SELFIE_AUTH_TYPE, self::PIN_AUTH_TYPE, 99],
+ 'wanted_remember_me' => false,
+ 'wanted_remember_me_optional' => false,
+ 'identity_profile_requirements' => null,
+ ];
+
+ $this->assertEquals(json_encode($expectedWantedAttributeData), json_encode($policy));
+ }
+
+ /**
+ * @covers ::withSelfieAuthentication
+ * @covers ::withPinAuthentication
+ * @covers ::withWantedAuthType
+ */
+ public function testWithAuthTypesTrue()
+ {
+ $policy = (new PolicyBuilder())
+ ->withSelfieAuthentication()
+ ->withPinAuthentication()
+ ->withWantedAuthType(99)
+ ->build();
+
+ $expectedWantedAttributeData = [
+ 'wanted' => [],
+ 'wanted_auth_types' => [self::SELFIE_AUTH_TYPE, self::PIN_AUTH_TYPE, 99],
+ 'wanted_remember_me' => false,
+ 'wanted_remember_me_optional' => false,
+ 'identity_profile_requirements' => null
+ ];
+
+ $this->assertEquals(json_encode($expectedWantedAttributeData), json_encode($policy));
+ }
+
+ /**
+ * @covers ::withSelfieAuthentication
+ * @covers ::withPinAuthentication
+ * @covers ::withWantedAuthType
+ */
+ public function testWithAuthTypesFalse()
+ {
+ $policy = (new PolicyBuilder())
+ ->withSelfieAuthentication(false)
+ ->withPinAuthentication(false)
+ ->withWantedAuthType(99, false)
+ ->build();
+
+ $expectedWantedAttributeData = [
+ 'wanted' => [],
+ 'wanted_auth_types' => [],
+ 'wanted_remember_me' => false,
+ 'wanted_remember_me_optional' => false,
+ 'identity_profile_requirements' => null,
+ ];
+
+ $this->assertEquals(json_encode($expectedWantedAttributeData), json_encode($policy));
+ }
+
+ /**
+ * @covers ::withSelfieAuthentication
+ * @covers ::withPinAuthentication
+ */
+ public function testWithAuthEnabledThenDisabled()
+ {
+ $policy = (new PolicyBuilder())
+ ->withSelfieAuthentication()
+ ->withSelfieAuthentication(false)
+ ->withPinAuthentication()
+ ->withPinAuthentication(false)
+ ->build();
+
+ $expectedWantedAttributeData = [
+ 'wanted' => [],
+ 'wanted_auth_types' => [],
+ 'wanted_remember_me' => false,
+ 'wanted_remember_me_optional' => false,
+ 'identity_profile_requirements' => null,
+ ];
+
+ $this->assertEquals(json_encode($expectedWantedAttributeData), json_encode($policy));
+ }
+
+ /**
+ * @covers ::withSelfieAuthentication
+ */
+ public function testWithSameAuthTypeAddedOnlyOnce()
+ {
+ $policy = (new PolicyBuilder())
+ ->withSelfieAuthentication()
+ ->withSelfieAuthentication()
+ ->withSelfieAuthentication()
+ ->build();
+
+ $expectedWantedAttributeData = [
+ 'wanted' => [],
+ 'wanted_auth_types' => [self::SELFIE_AUTH_TYPE],
+ 'wanted_remember_me' => false,
+ 'wanted_remember_me_optional' => false,
+ 'identity_profile_requirements' => null,
+ ];
+
+ $this->assertEquals(json_encode($expectedWantedAttributeData), json_encode($policy));
+ }
+
+ /**
+ * @covers ::withSelfieAuthentication
+ */
+ public function testWithOnlyTwoAuthTypes()
+ {
+ $policy = (new PolicyBuilder())
+ ->withSelfieAuthentication()
+ ->withPinAuthentication()
+ ->build();
+
+ $expectedWantedAttributeData = [
+ 'wanted' => [],
+ 'wanted_auth_types' => [self::SELFIE_AUTH_TYPE, self::PIN_AUTH_TYPE],
+ 'wanted_remember_me' => false,
+ 'wanted_remember_me_optional' => false,
+ 'identity_profile_requirements' => null,
+ ];
+
+ $this->assertEquals(json_encode($expectedWantedAttributeData), json_encode($policy));
+ }
+
+ /**
+ * @covers ::withSelfieAuthentication
+ */
+ public function testWithNoSelfieAuthAfterRemoval()
+ {
+ $policy = (new PolicyBuilder())
+ ->withSelfieAuthentication()
+ ->withSelfieAuthentication(false)
+ ->build();
+
+ $expectedWantedAttributeData = [
+ 'wanted' => [],
+ 'wanted_auth_types' => [],
+ 'wanted_remember_me' => false,
+ 'wanted_remember_me_optional' => false,
+ 'identity_profile_requirements' => null,
+ ];
+
+ $this->assertEquals(json_encode($expectedWantedAttributeData), json_encode($policy));
+ }
+
+ /**
+ * @covers ::withSelfieAuthentication
+ */
+ public function testWithNoPinAuthAfterRemoval()
+ {
+ $policy = (new PolicyBuilder())
+ ->withPinAuthentication()
+ ->withPinAuthentication(false)
+ ->build();
+
+ $expectedWantedAttributeData = [
+ 'wanted' => [],
+ 'wanted_auth_types' => [],
+ 'wanted_remember_me' => false,
+ 'wanted_remember_me_optional' => false,
+ 'identity_profile_requirements' => null,
+ ];
+
+ $this->assertEquals(json_encode($expectedWantedAttributeData), json_encode($policy));
+ }
+
+
+ /**
+ * @covers ::withWantedRememberMe
+ */
+ public function testWithRememberMe()
+ {
+ $policy = (new PolicyBuilder())
+ ->withWantedRememberMe(true)
+ ->build();
+
+ $expectedWantedAttributeData = [
+ 'wanted' => [],
+ 'wanted_auth_types' => [],
+ 'wanted_remember_me' => true,
+ 'wanted_remember_me_optional' => false,
+ 'identity_profile_requirements' => null,
+ ];
+
+ $this->assertEquals(json_encode($expectedWantedAttributeData), json_encode($policy));
+ }
+
+ /**
+ * @covers ::withWantedRememberMe
+ */
+ public function testWithoutRememberMe()
+ {
+ $policy = (new PolicyBuilder())
+ ->withWantedRememberMe(false)
+ ->build();
+
+ $expectedWantedAttributeData = [
+ 'wanted' => [],
+ 'wanted_auth_types' => [],
+ 'wanted_remember_me' => false,
+ 'wanted_remember_me_optional' => false,
+ 'identity_profile_requirements' => null
+ ];
+
+ $this->assertEquals(json_encode($expectedWantedAttributeData), json_encode($policy));
+ }
+
+ /**
+ * @covers ::withWantedRememberMeOptional
+ */
+ public function testWithRememberMeOptional()
+ {
+ $policy = (new PolicyBuilder())
+ ->withWantedRememberMeOptional(true)
+ ->build();
+
+ $expectedWantedAttributeData = [
+ 'wanted' => [],
+ 'wanted_auth_types' => [],
+ 'wanted_remember_me' => false,
+ 'wanted_remember_me_optional' => true,
+ 'identity_profile_requirements' => null,
+ ];
+
+ $this->assertEquals(json_encode($expectedWantedAttributeData), json_encode($policy));
+ }
+
+ /**
+ * @covers ::withWantedRememberMeOptional
+ */
+ public function testWithoutRememberMeOptional()
+ {
+ $policy = (new PolicyBuilder())
+ ->withWantedRememberMeOptional(false)
+ ->build();
+
+ $expectedWantedAttributeData = [
+ 'wanted' => [],
+ 'wanted_auth_types' => [],
+ 'wanted_remember_me' => false,
+ 'wanted_remember_me_optional' => false,
+ 'identity_profile_requirements' => null
+ ];
+
+ $this->assertEquals(json_encode($expectedWantedAttributeData), json_encode($policy));
+ }
+
+ /**
+ * @covers ::withIdentityProfileRequirements
+ * @covers \Yoti\Identity\Policy\Policy::__construct
+ * @covers \Yoti\Identity\Policy\Policy::getIdentityProfileRequirements
+ * @covers \Yoti\Identity\Policy\Policy::jsonSerialize
+ */
+ public function testWithIdentityProfileRequirements()
+ {
+ $identityProfileSample = (object)[
+ 'trust_framework' => 'UK_TFIDA',
+ 'scheme' => [
+ 'type' => 'DBS',
+ 'objective' => 'STANDARD'
+ ]
+ ];
+
+ $expectedWantedAttributeData = [
+ 'wanted' => [],
+ 'wanted_auth_types' => [],
+ 'wanted_remember_me' => false,
+ 'wanted_remember_me_optional' => false,
+ 'identity_profile_requirements' => $identityProfileSample
+ ];
+
+ $policy = (new PolicyBuilder())
+ ->withIdentityProfileRequirements($identityProfileSample)
+ ->build();
+
+ $this->assertEquals(json_encode($expectedWantedAttributeData), json_encode($policy));
+ $this->assertEquals($identityProfileSample, $policy->getIdentityProfileRequirements());
+ }
+}
diff --git a/tests/Identity/Policy/WantedAnchorBuilderTest.php b/tests/Identity/Policy/WantedAnchorBuilderTest.php
new file mode 100644
index 00000000..2d0479e0
--- /dev/null
+++ b/tests/Identity/Policy/WantedAnchorBuilderTest.php
@@ -0,0 +1,39 @@
+withValue($someName)
+ ->withSubType($someSubType)
+ ->build();
+
+ $expectedJsonData = [
+ 'name' => $someName,
+ 'sub_type' => $someSubType,
+ ];
+
+ $this->assertEquals(json_encode($expectedJsonData), json_encode($wantedAnchor));
+ }
+}
diff --git a/tests/Identity/Policy/WantedAttributeBuilderTest.php b/tests/Identity/Policy/WantedAttributeBuilderTest.php
new file mode 100644
index 00000000..7cd8a8f1
--- /dev/null
+++ b/tests/Identity/Policy/WantedAttributeBuilderTest.php
@@ -0,0 +1,162 @@
+withWantedAnchor(new WantedAnchor('SOME'))
+ ->build();
+
+ $wantedAttribute = (new WantedAttributeBuilder())
+ ->withName($someName)
+ ->withDerivation($someDerivation)
+ ->withOptional(true)
+ ->withConstraint($sourceConstraint)
+ ->withAcceptSelfAsserted(false)
+ ->build();
+
+ $expectedJsonData = [
+ 'name' => $someName,
+ 'optional' => true,
+ 'derivation' => $someDerivation,
+ 'constraints' => [$sourceConstraint],
+ 'accept_self_asserted' => false,
+ ];
+
+ $this->assertEquals(json_encode($expectedJsonData), json_encode($wantedAttribute));
+ $this->assertTrue($wantedAttribute->getOptional());
+ $this->assertContains($sourceConstraint, $wantedAttribute->getConstraints());
+ $this->assertFalse($wantedAttribute->getAcceptSelfAsserted());
+ }
+
+ /**
+ * @covers ::build
+ * @covers ::withName
+ */
+ public function testEmptyName()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('name cannot be empty');
+
+ (new WantedAttributeBuilder())
+ ->withName('')
+ ->build();
+ }
+
+ /**
+ * @covers ::withAcceptSelfAsserted
+ * @covers \Yoti\ShareUrl\Policy\WantedAttribute::__construct
+ * @covers \Yoti\ShareUrl\Policy\WantedAttribute::jsonSerialize
+ * @covers \Yoti\ShareUrl\Policy\WantedAttribute::getAcceptSelfAsserted
+ */
+ public function testAcceptSelfAsserted()
+ {
+ $someName = 'some name';
+
+ $expectedJsonData = [
+ 'name' => $someName,
+ 'optional' => false,
+ 'accept_self_asserted' => true,
+ ];
+
+ $wantedAttributeDefault = (new WantedAttributeBuilder())
+ ->withName($someName)
+ ->withAcceptSelfAsserted(true)
+ ->build();
+
+ $this->assertEquals(json_encode($expectedJsonData), json_encode($wantedAttributeDefault));
+
+ $wantedAttribute = (new WantedAttributeBuilder())
+ ->withName($someName)
+ ->withAcceptSelfAsserted(true)
+ ->build();
+
+ $this->assertEquals(json_encode($expectedJsonData), json_encode($wantedAttribute));
+ }
+
+ /**
+ * @covers ::withAcceptSelfAsserted
+ * @covers \Yoti\ShareUrl\Policy\WantedAttribute::__construct
+ * @covers \Yoti\ShareUrl\Policy\WantedAttribute::jsonSerialize
+ * @covers \Yoti\ShareUrl\Policy\WantedAttribute::getAcceptSelfAsserted
+ */
+ public function testWithoutAcceptSelfAsserted()
+ {
+ $someName = 'some name';
+
+ $expectedJsonData = [
+ 'name' => $someName,
+ 'optional' => false,
+ 'accept_self_asserted' => false,
+ ];
+
+ $wantedAttribute = (new WantedAttributeBuilder())
+ ->withName($someName)
+ ->withAcceptSelfAsserted(false)
+ ->build();
+
+ $this->assertEquals(json_encode($expectedJsonData), json_encode($wantedAttribute));
+ }
+
+ /**
+ * @covers ::withAcceptSelfAsserted
+ * @covers ::withConstraints
+ * @covers \Yoti\ShareUrl\Policy\WantedAttribute::__construct
+ * @covers \Yoti\ShareUrl\Policy\WantedAttribute::jsonSerialize
+ * @covers \Yoti\ShareUrl\Policy\WantedAttribute::getAcceptSelfAsserted
+ */
+ public function testWithMultipleConstraints()
+ {
+ $sourceConstraint = (new SourceConstraintBuilder())
+ ->withWantedAnchor(new WantedAnchor('SOME'))
+ ->build();
+
+ $sourceConstraint2 = (new SourceConstraintBuilder())
+ ->withWantedAnchor(new WantedAnchor('SOME_2'))
+ ->build();
+
+
+ $constraints = [
+ $sourceConstraint,
+ $sourceConstraint2
+ ];
+
+ $wantedAttribute = (new WantedAttributeBuilder())
+ ->withName('someName')
+ ->withAcceptSelfAsserted(false)
+ ->withConstraints($constraints)
+ ->build();
+
+ $this->assertEquals($constraints, $wantedAttribute->getConstraints());
+ }
+}
diff --git a/tests/Identity/ReceiptItemKeyTest.php b/tests/Identity/ReceiptItemKeyTest.php
new file mode 100644
index 00000000..38190bbb
--- /dev/null
+++ b/tests/Identity/ReceiptItemKeyTest.php
@@ -0,0 +1,37 @@
+ $someId,
+ 'iv' => $someIv,
+ 'value' => $someValue
+ ];
+
+ $receiptItemKey = new ReceiptItemKey($sessionData);
+
+ $this->assertEquals($someId, $receiptItemKey->getId());
+ $this->assertEquals($someValue, $receiptItemKey->getValue());
+ }
+}
diff --git a/tests/Identity/ReceiptTest.php b/tests/Identity/ReceiptTest.php
new file mode 100644
index 00000000..adbc392e
--- /dev/null
+++ b/tests/Identity/ReceiptTest.php
@@ -0,0 +1,106 @@
+createMock(ApplicationContent::class);
+ $userContent = $this->createMock(UserContent::class);
+ $rememberId = 'SOME_REMEMBER_ID';
+ $parentRememberId = 'SOME_PARENT_REMEMBER_ID';
+ $someError = 'SOME_ERROR';
+
+ $receipt = new Receipt(
+ $someId,
+ $sessionId,
+ $someTime,
+ $applicationContent,
+ $userContent,
+ $rememberId,
+ $parentRememberId,
+ $someError
+ );
+
+ $this->assertEquals($someId, $receipt->getId());
+ $this->assertEquals($sessionId, $receipt->getSessionId());
+ $this->assertEquals($someTime, $receipt->getTimestamp());
+ $this->assertEquals($applicationContent, $receipt->getApplicationContent());
+ $this->assertEquals($userContent, $receipt->getUserContent());
+ $this->assertEquals($rememberId, $receipt->getRememberMeId());
+ $this->assertEquals($parentRememberId, $receipt->getParentRememberMeId());
+ $this->assertEquals($someError, $receipt->getError());
+ }
+
+ /**
+ * @covers \Yoti\Identity\ReceiptBuilder::withError
+ * @covers \Yoti\Identity\ReceiptBuilder::withApplicationContent
+ * @covers \Yoti\Identity\ReceiptBuilder::withId
+ * @covers \Yoti\Identity\ReceiptBuilder::withTimestamp
+ * @covers \Yoti\Identity\ReceiptBuilder::withSessionId
+ * @covers \Yoti\Identity\ReceiptBuilder::withParentRememberMeId
+ * @covers \Yoti\Identity\ReceiptBuilder::withRememberMeId
+ * @covers \Yoti\Identity\ReceiptBuilder::withUserContent
+ * @covers \Yoti\Identity\ReceiptBuilder::build
+ */
+ public function testShouldBuildCorrectlyThroughBuilder()
+ {
+ $someId = 'SOME_ID';
+ $sessionId = 'SESSION_ID';
+ $someTime = new \DateTime('2021-08-11 13:11:17');
+ $userProfile = $this->createMock(UserProfile::class);
+ $applicationProfile = $this->createMock(ApplicationProfile::class);
+ $rememberId = 'SOME_REMEMBER_ID';
+ $parentRememberId = 'SOME_PARENT_REMEMBER_ID';
+ $someError = 'SOME_ERROR';
+
+ $receipt = (new ReceiptBuilder())
+ ->withId($someId)
+ ->withSessionId($sessionId)
+ ->withTimestamp($someTime)
+ ->withUserContent($userProfile)
+ ->withApplicationContent($applicationProfile)
+ ->withRememberMeId($rememberId)
+ ->withParentRememberMeId($parentRememberId)
+ ->withError($someError)
+ ->build();
+
+
+ $this->assertEquals($someId, $receipt->getId());
+ $this->assertEquals($sessionId, $receipt->getSessionId());
+ $this->assertEquals($someTime, $receipt->getTimestamp());
+ $this->assertInstanceOf(ApplicationContent::class, $receipt->getApplicationContent());
+ $this->assertInstanceOf(UserContent::class, $receipt->getUserContent());
+ $this->assertEquals($rememberId, $receipt->getRememberMeId());
+ $this->assertEquals($parentRememberId, $receipt->getParentRememberMeId());
+ $this->assertEquals($someError, $receipt->getError());
+ }
+}
diff --git a/tests/Identity/ShareSessionCreatedQrCodeTest.php b/tests/Identity/ShareSessionCreatedQrCodeTest.php
new file mode 100644
index 00000000..c0fc50ba
--- /dev/null
+++ b/tests/Identity/ShareSessionCreatedQrCodeTest.php
@@ -0,0 +1,42 @@
+ self::SOME_ID,
+ 'uri' => self::SOME_URI,
+ 'failed' => 'failed'
+ ]);
+
+ $expected = [
+ 'id' => self::SOME_ID,
+ 'uri' => self::SOME_URI,
+ ];
+
+ $this->assertInstanceOf(ShareSessionCreatedQrCode::class, $qrCode);
+
+ $this->assertEquals(self::SOME_ID, $qrCode->getId());
+ $this->assertEquals(self::SOME_URI, $qrCode->getUri());
+
+ $this->assertEquals(json_encode($expected), json_encode($qrCode));
+ }
+}
diff --git a/tests/Identity/ShareSessionCreatedTest.php b/tests/Identity/ShareSessionCreatedTest.php
new file mode 100644
index 00000000..380be268
--- /dev/null
+++ b/tests/Identity/ShareSessionCreatedTest.php
@@ -0,0 +1,45 @@
+ self::SOME_ID,
+ 'status' => self::SOME_STATUS,
+ 'expiry' => self::SOME_EXPIRY,
+ 'failed' => 'SQL injection'
+ ]);
+
+ $expected = [
+ 'id' => self::SOME_ID,
+ 'status' => self::SOME_STATUS,
+ 'expiry' => self::SOME_EXPIRY,
+ ];
+
+ $this->assertInstanceOf(ShareSessionCreated::class, $shareSession);
+ $this->assertEquals(self::SOME_ID, $shareSession->getId());
+ $this->assertEquals(self::SOME_STATUS, $shareSession->getStatus());
+ $this->assertEquals(self::SOME_EXPIRY, $shareSession->getExpiry());
+ $this->assertEquals(json_encode($expected), json_encode($shareSession));
+ }
+}
diff --git a/tests/Identity/ShareSessionFetchedQrCodeTest.php b/tests/Identity/ShareSessionFetchedQrCodeTest.php
new file mode 100644
index 00000000..3be47c69
--- /dev/null
+++ b/tests/Identity/ShareSessionFetchedQrCodeTest.php
@@ -0,0 +1,76 @@
+ 'some', 'content' => 'content'],
+ ['type' => 'some2', 'content' => 'content2'],
+ ];
+
+ $shareSession = [
+ 'id' => 'some',
+ 'status' => 'status',
+ 'expiry' => 'expiry',
+ ];
+
+ $qrCode = new ShareSessionFetchedQrCode([
+ 'id' => self::SOME_ID,
+ 'expiry' => self::SOME_EXPIRY,
+ 'policy' => self::SOME_POLICY,
+ 'extensions' => $extensions,
+ 'session' => $shareSession,
+ 'redirectUri' => self::SOME_REDIRECT_URI,
+ ]);
+
+ $expected = [
+ 'id' => self::SOME_ID,
+ 'expiry' => self::SOME_EXPIRY,
+ 'policy' => self::SOME_POLICY,
+ 'extensions' => $extensions,
+ 'session' => $shareSession,
+ 'redirectUri' => self::SOME_REDIRECT_URI,
+ ];
+
+ $this->assertInstanceOf(ShareSessionFetchedQrCode::class, $qrCode);
+
+ $this->assertEquals(self::SOME_ID, $qrCode->getId());
+ $this->assertEquals(self::SOME_EXPIRY, $qrCode->getExpiry());
+ $this->assertEquals(self::SOME_POLICY, $qrCode->getPolicy());
+ $this->assertEquals(self::SOME_REDIRECT_URI, $qrCode->getRedirectUri());
+
+ $this->assertInstanceOf(ShareSessionCreated::class, $qrCode->getSession());
+
+ $this->assertContainsOnlyInstancesOf(Extension::class, $qrCode->getExtensions());
+
+ $this->assertEquals(self::SOME_REDIRECT_URI, $qrCode->getRedirectUri());
+
+ $this->assertEquals(json_encode($expected), json_encode($qrCode));
+ }
+}
diff --git a/tests/Identity/ShareSessionFetchedTest.php b/tests/Identity/ShareSessionFetchedTest.php
new file mode 100644
index 00000000..cc38224d
--- /dev/null
+++ b/tests/Identity/ShareSessionFetchedTest.php
@@ -0,0 +1,66 @@
+ self::SOME_ID,
+ 'status' => self::SOME_STATUS,
+ 'expiry' => self::SOME_EXPIRY,
+ 'created' => self::SOME_CREATED,
+ 'updated' => self::SOME_UPDATED,
+ 'failed' => 'SQL injection',
+ 'qrCode' => ['id' => self::SOME_QRCODE_ID],
+ 'receipt' => ['id' => self::SOME_RECEIPT_ID],
+ ]);
+
+ $expected = [
+ 'id' => self::SOME_ID,
+ 'status' => self::SOME_STATUS,
+ 'expiry' => self::SOME_EXPIRY,
+ 'created' => self::SOME_CREATED,
+ 'updated' => self::SOME_UPDATED,
+ 'qrCodeId' => self::SOME_QRCODE_ID,
+ 'receiptId' => self::SOME_RECEIPT_ID,
+ ];
+
+ $this->assertInstanceOf(ShareSessionFetched::class, $shareSession);
+ $this->assertEquals(self::SOME_ID, $shareSession->getId());
+ $this->assertEquals(self::SOME_STATUS, $shareSession->getStatus());
+ $this->assertEquals(self::SOME_EXPIRY, $shareSession->getExpiry());
+ $this->assertEquals(self::SOME_CREATED, $shareSession->getCreated());
+ $this->assertEquals(self::SOME_UPDATED, $shareSession->getUpdated());
+ $this->assertEquals(self::SOME_QRCODE_ID, $shareSession->getQrCodeId());
+ $this->assertEquals(self::SOME_RECEIPT_ID, $shareSession->getReceiptId());
+ $this->assertEquals(json_encode($expected), json_encode($shareSession));
+ }
+}
diff --git a/tests/Identity/ShareSessionNotificationBuilderTest.php b/tests/Identity/ShareSessionNotificationBuilderTest.php
new file mode 100644
index 00000000..ffee1e5e
--- /dev/null
+++ b/tests/Identity/ShareSessionNotificationBuilderTest.php
@@ -0,0 +1,80 @@
+ 'auth', 'header_3' => 'auth_3'];
+
+ /**
+ * @covers ::withUrl
+ * @covers ::withMethod
+ * @covers ::withHeader
+ * @covers ::withVerifyTls
+ * @covers ::build
+ * @covers \Yoti\Identity\ShareSessionNotification::getUrl
+ * @covers \Yoti\Identity\ShareSessionNotification::getHeaders
+ * @covers \Yoti\Identity\ShareSessionNotification::getMethod
+ * @covers \Yoti\Identity\ShareSessionNotification::getUrl
+ * @covers \Yoti\Identity\ShareSessionNotification::__construct
+ */
+ public function testShouldBuildCorrectly()
+ {
+ $shareNotification = (new ShareSessionNotificationBuilder())
+ ->withMethod()
+ ->withUrl(self::URL)
+ ->withHeader(self::HEADER_KEY, self::HEADER_VALUE)
+ ->withVerifyTls()
+ ->build();
+
+ $this->assertInstanceOf(ShareSessionNotification::class, $shareNotification);
+
+ $this->assertEquals(self::URL, $shareNotification->getUrl());
+ $this->assertEquals([self::HEADER_KEY => self::HEADER_VALUE], $shareNotification->getHeaders());
+ $this->assertEquals('POST', $shareNotification->getMethod());
+ }
+
+ /**
+ * @covers ::withUrl
+ * @covers ::withMethod
+ * @covers ::withHeaders
+ * @covers ::withVerifyTls
+ * @covers ::build
+ * @covers \Yoti\Identity\ShareSessionNotification::getHeaders
+ * @covers \Yoti\Identity\ShareSessionNotification::isVerifyTls
+ * @covers \Yoti\Identity\ShareSessionNotification::jsonSerialize
+ * @covers \Yoti\Identity\ShareSessionNotification::__construct
+ */
+ public function testShouldBuildCorrectlyWithMultipleHeaders()
+ {
+ $shareNotification = (new ShareSessionNotificationBuilder())
+ ->withMethod()
+ ->withUrl(self::URL)
+ ->withHeaders(self::HEADERS)
+ ->withVerifyTls(false)
+ ->build();
+
+ $expected = [
+ 'url' => self::URL,
+ 'method' => 'POST',
+ 'verifyTls' => false,
+ 'headers' => self::HEADERS,
+ ];
+
+ $this->assertEquals(self::HEADERS, $shareNotification->getHeaders());
+ $this->assertFalse($shareNotification->isVerifyTls());
+ $this->assertEquals(json_encode($expected), json_encode($shareNotification));
+ }
+}
diff --git a/tests/Identity/ShareSessionRequestBuilderTest.php b/tests/Identity/ShareSessionRequestBuilderTest.php
new file mode 100644
index 00000000..4be3a1ef
--- /dev/null
+++ b/tests/Identity/ShareSessionRequestBuilderTest.php
@@ -0,0 +1,100 @@
+extensionMock = $this->createMock(Extension::class);
+ $this->policyMock = $this->createMock(Policy::class);
+ }
+
+ /**
+ * @covers ::withRedirectUri
+ * @covers ::withPolicy
+ * @covers ::withExtension
+ * @covers ::withNotification
+ * @covers ::withSubject
+ * @covers ::build
+ * @covers \Yoti\Identity\ShareSessionRequest::getPolicy
+ * @covers \Yoti\Identity\ShareSessionRequest::getNotification
+ * @covers \Yoti\Identity\ShareSessionRequest::getExtensions
+ * @covers \Yoti\Identity\ShareSessionRequest::getSubject
+ * @covers \Yoti\Identity\ShareSessionRequest::__construct
+ */
+ public function testShouldBuildCorrectly()
+ {
+ $subject = [
+ 'key' => (object)['some' => 'good']
+ ];
+
+ $shareNotification = (new ShareSessionNotificationBuilder())
+ ->withMethod()
+ ->withUrl('some')
+ ->withHeader('some', 'some')
+ ->withVerifyTls()
+ ->build();
+
+ $shareRequest = (new ShareSessionRequestBuilder())
+ ->withSubject($subject)
+ ->withNotification($shareNotification)
+ ->withPolicy($this->policyMock)
+ ->withRedirectUri(self::URI)
+ ->withExtension($this->extensionMock)
+ ->build();
+
+ $this->assertInstanceOf(ShareSessionRequest::class, $shareRequest);
+
+ $this->assertEquals($subject, $shareRequest->getSubject());
+ $this->assertEquals([$this->extensionMock], $shareRequest->getExtensions());
+ $this->assertEquals($this->policyMock, $shareRequest->getPolicy());
+ $this->assertEquals($shareNotification, $shareRequest->getNotification());
+ $this->assertEquals(self::URI, $shareRequest->getRedirectUri());
+ }
+
+ /**
+ * @covers ::withRedirectUri
+ * @covers ::withPolicy
+ * @covers ::withExtensions
+ * @covers ::withNotification
+ * @covers ::withSubject
+ * @covers ::build
+ * @covers \Yoti\Identity\ShareSessionRequest::getExtensions
+ * @covers \Yoti\Identity\ShareSessionRequest::__construct
+ * @covers \Yoti\Identity\ShareSessionRequest::jsonSerialize
+ */
+ public function testShouldBuildCorrectlyWithMultipleExtensions()
+ {
+ $shareRequest = (new ShareSessionRequestBuilder())
+ ->withPolicy($this->policyMock)
+ ->withRedirectUri(self::URI)
+ ->withExtensions([$this->extensionMock])
+ ->build();
+
+
+ $expected = [
+ 'policy' => $this->policyMock,
+ 'redirectUri' => self::URI,
+ 'extensions' => [$this->extensionMock],
+ ];
+
+ $this->assertEquals([$this->extensionMock], $shareRequest->getExtensions());
+ $this->assertEquals(json_encode($expected), json_encode($shareRequest));
+ }
+}
diff --git a/tests/Profile/ServiceTest.php b/tests/Profile/ServiceTest.php
index dc5cc04c..3af3ed9c 100644
--- a/tests/Profile/ServiceTest.php
+++ b/tests/Profile/ServiceTest.php
@@ -139,6 +139,7 @@ public function testInvalidConnectToken()
* @covers ::getActivityDetails
* @covers ::decryptConnectToken
*/
+ /*
public function testWrongPemFile()
{
$this->expectException(\Yoti\Exception\ActivityDetailsException::class);
@@ -155,7 +156,7 @@ public function testWrongPemFile()
$profileService->getActivityDetails(file_get_contents(TestData::YOTI_CONNECT_TOKEN));
}
-
+*/
/**
* @covers ::getActivityDetails
*/
diff --git a/tests/Util/JsonTest.php b/tests/Util/JsonTest.php
index c215ce98..df232c6e 100644
--- a/tests/Util/JsonTest.php
+++ b/tests/Util/JsonTest.php
@@ -75,4 +75,36 @@ public function testWithoutNullValues()
$this->assertArrayNotHasKey('other_key', $withoutNull);
}
+
+ /**
+ * @covers ::convertFromLatin1ToUtf8Recursively
+ */
+ public function testConvertFromLatin1ToUtf8Recursively()
+ {
+ $latin1String = utf8_decode('éàê');
+ $latin1Array = [utf8_decode('éàê'), utf8_decode('çî')];
+ $nestedLatin1Array = [utf8_decode('éàê'), [utf8_decode('çî'), utf8_decode('üñ')]];
+
+ $latin1Object = new \stdClass();
+ $latin1Object->property1 = utf8_decode('éàê');
+ $latin1Object->property2 = utf8_decode('çî');
+
+ $nestedLatin1Object = new \stdClass();
+ $nestedLatin1Object->property = utf8_decode('çî');
+ $latin1ObjectWithNestedObject = new \stdClass();
+ $latin1ObjectWithNestedObject->property1 = utf8_decode('éàê');
+ $latin1ObjectWithNestedObject->property2 = $nestedLatin1Object;
+
+ $this->assertSame('éàê', Json::convertFromLatin1ToUtf8Recursively($latin1String));
+ $this->assertSame(['éàê', 'çî'], Json::convertFromLatin1ToUtf8Recursively($latin1Array));
+ $this->assertSame(['éàê', ['çî', 'üñ']], Json::convertFromLatin1ToUtf8Recursively($nestedLatin1Array));
+
+ $utf8Object = Json::convertFromLatin1ToUtf8Recursively($latin1Object);
+ $this->assertSame('éàê', $utf8Object->property1);
+ $this->assertSame('çî', $utf8Object->property2);
+
+ $utf8NestedObject = Json::convertFromLatin1ToUtf8Recursively($latin1ObjectWithNestedObject);
+ $this->assertSame('éàê', $utf8NestedObject->property1);
+ $this->assertSame('çî', $utf8NestedObject->property2->property);
+ }
}
diff --git a/tests/YotiClientTest.php b/tests/YotiClientTest.php
index 24ddaa36..9f345a5a 100755
--- a/tests/YotiClientTest.php
+++ b/tests/YotiClientTest.php
@@ -204,6 +204,7 @@ public function testCreateShareUrl()
$this->assertInstanceOf(ShareUrlResult::class, $result);
}
+
/**
* @covers ::getLoginUrl
*/