diff options
Diffstat (limited to 'doc/development/spam_protection_and_captcha')
4 files changed, 116 insertions, 24 deletions
diff --git a/doc/development/spam_protection_and_captcha/graphql_api.md b/doc/development/spam_protection_and_captcha/graphql_api.md index b47e3f84320..e3f4e9069e5 100644 --- a/doc/development/spam_protection_and_captcha/graphql_api.md +++ b/doc/development/spam_protection_and_captcha/graphql_api.md @@ -13,28 +13,27 @@ related to changing a model's confidential/public flag. ## Add support to the GraphQL mutations -This implementation is very similar to the controller implementation. You create a `spam_params` -instance based on the request, and pass it to the relevant Service class constructor. +The main steps are: -The three main differences from the controller implementation are: +1. Use `include Mutations::SpamProtection` in your mutation. +1. Create a `spam_params` instance based on the request. Obtain the request from the context + via `context[:request]` when creating the `SpamParams` instance. +1. Pass `spam_params` to the relevant Service class constructor. +1. After you create or update the `Spammable` model instance, call `#check_spam_action_response!` + and pass it the model instance. This call: + 1. Performs the necessary spam checks on the model. + 1. If spam is detected: + - Raises a `GraphQL::ExecutionError` exception. + - Includes the relevant information added as error fields to the response via the `extensions:` parameter. + For more details on these fields, refer to the section in the GraphQL API documentation on + [Resolve mutations detected as spam](../../api/graphql/index.md#resolve-mutations-detected-as-spam). -1. Use `include Mutations::SpamProtection` instead of `...JsonFormatActionsSupport`. -1. Obtain the request from the context via `context[:request]` when creating the `SpamParams` - instance. -1. After you create or updated the `Spammable` model instance, call `#check_spam_action_response!` - and pass it the model instance. This call will: - 1. Perform the necessary spam checks on the model. - 1. If spam is detected: - - Raise a `GraphQL::ExecutionError` exception. - - Include the relevant information added as error fields to the response via the `extensions:` parameter. - For more details on these fields, refer to the section on - [Spam and CAPTCHA support in the GraphQL API](../../api/graphql/index.md#resolve-mutations-detected-as-spam). - - NOTE: - If you use the standard ApolloLink or Axios interceptor CAPTCHA support described - above, the field details are unimportant. They become important if you - attempt to use the GraphQL API directly to process a failed check for potential spam, and - resubmit the request with a solved CAPTCHA response. + NOTE: + If you use the standard ApolloLink or Axios interceptor CAPTCHA support described + above, you can ignore the field details, because they are handled + automatically. They become relevant if you attempt to use the GraphQL API directly to + process a failed check for potential spam, and resubmit the request with a solved + CAPTCHA response. For example: @@ -57,10 +56,13 @@ module Mutations widget = service_response.payload[:widget] check_spam_action_response!(widget) - # If possible spam wasdetected, an exception would have been thrown by + # If possible spam was detected, an exception would have been thrown by # `#check_spam_action_response!`, so the normal resolve return logic can follow below. end end end end ``` + +Refer to the [Exploratory Testing](exploratory_testing.md) section for instructions on how to test +CAPTCHA behavior in the GraphQL API. diff --git a/doc/development/spam_protection_and_captcha/index.md b/doc/development/spam_protection_and_captcha/index.md index 9b195df536d..dbe8c4aa4e9 100644 --- a/doc/development/spam_protection_and_captcha/index.md +++ b/doc/development/spam_protection_and_captcha/index.md @@ -16,7 +16,7 @@ To add this support, you must implement the following areas as applicable: 1. [Model and Services](model_and_services.md): The basic prerequisite changes to the backend code which are required to add spam or CAPTCHA API and UI support for a feature which does not yet have support. -1. REST API (Supported, documentation coming soon): The changes needed to add +1. [REST API](rest_api.md): The changes needed to add spam or CAPTCHA support to Grape REST API endpoints. Refer to the related [REST API documentation](../../api/index.md#resolve-requests-detected-as-spam). 1. [GraphQL API](graphql_api.md): The changes needed to add spam or CAPTCHA support to GraphQL diff --git a/doc/development/spam_protection_and_captcha/rest_api.md b/doc/development/spam_protection_and_captcha/rest_api.md new file mode 100644 index 00000000000..ad74977eb67 --- /dev/null +++ b/doc/development/spam_protection_and_captcha/rest_api.md @@ -0,0 +1,90 @@ +--- +stage: Manage +group: Authentication and Authorization +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments +--- + +# REST API spam protection and CAPTCHA support + +If the model can be modified via the REST API, you must also add support to all of the +relevant API endpoints which may modify spammable or spam-related attributes. This +definitely includes the `POST` and `PUT` mutations, but may also include others, such as those +related to changing a model's confidential/public flag. + +## Add support to the REST endpoints + +The main steps are: + +1. Add `helpers SpammableActions::CaptchaCheck::RestApiActionsSupport` in your `resource`. +1. Create a `spam_params` instance based on the request. +1. Pass `spam_params` to the relevant Service class constructor. +1. After you create or update the `Spammable` model instance, call `#check_spam_action_response!`, + save the created or updated instance in a variable. +1. Identify the error handling logic for the `failure` case of the request, + when create or update was not successful. These indicate possible spam detection, + which adds an error to the `Spammable` instance. + The error is usually similar to `render_api_error!` or `render_validation_error!`. +1. Wrap the existing error handling logic in a + `with_captcha_check_rest_api(spammable: my_spammable_instance)` call, passing the `Spammable` + model instance you saved in a variable as the `spammable:` named argument. This call will: + 1. Perform the necessary spam checks on the model. + 1. If spam is detected: + - Raise a Grape `#error!` exception with a descriptive spam-specific error message. + - Include the relevant information added as error fields to the response. + For more details on these fields, refer to the section in the REST API documentation on + [Resolve requests detected as spam](../../api/index.md#resolve-requests-detected-as-spam). + + NOTE: + If you use the standard ApolloLink or Axios interceptor CAPTCHA support described + above, you can ignore the field details, because they are handled + automatically. They become relevant if you attempt to use the GraphQL API directly to + process a failed check for potential spam, and resubmit the request with a solved + CAPTCHA response. + +Here is an example for the `post` and `put` actions on the `snippets` resource: + +```ruby +module API + class Snippets < ::API::Base + #... + resource :snippets do + # This helper provides `#with_captcha_check_rest_api` + helpers SpammableActions::CaptchaCheck::RestApiActionsSupport + + post do + #... + spam_params = ::Spam::SpamParams.new_from_request(request: request) + service_response = ::Snippets::CreateService.new(project: nil, current_user: current_user, params: attrs, spam_params: spam_params).execute + snippet = service_response.payload[:snippet] + + if service_response.success? + present snippet, with: Entities::PersonalSnippet, current_user: current_user + else + # Wrap the normal error response in a `with_captcha_check_rest_api(spammable: snippet)` block + with_captcha_check_rest_api(spammable: snippet) do + # If possible spam was detected, an exception would have been thrown by + # `#with_captcha_check_rest_api` for Grape to handle via `error!` + render_api_error!({ error: service_response.message }, service_response.http_status) + end + end + end + + put ':id' do + #... + spam_params = ::Spam::SpamParams.new_from_request(request: request) + service_response = ::Snippets::UpdateService.new(project: nil, current_user: current_user, params: attrs, spam_params: spam_params).execute(snippet) + + snippet = service_response.payload[:snippet] + + if service_response.success? + present snippet, with: Entities::PersonalSnippet, current_user: current_user + else + # Wrap the normal error response in a `with_captcha_check_rest_api(spammable: snippet)` block + with_captcha_check_rest_api(spammable: snippet) do + # If possible spam was detected, an exception would have been thrown by + # `#with_captcha_check_rest_api` for Grape to handle via `error!` + render_api_error!({ error: service_response.message }, service_response.http_status) + end + end + end +``` diff --git a/doc/development/spam_protection_and_captcha/web_ui.md b/doc/development/spam_protection_and_captcha/web_ui.md index 6aa01f401bd..9aeb9e96d44 100644 --- a/doc/development/spam_protection_and_captcha/web_ui.md +++ b/doc/development/spam_protection_and_captcha/web_ui.md @@ -37,7 +37,7 @@ additional fields being added to the models. Instead, communication is handled: The spam and CAPTCHA-related logic is also cleanly abstracted into reusable modules and helper methods which can wrap existing logic, and only alter the existing flow if potential spam is detected or a CAPTCHA display is needed. This approach allows the spam and CAPTCHA -support to be easily added to new areas of the application with minimal changes to +support to be added to new areas of the application with minimal changes to existing logic. In the case of the frontend, potentially **zero** changes are needed! On the frontend, this is handled abstractly and transparently using `ApolloLink` for Apollo, and an @@ -75,7 +75,7 @@ sequenceDiagram The backend is also cleanly abstracted via mixin modules and helper methods. The three main changes required to the relevant backend controller actions (normally just `create`/`update`) are: -1. Create a `SpamParams` parameter object instance based on the request, using the simple static +1. Create a `SpamParams` parameter object instance based on the request, using the static `#new_from_request` factory method. This method takes a request, and returns a `SpamParams` instance. 1. Pass the created `SpamParams` instance as the `spam_params` named argument to the Service class constructor, which you should have already added. If the spam check indicates |