Ensure Arkose::TokenVerificationService returns are consistent
What does this MR do and why?
Relates to gitlab-com/gl-infra/production-engineering#25654 (closed)
This MR refactors the Arkose::TokenVerificationService
to return a consistent service response. Previously, if exceptions were rescued the service returned a ServiceResponse.success
but with a payload that differed from when there were no errors. This lead to an S2 incident that prevented Oauth sign-ups.
Additionally, feature specs were added to ensure that we fail as expected with various failure states we may encounter from Arkose.
MR acceptance checklist
Please evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.
Screenshots or screen recordings
Screenshots are required for UI changes, and strongly recommended for all other merge requests.
Before | After |
---|---|
How to set up and validate locally
Numbered steps to set up and validate the change are strongly suggested.
- Start GDK with SaaS simulation
$ export GITLAB_SIMULATE_SAAS=1 $ gdk start
- Configure the required application settings for Arkose. Credentials can be found in 1Password in under
ArkoseLabs API keys
. The development keys should be used.> ::Gitlab::CurrentSettings.update(arkose_labs_public_api_key: 'PUBLIC_KEY', arkose_labs_private_api_key: 'PRIVATE_KEY', arkose_labs_namespace: 'client')
- For each execution of the
Arkose::TokenVerificationService
you need to obtain a new session token using the following curl command. ReplaceARKOSE_PUBLIC_KEY
with the value obtained from 1Password.curl -s -X POST -H 'Content-Type: application/json' --data-binary '{"public_key": "ARKOSE_PUBLIC_KEY"}' 'https://client-api.arkoselabs.com/fc/gt2/public_key/ARKOSE_PUBLIC_KEY' | jq -r '.token'
- Execute the
Arkose::TokenVerificationService
with a user. The response should be an error that says the captcha has not been solved.[1] pry(main)> session_token = 'VALUE_FROM_CURL_COMMAND' [2] pry(main)> response = Arkose::TokenVerificationService.new(session_token: session_token, user: User.last).execute => #<ServiceResponse:0x00007f2cd07ec580 @http_status=nil, @message="Captcha was not solved", @payload={}, @reason=nil, @status=:error>
- Apply the following patch to fake an allowlisted Arkose session.
diff --git a/ee/lib/arkose/verify_response.rb b/ee/lib/arkose/verify_response.rb index 98d6ee2830f3..b755c02debd2 100644 --- a/ee/lib/arkose/verify_response.rb +++ b/ee/lib/arkose/verify_response.rb @@ -54,8 +54,7 @@ def low_risk? end def allowlisted? - telltale_list = response&.dig('session_details', 'telltale_list') || [] - telltale_list.include?(ALLOWLIST_TELLTALE) + true end def custom_score
- Execute the
Arkose::TokenVerificationService
with a user. The response should be successful and include anAkrose::VerifyResponse
in the payload.[5] pry(main)> response = Arkose::TokenVerificationService.new(session_token: session_token, user: User.last).execute [6] pry(main)> response.payload[:response].risk_band => "Medium"
- Execute the
Arkose::TokenVerificationService
without a user. The response should be successful and include anAkrose::VerifyResponse
in the payload.[11] pry(main)> response = Arkose::TokenVerificationService.new(session_token: session_token).execute [12] pry(main)> response.payload[:response].risk_band => "Medium"
- Apply the following patch to raise an unexpected error during the Arkose check.
diff --git a/ee/app/services/arkose/token_verification_service.rb b/ee/app/services/arkose/token_verification_service.rb index ef70f34f8806..f6b0d9135ea7 100644 --- a/ee/app/services/arkose/token_verification_service.rb +++ b/ee/app/services/arkose/token_verification_service.rb @@ -13,6 +13,7 @@ def initialize(session_token:, user: nil) end def execute + raise 'boom!' parsed_response = Gitlab::HTTP.perform_request(Net::HTTP::Post, arkose_verify_url, body: body).parsed_response @response = ::Arkose::VerifyResponse.new(parsed_response)
- Execute the
Arkose::TokenVerificationService
with a user. The response should be successful and include anAkrose::VerifyResponse
in the payload.[21] pry(main)> response = Arkose::TokenVerificationService.new(session_token: session_token, user: User.last).execute [22] pry(main)> response.payload[:response].risk_band => "Low"
- Execute the
Arkose::TokenVerificationService
without a user. The response should be successful and include anAkrose::VerifyResponse
in the payload.[19] pry(main)> response = Arkose::TokenVerificationService.new(session_token: session_token).execute [20] pry(main)> response.payload[:response].risk_band => "Low"