Commit 1b19205e authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 93bb0d9c b8c70280
......@@ -335,13 +335,9 @@ gem 'peek', '~> 1.1'
gem 'snowplow-tracker', '~> 0.6.1'
# Metrics
group :metrics do
gem 'method_source', '~> 1.0', require: false
gem 'webrick', '~> 1.6.1', require: false
# Prometheus
gem 'prometheus-client-mmap', '~> 0.12.0'
end
gem 'method_source', '~> 1.0', require: false
gem 'webrick', '~> 1.6.1', require: false
gem 'prometheus-client-mmap', '~> 0.12.0', require: 'prometheus/client'
group :development do
gem 'lefthook', '~> 0.7.0', require: false
......
const supportedMethods = ['patch', 'post', 'put'];
const SUPPORTED_METHODS = ['patch', 'post', 'put'];
export function registerCaptchaModalInterceptor(axios) {
return axios.interceptors.response.use(
(response) => {
return response;
},
(err) => {
if (
supportedMethods.includes(err?.config?.method) &&
err?.response?.data?.needs_captcha_response
) {
const { data } = err.response;
const captchaSiteKey = data.captcha_site_key;
const spamLogId = data.spam_log_id;
// eslint-disable-next-line promise/no-promise-in-callback
return import('~/captcha/wait_for_captcha_to_be_solved')
.then(({ waitForCaptchaToBeSolved }) => waitForCaptchaToBeSolved(captchaSiteKey))
.then((captchaResponse) => {
const errConfig = err.config;
function needsCaptchaResponse(err) {
return (
SUPPORTED_METHODS.includes(err?.config?.method) && err?.response?.data?.needs_captcha_response
);
}
const showCaptchaModalAndResubmit = async (axios, data, errConfig) => {
// NOTE: We asynchronously import and unbox the module. Since this is included globally, we don't
// do a regular import because that would increase the size of the webpack bundle.
const { waitForCaptchaToBeSolved } = await import('~/captcha/wait_for_captcha_to_be_solved');
// show the CAPTCHA modal and wait for it to be solved or closed
const captchaResponse = await waitForCaptchaToBeSolved(data.captcha_site_key);
// resubmit the original request with the captcha_response and spam_log_id in the headers
const originalData = JSON.parse(errConfig.data);
const originalHeaders = errConfig.headers;
return axios({
method: errConfig.method,
url: errConfig.url,
data: {
...originalData,
captcha_response: captchaResponse,
spam_log_id: spamLogId,
headers: {
...originalHeaders,
'X-GitLab-Captcha-Response': captchaResponse,
'X-GitLab-Spam-Log-Id': data.spam_log_id,
},
data: originalData,
});
});
};
export function registerCaptchaModalInterceptor(axios) {
return axios.interceptors.response.use(
(response) => {
return response;
},
(err) => {
if (needsCaptchaResponse(err)) {
return showCaptchaModalAndResubmit(axios, err.response.data, err.config);
}
return Promise.reject(err);
......
......@@ -118,7 +118,10 @@ export const fetchDiffFilesBatch = ({ commit, state, dispatch }) => {
commit(types.SET_BATCH_LOADING, false);
if (window.gon?.features?.diffsVirtualScrolling && !scrolledVirtualScroller) {
const index = state.diffFiles.findIndex((f) => f.file_hash === hash);
const index = state.diffFiles.findIndex(
(f) =>
f.file_hash === hash || f[INLINE_DIFF_LINES_KEY].find((l) => l.line_code === hash),
);
if (index >= 0) {
eventHub.$emit('scrollToIndex', index);
......
import { registerCaptchaModalInterceptor } from '~/captcha/captcha_modal_axios_interceptor';
import axios from '../../lib/utils/axios_utils';
export default class Service {
constructor(endpoint) {
this.endpoint = `${endpoint}.json`;
this.realtimeEndpoint = `${endpoint}/realtime_changes`;
registerCaptchaModalInterceptor(axios);
}
getData() {
......
import axios from 'axios';
import { registerCaptchaModalInterceptor } from '~/captcha/captcha_modal_axios_interceptor';
import setupAxiosStartupCalls from './axios_startup_calls';
import csrf from './csrf';
import suppressAjaxErrorsDuringNavigation from './suppress_ajax_errors_during_navigation';
......@@ -41,6 +42,8 @@ axios.interceptors.response.use(
(err) => suppressAjaxErrorsDuringNavigation(err, isUserNavigating),
);
registerCaptchaModalInterceptor(axios);
export default axios;
/**
......
......@@ -47,17 +47,13 @@ module SpammableActions
end
end
# TODO: This method is currently only needed for issue create and update. It can be removed when:
#
# 1. Issue create is is converted to a client/JS based approach instead of the legacy HAML
# TODO: This method is currently only needed for issue create, to convert spam/CAPTCHA values from
# params, and instead be passed as headers, as the spam services now all expect. It can be removed
# when issue create is is converted to a client/JS based approach instead of the legacy HAML
# `_recaptcha_form.html.haml` which is rendered via the `projects/issues/verify` template.
# In this case, which is based on the legacy reCAPTCHA implementation using the HTML/HAML form,
# In that case, which is based on the legacy reCAPTCHA implementation using the HTML/HAML form,
# the 'g-recaptcha-response' field name comes from `Recaptcha::ClientHelper#recaptcha_tags` in the
# recaptcha gem, which is called from the HAML `_recaptcha_form.html.haml` form.
# 2. Issue update is converted to use the headers-based approach, which will require adding
# support to captcha_modal_axios_interceptor.js like we have already added to
# apollo_captcha_link.js.
# In this case, the `captcha_response` field name comes from our captcha_modal_axios_interceptor.js.
def extract_legacy_spam_params_to_headers
request.headers['X-GitLab-Captcha-Response'] = params['g-recaptcha-response'] || params[:captcha_response]
request.headers['X-GitLab-Spam-Log-Id'] = params[:spam_log_id]
......
......@@ -336,7 +336,6 @@ class Projects::IssuesController < Projects::ApplicationController
end
def update_service
extract_legacy_spam_params_to_headers
spam_params = ::Spam::SpamParams.new_from_request(request: request)
::Issues::UpdateService.new(project: project, current_user: current_user, params: issue_params, spam_params: spam_params)
end
......
---
name: gitlab_experiment_middleware
introduced_by_url:
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/323643
milestone: '14.1'
type: development
group: group::adoption
default_enabled: false
# frozen_string_literal: true
require 'prometheus/client'
# Keep separate directories for separate processes
def prometheus_default_multiproc_dir
return unless Rails.env.development? || Rails.env.test?
......
......@@ -7,17 +7,3 @@ Gitlab::Experiment.configure do |config|
pool: ->(&block) { Gitlab::Redis::SharedState.with { |redis| block.call(redis) } }
)
end
# TODO: This shim should be removed after the feature flag is rolled out, as
# it only exists to facilitate the feature flag control of the behavior.
module Gitlab::Experiment::MiddlewareWithFeatureFlags
attr_reader :app
def call(env)
return app.call(env) unless Feature.enabled?(:gitlab_experiment_middleware)
super
end
end
Gitlab::Experiment::Middleware.prepend(Gitlab::Experiment::MiddlewareWithFeatureFlags)
......@@ -29,6 +29,21 @@ hosts or use IP ranges:
---
**For installations using cloud native Helm charts**
You can set the required IPs under the `gitlab.webservice.monitoring.ipWhitelist` key. For example:
```yaml
gitlab:
webservice:
monitoring:
# Monitoring IP whitelist
ipWhitelist:
- 0.0.0.0/0 # Default
```
---
**For installations from source**
1. Edit `config/gitlab.yml`:
......
......@@ -541,7 +541,7 @@ You must use a Kubernetes network plugin that implements support for
`NetworkPolicy`. The default network plugin for Kubernetes (`kubenet`)
[does not implement](https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/network-plugins/#kubenet)
support for it. The [Cilium](https://cilium.io/) network plugin can be
installed as a [cluster application](../../user/clusters/applications.md#install-cilium-using-gitlab-cicd)
installed as a [cluster application](../../user/project/clusters/protect/container_network_security/quick_start_guide.md#use-the-cluster-management-template-to-install-cilium)
to enable support for network policies.
You can enable deployment of a network policy by setting the following
......@@ -577,7 +577,7 @@ networkPolicy:
```
For more information on installing Network Policies, see
[Install Cilium using GitLab CI/CD](../../user/clusters/applications.md#install-cilium-using-gitlab-cicd).
[Use the Cluster Management Template to Install Cilium](../../user/project/clusters/protect/container_network_security/quick_start_guide.md#use-the-cluster-management-template-to-install-cilium).
### Cilium Network Policy
......@@ -596,7 +596,7 @@ As the default network plugin for Kubernetes (`kubenet`)
support for it, you must have [Cilium](https://docs.cilium.io/en/v1.8/intro/) as your Kubernetes network plugin.
The [Cilium](https://cilium.io/) network plugin can be
installed as a [cluster application](../../user/clusters/applications.md#install-cilium-using-gitlab-cicd)
installed with a [cluster management project template](../../user/project/clusters/protect/container_network_security/quick_start_guide.md#use-the-cluster-management-template-to-install-cilium)
to enable support for network policies.
#### Configuration
......@@ -643,11 +643,10 @@ ciliumNetworkPolicy:
enabled: true
alerts:
enabled: true
```
For more information on installing Network Policies, see
[Install Cilium using GitLab CI/CD](../../user/clusters/applications.md#install-cilium-using-gitlab-cicd).
[Use the Cluster Management Template to Install Cilium](../../user/project/clusters/protect/container_network_security/quick_start_guide.md#use-the-cluster-management-template-to-install-cilium).
### Running commands in the container
......
......@@ -50,9 +50,9 @@ The following table shows the supported metrics, at which level they are support
| Metric | Level | API version | Chart (UI) version | Comments |
|---------------------------|---------------------|--------------------------------------|---------------------------------------|-----------|
| `deployment_frequency` | Project-level | [13.7+](../../api/dora/metrics.md) | [13.8+](#deployment-frequency-charts) | The [old API endpoint](../../api/dora4_project_analytics.md) was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/323713) in 13.10. |
| `deployment_frequency` | Group-level | [13.10+](../../api/dora/metrics.md) | To be supported | |
| `deployment_frequency` | Group-level | [13.10+](../../api/dora/metrics.md) | [13.12+](#deployment-frequency-charts) | |
| `lead_time_for_changes` | Project-level | [13.10+](../../api/dora/metrics.md) | [13.11+](#lead-time-charts) | Unit in seconds. Aggregation method is median. |
| `lead_time_for_changes` | Group-level | [13.10+](../../api/dora/metrics.md) | To be supported | Unit in seconds. Aggregation method is median. |
| `lead_time_for_changes` | Group-level | [13.10+](../../api/dora/metrics.md) | [14.0+](#lead-time-charts) | Unit in seconds. Aggregation method is median. |
| `change_failure_rate` | Project/Group-level | To be supported | To be supported | |
| `time_to_restore_service` | Project/Group-level | To be supported | To be supported | |
......
......@@ -27,16 +27,15 @@ your application's Kubernetes namespace. This section has the following
prerequisites:
- Your project contains at least one [environment](../../../ci/environments/index.md)
- You've [installed Cilium](../../clusters/applications.md#install-cilium-using-gitlab-cicd)
- You've [installed Cilium](../../project/clusters/protect/container_network_security/quick_start_guide.md#use-the-cluster-management-template-to-install-cilium)
- You've configured the [Prometheus service](../../project/integrations/prometheus.md#enabling-prometheus-integration)
If you're using custom Helm values for Cilium, you must enable Hubble
with flow metrics for each namespace by adding the following lines to
your [Cilium values](../../clusters/applications.md#install-cilium-using-gitlab-cicd):
your [Cilium values](../../project/clusters/protect/container_network_security/quick_start_guide.md#use-the-cluster-management-template-to-install-cilium):
```yaml
global:
hubble:
hubble:
enabled: true
metrics:
enabled:
......@@ -54,7 +53,11 @@ about your packet flow:
If a significant percentage of packets is dropped, you should
investigate it for potential threats by
[examining the Cilium logs](../../clusters/applications.md#install-cilium-using-gitlab-cicd).
examining the Cilium logs:
```shell
kubectl -n gitlab-managed-apps logs -l k8s-app=cilium -c cilium-monitor
```
## Container Network Policy management
......@@ -67,7 +70,7 @@ status, and create and edit deployed policies. This section has the
following prerequisites:
- Your project contains at least one [environment](../../../ci/environments/index.md)
- You've [installed Cilium](../../clusters/applications.md#install-cilium-using-gitlab-cicd)
- You've [installed Cilium](../../project/clusters/protect/container_network_security/quick_start_guide.md#use-the-cluster-management-template-to-install-cilium)
Network policies are fetched directly from the selected environment's
deployment platform. Changes performed outside of this tab are
......
......@@ -448,42 +448,21 @@ There are several components that work in concert for the Agent to generate the
- A working Kubernetes cluster.
- Cilium integration through either of these options:
- Installation through [GitLab Managed Apps](../applications.md#install-cilium-using-gitlab-cicd).
- Installation through [cluster management template](../../project/clusters/protect/container_network_security/quick_start_guide.md#use-the-cluster-management-template-to-install-cilium).
- Enablement of [hubble-relay](https://docs.cilium.io/en/v1.8/concepts/overview/#hubble) on an
existing installation.
- One or more network policies through any of these options:
- Use the [Container Network Policy editor](../../application_security/threat_monitoring/index.md#container-network-policy-editor) to create and manage policies.
- Use an [AutoDevOps](../../application_security/threat_monitoring/index.md#container-network-policy-management) configuration.
- Add the required labels and annotations to existing network policies.
- Use a configuration repository to inform the Agent through a `config.yaml` file, which
repositories can synchronize with. This repository might be the same, or a separate GitLab
project.
- A configuration repository with [Cilium configured in `config.yaml`](repository.md#surface-network-security-alerts-from-cluster-to-gitlab)
The setup process follows the same steps as [GitOps](#get-started-with-gitops-and-the-gitlab-agent),
with the following differences:
- When you define a configuration repository, you must do so with [Cilium settings](#define-a-configuration-repository-with-cilium-settings).
- When you define a configuration repository, you must do so with [Cilium settings](repository.md#surface-network-security-alerts-from-cluster-to-gitlab).
- You do not need to specify the `gitops` configuration section.
### Define a configuration repository with Cilium settings
You need a GitLab repository to contain your Agent configuration. The minimal repository layout
looks like this:
```plaintext
.gitlab/agents/<agent-name>/config.yaml
```
Your `config.yaml` file must specify the `host` and `port` of your Hubble Relay service. If your
Cilium integration was performed through [GitLab Managed Apps](../applications.md#install-cilium-using-gitlab-cicd),
you can use `hubble-relay.gitlab-managed-apps.svc.cluster.local:80`:
```yaml
cilium:
hubble_relay_address: "<hubble-relay-host>:<hubble-relay-port>"
...
```
## Management interfaces
Users with at least the [Developer](../../permissions.md) can access the user interface
......
......@@ -157,7 +157,9 @@ cilium:
hubble_relay_address: "<hubble-relay-host>:<hubble-relay-port>"
```
If your Cilium integration was performed through GitLab Managed Apps, you can use `hubble-relay.gitlab-managed-apps.svc.cluster.local:80` as the address:
If your Cilium integration was performed through [GitLab Managed Apps](../applications.md#install-cilium-using-gitlab-cicd) or the
[cluster management template](../../project/clusters/protect/container_network_security/quick_start_guide.md#use-the-cluster-management-template-to-install-cilium),
you can use `hubble-relay.gitlab-managed-apps.svc.cluster.local:80` as the address:
```yaml
cilium:
......
......@@ -460,7 +460,7 @@ You can check Cilium's installation status on the cluster management page:
WARNING:
Installation and removal of the Cilium requires a **manual**
[restart](https://docs.cilium.io/en/stable/gettingstarted/k8s-install-gke/#restart-unmanaged-pods)
[restart](https://docs.cilium.io/en/stable/gettingstarted/k8s-install-helm/#restart-unmanaged-pods)
of all affected pods in all namespaces to ensure that they are
[managed](https://docs.cilium.io/en/v1.8/operations/troubleshooting/#ensure-managed-pod)
by the correct networking plugin. Whenever Hubble is enabled, its related pod might require a
......
......@@ -532,12 +532,24 @@ If you get this error, ensure that:
### `npm publish` returns `npm ERR! 400 Bad Request`
If you get this error, your package name may not meet the
If you get this error, one of the following problems could be causing it.
#### Package name does not meet the naming convention
Your package name may not meet the
[`@scope/package-name` package naming convention](#package-naming-convention).
Ensure the name meets the convention exactly, including the case.
Then try to publish again.
#### Package already exists
Your package has already been published to another project in the same
root namespace and therefore cannot be published again using the same name.
This is also true even if the prior published package shares the same name,
but not the version.
### `npm publish` returns `npm ERR! 500 Internal Server Error - PUT`
This is a [known issue](https://gitlab.com/gitlab-org/gitlab/-/issues/238950) in GitLab
......
......@@ -24,16 +24,110 @@ The following steps are recommended to install and use Container Network Securit
into the **Base domain** field on the **Details** tab. Save the changes to the Kubernetes
cluster.
1. [Install and configure Cilium](../../../../clusters/applications.md#install-cilium-using-gitlab-cicd).
1. [Install and configure Cilium](#use-the-cluster-management-template-to-install-cilium).
1. Be sure to restart all pods that were running before Cilium was installed by running this command
in your cluster:
`kubectl get pods --all-namespaces -o custom-columns=NAMESPACE:.metadata.namespace,NAME:.metadata.name,HOSTNETWORK:.spec.hostNetwork --no-headers=true | grep '<none>' | awk '{print "-n "$1" "$2}' | xargs -L 1 -r kubectl delete pod`
You can skip this step if `nodeinit.restartPods` is set to `true` on your Helm chart.
It's possible to install and manage Cilium in other ways. For example, you could use the GitLab Helm
chart to install Cilium manually in a Kubernetes cluster, and then connect it back to GitLab.
However, such methods aren't documented or officially supported by GitLab.
### Use the Cluster Management template to install Cilium
[Cilium](https://cilium.io/) is a networking plug-in for Kubernetes that you can use to implement
support for [`NetworkPolicy`](https://kubernetes.io/docs/concepts/services-networking/network-policies/)
resources. For more information, see [Network Policies](../../../../../topics/autodevops/stages.md#network-policy).
You can use the [Cluster Management Project Template](../../../../clusters/management_project_template.md)
to install Cilium in your Kubernetes cluster.
1. In your cluster management project, go to `helmfile.yaml` and uncomment `- path: applications/cilium/helmfile.yaml`.
1. In `applications/cilium/helmfile.yaml`, set `clusterType` to either `gke` or `eks` based on which Kubernetes provider your are using.
```yaml
environments:
default:
values:
# Set to "gke" or "eks" based on your cluster type
- clusterType: ""
```
1. Merge or push these changes to the default branch of your cluster management project,
and [GitLab CI/CD](../../../../../ci/README.md) will automatically install Cilium.
WARNING:
Installation and removal of the Cilium requires a **manual**
[restart](https://docs.cilium.io/en/stable/gettingstarted/k8s-install-helm/#restart-unmanaged-pods)
of all affected pods in all namespaces to ensure that they are
[managed](https://docs.cilium.io/en/stable/operations/troubleshooting/#ensure-managed-pod)
by the correct networking plug-in. When Hubble is enabled, its related pod might require a
restart depending on whether it started prior to Cilium. For more information, see
[Failed Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#failed-deployment)
in the Kubernetes docs.
NOTE:
Major upgrades might require additional setup steps. For more information, see
the official [upgrade guide](https://docs.cilium.io/en/stable/operations/upgrade/).
Support for installing the Cilium application is provided by the
GitLab Container Security group. If you run into unknown issues,
[open a new issue](https://gitlab.com/gitlab-org/gitlab/-/issues/new), and ping at
least 2 people from the
[Container Security group](https://about.gitlab.com/handbook/product/categories/#container-security-group).
### Configure the Cilium Helm chart
You can customize Cilium's Helm variables by editing the `applications/cilium/values.yaml`
file in your cluster management project. Refer to the [Cilium Helm reference](https://docs.cilium.io/en/stable/helm-reference/)
for the available configuration options.
By default, Cilium's
[audit mode](https://docs.cilium.io/en/stable/gettingstarted/policy-creation/#enable-policy-audit-mode)
is enabled. In audit mode, Cilium doesn't drop disallowed packets. You
can use `policy-verdict` log to observe policy-related decisions. You
can disable audit mode by setting `policyAuditMode: false` in
`applications/cilium/values.yaml`.
The Cilium monitor log for traffic is logged out by the
`cilium-monitor` sidecar container. You can check these logs with the following command:
```shell
kubectl -n gitlab-managed-apps logs -l k8s-app=cilium -c cilium-monitor
```
You can disable the monitor log in `application/cilium/values.yaml`:
```yaml
monitor:
enabled: false
```
The [Hubble](https://github.com/cilium/hubble) monitoring daemon is enabled by default
and it's set to collect per namespace flow metrics. This metrics are accessible on the
[Threat Monitoring](../../../../application_security/threat_monitoring/index.md)
dashboard. You can disable Hubble by adding the following to
`applications/cilium/values.yaml`:
```yaml
hubble:
enabled: false
```
You can also adjust Helm values for Hubble by using
`applications/cilium/values.yaml`:
```yaml
hubble:
enabled: true
metrics:
enabled:
- 'flow:sourceContext=namespace;destinationContext=namespace'
```
## Managing Network Policies
Managing NetworkPolicies through GitLab is advantageous over managing the policies in Kubernetes
......@@ -62,12 +156,10 @@ editor.
To view statistics for Container Network Security, you must follow the installation steps above and
configure GitLab integration with Prometheus. Also, if you use custom Helm values for Cilium, you
must enable Hubble with flow metrics for each namespace by adding the following lines to
your [Cilium values](../../../../clusters/applications.md#install-cilium-using-gitlab-cicd):
your [Cilium values](../../../../clusters/applications.md#install-cilium-using-gitlab-cicd):
your [Cilium values](#use-the-cluster-management-template-to-install-cilium):
```yaml
global:
hubble:
hubble:
enabled: true
metrics:
enabled:
......@@ -97,14 +189,13 @@ kubectl -n gitlab-managed-apps logs -l k8s-app=cilium -c cilium-monitor
By default, Cilium is installed in Audit mode only, meaning that NetworkPolicies log policy
violations but don't block any traffic. To set Cilium to Blocking mode, you must add the following
lines to the `.gitlab/managed-apps/cilium/values.yaml` file in your cluster management project:
lines to the `applications/cilium/values.yaml` file in your cluster management project:
```yaml
config:
policyAuditMode: false
agent:
monitor:
monitor:
eventTypes: ["drop"]
```
......
......@@ -139,8 +139,11 @@ describe('BoardsSelector', () => {
await wrapper.setData({
loadingBoards: false,
});
await Promise.all([allBoardsResponse, recentBoardsResponse]);
return nextTick();
// NOTE: Due to timing issues, this `return` of `Promise.all` is required because
// `app/assets/javascripts/boards/components/boards_selector.vue` does a `$nextTick()` in
// loadRecentBoards. For some unknown reason it doesn't work with `await`, the `return`
// is required.
return Promise.all([allBoardsResponse, recentBoardsResponse]).then(() => nextTick());
});
it('hides loading spinner', async () => {
......
......@@ -46,7 +46,7 @@ RSpec.describe Gitlab::Audit::Auditor do
it 'records audit events in correct order', :aggregate_failures do
expect { audit! }.to change(AuditEvent, :count).by(2)
event_messages = AuditEvent.all.map { |event| event.details[:custom_message] }
event_messages = AuditEvent.order(:id).map { |event| event.details[:custom_message] }
expect(event_messages).to eq([add_message, remove_message])
end
......
# frozen_string_literal: true
require 'webrick'
require 'prometheus/client/rack/exporter'
module Gitlab
module Metrics
module Exporter
......
# frozen_string_literal: true
require 'webrick'
require 'prometheus/client/rack/exporter'
module Gitlab
module Metrics
module Exporter
......
# frozen_string_literal: true
require 'webrick'
require 'prometheus/client/rack/exporter'
module Gitlab
module Metrics
module Exporter
......
# frozen_string_literal: true
require 'prometheus/client'
module Gitlab
module Metrics
module Prometheus
......
......@@ -1016,10 +1016,13 @@ RSpec.describe Projects::IssuesController do
let(:spammy_title) { 'Whatever' }
let!(:spam_logs) { create_list(:spam_log, 2, user: user, title: spammy_title) }
before do
request.headers['X-GitLab-Captcha-Response'] = 'a-valid-captcha-response'
request.headers['X-GitLab-Spam-Log-Id'] = spam_logs.last.id
end
def update_verified_issue
update_issue(
issue_params: { title: spammy_title },
additional_params: { spam_log_id: spam_logs.last.id, 'g-recaptcha-response': true })
update_issue(issue_params: { title: spammy_title })
end
it 'returns 200 status' do
......@@ -1036,8 +1039,9 @@ RSpec.describe Projects::IssuesController do
it 'does not mark spam log as recaptcha_verified when it does not belong to current_user' do
spam_log = create(:spam_log)
request.headers['X-GitLab-Spam-Log-Id'] = spam_log.id
expect { update_issue(issue_params: { spam_log_id: spam_log.id, 'g-recaptcha-response': true }) }
expect { update_issue }
.not_to change { SpamLog.last.recaptcha_verified }
end
end
......
import MockAdapter from 'axios-mock-adapter';
import { registerCaptchaModalInterceptor } from '~/captcha/captcha_modal_axios_interceptor';
import UnsolvedCaptchaError from '~/captcha/unsolved_captcha_error';
import { waitForCaptchaToBeSolved } from '~/captcha/wait_for_captcha_to_be_solved';
import axios from '~/lib/utils/axios_utils';
import httpStatusCodes from '~/lib/utils/http_status';
......@@ -25,22 +26,24 @@ describe('registerCaptchaModalInterceptor', () => {
let mock;
beforeEach(() => {
waitForCaptchaToBeSolved.mockRejectedValue(new UnsolvedCaptchaError());
mock = new MockAdapter(axios);
mock.onAny('/no-captcha').reply(200, AXIOS_RESPONSE);
mock.onAny('/error').reply(404, AXIOS_RESPONSE);
mock.onAny('/captcha').reply((config) => {
mock.onAny('/endpoint-without-captcha').reply(200, AXIOS_RESPONSE);
mock.onAny('/endpoint-with-unrelated-error').reply(404, AXIOS_RESPONSE);
mock.onAny('/endpoint-with-captcha').reply((config) => {
if (!supportedMethods.includes(config.method)) {
return [httpStatusCodes.METHOD_NOT_ALLOWED, { method: config.method }];
}
try {
const { captcha_response, spam_log_id, ...rest } = JSON.parse(config.data);
// eslint-disable-next-line babel/camelcase
if (captcha_response === CAPTCHA_RESPONSE && spam_log_id === SPAM_LOG_ID) {
return [httpStatusCodes.OK, { ...rest, method: config.method, CAPTCHA_SUCCESS }];
}
} catch (e) {
return [httpStatusCodes.BAD_REQUEST, { method: config.method }];
const data = JSON.parse(config.data);
const {
'X-GitLab-Captcha-Response': captchaResponse,
'X-GitLab-Spam-Log-Id': spamLogId,
} = config.headers;
if (captchaResponse === CAPTCHA_RESPONSE && spamLogId === SPAM_LOG_ID) {
return [httpStatusCodes.OK, { ...data, method: config.method, CAPTCHA_SUCCESS }];
}
return [httpStatusCodes.CONFLICT, NEEDS_CAPTCHA_RESPONSE];
......@@ -56,7 +59,7 @@ describe('registerCaptchaModalInterceptor', () => {
describe.each([...supportedMethods, ...unsupportedMethods])('For HTTP method %s', (method) => {
it('successful requests are passed through', async () => {
const { data, status } = await axios[method]('/no-captcha');
const { data, status } = await axios[method]('/endpoint-without-captcha');
expect(status).toEqual(httpStatusCodes.OK);
expect(data).toEqual(AXIOS_RESPONSE);
......@@ -64,7 +67,7 @@ describe('registerCaptchaModalInterceptor', () => {
});
it('error requests without needs_captcha_response_errors are passed through', async () => {
await expect(() => axios[method]('/error')).rejects.toThrow(
await expect(() => axios[method]('/endpoint-with-unrelated-error')).rejects.toThrow(
expect.objectContaining({
response: expect.objectContaining({
status: httpStatusCodes.NOT_FOUND,
......@@ -79,21 +82,35 @@ describe('registerCaptchaModalInterceptor', () => {
describe.each(supportedMethods)('For HTTP method %s', (method) => {
describe('error requests with needs_captcha_response_errors', () => {
const submittedData = { ID: 12345 };
const submittedHeaders = { 'Submitted-Header': 67890 };
it('re-submits request if captcha was solved correctly', async () => {
waitForCaptchaToBeSolved.mockResolvedValue(CAPTCHA_RESPONSE);
const { data: returnedData } = await axios[method]('/captcha', submittedData);
waitForCaptchaToBeSolved.mockResolvedValueOnce(CAPTCHA_RESPONSE);
const axiosResponse = await axios[method]('/endpoint-with-captcha', submittedData, {
headers: submittedHeaders,
});
const {
data: returnedData,
config: { headers: returnedHeaders },
} = axiosResponse;
expect(waitForCaptchaToBeSolved).toHaveBeenCalledWith(CAPTCHA_SITE_KEY);
expect(returnedData).toEqual({ ...submittedData, CAPTCHA_SUCCESS, method });
expect(returnedHeaders).toEqual(
expect.objectContaining({
...submittedHeaders,
'X-GitLab-Captcha-Response': CAPTCHA_RESPONSE,
'X-GitLab-Spam-Log-Id': SPAM_LOG_ID,
}),
);
expect(mock.history[method]).toHaveLength(2);
});
it('does not re-submit request if captcha was not solved', async () => {
const error = new Error('Captcha not solved');
waitForCaptchaToBeSolved.mockRejectedValue(error);
await expect(() => axios[method]('/captcha', submittedData)).rejects.toThrow(error);
await expect(() => axios[method]('/endpoint-with-captcha', submittedData)).rejects.toThrow(
new UnsolvedCaptchaError(),
);
expect(waitForCaptchaToBeSolved).toHaveBeenCalledWith(CAPTCHA_SITE_KEY);
expect(mock.history[method]).toHaveLength(1);
......@@ -103,7 +120,7 @@ describe('registerCaptchaModalInterceptor', () => {
describe.each(unsupportedMethods)('For HTTP method %s', (method) => {
it('ignores captcha response', async () => {
await expect(() => axios[method]('/captcha')).rejects.toThrow(
await expect(() => axios[method]('/endpoint-with-captcha')).rejects.toThrow(
expect.objectContaining({
response: expect.objectContaining({
status: httpStatusCodes.METHOD_NOT_ALLOWED,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment