361 lines
12 KiB
Markdown
361 lines
12 KiB
Markdown
---
|
|
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
|
|
---
|
|
|
|
# Exploratory testing of CAPTCHAs
|
|
|
|
You can reliably test CAPTCHA on review apps, and in your local development environment (GDK).
|
|
You can always:
|
|
|
|
- Force a reCAPTCHA to appear where it is supported.
|
|
- Force a checkbox to display, instead of street sign images to find and select.
|
|
|
|
To set up testing, follow the configuration on this page.
|
|
|
|
## Use appropriate test data
|
|
|
|
Make sure you are testing a scenario which has spam/CAPTCHA enabled. For example:
|
|
make sure you are editing a _public_ snippet, as only public snippets are checked for spam.
|
|
|
|
## Enable feature flags
|
|
|
|
Enable any relevant feature flag, if the spam/CAPTCHA support is behind a feature flag.
|
|
|
|
## Set up Akismet and reCAPTCHA
|
|
|
|
1. To set up reCAPTCHA:
|
|
1. Review the [GitLab reCAPTCHA documentation](../../integration/recaptcha.md).
|
|
1. Get Google's official test reCAPTCHA credentials using the instructions from
|
|
[Google's reCAPTCHA documentation](https://developers.google.com/recaptcha/docs/faq#id-like-to-run-automated-tests-with-recaptcha.-what-should-i-do).
|
|
1. For **Site key**, use: `6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI`
|
|
1. For **Secret key**, use: `6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe`
|
|
1. Go to **Admin -> Settings -> Reporting** settings: `http://gdk.test:3000/admin/application_settings/reporting#js-spam-settings`
|
|
1. Select **Enable reCAPTCHA**. Enabling for login is not required unless you are testing that feature.
|
|
1. Enter the **Site key** and **Secret key**.
|
|
1. To set up Akismet:
|
|
1. Review the [GitLab documentation on Akismet](../../integration/akismet.md).
|
|
1. Get an Akismet API key. You can sign up for [a testing key from Akismet](https://akismet.com).
|
|
You must enter your local host (such as`gdk.test`) and email when signing up.
|
|
1. Go to GitLab Akismet settings page, for example:
|
|
`http://gdk.test:3000/admin/application_settings/reporting#js-spam-settings`
|
|
1. Enable Akismet and enter your Akismet **API key**.
|
|
1. To force an Akismet false-positive spam check, refer to the
|
|
[Akismet API documentation](https://akismet.com/development/api/#comment-check) and
|
|
[Akismet Getting Started documentation](https://docs.akismet.com/getting-started/confirm/) for more details:
|
|
1. You can use `akismet-guaranteed-spam@example.com` as the author email to force spam using the following steps:
|
|
1. Go to user email settings: `http://gdk.test:3000/-/profile/emails`
|
|
1. Add `akismet-guaranteed-spam@example.com` as a secondary email for the administrator user.
|
|
1. Confirm it in the Rails console: `bin/rails c` -> `User.find_by_username('root').emails.last.confirm`
|
|
1. Switch this verified email to be your primary email:
|
|
1. Go to **Avatar dropdown list -> Edit Profile -> Main Settings**.
|
|
1. For **Email**, enter `akismet-guaranteed-spam@example.com` to replace `admin@example.com`.
|
|
1. Select **Update Profile Settings** to save your changes.
|
|
|
|
## Test in the web UI
|
|
|
|
After you have all the above configuration in place, you can test CAPTCHAs. Test
|
|
in an area of the application which already has CAPTCHA support, such as:
|
|
|
|
- Creating or editing an issue.
|
|
- Creating or editing a public snippet. Only **public** snippets are checked for spam.
|
|
|
|
## Test in a development environment
|
|
|
|
After you force Spam Flagging + CAPTCHA using the steps above, you can test the
|
|
behavior with any spam-protected model/controller action.
|
|
|
|
### Test with CAPTCHA enabled (CONDITIONAL_ALLOW verdict)
|
|
|
|
If CAPTCHA is enabled in these areas, you must solve the CAPTCHA popup modal before you can resubmit the form:
|
|
|
|
- **Admin -> Settings -> Reporting -> Spam**
|
|
- **Anti-bot Protection -> Enable reCAPTCHA**
|
|
|
|
<!-- vale gitlab.Substitutions = NO -->
|
|
|
|
### Testing with CAPTCHA disabled ("DISALLOW" verdict)
|
|
|
|
<!-- vale gitlab.Substitutions = YES -->
|
|
|
|
If CAPTCHA is disabled in **Admin -> Settings -> Reporting -> Spam** and **Anti-bot Protection -> Enable reCAPTCHA**,
|
|
no CAPTCHA popup displays. You are prevented from submitting the form at all.
|
|
|
|
### HTML page to render reCAPTCHA
|
|
|
|
NOTE:
|
|
If you use **Google's official test reCAPTCHA credentials** listed in
|
|
[Set up Akismet and reCAPTCHA](#set-up-akismet-and-recaptcha), the
|
|
CAPTCHA response string does not matter. It can be any string. If you use a
|
|
real, valid key pair, you must solve the CAPTCHA to obtain a
|
|
valid CAPTCHA response to use. You can do this once only, and only before it expires.
|
|
|
|
To directly test the GraphQL API via [GraphQL Explorer](http://gdk.test:3000/-/graphql-explorer),
|
|
get a reCAPTCHA response string via this form: `public/recaptcha.html` (`http://gdk.test:3000/recaptcha.html`):
|
|
|
|
```html
|
|
<html>
|
|
<head>
|
|
<title>reCAPTCHA demo: Explicit render after an onload callback</title>
|
|
<script type="text/javascript">
|
|
var onloadCallback = function() {
|
|
grecaptcha.render('html_element', {
|
|
'sitekey' : '6Ld05AsaAAAAAMsm1yTUp4qsdFARN15rQJPPqv6i'
|
|
});
|
|
};
|
|
function onSubmit() {
|
|
window.document.getElementById('recaptchaResponse').innerHTML = grecaptcha.getResponse();
|
|
return false;
|
|
}
|
|
</script>
|
|
</head>
|
|
<body>
|
|
<form onsubmit="return onSubmit()">
|
|
<div id="html_element"></div>
|
|
<br>
|
|
<input type="submit" value="Submit">
|
|
</form>
|
|
<div>
|
|
<h1>recaptchaResponse:</h1>
|
|
<div id="recaptchaResponse"></div>
|
|
</div>
|
|
<script src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit"
|
|
async defer>
|
|
</script>
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
## Spam/CAPTCHA API exploratory testing examples
|
|
|
|
These sections describe the steps needed to perform manual exploratory testing of
|
|
various scenarios of the Spam and CAPTCHA behavior for the REST and GraphQL APIs.
|
|
|
|
For the prerequisites, you must:
|
|
|
|
1. Perform all the steps listed above to enable Spam and CAPTCHA in the development environment,
|
|
and force form submissions to require a CAPTCHA.
|
|
1. Ensure you have created an HTML page to render CAPTCHA under the `/public` directory,
|
|
with a page that contains a form to manually generate a valid CAPTCHA response string.
|
|
If you use **Google's official test reCAPTCHA credentials** listed in
|
|
[Set up Akismet and reCAPTCHA](#set-up-akismet-and-recaptcha), the contents of the
|
|
CAPTCHA response string don't matter.
|
|
1. Go to **Admin -> Settings -> Reporting -> Spam and Anti-bot protection**.
|
|
1. Select or clear **Enable reCAPTCHA** and **Enable Akismet** according to your
|
|
scenario's needs.
|
|
|
|
The following examples use snippet creation as an example. You could also use
|
|
snippet updates, issue creation, or issue updates. Issues and snippets are the
|
|
only models with full Spam and CAPTCHA support.
|
|
|
|
### Initial setup
|
|
|
|
1. Create an API token.
|
|
1. Export it in your terminal for the REST commands: `export PRIVATE_TOKEN=<your_api_token>`
|
|
1. Ensure you are logged into GitLab development environment at `localhost:3000` before using GraphiQL explorer,
|
|
because it uses your logged-in user as authorization for running GraphQL queries.
|
|
1. For the GraphQL examples, use the GraphiQL explorer at `http://localhost:3000/-/graphql-explorer`.
|
|
1. Use the `--include` (`-i`) option to `curl` to print the HTTP response headers, including the status code.
|
|
|
|
### Scenario: Akismet and CAPTCHA enabled
|
|
|
|
In this example, Akismet and CAPTCHA are enabled:
|
|
|
|
1. [Initial request](#initial-request).
|
|
|
|
<!-- TODO in future edit
|
|
|
|
Some example videos:
|
|
|
|
- REST API:
|
|
|
|
![CAPTCHA REST API](/uploads/b148cbe45496e6f4a4f63d00bb9fbd8a/captcha_rest_api.mov)
|
|
|
|
GraphQL API:
|
|
|
|
![CAPTCHA GraphQL API](/uploads/3c7ef0fad0b84bd588572bae51519463/captcha_graphql_api.mov)
|
|
|
|
-->
|
|
|
|
#### Initial request
|
|
|
|
This initial request fails because no CAPTCHA response is provided.
|
|
|
|
REST request:
|
|
|
|
```shell
|
|
curl --request POST --header "PRIVATE-TOKEN: $PRIVATE_TOKEN" "http://localhost:3000/api/v4/snippets?title=Title&file_name=FileName&content=Content&visibility=public"
|
|
```
|
|
|
|
REST response:
|
|
|
|
```shell
|
|
{"needs_captcha_response":true,"spam_log_id":42,"captcha_site_key":"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX","message":{"error":"Your snippet has been recognized as spam. Please, change the content or solve the reCAPTCHA to proceed."}}
|
|
```
|
|
|
|
GraphQL request:
|
|
|
|
```graphql
|
|
mutation {
|
|
createSnippet(input: {
|
|
title: "Title"
|
|
visibilityLevel: public
|
|
blobActions: [
|
|
{
|
|
action: create
|
|
filePath: "BlobPath"
|
|
content: "BlobContent"
|
|
}
|
|
]
|
|
}) {
|
|
snippet {
|
|
id
|
|
title
|
|
}
|
|
errors
|
|
}
|
|
}
|
|
```
|
|
|
|
GraphQL response:
|
|
|
|
```json
|
|
{
|
|
"data": {
|
|
"createSnippet": null
|
|
},
|
|
"errors": [
|
|
{
|
|
"message": "Request denied. Solve CAPTCHA challenge and retry",
|
|
"locations": [
|
|
{
|
|
"line": 22,
|
|
"column": 5
|
|
}
|
|
],
|
|
"path": [
|
|
"createSnippet"
|
|
],
|
|
"extensions": {
|
|
"needs_captcha_response": true,
|
|
"spam_log_id": 140,
|
|
"captcha_site_key": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
#### Second request
|
|
|
|
This request succeeds because a CAPTCHA response is provided.
|
|
|
|
REST request:
|
|
|
|
```shell
|
|
export CAPTCHA_RESPONSE="<CAPTCHA response obtained from HTML page to render CAPTCHA>"
|
|
export SPAM_LOG_ID="<spam_log_id obtained from initial REST response>"
|
|
curl --request POST --header "PRIVATE-TOKEN: $PRIVATE_TOKEN" --header "X-GitLab-Captcha-Response: $CAPTCHA_RESPONSE" --header "X-GitLab-Spam-Log-Id: $SPAM_LOG_ID" "http://localhost:3000/api/v4/snippets?title=Title&file_name=FileName&content=Content&visibility=public"
|
|
```
|
|
|
|
REST response:
|
|
|
|
```shell
|
|
{"id":42,"title":"Title","description":null,"visibility":"public", "other_fields": "..."}
|
|
```
|
|
|
|
GraphQL request:
|
|
|
|
NOTE:
|
|
The GitLab GraphiQL implementation doesn't allow passing of headers, so we must write
|
|
this as a `curl` query. Here, `--data-binary` is used to properly handle escaped double quotes
|
|
in the JSON-embedded query.
|
|
|
|
```shell
|
|
export CAPTCHA_RESPONSE="<CAPTCHA response obtained from HTML page to render CAPTCHA>"
|
|
export SPAM_LOG_ID="<spam_log_id obtained from initial REST response>"
|
|
curl --include "http://localhost:3000/api/graphql" --header "Authorization: Bearer $PRIVATE_TOKEN" --header "Content-Type: application/json" --header "X-GitLab-Captcha-Response: $CAPTCHA_RESPONSE" --header "X-GitLab-Spam-Log-Id: $SPAM_LOG_ID" --request POST --data-binary '{"query": "mutation {createSnippet(input: {title: \"Title\" visibilityLevel: public blobActions: [ { action: create filePath: \"BlobPath\" content: \"BlobContent\" } ] }) { snippet { id title } errors }}"}'
|
|
```
|
|
|
|
GraphQL response:
|
|
|
|
```json
|
|
{"data":{"createSnippet":{"snippet":{"id":"gid://gitlab/PersonalSnippet/42","title":"Title"},"errors":[]}}}
|
|
```
|
|
|
|
### Scenario: Akismet enabled, CAPTCHA disabled
|
|
|
|
For this scenario, ensure you clear **Enable reCAPTCHA** in the Admin Area settings as described above.
|
|
If CAPTCHA is not enabled, any request flagged as potential spam fails with no chance to resubmit,
|
|
even if it could otherwise be resubmitted if CAPTCHA were enabled and successfully solved.
|
|
|
|
The REST request is the same as if CAPTCHA was enabled:
|
|
|
|
```shell
|
|
curl --request POST --header "PRIVATE-TOKEN: $PRIVATE_TOKEN" "http://localhost:3000/api/v4/snippets?title=Title&file_name=FileName&content=Content&visibility=public"
|
|
```
|
|
|
|
REST response:
|
|
|
|
```shell
|
|
{"message":{"error":"Your snippet has been recognized as spam and has been discarded."}}
|
|
```
|
|
|
|
GraphQL request:
|
|
|
|
```graphql
|
|
mutation {
|
|
createSnippet(input: {
|
|
title: "Title"
|
|
visibilityLevel: public
|
|
blobActions: [
|
|
{
|
|
action: create
|
|
filePath: "BlobPath"
|
|
content: "BlobContent"
|
|
}
|
|
]
|
|
}) {
|
|
snippet {
|
|
id
|
|
title
|
|
}
|
|
errors
|
|
}
|
|
}
|
|
```
|
|
|
|
GraphQL response:
|
|
|
|
```json
|
|
{
|
|
"data": {
|
|
"createSnippet": null
|
|
},
|
|
"errors": [
|
|
{
|
|
"message": "Request denied. Spam detected",
|
|
"locations": [
|
|
{
|
|
"line": 22,
|
|
"column": 5
|
|
}
|
|
],
|
|
"path": [
|
|
"createSnippet"
|
|
],
|
|
"extensions": {
|
|
"spam": true
|
|
}
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Scenario: allow_possible_spam feature flag enabled
|
|
|
|
With the `allow_possible_spam` feature flag enabled, the API returns a 200 response. Any
|
|
valid request is successful and no CAPTCHA is presented, even if the request is considered
|
|
spam.
|