FSD0R%Q%SY^O!Ey3n{Mf9>T`Tid#`
z)B2H!N?k4LJhGu+Q;JBs7IhR+52`tW4?3N$l`TLz^H;g?`y*LuDyK%)Jmw%p&*KbR
znihv<@~dtcvb-MKeJ8HM-g$#U?7Xf+e19BXSDQNUU_dNS_J&J@of^c|fTp5mh;GJ=
zGYX0+{655nV8uT$n4Pap=lhMGhSF@)V2c*bad#q%*3sSi&D2)7DKd
z?~^-5CQbHOM#QGF#b#Zah8e$K3pgg_)drF8t6ki2k&D@%Ki!}VJ)+DkLfv2CEPC!Db
zmnQ0UAL^HE(7ouN1Pzu=6LFrzw5Q8I^9p7)AI_!&*cvyhd8e)&C?`{U(nQ&5=hC}u
zPQw0PG9NvbD0zmbqx=AAG{1PXRpiW8cXoleuDI}6K~BWie)oj0)}b+qPxw^F;w26K
zyp298&7ar6cU68!fLuGyyx(OPIVsSNbc6r`XpHo9w()a1#Z
z-P{UH1YD@FPfywE?L@)E07fLN)SQ`^)Jpfe2*SC_S;7_%7=5X)c=btXVt$aD{OEAy
zuY!fTY?sEeCHvS&q9AZLbPHNp&n0da#+v~Yt8;~E{G7(#cGD7R1y(%o<5`@ciK+Ga
zM+OniHFg*37t}~KAOq$<(AqAP_dlOb?u~FcbkftJ$4>PtU`j9Q5P8V-x?ynASYla+
z6O+XUo%15nKoQ+J+byM331Z`lV~oZbpcdOh2~XB=xKo^Bh#>xGsPO)BuqVYKI}V%@
zT`4BDtqNd#816)sg5cgMfQg1My*4|23jkK{lqH=Hd-Ryy|G<)wbneM)MWQz-FXy$q
z`@u?FH5gMDIhW^h{+?2kkd&ae4bc8C31+0xD}JMpmeX`x)P&Oaup}EH+5VC^ffUW;
zuFLg-E0sT)+RZmUaLLtox(u=f*~To`%RzLkJ-G+`Q5Z#PX~wwI`|{t!jdA@+USv>?
z!H@KB4xsTn%dAY)i6r@Zp)3p`LL)m89e>kZ=Jq)HDUB^Lb8kFRKzmMfg2BjU?K$tm
zDzR5{H-kOE$Z1D2);xZbC^AoIxT`Zpt*!vX_2p{kox+&j9z!l-x>)v
zn>W5i#W5t4;gvXzKN?M@_Djpp)+BKXObG6!$;e|V?jgxi(#c(vbQl~F+_Hr(XSweY
zLPFxcvD5B2larc=#cV&wAch)ZAo}wl2K|&Js!k|DF!kIE<^$rm_2@S<;gf$6=Rq-f
z@5w?u-kq+}DI%jc`$_o@EfR>n%;;?>>iTlAsV`SGpXeF-q5ZfN=wuR#)~
zso&=uTAx@L?cCy!6pj&wd<><-z(j_fC%5A3@2Nv4SL>;JX>1w@Y=MsEc65lY@!mwV
z5oJ3BkXJ^c8})m8^@GSJG#KWjF}OMjX!kJN$)CD%-UZ;0jPR9rHXmNx`Gs)_IXiWs
z@zl7CoDnv{B}n7+?sAl4r=os>8d>~8A7b7ts%XfFOn+2oD+a69|z-qn8NwWWMFH>9ASfD)>#`@
zTMY>LyH6MMG#1}O_u#WGr1E$5LU)GVQi7HJ3$a_BRDTQP8h4ESBa^o8JLw5upLVcb
z(>Hl&x}Qmi8D}{PlH<`bDfJ0e==5fNV7ecDU?%p&jlI>4_w3v2V5pARUvQrTfUdq2
z@2LYSq>PJb#`7xdQ#a>=F21UdAE|j!U452p7IL~R4;Z!d^#Og7B-No;QLsC^d16x*
zYvc0u=v7>T)dgf%cqq#u#sn-^#eTQbx^OJkI~mb&h2ge}t8`VpbM>85qDY0Q$EDO*
z6EVUBO|by@<|JB;kB>U`y4^PLcdL@&bxGa>#Ll=(ysp~gKq_k7+`3ZqKzt_uT*6Z*cT#JPlnS7)}{uFvfo8kWjxnX}z5H2DdiOlV&UPSLJHjH&ON^Ql(`e7RJhQ1GZmXU#ZOqJLscF
zj`$0Pe4O9o)n0K{zjbFm=2$eS;kf9*#|mQ}KntZyjl-jeW2#rihljF-;6=p|x4kLl
z2S$!EV*eV;imL}8af^yOeS!je)ANo!Pak17YF;%9_@~ege5%@CoRJZO?|<;r@ag3x
zaQmYFXzD+6BgKtnDGOY=U&`CMG6-W;L(5iVfL2Zjq_^G9HNj7m@F$c63&=*)tS62j->fIJjwQ|k3IV2
zwLd@SzwDZ!e<1evLx}y?Be*}_z+an`KJOS^){f6c-xBo-N!k%=JrTRh%DPfZM>i7C
z-YO@=&Cffu>+7NXh?Lm_Y^o6~s>-7dBAL&l%0(a`bhMNe#Xd<2{KDKEA4oj{>Y*F`
zL7b`xBDz1p<0{J&HO>Uop5C&itU2h}wP*B2I_1KayQ4I!ea&{zF1v&Fw#QR)zI%i{
zjzf{;P{(+*iVoSDI_z$E{n@i2Sf*rV^^#FIv){?63{V*&6y?I)bYBm!<@4X8pwuNt
zfg~zcI#|q`NjrHVFbudVz_tju%wQ{?GWb?0d3ba^afHZdR3Z}#P1OsAin@}1?aU>K
zWE|%GTH2EzHrxX`c6vHzz4cz?z(zyQzv#FMG5Et3fuo!GqCDJR0?-hZ8xkbePB
zC`+7#pmbqUyTDw5C)d5x>#c#1prrX^A74W(`y&oziL_cqbRuQ8(!ovEJppyS@|D
z)#!1=f<+(oLPJu*a6INUj(spZ4d)>&G0AsP?c7$&&Wl5#$y(9C?2WYKRsL5j{2hJ{P+%6}`CIuOS(Z&X6_bEHmP(N3<_`*p(
zzJ<%9%owFAZ@`Jp_C!;nNyMnMa;Vq-QZja=-6Z#yTaJp98>Pn1UE^UEtV(3!p$U7O
z+M(JK;73Q5R`$ivbgzFfwx#Yc&cxFu-Ayyz{-gy&Z1qw>o&Xw;(xJ4(AP>1zViMqI
z?kR+C{bM{Lq8Z?hF-OFX`I#=_{YTjx68Og>%FNGg|CiAJ;L;6tDCsc>{9k2$UEHzv
zG+1h(naB57Z~lFtiu(>Heyy767NZ(*J7UH$1r@43MLn4!Zi#u@YHs!B(SKurry
zI)e6`0t_a$7jDu&QQz&hsa!41*j1OuGCvNeh$O~V-`n0)_>UaW9az+husbHM0+-SU
z5e^9oXcElm!swqfvN2x^;=jmL37G`2O#+VenJusT&|>^NwWx#7L
+
+
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 0000000000000000000000000000000000000000..c60227fabf339e9540e5daac4d2d25e121137752
GIT binary patch
literal 2988
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
+ */
+
+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/advancedidentity.blade.php b/examples/digitalidentity/resources/views/advancedidentity.blade.php
new file mode 100644
index 00000000..289e9e1e
--- /dev/null
+++ b/examples/digitalidentity/resources/views/advancedidentity.blade.php
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+ {{ $title }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/digitalidentity/resources/views/dbs.blade.php b/examples/digitalidentity/resources/views/dbs.blade.php
new file mode 100644
index 00000000..1359cc7c
--- /dev/null
+++ b/examples/digitalidentity/resources/views/dbs.blade.php
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+ {{ $title }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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..ec1dc60d
--- /dev/null
+++ b/examples/digitalidentity/resources/views/partial/report.blade.php
@@ -0,0 +1,54 @@
+@if (isset($key) && is_array($key))
+ @foreach ($report as $key => $value)
+
+
+
+
+ {{ $key }}
+
+
+
+
+ @foreach ($value as $name => $result)
+ @if (isset($result) && 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
+@else
+
+ @foreach ($report as $key => $value)
+
+
+ {{ $key }}
+
+ {!! json_encode($value, JSON_PRETTY_PRINT) !!}
+
+
+
+ @endforeach
+
+@endif
diff --git a/examples/digitalidentity/resources/views/receipt.blade.php b/examples/digitalidentity/resources/views/receipt.blade.php
new file mode 100644
index 00000000..b166dbdd
--- /dev/null
+++ b/examples/digitalidentity/resources/views/receipt.blade.php
@@ -0,0 +1,133 @@
+
+
+
+
+
+ Yoti client example
+
+
+
+
+
+
+
+
+
+
Powered by
+
+
+
+
+
+
+ @if ($selfie)
+
+
+
+
+ @endif
+
+ @if ($fullName)
+
+ {{ $fullName->getValue() }}
+
+ @endif
+
+
+
+
+
+
+
+
+ @if ($error)
+
+
+
+
+
+
+
+ @endif
+
+
+
+
+
+ @if(@$profileAttributes)
+ @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
+ @endif
+
+
+
+
+
+
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..3fb377e7
--- /dev/null
+++ b/examples/digitalidentity/routes/web.php
@@ -0,0 +1,22 @@
+withSources($searchProfileSources)
->withShareUrl(false)
->withRemoveDeceased(true)
- ->withApiKey('qiKTHG7Mgqj31mK2d21F7QPpaVBp9zKc')
+ ->withApiKey('api-key')
->withClientRef("string")
->withMonitoring(true)
->withTags(['tag1'])
@@ -77,18 +77,30 @@ public function show(Request $request, DocScanClient $client)
->withRemoveDeceased(true)
->build();
+ //Identity Profile Requeirements Object
+ /*$identityProfileRequirements = (object)[
+ 'trust_framework' => 'UK_TFIDA',
+ 'scheme' => [
+ 'type' => 'DBS',
+ 'objective' => 'BASIC'
+ ]
+ ];*/
$sessionSpec = (new SessionSpecificationBuilder())
->withClientSessionTokenTtl(600)
- ->withResourcesTtl(90000)
+ ->withResourcesTtl(604800)
->withUserTrackingId('some-user-tracking-id')
+ //For Identity Profile Requirements Object
+ //->withBlockBiometricConsent(false) //User needs to provide consent for the liveness detection
+ //->withIdentityProfileRequirements($identityProfileRequirements)
->withRequestedCheck(
(new RequestedDocumentAuthenticityCheckBuilder())
->build()
)
->withRequestedCheck(
(new RequestedLivenessCheckBuilder())
- ->forZoomLiveness()
+ ->forStaticLiveness()
+ ->withMaxRetries(3)
->build()
)
->withRequestedCheck(
@@ -98,7 +110,7 @@ public function show(Request $request, DocScanClient $client)
)
->withRequestedCheck(
(new RequestedFaceMatchCheckBuilder())
- ->withManualCheckAlways()
+ ->withManualCheckFallback()
->build()
)
->withRequestedCheck(
@@ -116,20 +128,20 @@ public function show(Request $request, DocScanClient $client)
)
->withRequestedTask(
(new RequestedTextExtractionTaskBuilder())
- ->withManualCheckAlways()
+ ->withManualCheckFallback()
->withChipDataDesired()
->withCreateExpandedDocumentFields(true)
->build()
)
->withRequestedTask(
(new RequestedSupplementaryDocTextExtractionTaskBuilder())
- ->withManualCheckAlways()
+ ->withManualCheckFallback()
->build()
)
->withSdkConfig(
(new SdkConfigBuilder())
->withAllowsCameraAndUpload()
- ->withPrimaryColour('#2d9fff')
+ ->withPrimaryColour('#2875BC')
->withSecondaryColour('#FFFFFF')
->withFontColour('#FFFFFF')
->withLocale('en-GB')
diff --git a/examples/doc-scan/resources/views/success.blade.php b/examples/doc-scan/resources/views/success.blade.php
index 0e49c218..63b0b486 100644
--- a/examples/doc-scan/resources/views/success.blade.php
+++ b/examples/doc-scan/resources/views/success.blade.php
@@ -293,7 +293,61 @@
@endif
-
+ @if (isset($sessionResult))
+ @if ($sessionResult->getIdentityProfile() != null)
+ @if ($sessionResult->getIdentityProfile()->getFailureReason() != null)
+ @if ($sessionResult->getIdentityProfile()->getFailureReason()->getReasonCode())
+
+
+
Identity Result Error
+
+
+ @if ($sessionResult->getIdentityProfile()->getFailureReason()->getReasonCode())
+
+
+
+ Reason Code
+
+ {{$sessionResult->getIdentityProfile()->getFailureReason()->getReasonCode()}}
+
+
+
+ Failure Type
+
+ {{$sessionResult->getIdentityProfile()->getFailureReason()->getRequirementNotMetDetails()->getFailureType()}}
+
+
+
+ Details
+
+ {{$sessionResult->getIdentityProfile()->getFailureReason()->getRequirementNotMetDetails()->getDetails()}}
+
+
+
+ Audit Id
+
+ {{$sessionResult->getIdentityProfile()->getFailureReason()->getRequirementNotMetDetails()->getAuditId()}}
+
+
+
+ Country ISO Code
+
+ {{$sessionResult->getIdentityProfile()->getFailureReason()->getRequirementNotMetDetails()->getDocumentCountryIsoCode()}}
+
+
+
+ Document Type
+
+ {{$sessionResult->getIdentityProfile()->getFailureReason()->getRequirementNotMetDetails()->getDocumentType()}}
+
+
+
+
+ @endif
+ @endif
+ @endif
+ @endif
+ @endif
@if (count($sessionResult->getResources()->getIdDocuments()) > 0)
diff --git a/examples/profile/app/Http/Controllers/AdvancedIdentityController.php b/examples/profile/app/Http/Controllers/AdvancedIdentityController.php
new file mode 100644
index 00000000..25596559
--- /dev/null
+++ b/examples/profile/app/Http/Controllers/AdvancedIdentityController.php
@@ -0,0 +1,68 @@
+ [(object)[
+
+ "trust_framework" => "YOTI_GLOBAL",
+ "schemes" => [(object)[
+
+ "label" => "identity-AL-L1",
+ "type" => "IDENTITY",
+ "objective" => "AL_L1"
+ ],
+ [
+ "label" => "identity-AL-M1",
+ "type" => "IDENTITY",
+ "objective" => "AL_M1"
+ ]
+ ]
+ ]
+ ]
+ ]
+ ;
+
+ $policy = (new DynamicPolicyBuilder())
+ ->withAdvancedIdentityProfileRequirements($advancedIdentityProfileJson)
+ ->build();
+
+ $dynamicScenario = (new DynamicScenarioBuilder())
+ ->withCallbackEndpoint("/profile")
+ ->withPolicy($policy)
+ ->withSubject((object)[
+ 'subject_id' => "some_subject_id_string"
+ ])
+ ->build();
+
+ return view('advanced', [
+ 'title' => 'Advanced Identity Share Example',
+ 'buttonConfig' => [
+ 'elements' => [
+ [
+ 'domId' => 'yoti-share-button',
+ 'clientSdkId' => config('yoti')['client.sdk.id'],
+ 'shareUrl' => $client->createShareUrl($dynamicScenario)->getShareUrl(),
+ 'button' => [
+ 'label' => 'Use Yoti',
+ 'align' => 'center',
+ 'width' => 'auto',
+ 'verticalAlign' => 'top'
+ ],
+ 'type' => 'modal'
+ ]
+ ]
+ ]
+ ]);
+ }
+}
diff --git a/examples/profile/resources/views/advanced.blade.php b/examples/profile/resources/views/advanced.blade.php
new file mode 100644
index 00000000..f158d88b
--- /dev/null
+++ b/examples/profile/resources/views/advanced.blade.php
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
{{ $title }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/profile/resources/views/partial/report.blade.php b/examples/profile/resources/views/partial/report.blade.php
index 707abe99..d3466436 100644
--- a/examples/profile/resources/views/partial/report.blade.php
+++ b/examples/profile/resources/views/partial/report.blade.php
@@ -1,40 +1,54 @@
-@foreach ($report as $key => $value)
-
-
-
-
- {{ $key }}
-
-
-
-
- @if (isset($value) && is_array($value))
- @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
-
+@if (isset($key) && is_array($key))
+ @foreach ($report as $key => $value)
+
+
+
+
+ {{ $key }}
+
+
+
+
+ @foreach ($value as $name => $result)
+ @if (isset($result) && 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
+
+ @endif
+ @endforeach
+
+
+
+ @endforeach
+@else
+
+ @foreach ($report as $key => $value)
+
+
+ {{ $key }}
+
+ {!! json_encode($value, JSON_PRETTY_PRINT) !!}
+
+
+
@endforeach
- @endif
-
- @endforeach
+@endif
\ No newline at end of file
diff --git a/examples/profile/routes/web.php b/examples/profile/routes/web.php
index 9565f235..4a577e36 100644
--- a/examples/profile/routes/web.php
+++ b/examples/profile/routes/web.php
@@ -20,3 +20,4 @@
Route::get('/dynamic-share', 'DynamicShareController@show');
Route::get('/dbs-check', 'DbsCheckController@show');
+Route::get('/advanced-identity', 'AdvancedIdentityController@show');
diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
index e69de29b..c369e4e0 100644
--- a/phpstan-baseline.neon
+++ b/phpstan-baseline.neon
@@ -0,0 +1,3 @@
+parameters:
+ ignoreErrors:
+ - '#Variable property access on (.*)#'
diff --git a/src/Constants.php b/src/Constants.php
index 300def72..b1cb4499 100644
--- a/src/Constants.php
+++ b/src/Constants.php
@@ -15,6 +15,12 @@ class Constants
/** Environment variable to override the default API URL */
public const ENV_API_URL = 'YOTI_API_URL';
+ /** Default Digital Identity API URL */
+ public const DIGITAL_IDENTITY_API_URL = self::API_BASE_URL . '/share';
+
+ /** Environment variable to override the default Digital Identity API URL */
+ public const ENV_DIGITAL_IDENTITY_API_URL = 'YOTI_DIGITAL_IDENTITY_API_URL';
+
/** Default Doc Scan API URL */
public const DOC_SCAN_API_URL = self::API_BASE_URL . '/idverify/v1';
@@ -25,7 +31,7 @@ class Constants
public const SDK_IDENTIFIER = 'PHP';
/** Default SDK version */
- public const SDK_VERSION = '4.2.2';
+ public const SDK_VERSION = '4.3.0';
/** Base url for connect page (user will be redirected to this page eg. baseurl/app-id) */
public const CONNECT_BASE_URL = 'https://www.yoti.com/connect';
diff --git a/src/DigitalIdentityClient.php b/src/DigitalIdentityClient.php
new file mode 100644
index 00000000..0125cd96
--- /dev/null
+++ b/src/DigitalIdentityClient.php
@@ -0,0 +1,119 @@
+
+ */
+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/GetSessionResult.php b/src/DocScan/Session/Retrieve/GetSessionResult.php
index 919053c3..857e3263 100644
--- a/src/DocScan/Session/Retrieve/GetSessionResult.php
+++ b/src/DocScan/Session/Retrieve/GetSessionResult.php
@@ -313,7 +313,11 @@ function ($checkResponse) use ($class): bool {
public function getIdentityProfile(): ?IdentityProfileResponse
{
- return $this->identityProfile;
+ if (isset($this->identityProfile)) {
+ return $this->identityProfile;
+ } else {
+ return null;
+ }
}
public function getIdentityProfilePreview(): ?IdentityProfilePreviewResponse
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/DocScan/Session/Retrieve/IdentityProfile/FailureReasonResponse.php b/src/DocScan/Session/Retrieve/IdentityProfile/FailureReasonResponse.php
index f674263d..59026c57 100644
--- a/src/DocScan/Session/Retrieve/IdentityProfile/FailureReasonResponse.php
+++ b/src/DocScan/Session/Retrieve/IdentityProfile/FailureReasonResponse.php
@@ -7,21 +7,32 @@ class FailureReasonResponse
/**
* @var string
*/
- private $stringCode;
-
+ private $reasonCode;
+ /**
+ * @var RequirementNotMetDetails
+ */
+ private $requirementsNotMetDetails;
/**
- * @param string $stringCode
+ * @param array $data
*/
- public function __construct(string $stringCode)
+ public function __construct(array $data)
{
- $this->stringCode = $stringCode;
+ $this->reasonCode = $data["reason_code"];
+ $this->requirementsNotMetDetails = new RequirementNotMetDetails($data["requirements_not_met_details"]);
}
/**
* @return string
*/
- public function getStringCode(): string
+ public function getReasonCode(): string
+ {
+ return $this->reasonCode;
+ }
+ /**
+ * @return RequirementNotMetDetails
+ */
+ public function getRequirementNotMetDetails(): RequirementNotMetDetails
{
- return $this->stringCode;
+ return $this->requirementsNotMetDetails;
}
}
diff --git a/src/DocScan/Session/Retrieve/IdentityProfile/RequirementNotMetDetails.php b/src/DocScan/Session/Retrieve/IdentityProfile/RequirementNotMetDetails.php
new file mode 100644
index 00000000..2f9f4113
--- /dev/null
+++ b/src/DocScan/Session/Retrieve/IdentityProfile/RequirementNotMetDetails.php
@@ -0,0 +1,75 @@
+> $data
+ */
+ public function __construct(array $data)
+ {
+ $this->failureType = $data[0]["failure_type"] ?? '';
+ $this->details = $data[0]["details"] ?? '';
+ $this->auditId = $data[0]["audit_id"] ?? '';
+ $this->documentCountryIsoCode = $data[0]["document_country_iso_code"] ?? '';
+ $this->documentType = $data[0]["document_type"] ?? '';
+ }
+
+ /**
+ * @return string
+ */
+ public function getFailureType(): string
+ {
+ return $this->failureType;
+ }
+ /**
+ * @return string
+ */
+ public function getDetails(): string
+ {
+ return $this->details;
+ }
+ /**
+ * @return string
+ */
+ public function getAuditId(): string
+ {
+ return $this->auditId;
+ }
+ /**
+ * @return string
+ */
+ public function getDocumentCountryIsoCode(): string
+ {
+ return $this->documentCountryIsoCode;
+ }
+ /**
+ * @return string
+ */
+ public function getDocumentType(): string
+ {
+ return $this->documentType;
+ }
+}
diff --git a/src/DocScan/Session/Retrieve/IdentityProfileResponse.php b/src/DocScan/Session/Retrieve/IdentityProfileResponse.php
index b7eb2fd0..06bea9c2 100644
--- a/src/DocScan/Session/Retrieve/IdentityProfileResponse.php
+++ b/src/DocScan/Session/Retrieve/IdentityProfileResponse.php
@@ -31,11 +31,11 @@ class IdentityProfileResponse
*/
public function __construct(array $sessionData)
{
- $this->subjectId = $sessionData['subject_id'];
+ $this->subjectId = $sessionData['subject_id'] ?? '';
$this->result = $sessionData['result'];
if (isset($sessionData['failure_reason'])) {
- $this->failureReason = new FailureReasonResponse($sessionData['failure_reason']['reason_code']);
+ $this->failureReason = new FailureReasonResponse($sessionData['failure_reason']);
}
if (isset($sessionData['identity_profile_report'])) {
@@ -62,7 +62,7 @@ public function getResult(): string
/**
* @return FailureReasonResponse
*/
- public function getFailureReason(): FailureReasonResponse
+ public function getFailureReason(): ?FailureReasonResponse
{
return $this->failureReason;
}
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/ErrorReason.php b/src/Identity/ErrorReason.php
new file mode 100644
index 00000000..787fbf1e
--- /dev/null
+++ b/src/Identity/ErrorReason.php
@@ -0,0 +1,37 @@
+> $data
+ */
+ public function __construct(array $data)
+ {
+ if (isset($data[0])) {
+ $this->requirementNotMetDetails = new RequirementNotMetDetails($data);
+ } else {
+ $this->requirementNotMetDetails = new RequirementNotMetDetails([[
+ "failure_type" => '',
+ "details" => '',
+ "audit_id" => '',
+ "document_country_iso_code" => '',
+ "document_type" => ''
+ ]]);
+ }
+ }
+
+ /**
+ * @return RequirementNotMetDetails
+ */
+ public function getRequirementNotMetDetails(): RequirementNotMetDetails
+ {
+ return $this->requirementNotMetDetails;
+ }
+}
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..5c54f2fa
--- /dev/null
+++ b/src/Identity/Policy/Policy.php
@@ -0,0 +1,94 @@
+wantedAttributes = $wantedAttributes;
+
+ Validation::isArrayOfIntegers($wantedAuthTypes, 'wantedAuthTypes');
+ $this->wantedAuthTypes = $wantedAuthTypes;
+
+ $this->wantedRememberMe = $wantedRememberMe;
+ $this->wantedRememberMeOptional = $wantedRememberMeOptional;
+ $this->identityProfileRequirements = $identityProfileRequirements;
+ $this->advancedIdentityProfileRequirements = $advancedIdentityProfileRequirements;
+ }
+
+
+ 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,
+ 'advanced_identity_profile_requirements' => $this->advancedIdentityProfileRequirements,
+ ];
+ }
+
+ /**
+ * IdentityProfileRequirements requested in the policy
+ *
+ * @return object|null
+ */
+ public function getIdentityProfileRequirements()
+ {
+ return $this->identityProfileRequirements;
+ }
+
+ /**
+ * AdvancedIdentityProfileRequirements requested in the policy
+ *
+ * @return object|null
+ */
+ public function getAdvancedIdentityProfileRequirements()
+ {
+ return $this->advancedIdentityProfileRequirements;
+ }
+}
diff --git a/src/Identity/Policy/PolicyBuilder.php b/src/Identity/Policy/PolicyBuilder.php
new file mode 100644
index 00000000..a3b8f479
--- /dev/null
+++ b/src/Identity/Policy/PolicyBuilder.php
@@ -0,0 +1,345 @@
+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;
+ }
+
+ /**
+ * Use an Advanced Identity Profile Requirement object for the share
+ *
+ * @param object $advancedIdentityProfileRequirements
+ * @return $this
+ */
+ public function withAdvancedIdentityProfileRequirements($advancedIdentityProfileRequirements): self
+ {
+ $this->advancedIdentityProfileRequirements = $advancedIdentityProfileRequirements;
+ return $this;
+ }
+
+ public function build(): Policy
+ {
+ return new Policy(
+ array_values($this->wantedAttributes),
+ array_values($this->wantedAuthTypes),
+ $this->wantedRememberMe,
+ $this->wantedRememberMeOptional,
+ $this->identityProfileRequirements,
+ $this->advancedIdentityProfileRequirements
+ );
+ }
+}
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;
+ $this->errorReason = $errorReason;
+ }
+
+ 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
+ {
+ if ($this->userContent !== null) {
+ return $this->userContent->getProfile();
+ } else {
+ return null;
+ }
+ }
+
+ public function getExtraData(): ?ExtraData
+ {
+ if ($this->userContent !== null) {
+ return $this->userContent->getExtraData();
+ } else {
+ return null;
+ }
+ }
+
+ 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;
+ }
+
+ public function getErrorReason(): ?ErrorReason
+ {
+ return $this->errorReason;
+ }
+}
diff --git a/src/Identity/ReceiptBuilder.php b/src/Identity/ReceiptBuilder.php
new file mode 100644
index 00000000..0851a088
--- /dev/null
+++ b/src/Identity/ReceiptBuilder.php
@@ -0,0 +1,108 @@
+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 withErrorReason(ErrorReason $errorReason = null): self
+ {
+ $this->errorReason = $errorReason;
+
+ 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,
+ $this->errorReason
+ );
+ }
+}
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..904ef7b7
--- /dev/null
+++ b/src/Identity/ReceiptParser.php
@@ -0,0 +1,169 @@
+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())
+ ->withErrorReason($wrappedReceipt->getErrorReason())
+ ->build();
+ }
+
+ private function decryptReceiptKey(?string $wrappedKey, ReceiptItemKey $wrappedItemKey, PemFile $pemFile): string
+ {
+ if ($wrappedKey == null) {
+ throw new EncryptedDataException('Wrapped is null');
+ }
+ // 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/RequirementNotMetDetails.php b/src/Identity/RequirementNotMetDetails.php
new file mode 100644
index 00000000..bcd3e40e
--- /dev/null
+++ b/src/Identity/RequirementNotMetDetails.php
@@ -0,0 +1,75 @@
+> $data
+ */
+ public function __construct(array $data)
+ {
+ $this->failureType = $data[0]["failure_type"] ?? '';
+ $this->details = $data[0]["details"] ?? '';
+ $this->auditId = $data[0]["audit_id"] ?? '';
+ $this->documentCountryIsoCode = $data[0]["document_country_iso_code"] ?? '';
+ $this->documentType = $data[0]["document_type"] ?? '';
+ }
+
+ /**
+ * @return string
+ */
+ public function getFailureType(): string
+ {
+ return $this->failureType;
+ }
+ /**
+ * @return string
+ */
+ public function getDetails(): string
+ {
+ return $this->details;
+ }
+ /**
+ * @return string
+ */
+ public function getAuditId(): string
+ {
+ return $this->auditId;
+ }
+ /**
+ * @return string
+ */
+ public function getDocumentCountryIsoCode(): string
+ {
+ return $this->documentCountryIsoCode;
+ }
+ /**
+ * @return string
+ */
+ public function getDocumentType(): string
+ {
+ return $this->documentType;
+ }
+}
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..28e93f60
--- /dev/null
+++ b/src/Identity/WrappedReceipt.php
@@ -0,0 +1,156 @@
+ $sessionData
+ */
+ public function __construct(array $sessionData)
+ {
+ $this->id = $sessionData['id'];
+ $this->sessionId = $sessionData['sessionId'];
+ $this->timestamp = DateTime::stringToDateTime($sessionData['timestamp']);
+ $this->wrappedItemKeyId = $sessionData['wrappedItemKeyId'] ?? null;
+ $this->wrappedKey = $sessionData['wrappedKey'] ?? null;
+
+ 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'];
+ }
+ if (isset($sessionData['errorReason'])) {
+ if (isset($sessionData["errorReason"]["requirements_not_met_details"])) {
+ $this->errorReason = new ErrorReason(
+ $sessionData["errorReason"]["requirements_not_met_details"]
+ );
+ }
+ }
+ }
+
+ 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;
+ }
+
+ public function getErrorReason(): ?ErrorReason
+ {
+ return $this->errorReason;
+ }
+
+ 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/UserProfile.php b/src/Profile/UserProfile.php
index d9ac0210..e4189a56 100644
--- a/src/Profile/UserProfile.php
+++ b/src/Profile/UserProfile.php
@@ -28,7 +28,6 @@ class UserProfile extends BaseProfile
public const ATTR_DOCUMENT_IMAGES = 'document_images';
public const ATTR_STRUCTURED_POSTAL_ADDRESS = 'structured_postal_address';
public const ATTR_IDENTITY_PROFILE_REPORT = 'identity_profile_report';
-
/** @var \Yoti\Profile\Attribute\AgeVerification[] */
private $ageVerifications;
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/ShareUrl/Policy/DynamicPolicy.php b/src/ShareUrl/Policy/DynamicPolicy.php
index 9313e4d8..7e6e81b8 100644
--- a/src/ShareUrl/Policy/DynamicPolicy.php
+++ b/src/ShareUrl/Policy/DynamicPolicy.php
@@ -33,6 +33,11 @@ class DynamicPolicy implements \JsonSerializable
*/
private $identityProfileRequirements;
+ /**
+ * @var object|null
+ */
+ private $advancedIdentityProfileRequirements;
+
/**
* @param \Yoti\ShareUrl\Policy\WantedAttribute[] $wantedAttributes
* Array of attributes to be requested.
@@ -40,12 +45,14 @@ class DynamicPolicy implements \JsonSerializable
* Auth types represents the authentication type to be used.
* @param bool $wantedRememberMe
* @param object $identityProfileRequirements
+ * @param object $advancedIdentityProfileRequirements
*/
public function __construct(
array $wantedAttributes,
array $wantedAuthTypes,
bool $wantedRememberMe = false,
- $identityProfileRequirements = null
+ $identityProfileRequirements = null,
+ $advancedIdentityProfileRequirements = null
) {
Validation::isArrayOfType($wantedAttributes, [WantedAttribute::class], 'wantedAttributes');
$this->wantedAttributes = $wantedAttributes;
@@ -55,6 +62,7 @@ public function __construct(
$this->wantedRememberMe = $wantedRememberMe;
$this->identityProfileRequirements = $identityProfileRequirements;
+ $this->advancedIdentityProfileRequirements = $advancedIdentityProfileRequirements;
}
/**
@@ -70,6 +78,7 @@ public function jsonSerialize(): stdClass
'wanted_remember_me' => $this->wantedRememberMe,
'wanted_remember_me_optional' => false,
'identity_profile_requirements' => $this->identityProfileRequirements,
+ 'advanced_identity_profile_requirements' => $this->advancedIdentityProfileRequirements,
];
}
@@ -90,4 +99,14 @@ public function getIdentityProfileRequirements()
{
return $this->identityProfileRequirements;
}
+
+ /**
+ * AdvancedIdentityProfileRequirements requested in the policy
+ *
+ * @return object|null
+ */
+ public function getAdvancedIdentityProfileRequirements()
+ {
+ return $this->advancedIdentityProfileRequirements;
+ }
}
diff --git a/src/ShareUrl/Policy/DynamicPolicyBuilder.php b/src/ShareUrl/Policy/DynamicPolicyBuilder.php
index 1dc7420d..4d3ba083 100644
--- a/src/ShareUrl/Policy/DynamicPolicyBuilder.php
+++ b/src/ShareUrl/Policy/DynamicPolicyBuilder.php
@@ -42,6 +42,11 @@ class DynamicPolicyBuilder
*/
private $identityProfileRequirements = null;
+ /**
+ * @var object|null
+ */
+ private $advancedIdentityProfileRequirements = null;
+
/**
* @param \Yoti\ShareUrl\Policy\WantedAttribute $wantedAttribute
*
@@ -400,6 +405,18 @@ public function withIdentityProfileRequirements($identityProfileRequirements): s
return $this;
}
+ /**
+ * Use an Identity Profile Requirement object for the share
+ *
+ * @param object $advancedIdentityProfileRequirements
+ * @return $this
+ */
+ public function withAdvancedIdentityProfileRequirements($advancedIdentityProfileRequirements): self
+ {
+ $this->advancedIdentityProfileRequirements = $advancedIdentityProfileRequirements;
+ return $this;
+ }
+
/**
* @return DynamicPolicy
*/
@@ -409,7 +426,8 @@ public function build(): DynamicPolicy
array_values($this->wantedAttributes),
array_values($this->wantedAuthTypes),
$this->wantedRememberMe,
- $this->identityProfileRequirements
+ $this->identityProfileRequirements,
+ $this->advancedIdentityProfileRequirements
);
}
}
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/DocScan/DocScanClientTest.php b/tests/DocScan/DocScanClientTest.php
index 5a58deca..306e1d17 100644
--- a/tests/DocScan/DocScanClientTest.php
+++ b/tests/DocScan/DocScanClientTest.php
@@ -513,7 +513,7 @@ public function testParseIdentityProfileResponse()
$this->assertEquals('someStringHere', $sessionResult->getIdentityProfile()->getSubjectId());
$this->assertEquals(
'MANDATORY_DOCUMENT_COULD_NOT_BE_PROVIDED',
- $sessionResult->getIdentityProfile()->getFailureReason()->getStringCode()
+ $sessionResult->getIdentityProfile()->getFailureReason()->getReasonCode()
);
$this->assertEquals(
diff --git a/tests/DocScan/Session/Retrieve/GetSessionResultTest.php b/tests/DocScan/Session/Retrieve/GetSessionResultTest.php
index 705e82fb..0d879a12 100644
--- a/tests/DocScan/Session/Retrieve/GetSessionResultTest.php
+++ b/tests/DocScan/Session/Retrieve/GetSessionResultTest.php
@@ -43,6 +43,16 @@ class GetSessionResultTest extends TestCase
'result' => 'SOME_ANOTHER_STRING',
'failure_reason' => [
'reason_code' => 'ANOTHER_STRING',
+ 'requirements_not_met_details' => [
+ 0 => [
+ 'failure_type' => 'ANOTHER_STRING',
+ 'document_type' => 'ANOTHER_STRING',
+ 'document_country_iso_code' => 'ANOTHER_STRING',
+ 'audit_id' => 'ANOTHER_STRING',
+ 'details' => 'ANOTHER_STRING'
+ ]
+
+ ]
],
'identity_profile_report' => [],
];
diff --git a/tests/DocScan/Session/Retrieve/IdentityProfileResponseTest.php b/tests/DocScan/Session/Retrieve/IdentityProfileResponseTest.php
index e68e22f9..35476731 100644
--- a/tests/DocScan/Session/Retrieve/IdentityProfileResponseTest.php
+++ b/tests/DocScan/Session/Retrieve/IdentityProfileResponseTest.php
@@ -13,6 +13,11 @@ class IdentityProfileResponseTest extends TestCase
{
private const RESULT = 'DONE';
private const SUBJECT_ID = 'someStringHere';
+ private const FAILURE_TYPE = 'someStringHere';
+ private const DOCUMENT_TYPE = 'someStringHere';
+ private const DOCUMENT_COUNTRY_ISO_CODE = 'someStringHere';
+ private const AUDIT_ID = 'someStringHere';
+ private const DETAILS = 'someStringHere';
private const REASON_CODE = 'MANDATORY_DOCUMENT_COULD_NOT_BE_PROVIDED';
private const IDENTITY_PROFILE_REPORT = [
'trust_framework' => 'UK_TFIDA',
@@ -47,17 +52,29 @@ public function shouldCreatedCorrectly(): void
'result' => self::RESULT,
'failure_reason' => [
'reason_code' => self::REASON_CODE,
+ 'requirements_not_met_details' => [
+ 0 => [
+ 'failure_type' => self::FAILURE_TYPE,
+ 'document_type' => self::DOCUMENT_TYPE,
+ 'document_country_iso_code' => self::DOCUMENT_COUNTRY_ISO_CODE,
+ 'audit_id' => self::AUDIT_ID,
+ 'details' => self::DETAILS
+ ]
+ ]
],
'identity_profile_report' => self::IDENTITY_PROFILE_REPORT,
];
$result = new IdentityProfileResponse($testData);
-
$this->assertEquals(self::RESULT, $result->getResult());
$this->assertEquals(self::SUBJECT_ID, $result->getSubjectId());
$this->assertEquals((object)self::IDENTITY_PROFILE_REPORT, $result->getIdentityProfileReport());
-
$this->assertInstanceOf(FailureReasonResponse::class, $result->getFailureReason());
- $this->assertEquals(self::REASON_CODE, $result->getFailureReason()->getStringCode());
+ $this->assertEquals(self::REASON_CODE, $result->getFailureReason()->getReasonCode());
+ $requriementNotMetDetailsResponse = $result->getFailureReason()->getRequirementNotMetDetails();
+ $this->assertEquals(self::FAILURE_TYPE, $requriementNotMetDetailsResponse->getFailureType());
+ $this->assertEquals(self::DOCUMENT_TYPE, $requriementNotMetDetailsResponse->getDocumentType());
+ $this->assertEquals(self::AUDIT_ID, $requriementNotMetDetailsResponse->getAuditId());
+ $this->assertEquals(self::DETAILS, $requriementNotMetDetailsResponse->getDetails());
}
}
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..4865504b
--- /dev/null
+++ b/tests/Identity/Policy/PolicyBuilderTest.php
@@ -0,0 +1,712 @@
+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,
+ 'advanced_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,
+ 'advanced_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,
+ 'advanced_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,
+ 'advanced_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,
+ 'advanced_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,
+ 'advanced_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,
+ 'advanced_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,
+ 'advanced_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,
+ 'advanced_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,
+ 'advanced_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,
+ 'advanced_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,
+ 'advanced_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,
+ 'advanced_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,
+ 'advanced_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,
+ 'advanced_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,
+ 'advanced_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,
+ 'advanced_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,
+ 'advanced_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,
+ 'advanced_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,
+ 'advanced_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,
+ 'advanced_identity_profile_requirements' => null
+ ];
+
+ $policy = (new PolicyBuilder())
+ ->withIdentityProfileRequirements($identityProfileSample)
+ ->build();
+
+ $this->assertEquals(json_encode($expectedWantedAttributeData), json_encode($policy));
+ $this->assertEquals($identityProfileSample, $policy->getIdentityProfileRequirements());
+ }
+
+ /**
+ * @covers ::withAdvancedIdentityProfileRequirements
+ * @covers \Yoti\Identity\Policy\Policy::__construct
+ * @covers \Yoti\Identity\Policy\Policy::getAdvancedIdentityProfileRequirements
+ * @covers \Yoti\Identity\Policy\Policy::jsonSerialize
+ */
+ public function testWithAdvancedIdentityProfileRequirements()
+ {
+ $advancedIdentityProfileSample =
+ (object)[
+ "profiles" => [(object)[
+
+ "trust_framework" => "YOTI_GLOBAL",
+ "schemes" => [(object)[
+
+ "label" => "identity-AL-L1",
+ "type" => "IDENTITY",
+ "objective" => "AL_L1"
+ ],
+ [
+ "label" => "identity-AL-M1",
+ "type" => "IDENTITY",
+ "objective" => "AL_M1"
+ ]
+ ]
+ ]
+ ]
+ ]
+ ;
+
+ $expectedWantedAttributeData = [
+ 'wanted' => [],
+ 'wanted_auth_types' => [],
+ 'wanted_remember_me' => false,
+ 'wanted_remember_me_optional' => false,
+ 'identity_profile_requirements' => null,
+ 'advanced_identity_profile_requirements' => $advancedIdentityProfileSample
+ ];
+
+ $policy = (new PolicyBuilder())
+ ->withAdvancedIdentityProfileRequirements($advancedIdentityProfileSample)
+ ->build();
+
+ $this->assertEquals(json_encode($expectedWantedAttributeData), json_encode($policy));
+ $this->assertEquals($advancedIdentityProfileSample, $policy->getAdvancedIdentityProfileRequirements());
+ }
+}
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..73c3b29e
--- /dev/null
+++ b/tests/Identity/ReceiptTest.php
@@ -0,0 +1,114 @@
+createMock(ApplicationContent::class);
+ $userContent = $this->createMock(UserContent::class);
+ $rememberId = 'SOME_REMEMBER_ID';
+ $parentRememberId = 'SOME_PARENT_REMEMBER_ID';
+ $someError = 'SOME_ERROR';
+ $someErrorReason = $this->createMock(ErrorReason::class);
+
+ $receipt = new Receipt(
+ $someId,
+ $sessionId,
+ $someTime,
+ $applicationContent,
+ $userContent,
+ $rememberId,
+ $parentRememberId,
+ $someError,
+ $someErrorReason
+ );
+
+ $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());
+ $this->assertEquals($someErrorReason, $receipt->getErrorReason());
+ }
+
+ /**
+ * @covers \Yoti\Identity\ReceiptBuilder::withError
+ * @covers \Yoti\Identity\ReceiptBuilder::withErrorReason
+ * @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';
+ $someErrorReason = $this->createMock(ErrorReason::class);
+
+ $receipt = (new ReceiptBuilder())
+ ->withId($someId)
+ ->withSessionId($sessionId)
+ ->withTimestamp($someTime)
+ ->withUserContent($userProfile)
+ ->withApplicationContent($applicationProfile)
+ ->withRememberMeId($rememberId)
+ ->withParentRememberMeId($parentRememberId)
+ ->withError($someError)
+ ->withErrorReason($someErrorReason)
+ ->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());
+ $this->assertEquals($someErrorReason, $receipt->getErrorReason());
+ }
+}
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/ShareUrl/DynamicScenarioBuilderTest.php b/tests/ShareUrl/DynamicScenarioBuilderTest.php
index 6b06267e..1a83f4f5 100644
--- a/tests/ShareUrl/DynamicScenarioBuilderTest.php
+++ b/tests/ShareUrl/DynamicScenarioBuilderTest.php
@@ -74,7 +74,8 @@ public function testBuild()
'wanted_auth_types' => [],
'wanted_remember_me' => false,
'wanted_remember_me_optional' => false,
- 'identity_profile_requirements' => null
+ 'identity_profile_requirements' => null,
+ 'advanced_identity_profile_requirements' => null
],
'extensions' => [
[
diff --git a/tests/ShareUrl/Policy/DynamicPolicyBuilderTest.php b/tests/ShareUrl/Policy/DynamicPolicyBuilderTest.php
index 07eb7aa8..0980b58e 100644
--- a/tests/ShareUrl/Policy/DynamicPolicyBuilderTest.php
+++ b/tests/ShareUrl/Policy/DynamicPolicyBuilderTest.php
@@ -76,6 +76,7 @@ public function testBuildWithAttributes()
'wanted_remember_me' => false,
'wanted_remember_me_optional' => false,
'identity_profile_requirements' => null,
+ 'advanced_identity_profile_requirements' => null
];
$this->assertEquals(json_encode($expectedWantedAttributeData), json_encode($dynamicPolicy));
@@ -127,6 +128,7 @@ public function testWithWantedAttributeByNameWithConstraints()
'wanted_remember_me' => false,
'wanted_remember_me_optional' => false,
'identity_profile_requirements' => null,
+ 'advanced_identity_profile_requirements' => null
];
$this->assertJsonStringEqualsJsonString(
@@ -154,6 +156,7 @@ public function testWithDuplicateAttribute()
'wanted_remember_me' => false,
'wanted_remember_me_optional' => false,
'identity_profile_requirements' => null,
+ 'advanced_identity_profile_requirements' => null
];
$this->assertEquals(json_encode($expectedWantedAttributeData), json_encode($dynamicPolicy));
@@ -215,6 +218,7 @@ public function testWithWantedAttributeByName()
'wanted_remember_me' => false,
'wanted_remember_me_optional' => false,
'identity_profile_requirements' => null,
+ 'advanced_identity_profile_requirements' => null
];
$this->assertEquals(json_encode($expectedWantedAttributeData), json_encode($dynamicPolicy));
@@ -248,6 +252,7 @@ public function testWithAttributeObjects()
'wanted_remember_me' => false,
'wanted_remember_me_optional' => false,
'identity_profile_requirements' => null,
+ 'advanced_identity_profile_requirements' => null
];
$this->assertEquals(json_encode($expectedWantedAttributeData), json_encode($dynamicPolicy));
@@ -279,6 +284,7 @@ public function testWithAgeDerivedAttributes()
'wanted_remember_me' => false,
'wanted_remember_me_optional' => false,
'identity_profile_requirements' => null,
+ 'advanced_identity_profile_requirements' => null
];
$this->assertEquals(json_encode($expectedWantedAttributeData), json_encode($dynamicPolicy));
@@ -327,6 +333,7 @@ public function testWithAgeDerivedAttributesWithConstraints()
'wanted_remember_me' => false,
'wanted_remember_me_optional' => false,
'identity_profile_requirements' => null,
+ 'advanced_identity_profile_requirements' => null
];
$this->assertJsonStringEqualsJsonString(
@@ -387,6 +394,7 @@ public function testWithDuplicateAgeDerivedAttributes()
'wanted_remember_me' => false,
'wanted_remember_me_optional' => false,
'identity_profile_requirements' => null,
+ 'advanced_identity_profile_requirements' => null
];
$this->assertEquals(json_encode($expectedWantedAttributeData), json_encode($dynamicPolicy));
@@ -411,6 +419,7 @@ public function testWithAuthTypes()
'wanted_remember_me' => false,
'wanted_remember_me_optional' => false,
'identity_profile_requirements' => null,
+ 'advanced_identity_profile_requirements' => null
];
$this->assertEquals(json_encode($expectedWantedAttributeData), json_encode($dynamicPolicy));
@@ -434,7 +443,8 @@ public function testWithAuthTypesTrue()
'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
+ 'identity_profile_requirements' => null,
+ 'advanced_identity_profile_requirements' => null
];
$this->assertEquals(json_encode($expectedWantedAttributeData), json_encode($dynamicPolicy));
@@ -459,6 +469,7 @@ public function testWithAuthTypesFalse()
'wanted_remember_me' => false,
'wanted_remember_me_optional' => false,
'identity_profile_requirements' => null,
+ 'advanced_identity_profile_requirements' => null
];
$this->assertEquals(json_encode($expectedWantedAttributeData), json_encode($dynamicPolicy));
@@ -483,6 +494,7 @@ public function testWithAuthEnabledThenDisabled()
'wanted_remember_me' => false,
'wanted_remember_me_optional' => false,
'identity_profile_requirements' => null,
+ 'advanced_identity_profile_requirements' => null
];
$this->assertEquals(json_encode($expectedWantedAttributeData), json_encode($dynamicPolicy));
@@ -505,6 +517,7 @@ public function testWithSameAuthTypeAddedOnlyOnce()
'wanted_remember_me' => false,
'wanted_remember_me_optional' => false,
'identity_profile_requirements' => null,
+ 'advanced_identity_profile_requirements' => null
];
$this->assertEquals(json_encode($expectedWantedAttributeData), json_encode($dynamicPolicy));
@@ -526,6 +539,7 @@ public function testWithOnlyTwoAuthTypes()
'wanted_remember_me' => false,
'wanted_remember_me_optional' => false,
'identity_profile_requirements' => null,
+ 'advanced_identity_profile_requirements' => null
];
$this->assertEquals(json_encode($expectedWantedAttributeData), json_encode($dynamicPolicy));
@@ -547,6 +561,7 @@ public function testWithNoSelfieAuthAfterRemoval()
'wanted_remember_me' => false,
'wanted_remember_me_optional' => false,
'identity_profile_requirements' => null,
+ 'advanced_identity_profile_requirements' => null
];
$this->assertEquals(json_encode($expectedWantedAttributeData), json_encode($dynamicPolicy));
@@ -568,6 +583,7 @@ public function testWithNoPinAuthAfterRemoval()
'wanted_remember_me' => false,
'wanted_remember_me_optional' => false,
'identity_profile_requirements' => null,
+ 'advanced_identity_profile_requirements' => null
];
$this->assertEquals(json_encode($expectedWantedAttributeData), json_encode($dynamicPolicy));
@@ -601,6 +617,7 @@ public function testWithRememberMe()
'wanted_remember_me' => true,
'wanted_remember_me_optional' => false,
'identity_profile_requirements' => null,
+ 'advanced_identity_profile_requirements' => null
];
$this->assertEquals(json_encode($expectedWantedAttributeData), json_encode($dynamicPolicy));
@@ -620,7 +637,8 @@ public function testWithoutRememberMe()
'wanted_auth_types' => [],
'wanted_remember_me' => false,
'wanted_remember_me_optional' => false,
- 'identity_profile_requirements' => null
+ 'identity_profile_requirements' => null,
+ 'advanced_identity_profile_requirements' => null
];
$this->assertEquals(json_encode($expectedWantedAttributeData), json_encode($dynamicPolicy));
@@ -648,7 +666,8 @@ public function testWithIdentityProfileRequirements()
'wanted_auth_types' => [],
'wanted_remember_me' => false,
'wanted_remember_me_optional' => false,
- 'identity_profile_requirements' => $identityProfileSample
+ 'identity_profile_requirements' => $identityProfileSample,
+ 'advanced_identity_profile_requirements' => null
];
$dynamicPolicy = (new DynamicPolicyBuilder())
@@ -658,4 +677,52 @@ public function testWithIdentityProfileRequirements()
$this->assertEquals(json_encode($expectedWantedAttributeData), json_encode($dynamicPolicy));
$this->assertEquals($identityProfileSample, $dynamicPolicy->getIdentityProfileRequirements());
}
+
+ /**
+ * @covers ::withAdvancedIdentityProfileRequirements
+ * @covers \Yoti\ShareUrl\Policy\DynamicPolicy::__construct
+ * @covers \Yoti\ShareUrl\Policy\DynamicPolicy::getAdvancedIdentityProfileRequirements
+ * @covers \Yoti\ShareUrl\Policy\DynamicPolicy::jsonSerialize
+ * @covers \Yoti\ShareUrl\Policy\DynamicPolicy::__toString
+ */
+ public function testWithAdvancedIdentityProfileRequirements()
+ {
+ $advancedIdentityProfileSample =
+ (object)[
+ "profiles" => [(object)[
+
+ "trust_framework" => "YOTI_GLOBAL",
+ "schemes" => [(object)[
+
+ "label" => "identity-AL-L1",
+ "type" => "IDENTITY",
+ "objective" => "AL_L1"
+ ],
+ [
+ "label" => "identity-AL-M1",
+ "type" => "IDENTITY",
+ "objective" => "AL_M1"
+ ]
+ ]
+ ]
+ ]
+ ]
+ ;
+
+ $expectedWantedAttributeData = [
+ 'wanted' => [],
+ 'wanted_auth_types' => [],
+ 'wanted_remember_me' => false,
+ 'wanted_remember_me_optional' => false,
+ 'identity_profile_requirements' => null,
+ 'advanced_identity_profile_requirements' => $advancedIdentityProfileSample
+ ];
+
+ $dynamicPolicy = (new DynamicPolicyBuilder())
+ ->withAdvancedIdentityProfileRequirements($advancedIdentityProfileSample)
+ ->build();
+
+ $this->assertEquals(json_encode($expectedWantedAttributeData), json_encode($dynamicPolicy));
+ $this->assertEquals($advancedIdentityProfileSample, $dynamicPolicy->getAdvancedIdentityProfileRequirements());
+ }
}
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
*/
diff --git a/tests/sample-data/sessionResultIdentityProfileResult.json b/tests/sample-data/sessionResultIdentityProfileResult.json
index b6850f06..8464b432 100644
--- a/tests/sample-data/sessionResultIdentityProfileResult.json
+++ b/tests/sample-data/sessionResultIdentityProfileResult.json
@@ -9,7 +9,16 @@
"subject_id": "someStringHere",
"result": "DONE",
"failure_reason": {
- "reason_code": "MANDATORY_DOCUMENT_COULD_NOT_BE_PROVIDED"
+ "reason_code": "MANDATORY_DOCUMENT_COULD_NOT_BE_PROVIDED",
+ "requirements_not_met_details": [
+ {
+ "failure_type": "someStringHere",
+ "document_type": "someStringHere",
+ "document_country_iso_code": "someStringHere",
+ "audit_id": "someStringHere",
+ "details": "someStringHere"
+ }
+ ]
},
"identity_profile_report": {
"trust_framework": "UK_TFIDA",
@@ -31,4 +40,4 @@
}
}
}
-}
\ No newline at end of file
+}
From df0a9ee3cfed700ebb2845dfa7f9b2bc427b75cb Mon Sep 17 00:00:00 2001
From: mehmet-yoti <111424390+mehmet-yoti@users.noreply.github.com>
Date: Thu, 1 Aug 2024 12:21:32 +0300
Subject: [PATCH 2/2] Release 4.3.0 - Update (#366)
* Updated readme, and removed unnecessary usings
---
README.md | 4 ++--
examples/digitalidentity/README.md | 7 +++++--
.../app/Http/Controllers/AdvancedIdentityController.php | 2 --
.../app/Http/Controllers/IdentityController.php | 2 --
.../app/Http/Controllers/ReceiptController.php | 3 +--
5 files changed, 8 insertions(+), 10 deletions(-)
diff --git a/README.md b/README.md
index 36510a9c..6f35b579 100755
--- a/README.md
+++ b/README.md
@@ -42,13 +42,13 @@ Add the Yoti SDK dependency:
```json
"require": {
- "yoti/yoti-php-sdk" : "^4.2.1"
+ "yoti/yoti-php-sdk" : "^4.3.0"
}
```
Or run this Composer command
```console
-$ composer require yoti/yoti-php-sdk "^4.2.1"
+$ composer require yoti/yoti-php-sdk "^4.3.0"
```
## Setup
diff --git a/examples/digitalidentity/README.md b/examples/digitalidentity/README.md
index 8faa9b32..9bf2d1b3 100644
--- a/examples/digitalidentity/README.md
+++ b/examples/digitalidentity/README.md
@@ -18,7 +18,10 @@ This example requires [Docker](https://docs.docker.com/)
* 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)
-
+> To see how to retrieve a profile using share receipt, refer to the [receipt controller](app/Http/Controllers/ReceiptController.php)
## Digital Identity Example
* Visit [/generate-share](https://localhost:4002/generate-share)
+## Digital Identity(Advanced) Share Example
+* Visit [/generate-advanced-identity-share](https://localhost:4002/generate-advanced-identity-share)
+* ## Digital Identity DBS Example
+* Visit [/generate-dbs-share](https://localhost:4002/generate-dbs-share)
diff --git a/examples/digitalidentity/app/Http/Controllers/AdvancedIdentityController.php b/examples/digitalidentity/app/Http/Controllers/AdvancedIdentityController.php
index cdfda0f6..c084faf2 100644
--- a/examples/digitalidentity/app/Http/Controllers/AdvancedIdentityController.php
+++ b/examples/digitalidentity/app/Http/Controllers/AdvancedIdentityController.php
@@ -4,12 +4,10 @@
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Facades\Log;
-use mysql_xdevapi\Exception;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Yoti\DigitalIdentityClient;
use Yoti\Identity\Policy\PolicyBuilder;
use Yoti\Identity\ShareSessionRequestBuilder;
-use Yoti\YotiClient;
class AdvancedIdentityController extends BaseController
{
diff --git a/examples/digitalidentity/app/Http/Controllers/IdentityController.php b/examples/digitalidentity/app/Http/Controllers/IdentityController.php
index 72ba9fc3..ca987617 100644
--- a/examples/digitalidentity/app/Http/Controllers/IdentityController.php
+++ b/examples/digitalidentity/app/Http/Controllers/IdentityController.php
@@ -4,12 +4,10 @@
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Support\Facades\Log;
-use mysql_xdevapi\Exception;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Yoti\DigitalIdentityClient;
use Yoti\Identity\Policy\PolicyBuilder;
use Yoti\Identity\ShareSessionRequestBuilder;
-use Yoti\YotiClient;
class IdentityController extends BaseController
{
diff --git a/examples/digitalidentity/app/Http/Controllers/ReceiptController.php b/examples/digitalidentity/app/Http/Controllers/ReceiptController.php
index cc529386..40299d42 100644
--- a/examples/digitalidentity/app/Http/Controllers/ReceiptController.php
+++ b/examples/digitalidentity/app/Http/Controllers/ReceiptController.php
@@ -3,13 +3,12 @@
namespace App\Http\Controllers;
use Yoti\DigitalIdentityClient;
-use Yoti\YotiClient;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller as BaseController;
use Yoti\Profile\Attribute;
use Yoti\Profile\UserProfile;
use Yoti\Util\Logger;
-use Yoti\Exception\ActivityDetailsException;
+
class ReceiptController extends BaseController
{
public function show(Request $request, DigitalIdentityClient $client, ?LoggerInterface $logger = null)