diff --git a/features/request/RequestContext.php b/features/request/RequestContext.php index 3ac70db..1d3a261 100644 --- a/features/request/RequestContext.php +++ b/features/request/RequestContext.php @@ -464,7 +464,7 @@ public function iHaveProvidedAnRporiginOf($rpOrigin) /** * @When I call mfaCreate */ - public function iCallMfacreate() + public function iCallMfaCreate() { $this->getIdBrokerClient()->mfaCreate( $this->requestData['employee_id'], @@ -475,9 +475,9 @@ public function iCallMfacreate() } /** - * @When I call deleteMfa + * @When I call mfaDelete */ - public function iCallDeletemfa() + public function iCallMfaDelete() { $this->getIdBrokerClient()->mfaDelete( $this->requestData['id'], @@ -485,10 +485,22 @@ public function iCallDeletemfa() ); } + /** + * @When I call mfaDeleteWebauthn + */ + public function iCallMfaDeleteWebauthn() + { + $this->getIdBrokerClient()->mfaDeleteWebauthn( + $this->requestData['id'], + $this->requestData['employee_id'], + $this->requestData['webauthn_id'], + ); + } + /** * @When I call mfaList */ - public function iCallMfalist() + public function iCallMfaList() { $this->getIdBrokerClient()->mfaList( $this->requestData['employee_id'], @@ -499,7 +511,7 @@ public function iCallMfalist() /** * @When I call mfaUpdate */ - public function iCallMfaupdate() + public function iCallMfaUpdate() { $this->getIdBrokerClient()->mfaUpdate( $this->requestData['id'], @@ -508,10 +520,23 @@ public function iCallMfaupdate() ); } + /** + * @When I call mfaUpdateWebauthn + */ + public function iCallMfaUpdateWebauthn() + { + $this->getIdBrokerClient()->mfaUpdateWebauthn( + $this->requestData['id'], + $this->requestData['employee_id'], + $this->requestData['label'], + $this->requestData['webauthn_id'], + ); + } + /** * @When I call mfaVerify */ - public function iCallMfaverify() + public function iCallMfaVerify() { $this->getIdBrokerClient()->mfaVerify( $this->requestData['id'], @@ -520,4 +545,18 @@ public function iCallMfaverify() $this->rpOrigin ); } + + /** + * @When I call mfaVerifyRegistration + */ + public function iCallMfaVerifyRegistration() + { + $this->getIdBrokerClient()->mfaVerify( + $this->requestData['id'], + $this->requestData['employee_id'], + $this->requestData['value'], + $this->rpOrigin, + 'registration', + ); + } } diff --git a/features/request/request.feature b/features/request/request.feature index 6008686..d93559f 100644 --- a/features/request/request.feature +++ b/features/request/request.feature @@ -328,7 +328,7 @@ Feature: Formatting requests for sending to the ID Broker API And I have indicated not to validate the id broker ip And I provide an "employee_id" of "123" And I provide an "id" of "789" - When I call deleteMfa + When I call mfaDelete Then the method should be "DELETE" And the url should be 'https://api.example.com/mfa/789' And an authorization header should be present @@ -339,6 +339,23 @@ Feature: Formatting requests for sending to the ID Broker API } """ + Scenario: Deleting an mfa webauthn option + Given I am using a baseUri of "https://api.example.com/" + And I have indicated not to validate the id broker ip + And I provide an "employee_id" of "123" + And I provide an "id" of "456" + And I provide an "webauthn_id" of "789" + When I call mfaDeleteWebauthn + Then the method should be "DELETE" + And the url should be 'https://api.example.com/mfa/456/webauthn/789' + And an authorization header should be present + And the body should equal the following: + """ + { + "employee_id": "123" + } + """ + Scenario: Listing mfa options Given I am using a baseUri of "https://api.example.com/" And I have provided an rpOrigin of "https://login.example.com" @@ -354,11 +371,30 @@ Feature: Formatting requests for sending to the ID Broker API And I have indicated not to validate the id broker ip And I provide an "id" of "789" And I provide an "employee_id" of "123" - And I provide an "label" of "Purple security key" + And I provide an "label" of "Authy1" When I call mfaUpdate Then the method should be "PUT" And the url should be 'https://api.example.com/mfa/789' And an authorization header should be present + And the body should equal the following: + """ + { + "employee_id": "123", + "label": "Authy1" + } + """ + + Scenario: Update an mfa webauthn option + Given I am using a baseUri of "https://api.example.com/" + And I have indicated not to validate the id broker ip + And I provide an "id" of "456" + And I provide an "employee_id" of "123" + And I provide an "label" of "Purple security key" + And I provide an "webauthn_id" of "789" + When I call mfaUpdateWebauthn + Then the method should be "PUT" + And the url should be 'https://api.example.com/mfa/456/webauthn/789' + And an authorization header should be present And the body should equal the following: """ { @@ -385,3 +421,23 @@ Feature: Formatting requests for sending to the ID Broker API "value": "01234987" } """ + + + Scenario: Verifying an mfa registration + Given I am using a baseUri of "https://api.example.com/" + And I have provided an rpOrigin of "https://login.example.com" + And I have indicated not to validate the id broker ip + And I provide an "id" of "789" + And I provide an "employee_id" of "123" + And I provide a "value" of "01234987" + When I call mfaVerifyRegistration + Then the method should be "POST" + And the url should be 'https://api.example.com/mfa/789/verify/registration?rpOrigin=https%3A%2F%2Flogin.example.com' + And an authorization header should be present + And the body should equal the following: + """ + { + "employee_id": "123", + "value": "01234987" + } + """ diff --git a/features/response/ResponseContext.php b/features/response/ResponseContext.php index a23891d..1ddd510 100644 --- a/features/response/ResponseContext.php +++ b/features/response/ResponseContext.php @@ -341,6 +341,24 @@ public function iCallMfaverifyWithTheNecessaryData() } } + /** + * @When I call mfaVerifyRegistration with the necessary data + */ + public function iCallMfaVerifyRegistrationWithTheNecessaryData() + { + try { + $this->result = $this->getIdBrokerClient()->mfaVerify( + '123', + '111111', + 'dummy-mfa-submission', + 'https://login.example.com', + 'registration', + ); + } catch (Exception $e) { + $this->exceptionThrown = $e; + } + } + /** * @When an MFA rate-limit exception SHOULD have been thrown */ diff --git a/features/response/response.feature b/features/response/response.feature index 989fa5b..a32c5a6 100644 --- a/features/response/response.feature +++ b/features/response/response.feature @@ -244,6 +244,27 @@ Feature: Handling responses from the ID Broker API When I call mfaVerify with the necessary data Then an exception with status code 429 SHOULD have been thrown + Scenario: Handling a "correct" response from mfaVerifyRegistration + Given a call to "mfaVerifyRegistration" will return a 200 response + When I call mfaVerifyRegistration with the necessary data + Then an exception should NOT have been thrown + And the result should be an array + + Scenario: Handling a "correct," but empty response from mfaVerifyRegistration + Given a call to "mfaVerifyRegistration" will return a 204 response + When I call mfaVerifyRegistration with the necessary data + Then the result should be true + + Scenario: Handling a "wrong" response from mfaVerifyRegistration + Given a call to "mfaVerifyRegistration" will return a 400 response + When I call mfaVerifyRegistration with the necessary data + Then an exception with status code 400 SHOULD have been thrown + + Scenario: Handling a rate limit exception from mfaVerifyRegistration + Given a call to "mfaVerifyRegistration" will return a 429 response + When I call mfaVerifyRegistration with the necessary data + Then an exception with status code 429 SHOULD have been thrown + Scenario: Handling a successful createMethod call Given a call to "createMethod" will return a 200 response When I call createMethod with the necessary data diff --git a/src/IdBrokerClient.php b/src/IdBrokerClient.php index e953de0..a792bea 100644 --- a/src/IdBrokerClient.php +++ b/src/IdBrokerClient.php @@ -354,6 +354,30 @@ public function mfaDelete(string $id, string $employeeId) $this->reportUnexpectedResponse($result, 1506710702); } + /** + * Delete a specific MFA webauthn configuration + * @param string $id + * @param string $employeeId + * @param string $webauthnID + * @return null + * @throws ServiceException + */ + public function mfaDeleteWebauthn(string $id, string $employeeId, string $webauthnID) + { + $result = $this->mfaDeleteWebauthnInternal([ + 'id' => $id, + 'employee_id' => $employeeId, + 'webauthn_id' => $webauthnID, + ]); + $statusCode = (int)$result[ 'statusCode' ]; + + if ($statusCode === 204 || $statusCode === 200) { + return null; + } + + $this->reportUnexpectedResponse($result, 1669902932); + } + /** * Get a list of MFA configurations for given user * @param string $employee_id @@ -400,24 +424,58 @@ public function mfaUpdate(string $id, string $employeeId, string $label): array $this->reportUnexpectedResponse($result, 1543879805); } + /** + * Update a specific MFA Webauthn configuration + * @param string $id + * @param string $employeeId + * @param string $label + * @param string $webauthnID + * @return array + * @throws ServiceException + */ + public function mfaUpdateWebauthn(string $id, string $employeeId, string $label, string $webauthnID): array + { + $result = $this->mfaUpdateWebauthnInternal([ + 'id' => $id, + 'employee_id' => $employeeId, + 'label' => $label, + 'webauthn_id' => $webauthnID, + ]); + $statusCode = (int)$result[ 'statusCode' ]; + + if ($statusCode === 200) { + return $this->getResultAsArrayWithoutStatusCode($result); + } + + $this->reportUnexpectedResponse($result, 1669902940); + } + /** * Verify an MFA value * @param string $id The MFA ID. * @param string $employeeId The Employee ID of the user with that MFA. * @param string|array $value The MFA value being verified. - * @param string $rpOrigin The Relying Party Origin, for WebAuthn MFA options. + * @param string $rpOrigin (optional) The Relying Party Origin, for WebAuthn MFA options. + * @param string $type (optional) For now, either blank or 'registration', for WebAuthn MFA options. * @return bool|array * @throws MfaRateLimitException * @throws ServiceException */ - public function mfaVerify(string $id, string $employeeId, $value, string $rpOrigin = '') + public function mfaVerify(string $id, string $employeeId, $value, string $rpOrigin = '', string $type = '') { - $result = $this->mfaVerifyInternal([ + $config = [ 'id' => $id, 'employee_id' => $employeeId, 'value' => $value, 'rpOrigin' => $rpOrigin, - ]); + ]; + + if ($type != '') { + $result = $this->mfaVerifyRegistrationInternal($config); + } else { + $result = $this->mfaVerifyInternal($config); + } + $statusCode = (int)$result[ 'statusCode' ]; /* diff --git a/src/descriptions/id-broker-api.php b/src/descriptions/id-broker-api.php index e310b9c..c8137f8 100644 --- a/src/descriptions/id-broker-api.php +++ b/src/descriptions/id-broker-api.php @@ -213,6 +213,28 @@ ], ], ], + 'mfaDeleteWebauthnInternal' => [ + 'httpMethod' => 'DELETE', + 'uri' => '/mfa/{id}/webauthn/{webauthn_id}', + 'responseModel' => 'Result', + 'parameters' => [ + 'id' => [ + 'required' => true, + 'type' => 'string', + 'location' => 'uri' + ], + 'webauthn_id' => [ + 'required' => true, + 'type' => 'string', + 'location' => 'uri' + ], + 'employee_id' => [ + 'required' => true, + 'type' => 'string', + 'location' => 'json' + ], + ], + ], 'mfaListInternal' => [ 'httpMethod' => 'GET', 'uri' => '/user/{employee_id}/mfa', @@ -252,6 +274,33 @@ ], ], ], + 'mfaUpdateWebauthnInternal' => [ + 'httpMethod' => 'PUT', + 'uri' => '/mfa/{id}/webauthn/{webauthn_id}', + 'responseModel' => 'Result', + 'parameters' => [ + 'id' => [ + 'required' => true, + 'type' => 'string', + 'location' => 'uri' + ], + 'webauthn_id' => [ + 'required' => true, + 'type' => 'string', + 'location' => 'uri' + ], + 'employee_id' => [ + 'required' => true, + 'type' => 'string', + 'location' => 'json' + ], + 'label' => [ + 'required' => true, + 'type' => 'string', + 'location' => 'json' + ], + ], + ], 'mfaVerifyInternal' => [ 'httpMethod' => 'POST', 'uri' => '/mfa/{id}/verify', @@ -279,6 +328,33 @@ ], ], ], + 'mfaVerifyRegistrationInternal' => [ + 'httpMethod' => 'POST', + 'uri' => '/mfa/{id}/verify/registration', + 'responseModel' => 'Result', + 'parameters' => [ + 'id' => [ + 'required' => true, + 'type' => 'string', + 'location' => 'uri' + ], + 'employee_id' => [ + 'required' => true, + 'type' => 'string', + 'location' => 'json' + ], + 'value' => [ + 'required' => true, + 'type' => [ 'string', 'object' ], + 'location' => 'json', + ], + 'rpOrigin' => [ + 'required' => false, + 'type' => 'string', + 'location' => 'query', + ], + ], + ], 'setPasswordInternal' => [ 'httpMethod' => 'PUT', 'uri' => '/user/{employee_id}/password',