From b9f23867ade36a8dbd777f206882b55d7296cf13 Mon Sep 17 00:00:00 2001 From: Matt Peel Date: Fri, 3 Sep 2021 02:37:54 +1200 Subject: [PATCH] API changes: - Adjust GlobalAuthenticationService->verifyIdentity() to require the Identity and optional customer reference, forcing the profile ID and optionally profile version to be specified prior to calling the method - Added new test to ensure profileId is set when calling verifyIdentity Minor: - Rename 'ID3Global' to 'ID3global' in documentation and comments (leave namespaces alone) - Update README to match changed API - Updated phpdoc --- README.md | 23 +-- composer.json | 2 +- .../IdentityVerificationFailureException.php | 2 +- src/Service/GlobalAuthenticationService.php | 132 ++++++++++++++---- .../GlobalAuthenticationGatewayFake.php | 19 ++- .../GlobalAuthenticationServiceTest.php | 16 ++- 6 files changed, 151 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 1f4e661..f94f388 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ The WSDL file gives an overview of the values that can be provided, these will v * [Online WSDL viewer](http://www.id3globalsupport.com/Website/content/Web-Service/WSDL%20Page/WSDL%20HTML/ID3%20Global%20WSDL-%20Live.xhtml) * [Sample code per country](http://www.id3globalsupport.com/Website/Sample-Code.html) -*Note:* The code below is entirely subject to change. It is primarily focused at the moment around the `AuthenticateSP` method of the ID3Global API, and specifically on NZ, however it should be generic enough to easily support non-NZ systems easily. +*Note:* The code below is entirely subject to change. It is primarily focused at the moment around the `AuthenticateSP` method of the ID3global API, and specifically on New Zealand (Aotearoa), however it should be generic enough to easily support non-NZ systems easily. ```php /** @@ -98,14 +98,20 @@ $documentContainer = new \ID3Global\Identity\Documents\DocumentContainer(); $documentContainer->addIdentityDocument(new \ID3Global\Identity\Documents\NZ\DrivingLicence(), 'New Zealand'); /** - * $result will be one of the following: - * - \ID3Global\Constants\Identity::IDENTITY_BAND_PASS - * - \ID3Global\Constants\Identity::IDENTITY_BAND_REFER - * - \ID3Global\Constants\Identity::IDENTITY_BAND_ALERT + * $result will be a string representing the 'BandText' as returned by the ID3global API. By default, this may be a word + * like 'PASS', 'REFER' or 'ALERT' but could also be any string value e.g. 'Name, Address and DOB Match'. The exact + * string returned is entirely dependent on how the profile is configured within ID3global, and can vary if you adjust + * the profile id and profile version. + * + * It is up to your implementation how these are handled. Note that generally there is only a single value that + * represents an identity that has passed the necessary verification, and multiple BandTexts that represent a failing + * identity. You **must** handle this in your own code, as the ID3Global API does not provide any kind of boolean value + * for whether a given identity passed identity verification or not. * - * It is up to the implementation how these are handled. * An exception is thrown if the web service fails or cannot be contacted. */ +$validIdentityBandText = 'PASS'; // See note above about how this may differ for you + $identity = new \ID3Global\Identity\Identity(); $identity ->setPersonalDetails($personalDetails) @@ -116,9 +122,10 @@ $identity $gateway = new \ID3Global\Gateway\GlobalAuthenticationGateway('username', 'password'); $id3Service = new \ID3Global\Service\GlobalAuthenticationService($gateway); $result = $id3Service - ->verifyIdentity($identity, 'profile-id'); + ->setProfileId('Profile ID as provided by ID3global interface') + ->verifyIdentity($identity, 'Unique customer reference'); -if($result === 'PASS') { +if($result === $validIdentityBandText) { // Identity is verified, continue processing } ``` diff --git a/composer.json b/composer.json index 384944f..c84ea36 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "madmatt/id3global-service", "description": "Allows a PHP-powered website to communicate with the GBG ID3global API to verify identities. This library is not affiliated with GBG PLC in any way.", "type": "library", - "keywords": [ "id3global", "GBG", "GBG PLC", "ID3Global", "identity verification", "identity" ], + "keywords": [ "id3global", "GBG", "GBG PLC", "ID3Global", "ID3global", "identity verification", "identity" ], "homepage": "https://github.com/madmatt/id3global-service/", "license": "BSD-3-Clause", diff --git a/src/Exceptions/IdentityVerificationFailureException.php b/src/Exceptions/IdentityVerificationFailureException.php index 9c8daa1..1aa877c 100644 --- a/src/Exceptions/IdentityVerificationFailureException.php +++ b/src/Exceptions/IdentityVerificationFailureException.php @@ -12,7 +12,7 @@ class IdentityVerificationFailureException extends Exception public function __construct($response) { $this->response = $response; - $message = sprintf('Invalid Response returned by ID3Global API. Serialized response: %s', serialize($response)); + $message = sprintf('Invalid Response returned by ID3global API. Serialized response: %s', serialize($response)); parent::__construct($message); } diff --git a/src/Service/GlobalAuthenticationService.php b/src/Service/GlobalAuthenticationService.php index 2bb9d7c..034b75a 100644 --- a/src/Service/GlobalAuthenticationService.php +++ b/src/Service/GlobalAuthenticationService.php @@ -3,6 +3,7 @@ namespace ID3Global\Service; use Exception; +use LogicException; use ID3Global\Exceptions\IdentityVerificationFailureException; use ID3Global\Gateway\GlobalAuthenticationGateway; use ID3Global\Identity\Identity; @@ -16,6 +17,31 @@ class GlobalAuthenticationService extends ID3BaseService */ private GlobalAuthenticationGateway $gateway; + /** + * @var string The Profile ID to be used when verifying identities via $this->verifyIdentity(). + * @see self::setProfileId() + */ + private string $profileId = ''; + + /** + * @var int The version of the Profile ID to be used when verifying identities via $this->verifyIdentity(). + * The special value of 0 is treated specially by ID3global and represents the 'most recent version of the profile'. + * @see self::setProfileVersion() + */ + private int $profileVersion = 0; + + /** + * @var Identity The most recent Identity object to be verified by the ID3global API (regardless of the outcome of + * the API request). + */ + private Identity $lastIdentity; + + /** + * @var string|null The most recent customer reference to be verified by the ID3global API (regardless of the + * outcome of the API request). + */ + private ?string $lastCustomerReference; + /** * @var stdClass|null The last response, directly from the API gateway. Can be retrieved using * {@link getLastVerifyIdentityResponse()}. @@ -39,39 +65,54 @@ public function __construct(GlobalAuthenticationGateway $gateway, array $soapOpt } /** - * @param Identity $identity - * @param string $profileId The Profile ID to be used when verifying a @link \ID3Global\Identity\Identity object - * @param int $profileVersion The Profile Version to be used when verifying a @link \ID3Global\Identity\Identity object. The version - * 0 represents the 'most recent version of the profile', which is generally what is required. - * @param string|null $customerReference A reference stored against this identity request. This is optional, but is recommended to set a - * customer reference and store it against the returned identity verification so it can be later tracked if + * Given an Identity and a profile of checks to perform, query ID3Global and verify the given $identity object. The + * raw response is the output of the 'BandText' as returned directly by the ID3Global API. Per the + * [ID3Global 'Integrate now' documentation](https://www.id3globalsupport.com/integrate-now/), you should use this + * value to determine whether or not to consider the identity to be sufficiently verified for your needs. + * + * If you want to dive deeper (e.g. to look at individual checks that were performed such as whether the identity + * matched on a driver's license or passport record), you can do this by calling + * $service->getLastVerifyIdentityResponse() after calling this method - this will return the full response from the + * API. + * + * Ensure you call at least ->setProfileId() prior to calling this method. + * Optionally call ->setProfileVersion() if you wish to set a specific profile version to query against. + * + * @param Identity $identity The full Identity object that should be verified with the ID3global API + * @param string|null $customerReference A reference stored against this identity request within the ID3global + * interface. This is optional, but is highly recommended to set a reference + * and store it against the identity so that it can be later tracked if * necessary for compliance purposes. * - * @throws IdentityVerificationFailureException + * @throws IdentityVerificationFailureException Thrown specifically if the SOAP response was 'valid' according to + * SOAP but does not conform to the expected response (missing BandText or Score elements of the response). + * @throws Exception May throw a generic Exception or SoapFault if any part of the SOAP callstack fails. * - * @return string One of Identity::IDENTITY_BAND_PASS, Identity::IDENTITY_BAND_REFER, or Identity::IDENTITY_BAND_ALERT - */ - public function verifyIdentity( - Identity $identity, - string $profileId, - int $profileVersion = 0, - ?string $customerReference = null - ): string { + * @return string The raw BandText as provided by the API. + */ + public function verifyIdentity(Identity $identity, ?string $customerReference = null): string { + $this->lastIdentity = $identity; + $this->lastCustomerReference = $customerReference; + $gateway = $this->getGateway(); + if (!$this->profileId) { + $error = 'An ID3global Profile ID must be set by calling setProfileId() before calling verifyIdentity().'; + throw new LogicException($error); + } + + $profileId = $this->profileId; + $profileVersion = $this->profileVersion; + try { - $response = $gateway->AuthenticateSP( - $profileId, - $profileVersion, - $customerReference, - $identity - ); + $response = $gateway->AuthenticateSP($profileId, $profileVersion, $customerReference, $identity); if ($gateway->getClient() instanceof SoapClient) { $this->lastRawRequest = $gateway->getClient()->__getLastRequest(); } $validResult = false; + $this->lastVerifyIdentityResponse = $response; if ( isset($response) && @@ -82,8 +123,6 @@ public function verifyIdentity( } if ($validResult) { - $this->lastVerifyIdentityResponse = $response; - return $response->AuthenticateSPResult->BandText; } else { throw new IdentityVerificationFailureException($response); @@ -93,9 +132,52 @@ public function verifyIdentity( } } + public function setProfileId(string $profileId): self + { + $this->profileId = $profileId; + + return $this; + } + + public function getProfileId(): string + { + return $this->profileId; + } + + public function setProfileVersion(int $profileVersion): self + { + $this->profileVersion = $profileVersion; + + return $this; + } + + public function getProfileVersion(): int + { + return $this->profileVersion; + } + + /** + * @return Identity|null The last Identity object to be verified by the API (regardless of whether it was + * successfully accepted by the ID3global API or not). Returns null if ->verifyIdentity() has not yet been called. + */ + public function getLastVerifiedIdentity(): ?Identity + { + return $this->lastIdentity; + } + + /** + * @return string|null The last customer reference value to be verified by the API (regardless of whether it was + * successfully accepted by the ID3global API or not). Returns null if ->verifyIdentity() has not yet been called. + */ + public function getLastCustomerReference(): ?string + { + return $this->lastCustomerReference; + } + /** - * @return stdClass|null Either the full response as returned by ID3Global, or null if no call has been made (or - * if the previous call failed for any reason) + * @return stdClass|null Either the full response as returned by ID3global, or null if no call has been made yet. If + * the request was made but failed to validate (e.g. the ID3global API returned an invalid SOAP object, this will + * still be populated. */ public function getLastVerifyIdentityResponse(): ?stdClass { diff --git a/src/Stubs/Gateway/GlobalAuthenticationGatewayFake.php b/src/Stubs/Gateway/GlobalAuthenticationGatewayFake.php index 8677dc7..411afcd 100644 --- a/src/Stubs/Gateway/GlobalAuthenticationGatewayFake.php +++ b/src/Stubs/Gateway/GlobalAuthenticationGatewayFake.php @@ -8,20 +8,20 @@ class GlobalAuthenticationGatewayFake extends GlobalAuthenticationGateway { /** - * @var string The const returned by the ID3Global API when this identity passed verification, according to the - * ruleset used. + * @var string A BandText value returned by the ID3global API when this identity passed verification, according to + * the selected profile. */ const IDENTITY_BAND_PASS = 'PASS'; /** - * @var string The const returned by the ID3Global API when this identity needs additional referral, according to - * the ruleset used. + * @var string A BandText value returned by the ID3global API when this identity needs additional referral, + * according to the selected profile. */ const IDENTITY_BAND_REFER = 'REFER'; /** - * The const returned by the ID3Global API when this identity needs additional referral, according to - * the ruleset used. + * @var string A BandText value returned by the ID3global API when this identity has a significant issue, according + * to the selected profile. */ const IDENTITY_BAND_ALERT = 'ALERT'; @@ -32,6 +32,11 @@ public function __construct($username, $password, $soapClientOptions = [], $useP public function AuthenticateSP($profileID, $profileVersion, $customerReference, Identity $identity) { - return unserialize('O:8:"stdClass":1:{s:20:"AuthenticateSPResult";O:8:"stdClass":12:{s:16:"AuthenticationID";s:36:"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";s:9:"Timestamp";s:33:"2016-01-01T00:00:00.0000000+01:00";s:11:"CustomerRef";s:1:"x";s:9:"ProfileID";s:36:"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";s:11:"ProfileName";s:15:"Default Profile";s:14:"ProfileVersion";i:1;s:15:"ProfileRevision";i:1;s:12:"ProfileState";s:9:"Effective";s:11:"ResultCodes";O:8:"stdClass":1:{s:26:"GlobalItemCheckResultCodes";a:0:{}}s:5:"Score";i:3000;s:8:"BandText";s:4:"PASS";s:7:"Country";s:11:"New Zealand";}}'); + $bandText = self::IDENTITY_BAND_PASS; + + return unserialize(sprintf( + 'O:8:"stdClass":1:{s:20:"AuthenticateSPResult";O:8:"stdClass":12:{s:16:"AuthenticationID";s:36:"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";s:9:"Timestamp";s:33:"2016-01-01T00:00:00.0000000+01:00";s:11:"CustomerRef";s:1:"x";s:9:"ProfileID";s:36:"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";s:11:"ProfileName";s:15:"Default Profile";s:14:"ProfileVersion";i:1;s:15:"ProfileRevision";i:1;s:12:"ProfileState";s:9:"Effective";s:11:"ResultCodes";O:8:"stdClass":1:{s:26:"GlobalItemCheckResultCodes";a:0:{}}s:5:"Score";i:3000;s:8:"BandText";s:4:"%s";s:7:"Country";s:11:"New Zealand";}}', + $bandText + )); } } diff --git a/tests/Service/GlobalAuthenticationServiceTest.php b/tests/Service/GlobalAuthenticationServiceTest.php index f46a634..0a9e768 100644 --- a/tests/Service/GlobalAuthenticationServiceTest.php +++ b/tests/Service/GlobalAuthenticationServiceTest.php @@ -4,6 +4,7 @@ use DateTime; use Exception; +use LogicException; use ID3Global\Identity\Identity; use ID3Global\Identity\PersonalDetails; use ID3Global\Service\GlobalAuthenticationService; @@ -36,18 +37,31 @@ public function testSuccessfulResponse() ->setSurname('Huntsman') ->setGender('Female') ->setDateOfBirth(DateTime::createFromFormat('Y-m-d', '1976-03-06')); + $identity = new Identity(); $identity->setPersonalDetails($personalDetails); $profileId = 'profile-id'; // Act - $bandText = $this->service->verifyIdentity($identity, $profileId, 0, 'x'); + $bandText = $this->service + ->setProfileId($profileId) + ->verifyIdentity($identity, 'customer reference'); // Assert $this->assertSame(GlobalAuthenticationGatewayFake::IDENTITY_BAND_PASS, $bandText); + $response = $this->service->getLastVerifyIdentityResponse(); + $this->assertSame('stdClass', get_class($response)); $this->assertSame(GlobalAuthenticationGatewayFake::IDENTITY_BAND_PASS, $response->AuthenticateSPResult->BandText); $this->assertSame('Default Profile', $response->AuthenticateSPResult->ProfileName); } + + public function testNotSettingProfileIdThrows() + { + $identity = new Identity(); + + $this->expectException(LogicException::class); + $this->service->verifyIdentity($identity); + } }