Commit 58730258 authored by Shinya Maeda's avatar Shinya Maeda

Merge branch 'master' into per-project-pipeline-iid-ee

parents 3bdee5b8 a454614d
...@@ -354,7 +354,7 @@ group :development, :test do ...@@ -354,7 +354,7 @@ group :development, :test do
gem 'capybara', '~> 2.15' gem 'capybara', '~> 2.15'
gem 'capybara-screenshot', '~> 1.0.0' gem 'capybara-screenshot', '~> 1.0.0'
gem 'selenium-webdriver', '~> 3.5' gem 'selenium-webdriver', '~> 3.12'
gem 'spring', '~> 2.0.0' gem 'spring', '~> 2.0.0'
gem 'spring-commands-rspec', '~> 1.0.4' gem 'spring-commands-rspec', '~> 1.0.4'
...@@ -396,7 +396,7 @@ group :test do ...@@ -396,7 +396,7 @@ group :test do
gem 'test-prof', '~> 0.2.5' gem 'test-prof', '~> 0.2.5'
end end
gem 'octokit', '~> 4.8' gem 'octokit', '~> 4.9'
gem 'mail_room', '~> 0.9.1' gem 'mail_room', '~> 0.9.1'
......
...@@ -123,7 +123,7 @@ GEM ...@@ -123,7 +123,7 @@ GEM
mime-types (>= 1.16) mime-types (>= 1.16)
cause (0.1) cause (0.1)
charlock_holmes (0.7.6) charlock_holmes (0.7.6)
childprocess (0.7.0) childprocess (0.9.0)
ffi (~> 1.0, >= 1.0.11) ffi (~> 1.0, >= 1.0.11)
chronic (0.10.2) chronic (0.10.2)
chronic_duration (0.10.6) chronic_duration (0.10.6)
...@@ -546,7 +546,7 @@ GEM ...@@ -546,7 +546,7 @@ GEM
multi_json (~> 1.3) multi_json (~> 1.3)
multi_xml (~> 0.5) multi_xml (~> 0.5)
rack (>= 1.2, < 3) rack (>= 1.2, < 3)
octokit (4.8.0) octokit (4.9.0)
sawyer (~> 0.8.0, >= 0.5.3) sawyer (~> 0.8.0, >= 0.5.3)
omniauth (1.8.1) omniauth (1.8.1)
hashie (>= 3.4.6, < 3.6.0) hashie (>= 3.4.6, < 3.6.0)
...@@ -857,9 +857,9 @@ GEM ...@@ -857,9 +857,9 @@ GEM
activesupport (>= 3.1) activesupport (>= 3.1)
select2-rails (3.5.9.3) select2-rails (3.5.9.3)
thor (~> 0.14) thor (~> 0.14)
selenium-webdriver (3.5.0) selenium-webdriver (3.12.0)
childprocess (~> 0.5) childprocess (~> 0.5)
rubyzip (~> 1.0) rubyzip (~> 1.2)
sentry-raven (2.7.2) sentry-raven (2.7.2)
faraday (>= 0.7.6, < 1.0) faraday (>= 0.7.6, < 1.0)
settingslogic (2.0.9) settingslogic (2.0.9)
...@@ -1121,7 +1121,7 @@ DEPENDENCIES ...@@ -1121,7 +1121,7 @@ DEPENDENCIES
net-ssh (~> 4.2.0) net-ssh (~> 4.2.0)
nokogiri (~> 1.8.2) nokogiri (~> 1.8.2)
oauth2 (~> 1.4) oauth2 (~> 1.4)
octokit (~> 4.8) octokit (~> 4.9)
omniauth (~> 1.8) omniauth (~> 1.8)
omniauth-auth0 (~> 2.0.0) omniauth-auth0 (~> 2.0.0)
omniauth-authentiq (~> 0.3.3) omniauth-authentiq (~> 0.3.3)
...@@ -1191,7 +1191,7 @@ DEPENDENCIES ...@@ -1191,7 +1191,7 @@ DEPENDENCIES
scss_lint (~> 0.56.0) scss_lint (~> 0.56.0)
seed-fu (~> 2.3.7) seed-fu (~> 2.3.7)
select2-rails (~> 3.5.9) select2-rails (~> 3.5.9)
selenium-webdriver (~> 3.5) selenium-webdriver (~> 3.12)
sentry-raven (~> 2.7) sentry-raven (~> 2.7)
settingslogic (~> 2.0.9) settingslogic (~> 2.0.9)
sham_rack (~> 1.3.6) sham_rack (~> 1.3.6)
......
...@@ -35,7 +35,12 @@ export default { ...@@ -35,7 +35,12 @@ export default {
</script> </script>
<template> <template>
<div class="hide-collapsed value issuable-show-labels js-value"> <div
class="hide-collapsed value issuable-show-labels js-value"
:class="{
'has-labels':!isEmpty,
}"
>
<span <span
v-if="isEmpty" v-if="isEmpty"
class="text-secondary" class="text-secondary"
...@@ -50,7 +55,7 @@ export default { ...@@ -50,7 +55,7 @@ export default {
> >
<span <span
v-tooltip v-tooltip
class="label color-label" class="badge color-label"
data-placement="bottom" data-placement="bottom"
data-container="body" data-container="body"
:style="labelStyle(label)" :style="labelStyle(label)"
......
...@@ -61,8 +61,18 @@ a { ...@@ -61,8 +61,18 @@ a {
code { code {
padding: 2px 4px; padding: 2px 4px;
color: $red-600;
background-color: $red-100; background-color: $red-100;
border-radius: 3px; border-radius: 3px;
.code & {
background-color: inherit;
padding: unset;
}
}
.code {
padding: 9.5px;
} }
table { table {
......
...@@ -33,16 +33,16 @@ module SearchHelper ...@@ -33,16 +33,16 @@ module SearchHelper
"Showing #{from} - #{to} of #{count} #{scope.humanize(capitalize: false)} for \"#{term}\"" "Showing #{from} - #{to} of #{count} #{scope.humanize(capitalize: false)} for \"#{term}\""
end end
def parse_search_result(result) def find_project_for_result_blob(result)
if result.is_a?(String) @project
Gitlab::ProjectSearchResults.parse_search_result(result)
else
Gitlab::Elastic::SearchResults.parse_search_result(result)
end end
def parse_search_result(result)
result
end end
def find_project_for_blob(blob) def search_blob_title(project, filename)
Project.find(blob['_parent']) filename
end end
private private
......
...@@ -13,12 +13,12 @@ module Clusters ...@@ -13,12 +13,12 @@ module Clusters
attr_encrypted :password, attr_encrypted :password,
mode: :per_attribute_iv, mode: :per_attribute_iv,
key: Settings.attr_encrypted_db_key_base, key: Settings.attr_encrypted_db_key_base_truncated,
algorithm: 'aes-256-cbc' algorithm: 'aes-256-cbc'
attr_encrypted :token, attr_encrypted :token,
mode: :per_attribute_iv, mode: :per_attribute_iv,
key: Settings.attr_encrypted_db_key_base, key: Settings.attr_encrypted_db_key_base_truncated,
algorithm: 'aes-256-cbc' algorithm: 'aes-256-cbc'
before_validation :enforce_namespace_to_lower_case before_validation :enforce_namespace_to_lower_case
......
...@@ -11,7 +11,7 @@ module Clusters ...@@ -11,7 +11,7 @@ module Clusters
attr_encrypted :access_token, attr_encrypted :access_token,
mode: :per_attribute_iv, mode: :per_attribute_iv,
key: Settings.attr_encrypted_db_key_base, key: Settings.attr_encrypted_db_key_base_truncated,
algorithm: 'aes-256-cbc' algorithm: 'aes-256-cbc'
validates :gcp_project_id, validates :gcp_project_id,
......
...@@ -154,10 +154,6 @@ class ProjectWiki ...@@ -154,10 +154,6 @@ class ProjectWiki
[title, title_array.join("/")] [title, title_array.join("/")]
end end
def search_files(query)
repository.search_files_by_content(query, default_branch)
end
def repository def repository
@repository ||= Repository.new(full_path, @project, disk_path: disk_path, is_wiki: true) @repository ||= Repository.new(full_path, @project, disk_path: disk_path, is_wiki: true)
end end
......
- project = @project || find_project_for_blob(blob) - project = find_project_for_result_blob(blob)
- file_name, blob = parse_search_result(blob)
- if blob.is_a?(Array)
- file_name, blob = blob
- else
- blob = parse_search_result(blob)
- file_name = blob.filename
- blob_link = project_blob_path(project, tree_join(blob.ref, file_name)) - blob_link = project_blob_path(project, tree_join(blob.ref, file_name))
.blob-result
.file-holder = render partial: 'search/results/blob_data', locals: { blob: blob, project: project, file_name: file_name, blob_link: blob_link }
.js-file-title.file-title
= link_to blob_link do
= icon('fa-file')
%strong
- if @project
= file_name
- else
#{project.full_name}:
%i= file_name
- if blob.data
.file-content.code.term
= render 'shared/file_highlight', blob: blob, first_line_number: blob.startline, blob_link: blob_link
.blob-result
.file-holder
.js-file-title.file-title
= link_to blob_link do
%i.fa.fa-file
%strong
= search_blob_title(project, file_name)
- if blob.data
.file-content.code.term
= render 'shared/file_highlight', blob: blob, first_line_number: blob.startline
- project = @project || find_project_for_blob(wiki_blob) - project = find_project_for_result_blob(wiki_blob)
- wiki_blob = parse_search_result(wiki_blob) - file_name, wiki_blob = parse_search_result(wiki_blob)
- wiki_blob_link = project_wiki_path(project, wiki_blob.basename)
.blob-result = render partial: 'search/results/blob_data', locals: { blob: wiki_blob, project: project, file_name: file_name, blob_link: wiki_blob_link }
.file-holder
.js-file-title.file-title
= link_to project_wiki_path(project, wiki_blob.basename) do
%i.fa.fa-file
%strong
- if @project
= wiki_blob.basename
- else
#{project.full_name}:
%i= wiki_blob.basename
.file-content.code.term
= render 'shared/file_highlight', blob: wiki_blob, first_line_number: wiki_blob.startline
---
title: Update selenium-webdriver to 3.12.0
merge_request: 19351
author: Takuya Noguchi
type: other
---
title: Added ability to search by wiki titles
merge_request: 19112
author:
type: added
---
title: Fix attr_encryption key settings
merge_request:
author:
type: fixed
...@@ -110,17 +110,24 @@ class Settings < Settingslogic ...@@ -110,17 +110,24 @@ class Settings < Settingslogic
File.expand_path(path, Rails.root) File.expand_path(path, Rails.root)
end end
# Returns a 256-bit key for attr_encrypted
def attr_encrypted_db_key_base
# Ruby 2.4+ requires passing in the exact required length for OpenSSL keys # Ruby 2.4+ requires passing in the exact required length for OpenSSL keys
# (https://github.com/ruby/ruby/commit/ce635262f53b760284d56bb1027baebaaec175d1). # (https://github.com/ruby/ruby/commit/ce635262f53b760284d56bb1027baebaaec175d1).
# Previous versions quietly truncated the input. # Previous versions quietly truncated the input.
# #
# The default mode for the attr_encrypted gem is to use a 256-bit key. # Use this when using :per_attribute_iv mode for attr_encrypted.
# We truncate the 128-byte string to 32 bytes. # We have to truncate the string to 32 bytes for a 256-bit cipher.
def attr_encrypted_db_key_base_truncated
Gitlab::Application.secrets.db_key_base[0..31] Gitlab::Application.secrets.db_key_base[0..31]
end end
# This should be used for :per_attribute_salt_and_iv mode. There is no
# need to truncate the key because the encryptor will use the salt to
# generate a hash of the password:
# https://github.com/attr-encrypted/encryptor/blob/c3a62c4a9e74686dd95e0548f9dc2a361fdc95d1/lib/encryptor.rb#L77
def attr_encrypted_db_key_base
Gitlab::Application.secrets.db_key_base
end
private private
def base_url(config) def base_url(config)
......
...@@ -48,7 +48,7 @@ class MigrateKubernetesServiceToNewClustersArchitectures < ActiveRecord::Migrati ...@@ -48,7 +48,7 @@ class MigrateKubernetesServiceToNewClustersArchitectures < ActiveRecord::Migrati
attr_encrypted :token, attr_encrypted :token,
mode: :per_attribute_iv, mode: :per_attribute_iv,
key: Settings.attr_encrypted_db_key_base, key: Settings.attr_encrypted_db_key_base_truncated,
algorithm: 'aes-256-cbc' algorithm: 'aes-256-cbc'
end end
......
...@@ -425,6 +425,18 @@ Role | Name | Upstream | Connection String ...@@ -425,6 +425,18 @@ Role | Name | Upstream | Connection String
If the 'Role' column for any node says "FAILED", check the If the 'Role' column for any node says "FAILED", check the
[Troubleshooting section](#troubleshooting) before proceeding. [Troubleshooting section](#troubleshooting) before proceeding.
Also, check that the check master command works successfully on each node:
```
su - gitlab-consul
gitlab-ctl repmgr-check-master
```
This command relies on exit codes to tell Consul whether a particular node is a master
or secondary. The most important thing here is that this command does not produce errors.
If there are errors it's most likely due to incorrect `gitlab-consul` database user permissions.
Check the [Troubleshooting section](#troubleshooting) before proceeding.
### Configuring the Pgbouncer node ### Configuring the Pgbouncer node
1. Make sure you collect [`CONSUL_SERVER_NODES`](#consul_information), [`CONSUL_PASSWORD_HASH`](#consul_information), and [`PGBOUNCER_PASSWORD_HASH`](#pgbouncer_information) before executing the next step. 1. Make sure you collect [`CONSUL_SERVER_NODES`](#consul_information), [`CONSUL_PASSWORD_HASH`](#consul_information), and [`PGBOUNCER_PASSWORD_HASH`](#pgbouncer_information) before executing the next step.
...@@ -972,6 +984,21 @@ For PostgreSQL, it is usually safe to restart the master node by default. Automa ...@@ -972,6 +984,21 @@ For PostgreSQL, it is usually safe to restart the master node by default. Automa
On the consul server nodes, it is important to restart the consul service in a controlled fashion. Read our [consul documentation](consul.md#restarting-the-server-cluster) for instructions on how to restart the service. On the consul server nodes, it is important to restart the consul service in a controlled fashion. Read our [consul documentation](consul.md#restarting-the-server-cluster) for instructions on how to restart the service.
#### `gitlab-ctl repmgr-check-master` command produces errors
If this command displays errors about database permissions it is likely that something failed during
install, resulting in the `gitlab-consul` database user getting incorrect permissions. Follow these
steps to fix the problem:
1. On the master database node, connect to the database prompt - `gitlab-psql -d template1`
1. Delete the `gitlab-consul` user - `DROP USER "gitlab-consul";`
1. Exit the database prompt - `\q`
1. [Reconfigure GitLab] and the user will be re-added with the proper permissions.
1. Change to the `gitlab-consul` user - `su - gitlab-consul`
1. Try the check command again - `gitlab-ctl repmgr-check-master`.
Now there should not be errors. If errors still occur then there is another problem.
#### PGBouncer error `ERROR: pgbouncer cannot connect to server` #### PGBouncer error `ERROR: pgbouncer cannot connect to server`
You may get this error when running `gitlab-rake gitlab:db:configure` or you You may get this error when running `gitlab-rake gitlab:db:configure` or you
......
...@@ -83,12 +83,12 @@ you need to add a [wildcard DNS A record][wiki-wildcard-dns] pointing to the ...@@ -83,12 +83,12 @@ you need to add a [wildcard DNS A record][wiki-wildcard-dns] pointing to the
host that GitLab runs. For example, an entry would look like this: host that GitLab runs. For example, an entry would look like this:
``` ```
*.example.io. 1800 IN A 1.1.1.1 *.example.io. 1800 IN A 192.0.2.1
*.example.io. 1800 IN AAAA 2001::1 *.example.io. 1800 IN AAAA 2001::1
``` ```
where `example.io` is the domain under which GitLab Pages will be served where `example.io` is the domain under which GitLab Pages will be served
and `1.1.1.1` is the IPv4 address of your GitLab instance and `2001::1` is the and `192.0.2.1` is the IPv4 address of your GitLab instance and `2001::1` is the
IPv6 address. If you don't have IPv6, you can omit the AAAA record. IPv6 address. If you don't have IPv6, you can omit the AAAA record.
> **Note:** > **Note:**
...@@ -193,13 +193,13 @@ world. Custom domains are supported, but no TLS. ...@@ -193,13 +193,13 @@ world. Custom domains are supported, but no TLS.
```shell ```shell
pages_external_url "http://example.io" pages_external_url "http://example.io"
nginx['listen_addresses'] = ['1.1.1.1'] nginx['listen_addresses'] = ['192.0.2.1']
pages_nginx['enable'] = false pages_nginx['enable'] = false
gitlab_pages['external_http'] = ['1.1.1.2:80', '[2001::2]:80'] gitlab_pages['external_http'] = ['192.0.2.2:80', '[2001::2]:80']
``` ```
where `1.1.1.1` is the primary IP address that GitLab is listening to and where `192.0.2.1` is the primary IP address that GitLab is listening to and
`1.1.1.2` and `2001::2` are the secondary IPs the GitLab Pages daemon `192.0.2.2` and `2001::2` are the secondary IPs the GitLab Pages daemon
listens on. If you don't have IPv6, you can omit the IPv6 address. listens on. If you don't have IPv6, you can omit the IPv6 address.
1. [Reconfigure GitLab][reconfigure] 1. [Reconfigure GitLab][reconfigure]
...@@ -228,16 +228,16 @@ world. Custom domains and TLS are supported. ...@@ -228,16 +228,16 @@ world. Custom domains and TLS are supported.
```shell ```shell
pages_external_url "https://example.io" pages_external_url "https://example.io"
nginx['listen_addresses'] = ['1.1.1.1'] nginx['listen_addresses'] = ['192.0.2.1']
pages_nginx['enable'] = false pages_nginx['enable'] = false
gitlab_pages['cert'] = "/etc/gitlab/ssl/example.io.crt" gitlab_pages['cert'] = "/etc/gitlab/ssl/example.io.crt"
gitlab_pages['cert_key'] = "/etc/gitlab/ssl/example.io.key" gitlab_pages['cert_key'] = "/etc/gitlab/ssl/example.io.key"
gitlab_pages['external_http'] = ['1.1.1.2:80', '[2001::2]:80'] gitlab_pages['external_http'] = ['192.0.2.2:80', '[2001::2]:80']
gitlab_pages['external_https'] = ['1.1.1.2:443', '[2001::2]:443'] gitlab_pages['external_https'] = ['192.0.2.2:443', '[2001::2]:443']
``` ```
where `1.1.1.1` is the primary IP address that GitLab is listening to and where `192.0.2.1` is the primary IP address that GitLab is listening to and
`1.1.1.2` and `2001::2` are the secondary IPs where the GitLab Pages daemon `192.0.2.2` and `2001::2` are the secondary IPs where the GitLab Pages daemon
listens on. If you don't have IPv6, you can omit the IPv6 address. listens on. If you don't have IPv6, you can omit the IPv6 address.
1. [Reconfigure GitLab][reconfigure] 1. [Reconfigure GitLab][reconfigure]
......
...@@ -67,11 +67,11 @@ you need to add a [wildcard DNS A record][wiki-wildcard-dns] pointing to the ...@@ -67,11 +67,11 @@ you need to add a [wildcard DNS A record][wiki-wildcard-dns] pointing to the
host that GitLab runs. For example, an entry would look like this: host that GitLab runs. For example, an entry would look like this:
``` ```
*.example.io. 1800 IN A 1.1.1.1 *.example.io. 1800 IN A 192.0.2.1
``` ```
where `example.io` is the domain under which GitLab Pages will be served where `example.io` is the domain under which GitLab Pages will be served
and `1.1.1.1` is the IP address of your GitLab instance. and `192.0.2.1` is the IP address of your GitLab instance.
> **Note:** > **Note:**
You should not use the GitLab domain to serve user pages. For more information You should not use the GitLab domain to serve user pages. For more information
...@@ -253,7 +253,7 @@ world. Custom domains are supported, but no TLS. ...@@ -253,7 +253,7 @@ world. Custom domains are supported, but no TLS.
port: 80 port: 80
https: false https: false
external_http: 1.1.1.2:80 external_http: 192.0.2.2:80
``` ```
1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in 1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in
...@@ -263,7 +263,7 @@ world. Custom domains are supported, but no TLS. ...@@ -263,7 +263,7 @@ world. Custom domains are supported, but no TLS.
``` ```
gitlab_pages_enabled=true gitlab_pages_enabled=true
gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.2:80" gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 192.0.2.2:80"
``` ```
1. Copy the `gitlab-pages-ssl` Nginx configuration file: 1. Copy the `gitlab-pages-ssl` Nginx configuration file:
...@@ -274,7 +274,7 @@ world. Custom domains are supported, but no TLS. ...@@ -274,7 +274,7 @@ world. Custom domains are supported, but no TLS.
``` ```
1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace 1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace
`0.0.0.0` with `1.1.1.1`, where `1.1.1.1` the primary IP where GitLab `0.0.0.0` with `192.0.2.1`, where `192.0.2.1` the primary IP where GitLab
listens to. listens to.
1. Restart NGINX 1. Restart NGINX
1. [Restart GitLab][restart] 1. [Restart GitLab][restart]
...@@ -320,8 +320,8 @@ world. Custom domains and TLS are supported. ...@@ -320,8 +320,8 @@ world. Custom domains and TLS are supported.
port: 443 port: 443
https: true https: true
external_http: 1.1.1.2:80 external_http: 192.0.2.2:80
external_https: 1.1.1.2:443 external_https: 192.0.2.2:443
``` ```
1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in 1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in
...@@ -333,7 +333,7 @@ world. Custom domains and TLS are supported. ...@@ -333,7 +333,7 @@ world. Custom domains and TLS are supported.
``` ```
gitlab_pages_enabled=true gitlab_pages_enabled=true
gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 1.1.1.2:80 -listen-https 1.1.1.2:443 -root-cert /path/to/example.io.crt -root-key /path/to/example.io.key gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 192.0.2.2:80 -listen-https 192.0.2.2:443 -root-cert /path/to/example.io.crt -root-key /path/to/example.io.key
``` ```
1. Copy the `gitlab-pages-ssl` Nginx configuration file: 1. Copy the `gitlab-pages-ssl` Nginx configuration file:
...@@ -344,7 +344,7 @@ world. Custom domains and TLS are supported. ...@@ -344,7 +344,7 @@ world. Custom domains and TLS are supported.
``` ```
1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace 1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace
`0.0.0.0` with `1.1.1.1`, where `1.1.1.1` the primary IP where GitLab `0.0.0.0` with `192.0.2.1`, where `192.0.2.1` the primary IP where GitLab
listens to. listens to.
1. Restart NGINX 1. Restart NGINX
1. [Restart GitLab][restart] 1. [Restart GitLab][restart]
......
...@@ -144,7 +144,7 @@ helm install --name gitlab -f values.yaml gitlab/gitlab-omnibus ...@@ -144,7 +144,7 @@ helm install --name gitlab -f values.yaml gitlab/gitlab-omnibus
or passing them on the command line: or passing them on the command line:
```bash ```bash
helm install --name gitlab --set baseDomain=gitlab.io,baseIP=1.1.1.1,gitlab=ee,gitlabEELicense=$LICENSE,legoEmail=email@gitlab.com gitlab/gitlab-omnibus helm install --name gitlab --set baseDomain=gitlab.io,baseIP=192.0.2.1,gitlab=ee,gitlabEELicense=$LICENSE,legoEmail=email@gitlab.com gitlab/gitlab-omnibus
``` ```
## Updating GitLab using the Helm Chart ## Updating GitLab using the Helm Chart
......
...@@ -110,7 +110,7 @@ On the sign in page there should now be a GitHub icon below the regular sign in ...@@ -110,7 +110,7 @@ On the sign in page there should now be a GitHub icon below the regular sign in
Click the icon to begin the authentication process. GitHub will ask the user to sign in and authorize the GitLab application. Click the icon to begin the authentication process. GitHub will ask the user to sign in and authorize the GitLab application.
If everything goes well the user will be returned to GitLab and will be signed in. If everything goes well the user will be returned to GitLab and will be signed in.
### GitHub Enterprise with Self-Signed Certificate ## GitHub Enterprise with self-signed Certificate
If you are attempting to import projects from GitHub Enterprise with a self-signed If you are attempting to import projects from GitHub Enterprise with a self-signed
certificate and the imports are failing, you will need to disable SSL verification. certificate and the imports are failing, you will need to disable SSL verification.
......
This diff is collapsed.
# GitHub Project Integration # GitHub project integration **[PREMIUM]**
GitLab provides integration for updating pipeline statuses on GitHub. This is especially useful if using GitLab for CI/CD only. > [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/3836) in GitLab Premium 10.6.
This project integration is separate from the [instance wide GitHub integration][gh-integration] and is automatically configured on [GitHub import][gh-import]. GitLab provides an integration for updating the pipeline statuses on GitHub.
This is especially useful if using GitLab for CI/CD only.
This project integration is separate from the [instance wide GitHub integration](../import/github.md#mirroring-and-pipeline-status-sharing)
and is automatically configured on [GitHub import](../../../integration/github.md).
![Pipeline status update on GitHub](img/github_status_check_pipeline_update.png) ![Pipeline status update on GitHub](img/github_status_check_pipeline_update.png)
...@@ -10,14 +14,14 @@ This project integration is separate from the [instance wide GitHub integration] ...@@ -10,14 +14,14 @@ This project integration is separate from the [instance wide GitHub integration]
### Complete these steps on GitHub ### Complete these steps on GitHub
This integration requires a [GitHub API token](https://github.com/settings/tokens) with `repo:status` access granted: This integration requires a [GitHub API token](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/)
with `repo:status` access granted:
1. Go to your "Personal access tokens" page at https://github.com/settings/tokens 1. Go to your "Personal access tokens" page at https://github.com/settings/tokens
1. Click "Generate New Token" 1. Click "Generate New Token"
1. Ensure that `repo:status` is checked and click "Generate token" 1. Ensure that `repo:status` is checked and click "Generate token"
1. Copy the generated token to use on GitLab 1. Copy the generated token to use on GitLab
### Complete these steps on GitLab ### Complete these steps on GitLab
1. Navigate to the project you want to configure. 1. Navigate to the project you want to configure.
...@@ -25,10 +29,7 @@ This integration requires a [GitHub API token](https://github.com/settings/token ...@@ -25,10 +29,7 @@ This integration requires a [GitHub API token](https://github.com/settings/token
1. Click "GitHub". 1. Click "GitHub".
1. Select the "Active" checkbox. 1. Select the "Active" checkbox.
1. Paste the token you've generated on GitHub 1. Paste the token you've generated on GitHub
1. Enter the path to your project on GitHub, such as "https://github.com/your-name/YourProject/" 1. Enter the path to your project on GitHub, such as `https://github.com/username/repository`
1. Save or optionally click "Test Settings". 1. Save or optionally click "Test Settings".
![Configure GitHub Project Integration](img/github_configuration.png) ![Configure GitHub Project Integration](img/github_configuration.png)
[gh-import]: ../import/github.md#mirroring
[gh-integration]: ../../../integration/github.md
...@@ -28,7 +28,7 @@ The following languages and frameworks are supported. ...@@ -28,7 +28,7 @@ The following languages and frameworks are supported.
| Language / framework | Scan tool | | Language / framework | Scan tool |
|-----------------------|----------------------------------------------------------------------------------------| |-----------------------|----------------------------------------------------------------------------------------|
| C/C++ | [Flawfinder](https://www.dwheeler.com/flawfinder/) | | C/C++ | [Flawfinder](https://www.dwheeler.com/flawfinder/) |
| Python | [bandit](https://github.com/openstack/bandit) | | Python | [bandit](https://github.com/PyCQA/bandit) |
| Ruby on Rails | [brakeman](https://brakemanscanner.org) | | Ruby on Rails | [brakeman](https://brakemanscanner.org) |
| Java (Maven & Gradle) | [find-sec-bugs](https://find-sec-bugs.github.io/) | | Java (Maven & Gradle) | [find-sec-bugs](https://find-sec-bugs.github.io/) |
| Go (experimental) | [Go AST Scanner](https://github.com/GoASTScanner/gas) | | Go (experimental) | [Go AST Scanner](https://github.com/GoASTScanner/gas) |
......
# Repository mirroring # Repository mirroring
Repository Mirroring is a way to mirror repositories from external sources. Repository mirroring is a way to mirror repositories from external sources.
It can be used to mirror all branches, tags, and commits that you have It can be used to mirror all branches, tags, and commits that you have
in your repository. in your repository.
...@@ -34,7 +34,7 @@ A few things/limitations to consider: ...@@ -34,7 +34,7 @@ A few things/limitations to consider:
- The Git LFS objects will not be synced. You'll need to push/pull them - The Git LFS objects will not be synced. You'll need to push/pull them
manually. manually.
## Use-cases ## Use cases
- You migrated to GitLab but still need to keep your project in another source. - You migrated to GitLab but still need to keep your project in another source.
In that case, you can simply set it up to mirror to GitLab (pull) and all the In that case, you can simply set it up to mirror to GitLab (pull) and all the
...@@ -294,11 +294,12 @@ by using the **Update now** button which is exposed in various places: ...@@ -294,11 +294,12 @@ by using the **Update now** button which is exposed in various places:
## Bidirectional mirroring ## Bidirectional mirroring
> **Warning:** There is no bidirectional support without conflicts. If you CAUTION: **Warning:**
> configure a repository to pull and push to a second remote, there is no There is no bidirectional support without conflicts. If you
> guarantee that it will update correctly on both remotes. If you configure configure a repository to pull and push to a second remote, there is no
> a repository for bidirectional mirroring, you should consider when conflicts guarantee that it will update correctly on both remotes. If you configure
> occur who and how they will be resolved. a repository for bidirectional mirroring, you should consider when conflicts
occur who and how they will be resolved.
Rewriting any mirrored commit on either remote will cause conflicts and Rewriting any mirrored commit on either remote will cause conflicts and
mirroring to fail. This can be prevented by [only pulling protected branches]( mirroring to fail. This can be prevented by [only pulling protected branches](
...@@ -318,10 +319,11 @@ custom Git hooks][hooks] on the GitLab server. ...@@ -318,10 +319,11 @@ custom Git hooks][hooks] on the GitLab server.
### Mirroring with Perforce via GitFusion ### Mirroring with Perforce via GitFusion
> **Warning:** Bidirectional mirroring should not be used as a permanent CAUTION: **Warning:**
> configuration. There is no bidirectional mirroring without conflicts. Bidirectional mirroring should not be used as a permanent
> Refer to [Migrating from Perforce Helix][perforce] for alternative migration configuration. There is no bidirectional mirroring without conflicts.
> approaches. Refer to [Migrating from Perforce Helix][perforce] for alternative migration
approaches.
GitFusion provides a Git interface to Perforce which can be used by GitLab to GitFusion provides a Git interface to Perforce which can be used by GitLab to
bidirectionally mirror projects with GitLab. This may be useful in some bidirectionally mirror projects with GitLab. This may be useful in some
...@@ -339,10 +341,11 @@ limitations of GitFusion. ...@@ -339,10 +341,11 @@ limitations of GitFusion.
[ee-3326]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3326 [ee-3326]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3326
[ee-3350]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3350 [ee-3350]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3350
[ee-3453]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3453 [ee-3453]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3453
[ee-4559]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4559
[ce-18715]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18715 [ce-18715]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18715
[perms]: ../user/permissions.md [perms]: ../user/permissions.md
[hooks]: ../administration/custom_hooks.html [hooks]: ../administration/custom_hooks.md
[deploy-key]: ../ssh/README.md#deploy-keys [deploy-key]: ../ssh/README.md#deploy-keys
[webhook]: ../user/project/integrations/webhooks.html#push-events [webhook]: ../user/project/integrations/webhooks.md#push-events
[pull-api]: ../api/projects.html#start-the-pull-mirroring-process-for-a-project [pull-api]: ../api/projects.md#start-the-pull-mirroring-process-for-a-project
[perforce]: ../user/project/import/perforce.html [perforce]: ../user/project/import/perforce.md
...@@ -23,7 +23,10 @@ export default { ...@@ -23,7 +23,10 @@ export default {
<template> <template>
<div <div
class="flex" class="flex"
:class="{ 'issue-info-container': !canReorder }" :class="{
'issue-info-container': !canReorder,
'card-body': canReorder,
}"
> >
<div class="block-truncated append-right-10"> <div class="block-truncated append-right-10">
<a <a
......
...@@ -93,10 +93,13 @@ export default { ...@@ -93,10 +93,13 @@ export default {
}, },
mounted() { mounted() {
if (this.canReorder) { if (this.canReorder) {
this.sortable = Sortable.create(this.$refs.list, Object.assign({}, sortableConfig, { this.sortable = Sortable.create(
this.$refs.list,
Object.assign({}, sortableConfig, {
onStart: this.addDraggingCursor, onStart: this.addDraggingCursor,
onEnd: this.reordered, onEnd: this.reordered,
})); }),
);
} }
}, },
methods: { methods: {
...@@ -225,7 +228,7 @@ issue-count-badge-add-button btn btn-sm btn-default" ...@@ -225,7 +228,7 @@ issue-count-badge-add-button btn btn-sm btn-default"
:class="{ :class="{
'user-can-drag': canReorder, 'user-can-drag': canReorder,
'sortable-row': canReorder, 'sortable-row': canReorder,
card: canReorder 'card-slim': canReorder
}" }"
:data-key="issue.id" :data-key="issue.id"
:data-epic-issue-id="issue.epic_issue_id" :data-epic-issue-id="issue.epic_issue_id"
......
<script> <script>
/** /**
* Renders DAST body text * Renders DAST body text
* [priority]: [name] * [severity] ([confidence]): [name]
*/ */
import ModalOpenName from './modal_open_name.vue'; import ModalOpenName from './modal_open_name.vue';
...@@ -27,7 +27,7 @@ export default { ...@@ -27,7 +27,7 @@ export default {
<template> <template>
<div class="report-block-list-issue-description prepend-top-5 append-bottom-5"> <div class="report-block-list-issue-description prepend-top-5 append-bottom-5">
<div class="report-block-list-issue-description-text"> <div class="report-block-list-issue-description-text">
<template v-if="issue.priority">{{ issue.priority }}:</template> {{ issue.severity }} ({{ issue.confidence }}):
<modal-open-name <modal-open-name
:issue="issue" :issue="issue"
......
...@@ -35,9 +35,20 @@ export default { ...@@ -35,9 +35,20 @@ export default {
this.dismissIssue(); this.dismissIssue();
} }
}, },
isLastValue(index, values) {
return index < values.length - 1;
},
hasValue(field) {
return field.value && field.value.length > 0;
},
hasInstances(field, key) { hasInstances(field, key) {
return key === 'instances' && field.value && field.value.length > 0; return key === 'instances' && this.hasValue(field);
},
hasIdentifiers(field, key) {
return key === 'identifiers' && this.hasValue(field);
},
hasLinks(field, key) {
return key === 'links' && this.hasValue(field);
}, },
}, },
}; };
...@@ -51,7 +62,7 @@ export default { ...@@ -51,7 +62,7 @@ export default {
<slot> <slot>
<div <div
v-for="(field, key, index) in modal.data" v-for="(field, key, index) in modal.data"
v-if="field.value || hasInstances(field, key)" v-if="field.value"
class="row prepend-top-10 append-bottom-10" class="row prepend-top-10 append-bottom-10"
:key="index" :key="index"
> >
...@@ -99,6 +110,42 @@ export default { ...@@ -99,6 +110,42 @@ export default {
</li> </li>
</ul> </ul>
</div> </div>
<template v-else-if="hasIdentifiers(field, key)">
<span
v-for="(identifier, i) in field.value"
:key="i"
>
<a
:class="`js-link-${key}`"
v-if="identifier.url"
target="_blank"
:href="identifier.url"
rel="noopener noreferrer"
>
{{ identifier.name }}
</a>
<span v-else>
{{ identifier.name }}
</span>
<span v-if="isLastValue(i, field.value)">,&nbsp;</span>
</span>
</template>
<template v-else-if="hasLinks(field, key)">
<span
v-for="(link, i) in field.value"
:key="i"
>
<a
:class="`js-link-${key}`"
target="_blank"
:href="link.url"
rel="noopener noreferrer"
>
{{ link.value || link.url }}
</a>
<span v-if="isLastValue(i, field.value)">,&nbsp;</span>
</span>
</template>
<template v-else> <template v-else>
<a <a
:class="`js-link-${key}`" :class="`js-link-${key}`"
......
...@@ -22,6 +22,6 @@ export default { ...@@ -22,6 +22,6 @@ export default {
@click="handleIssueClick()" @click="handleIssueClick()"
class="btn-link btn-blank text-left break-link vulnerability-name-button" class="btn-link btn-blank text-left break-link vulnerability-name-button"
> >
{{ issue.name }} {{ issue.title }}
</button> </button>
</template> </template>
...@@ -23,7 +23,7 @@ export default { ...@@ -23,7 +23,7 @@ export default {
<template> <template>
<div class="report-block-list-issue-description prepend-top-5 append-bottom-5"> <div class="report-block-list-issue-description prepend-top-5 append-bottom-5">
<div class="report-block-list-issue-description-text"> <div class="report-block-list-issue-description-text">
<template v-if="issue.priority">{{ issue.priority }}:</template> <template v-if="issue.severity">{{ issue.severity }}:</template>
<modal-open-name :issue="issue" /> <modal-open-name :issue="issue" />
</div> </div>
......
<script> <script>
/** /**
* Renders SAST body text * Renders SAST body text
* [priority]: [name] in [link] : [line] * [severity] ([confidence]): [name] in [link] : [line]
*/ */
import ReportLink from './report_link.vue'; import ReportLink from './report_link.vue';
import ModalOpenName from './modal_open_name.vue'; import ModalOpenName from './modal_open_name.vue';
...@@ -25,7 +25,10 @@ export default { ...@@ -25,7 +25,10 @@ export default {
<template> <template>
<div class="report-block-list-issue-description prepend-top-5 append-bottom-5"> <div class="report-block-list-issue-description prepend-top-5 append-bottom-5">
<div class="report-block-list-issue-description-text"> <div class="report-block-list-issue-description-text">
<template v-if="issue.priority">{{ issue.priority }}:</template> <template v-if="issue.severity && issue.confidence">
{{ issue.severity }} ({{ issue.confidence }}):
</template>
<template v-else-if="issue.priority">{{ issue.priority }}:</template>
<modal-open-name :issue="issue" /> <modal-open-name :issue="issue" />
</div> </div>
......
...@@ -248,28 +248,30 @@ export default { ...@@ -248,28 +248,30 @@ export default {
}, },
[types.SET_ISSUE_MODAL_DATA](state, issue) { [types.SET_ISSUE_MODAL_DATA](state, issue) {
state.modal.title = issue.name; state.modal.title = issue.title;
state.modal.data.description.value = issue.description; state.modal.data.description.value = issue.description;
state.modal.data.file.value = issue.file; state.modal.data.file.value = issue.location && issue.location.file;
state.modal.data.file.url = issue.urlPath; state.modal.data.file.url = issue.urlPath;
state.modal.data.className.value = issue.location && issue.location.class;
state.modal.data.methodName.value = issue.location && issue.location.method;
state.modal.data.namespace.value = issue.namespace; state.modal.data.namespace.value = issue.namespace;
if (issue.identifiers && issue.identifiers.length > 0) {
state.modal.data.identifiers.value = issue.identifiers;
} else {
// Force a null value for identifiers to avoid showing an empty array
state.modal.data.identifiers.value = null;
}
state.modal.data.severity.value = issue.severity; state.modal.data.severity.value = issue.severity;
state.modal.data.confidence.value = issue.confidence;
state.modal.data.solution.value = issue.solution; state.modal.data.solution.value = issue.solution;
state.modal.data.confidenceLevel.value = issue.confidence; if (issue.links && issue.links.length > 0) {
state.modal.data.source.value = issue.source; state.modal.data.links.value = issue.links;
state.modal.data.instances.value = issue.instances;
state.modal.vulnerability = issue;
// Link to CVE-ID for Container Scanning
if (issue.nameLink) {
state.modal.data.identifier.value = issue.name;
state.modal.data.identifier.isLink = true;
state.modal.data.identifier.url = issue.nameLink;
} else { } else {
state.modal.data.identifier.value = issue.identifier; // Force a null value for links to avoid showing an empty array
state.modal.data.identifier.isLink = false; state.modal.data.links.value = null;
state.modal.data.identifier.url = null;
} }
state.modal.data.instances.value = issue.instances;
state.modal.vulnerability = issue;
// clear previous state // clear previous state
state.modal.error = null; state.modal.error = null;
......
...@@ -77,21 +77,30 @@ export default () => ({ ...@@ -77,21 +77,30 @@ export default () => ({
text: s__('ciReport|Description'), text: s__('ciReport|Description'),
isLink: false, isLink: false,
}, },
identifiers: {
value: [],
text: s__('ciReport|Identifiers'),
isLink: false,
},
file: { file: {
value: null, value: null,
url: null, url: null,
text: s__('ciReport|File'), text: s__('ciReport|File'),
isLink: true, isLink: true,
}, },
namespace: { className: {
value: null, value: null,
text: s__('ciReport|Namespace'), text: s__('ciReport|Class'),
isLink: false, isLink: false,
}, },
identifier: { methodName: {
value: null, value: null,
url: null, text: s__('ciReport|Method'),
text: s__('ciReport|Identifier'), isLink: false,
},
namespace: {
value: null,
text: s__('ciReport|Namespace'),
isLink: false, isLink: false,
}, },
severity: { severity: {
...@@ -99,20 +108,20 @@ export default () => ({ ...@@ -99,20 +108,20 @@ export default () => ({
text: s__('ciReport|Severity'), text: s__('ciReport|Severity'),
isLink: false, isLink: false,
}, },
solution: { confidence: {
value: null, value: null,
text: s__('ciReport|Solution'), text: s__('ciReport|Confidence'),
isLink: false, isLink: false,
}, },
confidenceLevel: { solution: {
value: null, value: null,
text: s__('ciReport|Confidence Level'), text: s__('ciReport|Solution'),
isLink: false, isLink: false,
}, },
source: { links: {
value: null, value: [],
text: s__('ciReport|Source'), text: s__('ciReport|Links'),
isLink: true, isLink: false,
}, },
instances: { instances: {
value: [], value: [],
......
import sha1 from 'sha1'; import sha1 from 'sha1';
import _ from 'underscore';
import { stripHtml } from '~/lib/utils/text_utility'; import { stripHtml } from '~/lib/utils/text_utility';
import { n__, s__, sprintf } from '~/locale'; import { n__, s__, sprintf } from '~/locale';
...@@ -12,25 +13,25 @@ export const findIssueIndex = (issues, issue) => ...@@ -12,25 +13,25 @@ export const findIssueIndex = (issues, issue) =>
/** /**
* Returns given vulnerability enriched with the corresponding * Returns given vulnerability enriched with the corresponding
* feedbacks (`dismissal` or `issue` type) * feedback (`dismissal` or `issue` type)
* @param {Object} vulnerability * @param {Object} vulnerability
* @param {Array} feedbacks * @param {Array} feedback
*/ */
function enrichVulnerabilityWithfeedbacks(vulnerability, feedbacks = []) { function enrichVulnerabilityWithfeedback(vulnerability, feedback = []) {
return feedbacks.filter( return feedback.filter(
feedback => feedback.project_fingerprint === vulnerability.project_fingerprint, fb => fb.project_fingerprint === vulnerability.project_fingerprint,
).reduce((vuln, feedback) => { ).reduce((vuln, fb) => {
if (feedback.feedback_type === 'dismissal') { if (fb.feedback_type === 'dismissal') {
return { return {
...vuln, ...vuln,
isDismissed: true, isDismissed: true,
dismissalFeedback: feedback, dismissalFeedback: fb,
}; };
} else if (feedback.feedback_type === 'issue') { } else if (fb.feedback_type === 'issue') {
return { return {
...vuln, ...vuln,
hasIssue: true, hasIssue: true,
issueFeedback: feedback, issueFeedback: fb,
}; };
} }
return vuln; return vuln;
...@@ -38,107 +39,192 @@ function enrichVulnerabilityWithfeedbacks(vulnerability, feedbacks = []) { ...@@ -38,107 +39,192 @@ function enrichVulnerabilityWithfeedbacks(vulnerability, feedbacks = []) {
} }
/** /**
* Maps SAST issues: * Generates url to repository file and highlight section between start and end lines.
* { tool: String, message: String, url: String , cve: String , *
* file: String , solution: String, priority: String } * @param {Object} location
* to contain: * @param {String} pathPrefix
* { name: String, path: String, line: String, urlPath: String, priority: String } * @returns {String}
*/
function fileUrl(location, pathPrefix) {
let lineSuffix = '';
if (!_.isEmpty(location.start_line)) {
lineSuffix += `#L${location.start_line}`;
if (!_.isEmpty(location.end_line)) {
lineSuffix += `-${location.end_line}`;
}
}
return `${pathPrefix}/${location.file}${lineSuffix}`;
}
/**
* Parses issues with deprecated JSON format and adapts it to the new one.
*
* @param {Object} issue
* @returns {Object}
*/
function adaptDeprecatedFormat(issue) {
// Skip issue with new format (old format does not have a location property)
if (issue.location) {
return issue;
}
const adapted = {
...issue,
};
// Add the new links property
const links = [];
if (!_.isEmpty(adapted.url)) {
links.push({ url: adapted.url });
}
Object.assign(adapted, {
// Add the new location property
location: {
file: adapted.file,
start_line: adapted.line,
},
links,
});
return adapted;
}
/**
* Parses SAST results into a common format to allow to use the same Vue component.
*
* @param {Array} issues * @param {Array} issues
* @param {Array} feedback
* @param {String} path * @param {String} path
* @returns {Array}
*/ */
export const parseSastIssues = (issues = [], feedbacks = [], path = '') => export const parseSastIssues = (issues = [], feedback = [], path = '') =>
issues.map(issue => { issues.map(issue => {
const parsed = { const parsed = {
...issue, ...adaptDeprecatedFormat(issue),
category: 'sast', category: 'sast',
// TODO: replace with issue.project_fingerprint
project_fingerprint: sha1(issue.cve), project_fingerprint: sha1(issue.cve),
name: issue.message, title: issue.message,
path: issue.file,
urlPath: issue.line ? `${path}/${issue.file}#L${issue.line}` : `${path}/${issue.file}`,
}; };
return { return {
...parsed, ...parsed,
...enrichVulnerabilityWithfeedbacks(parsed, feedbacks), path: parsed.location.file,
urlPath: fileUrl(parsed.location, path),
...enrichVulnerabilityWithfeedback(parsed, feedback),
}; };
}); });
/** /**
* Maps Dependency scanning issues: * Parses Dependency Scanning results into a common format to allow to use the same Vue component.
* { tool: String, message: String, url: String , cve: String , *
* file: String , solution: String, priority: String }
* to contain:
* { name: String, path: String, line: String, urlPath: String, priority: String }
* @param {Array} issues * @param {Array} issues
* @param {Array} feedback
* @param {String} path * @param {String} path
* @returns {Array}
*/ */
export const parseDependencyScanningIssues = (issues = [], feedbacks = [], path = '') => export const parseDependencyScanningIssues = (issues = [], feedback = [], path = '') =>
issues.map(issue => { issues.map(issue => {
const parsed = { const parsed = {
...issue, ...adaptDeprecatedFormat(issue),
category: 'dependency_scanning', category: 'dependency_scanning',
// TODO: replace with issue.project_fingerprint
project_fingerprint: sha1(issue.cve || issue.message), project_fingerprint: sha1(issue.cve || issue.message),
name: issue.message, title: issue.message,
path: issue.file,
urlPath: issue.line ? `${path}/${issue.file}#L${issue.line}` : `${path}/${issue.file}`,
}; };
return { return {
...parsed, ...parsed,
...enrichVulnerabilityWithfeedbacks(parsed, feedbacks), path: parsed.location.file,
urlPath: fileUrl(parsed.location, path),
...enrichVulnerabilityWithfeedback(parsed, feedback),
}; };
}); });
/** /**
* Parses Sast Container results into a common format to allow to use the same Vue component * Parses Container Scanning results into a common format to allow to use the same Vue component.
* And adds an external link * Container Scanning report is currently the straigh output from the underlying tool
* (clair scanner) hence the formatting happenning here.
* *
* @param {Array} data * @param {Array} issues
* @param {Array} feedback
* @param {String} path
* @returns {Array} * @returns {Array}
*/ */
export const parseSastContainer = (issues = [], feedbacks = []) => export const parseSastContainer = (issues = [], feedback = []) =>
issues.map(issue => { issues.map(issue => {
const parsed = { const parsed = {
...issue, ...issue,
category: 'container_scanning', category: 'container_scanning',
// TODO: replace with issue.project_fingerprint
project_fingerprint: sha1(`${issue.namespace}:${issue.vulnerability}:${issue.featurename}:${issue.featureversion}`), project_fingerprint: sha1(`${issue.namespace}:${issue.vulnerability}:${issue.featurename}:${issue.featureversion}`),
name: issue.vulnerability, title: issue.vulnerability,
priority: issue.severity, description: !_.isEmpty(issue.description) ? issue.description :
sprintf(s__('ciReport|%{namespace} is affected by %{vulnerability}.'), {
namespace: issue.namespace,
vulnerability: issue.vulnerability,
}),
path: issue.namespace, path: issue.namespace,
// external link to provide better description identifiers: [{
nameLink: `https://cve.mitre.org/cgi-bin/cvename.cgi?name=${issue.vulnerability}`, type: 'CVE',
name: issue.vulnerability,
value: issue.vulnerability,
url: `https://cve.mitre.org/cgi-bin/cvename.cgi?name=${issue.vulnerability}`,
}],
}; };
// Generate solution
if (!_.isEmpty(issue.fixedby) &&
!_.isEmpty(issue.featurename) &&
!_.isEmpty(issue.featureversion)
) {
Object.assign(parsed, {
solution: sprintf(s__('ciReport|Upgrade %{name} from %{version} to %{fixed}.'), {
name: issue.featurename,
version: issue.featureversion,
fixed: issue.fixedby,
}),
});
}
return { return {
...parsed, ...parsed,
...enrichVulnerabilityWithfeedbacks(parsed, feedbacks), ...enrichVulnerabilityWithfeedback(parsed, feedback),
}; };
}); });
export const parseDastIssues = (issues = [], feedbacks = []) => /**
* Parses DAST into a common format to allow to use the same Vue component.
* DAST report is currently the straigh output from the underlying tool (ZAProxy)
* hence the formatting happenning here.
*
* @param {Array} issues
* @param {Array} feedback
* @returns {Array}
*/
export const parseDastIssues = (issues = [], feedback = []) =>
issues.map(issue => { issues.map(issue => {
const parsed = { const parsed = {
...issue, ...issue,
category: 'dast', category: 'dast',
// TODO: replace with issue.project_fingerprint
project_fingerprint: sha1(issue.pluginid), project_fingerprint: sha1(issue.pluginid),
parsedDescription: stripHtml(issue.desc, ' '), title: issue.name,
priority: issue.riskdesc,
solution: stripHtml(issue.solution, ' '),
description: stripHtml(issue.desc, ' '), description: stripHtml(issue.desc, ' '),
solution: stripHtml(issue.solution, ' '),
}; };
if (issue.cweid && issue.cweid !== '') { if (!_.isEmpty(issue.cweid)) {
Object.assign(parsed, { Object.assign(parsed, {
identifier: `CWE-${issue.cweid}`, identifiers: [{
type: 'CWE',
name: `CWE-${issue.cweid}`,
value: issue.cweid,
url: `https://cwe.mitre.org/data/definitions/${issue.cweid}.html`,
}],
}); });
} }
if (issue.riskdesc && issue.riskdesc !== '') { if (issue.riskdesc && issue.riskdesc !== '') {
// Split 'severity (confidence)' // Split riskdesc into severity and confidence.
// Riskdesc format is: "severity (confidence)"
const [, severity, confidence] = issue.riskdesc.match(/(.*) \((.*)\)/); const [, severity, confidence] = issue.riskdesc.match(/(.*) \((.*)\)/);
Object.assign(parsed, { Object.assign(parsed, {
severity, severity,
...@@ -148,7 +234,7 @@ export const parseDastIssues = (issues = [], feedbacks = []) => ...@@ -148,7 +234,7 @@ export const parseDastIssues = (issues = [], feedbacks = []) =>
return { return {
...parsed, ...parsed,
...enrichVulnerabilityWithfeedbacks(parsed, feedbacks), ...enrichVulnerabilityWithfeedback(parsed, feedback),
}; };
}); });
......
...@@ -109,9 +109,22 @@ class Projects::VulnerabilityFeedbackController < Projects::ApplicationControlle ...@@ -109,9 +109,22 @@ class Projects::VulnerabilityFeedbackController < Projects::ApplicationControlle
method method
uri uri
], ],
location: %i[
file
start_line
end_line
class
method
],
identifiers: %i[ identifiers: %i[
type
name name
value value
url
],
links: %i[
name
url
] ]
] ]
end end
......
...@@ -52,6 +52,8 @@ class EpicsFinder < IssuableFinder ...@@ -52,6 +52,8 @@ class EpicsFinder < IssuableFinder
private private
def groups_user_can_read_epics(groups) def groups_user_can_read_epics(groups)
groups = Gitlab::GroupPlansPreloader.new.preload(groups)
DeclarativePolicy.user_scope do DeclarativePolicy.user_scope do
groups.select { |g| Ability.allowed?(current_user, :read_epic, g) } groups.select { |g| Ability.allowed?(current_user, :read_epic, g) }
end end
......
module EE module EE
module SearchHelper module SearchHelper
extend ::Gitlab::Utils::Override
def search_filter_input_options(type) def search_filter_input_options(type)
options = super options = super
options[:data][:'multiple-assignees'] = 'true' if search_multiple_assignees?(type) options[:data][:'multiple-assignees'] = 'true' if search_multiple_assignees?(type)
...@@ -7,6 +9,29 @@ module EE ...@@ -7,6 +9,29 @@ module EE
options options
end end
override :find_project_for_result_blob
def find_project_for_result_blob(result)
super || ::Project.find(result['_parent'])
end
override :parse_search_result
def parse_search_result(result)
return super if result.is_a?(Array)
blob = ::Gitlab::Elastic::SearchResults.parse_search_result(result)
[blob.filename, blob]
end
override :search_blob_title
def search_blob_title(project, file_name)
if @project
file_name
else
(project.full_name + ': ' + content_tag(:i, file_name)).html_safe
end
end
private private
def search_multiple_assignees?(type) def search_multiple_assignees?(type)
......
...@@ -8,7 +8,8 @@ module EE ...@@ -8,7 +8,8 @@ module EE
'epic_issue_moved' => 'issues', 'epic_issue_moved' => 'issues',
'issue_added_to_epic' => 'epic', 'issue_added_to_epic' => 'epic',
'issue_removed_from_epic' => 'epic', 'issue_removed_from_epic' => 'epic',
'issue_changed_epic' => 'epic' 'issue_changed_epic' => 'epic',
'epic_date_changed' => 'calendar'
}.freeze }.freeze
override :system_note_icon_name override :system_note_icon_name
......
...@@ -70,13 +70,13 @@ module EE ...@@ -70,13 +70,13 @@ module EE
attr_encrypted :external_auth_client_key, attr_encrypted :external_auth_client_key,
mode: :per_attribute_iv, mode: :per_attribute_iv,
key: Settings.attr_encrypted_db_key_base, key: Settings.attr_encrypted_db_key_base_truncated,
algorithm: 'aes-256-gcm', algorithm: 'aes-256-gcm',
encode: true encode: true
attr_encrypted :external_auth_client_key_pass, attr_encrypted :external_auth_client_key_pass,
mode: :per_attribute_iv, mode: :per_attribute_iv,
key: Settings.attr_encrypted_db_key_base, key: Settings.attr_encrypted_db_key_base_truncated,
algorithm: 'aes-256-gcm', algorithm: 'aes-256-gcm',
encode: true encode: true
end end
......
...@@ -155,6 +155,19 @@ module EE ...@@ -155,6 +155,19 @@ module EE
actual_plan&.pipeline_size_limit.to_i actual_plan&.pipeline_size_limit.to_i
end end
def memoized_plans=(plans)
@plans = plans # rubocop: disable Gitlab/ModuleWithInstanceVariables
end
def plans
@plans ||=
if parent_id
Plan.where(id: self_and_ancestors.with_plan.reorder(nil).select(:plan_id))
else
Array(plan)
end
end
private private
def validate_plan_name def validate_plan_name
...@@ -180,14 +193,5 @@ module EE ...@@ -180,14 +193,5 @@ module EE
globally_available globally_available
end end
end end
def plans
@plans ||=
if parent_id
Plan.where(id: self_and_ancestors.with_plan.reorder(nil).select(:plan_id))
else
Array(plan)
end
end
end end
end end
...@@ -5,7 +5,7 @@ module EE ...@@ -5,7 +5,7 @@ module EE
EE_ICON_TYPES = %w[ EE_ICON_TYPES = %w[
weight approved unapproved relate unrelate weight approved unapproved relate unrelate
epic_issue_added issue_added_to_epic epic_issue_removed issue_removed_from_epic epic_issue_added issue_added_to_epic epic_issue_removed issue_removed_from_epic
epic_issue_moved issue_changed_epic epic_issue_moved issue_changed_epic epic_date_changed
].freeze ].freeze
override :icon_types override :icon_types
......
...@@ -42,7 +42,7 @@ class GeoNode < ActiveRecord::Base ...@@ -42,7 +42,7 @@ class GeoNode < ActiveRecord::Base
scope :with_url_prefix, ->(prefix) { where('url LIKE ?', "#{prefix}%") } scope :with_url_prefix, ->(prefix) { where('url LIKE ?', "#{prefix}%") }
attr_encrypted :secret_access_key, attr_encrypted :secret_access_key,
key: Settings.attr_encrypted_db_key_base, key: Settings.attr_encrypted_db_key_base_truncated,
algorithm: 'aes-256-gcm', algorithm: 'aes-256-gcm',
mode: :per_attribute_iv, mode: :per_attribute_iv,
encode: true encode: true
......
...@@ -8,10 +8,21 @@ module EE ...@@ -8,10 +8,21 @@ module EE
def execute(_issuable, _old_labels) def execute(_issuable, _old_labels)
super super
handle_weight_change_note handle_weight_change_note
handle_date_change_note
end end
private private
def handle_date_change_note
if issuable.previous_changes.include?('start_date')
::SystemNoteService.change_epic_date_note(issuable, current_user, 'start date', issuable['start_date'])
end
if issuable.previous_changes.include?('end_date')
::SystemNoteService.change_epic_date_note(issuable, current_user, 'finish date', issuable['end_date'])
end
end
def handle_weight_change_note def handle_weight_change_note
if issuable.previous_changes.include?('weight') if issuable.previous_changes.include?('weight')
create_weight_change_note create_weight_change_note
......
...@@ -24,5 +24,27 @@ module EE ...@@ -24,5 +24,27 @@ module EE
body = noteable.weight ? "changed weight to **#{noteable.weight}**," : 'removed the weight' body = noteable.weight ? "changed weight to **#{noteable.weight}**," : 'removed the weight'
create_note(NoteSummary.new(noteable, project, author, body, action: 'weight')) create_note(NoteSummary.new(noteable, project, author, body, action: 'weight'))
end end
# Called when the start or end date of an Issuable is changed
#
# noteable - Noteable object
# author - User performing the change
# date_type - 'start date' or 'finish date'
# date - New date
#
# Example Note text:
#
# "changed start date to FIXME"
#
# Returns the created Note object
def change_epic_date_note(noteable, author, date_type, date)
body = if date
"changed #{date_type} to #{date.strftime('%b %-d, %Y')}"
else
"removed the #{date_type}"
end
create_note(NoteSummary.new(noteable, nil, author, body, action: 'epic_date_changed'))
end
end end
end end
...@@ -2,14 +2,14 @@ module Issues ...@@ -2,14 +2,14 @@ module Issues
class CreateFromVulnerabilityDataService < ::BaseService class CreateFromVulnerabilityDataService < ::BaseService
def execute def execute
vulnerability = case @params[:category] vulnerability = case @params[:category]
when 'sast', 'dependency_scanning' when 'sast', 'dependency_scanning', 'dast'
Gitlab::Vulnerabilities::StandardVulnerability.new(params) Gitlab::Vulnerabilities::StandardVulnerability.new(params)
when 'container_scanning' when 'container_scanning'
Gitlab::Vulnerabilities::ContainerScanningVulnerability.new(params) Gitlab::Vulnerabilities::ContainerScanningVulnerability.new(params)
when 'dast'
Gitlab::Vulnerabilities::DastVulnerability.new(params)
end end
return error('Invalid vulnerability category') unless vulnerability
issue_params = { issue_params = {
title: "Investigate vulnerability: #{vulnerability.title}", title: "Investigate vulnerability: #{vulnerability.title}",
description: render_description(vulnerability) description: render_description(vulnerability)
......
...@@ -7,8 +7,8 @@ ...@@ -7,8 +7,8 @@
- ineligible_approver = issuable.author || current_user - ineligible_approver = issuable.author || current_user
- can_update_approvers = can?(current_user, :update_approvers, issuable) - can_update_approvers = can?(current_user, :update_approvers, issuable)
.form-group .form-group.row
= form.label :approver_ids, class: 'col-form-label' do = form.label :approver_ids, class: 'col-form-label col-sm-2' do
Approvers Approvers
.col-sm-10 .col-sm-10
- if can_update_approvers - if can_update_approvers
...@@ -61,7 +61,7 @@ ...@@ -61,7 +61,7 @@
Remove Remove
.col-sm-12 .col-sm-12
.form-group .form-group.row
= form.label :approvals_before_merge, class: 'label-light' do = form.label :approvals_before_merge, class: 'label-light' do
Approvals required Approvals required
= form.number_field :approvals_before_merge, class: 'form-control', value: issuable.approvals_required, readonly: !can_update_approvers = form.number_field :approvals_before_merge, class: 'form-control', value: issuable.approvals_required, readonly: !can_update_approvers
......
...@@ -19,11 +19,22 @@ ...@@ -19,11 +19,22 @@
### Identifiers: ### Identifiers:
<% vulnerability.identifiers.each do |identifier| %> <% vulnerability.identifiers.each do |identifier| %>
<% if identifier[:link].present? %> <% if identifier[:url].present? %>
* [<%= identifier[:value] %>](<%= identifier[:link] %>) * [<%= identifier[:name] %>](<%= identifier[:url] %>)
<% else %> <% else %>
* <%= identifier[:value] %> * <%= identifier[:name] %>
<% end %> <% end %>
<% end %> <% end %>
<% end %>
<% if vulnerability.links.present? %>
### Links:
<% vulnerability.links.each do |link| %>
<% if link[:name].present? %>
* [<%= link[:name] %>](<%= link[:url] %>)
<% else %>
* <%= link[:url] %>
<% end %>
<% end %>
<% end %> <% end %>
---
title: Enrich Security Reports with more data
merge_request: 5878
author:
type: changed
---
title: "[Geo] Fix: Deleted project events may be skipped on the secondary when selective
sync is used"
merge_request:
author:
type: fixed
---
title: Create system note on epic date change.
merge_request:
author:
type: added
---
title: Preload Group plans in EpicsFinder
merge_request:
author:
type: performance
module EE
module Gitlab
module SlashCommands
module Command
extend ActiveSupport::Concern
class_methods do
extend ::Gitlab::Utils::Override
override :commands
def commands
super.concat(
[
::Gitlab::SlashCommands::Run
]
)
end
end
end
end
end
end
module EE
module Gitlab
module SlashCommands
module Presenters
module IssueBase
extend ::Gitlab::Utils::Override
override :fields
def fields
super.concat(
[
{
title: "Weight",
value: resource.weight? ? resource.weight : "_None_",
short: true
}
]
)
end
end
end
end
end
end
...@@ -82,6 +82,9 @@ module Gitlab ...@@ -82,6 +82,9 @@ module Gitlab
def can_replay?(event_log) def can_replay?(event_log)
return true if event_log.project_id.nil? return true if event_log.project_id.nil?
# Always replay events for deleted projects
return true unless Project.exists?(event_log.project_id)
Gitlab::Geo.current_node&.projects_include?(event_log.project_id) Gitlab::Geo.current_node&.projects_include?(event_log.project_id)
end end
......
# frozen_string_literal: true
module Gitlab
# Preloading of Plans for one or more groups.
#
# This class can be used to efficiently preload the plans of a given list of
# groups, including any plans the groups may have access to based on their
# parent groups.
class GroupPlansPreloader
# Preloads all the plans for the given Groups.
#
# groups - An ActiveRecord::Relation returning a set of Group instances.
#
# Returns an Array containing all the Groups, including their preloaded
# plans.
def preload(groups)
groups_and_ancestors = groups_and_ancestors_for(groups)
# A Hash mapping group IDs to their corresponding Group instances.
groups_map = groups_and_ancestors.each_with_object({}) do |group, hash|
hash[group.id] = group
end
all_plan_ids = Set.new
# A Hash that for every group ID maps _all_ the plan IDs this group has
# access to.
plans_map = groups_and_ancestors
.each_with_object(Hash.new { |h, k| h[k] = [] }) do |group, hash|
current = group
while current
if (plan_id = current.plan_id)
hash[group.id] << plan_id
all_plan_ids << plan_id
end
current = groups_map[current.parent_id]
end
end
# Grab all the plans for all the Groups, using only a single query.
plans = Plan
.where(id: all_plan_ids.to_a)
.each_with_object({}) do |plan, hash|
hash[plan.id] = plan
end
# Assign all the plans to the groups that have access to them.
groups.each do |group|
group.memoized_plans = plans_map[group.id].map { |id| plans[id] }
end
end
# Returns an ActiveRecord::Relation that includes the given groups, and all
# their (recursive) ancestors.
def groups_and_ancestors_for(groups)
Gitlab::GroupHierarchy
.new(groups)
.base_and_ancestors
.select(:id, :parent_id, :plan_id)
end
end
end
...@@ -16,7 +16,7 @@ module Gitlab ...@@ -16,7 +16,7 @@ module Gitlab
end end
def commands def commands
Gitlab::SlashCommands::Command::COMMANDS Gitlab::SlashCommands::Command.commands
end end
end end
end end
......
...@@ -15,23 +15,12 @@ module Gitlab ...@@ -15,23 +15,12 @@ module Gitlab
confidence confidence
solution solution
identifiers identifiers
links
].each do |method_name| ].each do |method_name|
define_method(method_name) do define_method(method_name) do
raise NotImplementedError raise NotImplementedError
end end
end end
protected
# cve_id must be 'CVE-YYYY-XXXX' (prefix + year + digits)
def cve_link(cve_id)
"https://cve.mitre.org/cgi-bin/cvename.cgi?name=#{cve_id}"
end
# cve_id must be a number only (no 'CWE-' prefix)
def cwe_link(cwe_id)
"https://cwe.mitre.org/data/definitions/#{cwe_id}.html"
end
end end
end end
end end
...@@ -2,24 +2,24 @@ module Gitlab ...@@ -2,24 +2,24 @@ module Gitlab
module Vulnerabilities module Vulnerabilities
class ContainerScanningVulnerability < BaseVulnerability class ContainerScanningVulnerability < BaseVulnerability
def title def title
"#{@data[:name]} in #{@data[:namespace]}" "#{@data[:vulnerability]} in #{@data[:namespace]}"
end end
# Passthrough properties # Passthrough properties
%i[ %i[
confidence
severity severity
identifiers
links
].each do |method_name| ].each do |method_name|
define_method(method_name) do define_method(method_name) do
@data[method_name] @data[method_name]
end end
end end
def confidence
end
def description def description
@data[:description].presence || @data[:description].presence ||
"**#{@data[:namespace]}** is affected by #{@data[:name]}" "**#{@data[:namespace]}** is affected by #{@data[:vulnerability]}"
end end
def solution def solution
...@@ -30,13 +30,6 @@ module Gitlab ...@@ -30,13 +30,6 @@ module Gitlab
"Upgrade **#{@data[:featurename]}** from `#{@data[:featureversion]}` to `#{@data[:fixedby]}`" "Upgrade **#{@data[:featurename]}** from `#{@data[:featureversion]}` to `#{@data[:fixedby]}`"
end end
end end
def identifiers
[{
value: @data[:name],
link: cve_link(@data[:name])
}]
end
end end
end end
end end
module Gitlab
module Vulnerabilities
class DastVulnerability < BaseVulnerability
def title
@data[:name]
end
# Passthrough properties
%i[
severity
confidence
solution
].each do |method_name|
define_method(method_name) do
@data[method_name]
end
end
def description
@data[:desc]
end
def identifiers
ids = []
if @data[:cweid].present?
ids << {
value: "CWE-#{@data[:cweid]}",
link: cwe_link(@data[:cweid])
}
end
if @data[:wascid].present?
ids << {
value: "WASC-#{@data[:wascid]}"
}
end
ids
end
end
end
end
module Gitlab module Gitlab
module Vulnerabilities module Vulnerabilities
class StandardVulnerability < BaseVulnerability class StandardVulnerability < BaseVulnerability
def title
@data[:name]
end
# Passthrough properties # Passthrough properties
%i[ %i[
title
severity severity
confidence confidence
solution solution
identifiers
links
].each do |method_name| ].each do |method_name|
define_method(method_name) do define_method(method_name) do
@data[method_name] @data[method_name]
...@@ -17,30 +16,7 @@ module Gitlab ...@@ -17,30 +16,7 @@ module Gitlab
end end
def description def description
@data[:description].presence || @data[:name] @data[:description].presence || @data[:title]
end
def identifiers
return [] unless @data[:identifiers].present?
ids = []
@data[:identifiers].each do |identifier|
# Only show known identifiers
case identifier[:name]
when 'CVE'
ids << {
value: identifier[:value],
link: cve_link(identifier[:value])
}
when 'CWE'
ids << {
value: "CWE-#{identifier[:value]}",
link: cwe_link(identifier[:value])
}
end
end
ids
end end
end end
end end
......
...@@ -86,18 +86,37 @@ describe Projects::VulnerabilityFeedbackController do ...@@ -86,18 +86,37 @@ describe Projects::VulnerabilityFeedbackController do
feedback_type: 'dismissal', pipeline_id: pipeline.id, category: 'sast', feedback_type: 'dismissal', pipeline_id: pipeline.id, category: 'sast',
project_fingerprint: '418291a26024a1445b23fe64de9380cdcdfd1fa8', project_fingerprint: '418291a26024a1445b23fe64de9380cdcdfd1fa8',
vulnerability_data: { vulnerability_data: {
priority: 'Low', line: '41', category: 'sast',
file: 'subdir/src/main/java/com/gitlab/security_products/tests/App.java', severity: 'Low',
confidence: 'Medium',
cve: '818bf5dacb291e15d9e6dc3c5ac32178:PREDICTABLE_RANDOM', cve: '818bf5dacb291e15d9e6dc3c5ac32178:PREDICTABLE_RANDOM',
name: 'Predictable pseudorandom number generator', title: 'Predictable pseudorandom number generator',
description: 'Description of Predictable pseudorandom number generator', description: 'Description of Predictable pseudorandom number generator',
tool: 'find_sec_bugs' tool: 'find_sec_bugs',
location: {
file: 'subdir/src/main/java/com/gitlab/security_products/tests/App.java',
start_line: '41'
},
identifiers: [{
type: 'CVE',
name: 'CVE-2018-1234',
value: 'CVE-2018-1234',
url: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-1234'
}],
links: [{
name: 'Awesome-security blog post',
url: 'https;//example.com/blog-post'
}]
} }
} }
end end
context 'with valid params' do context 'with valid params' do
it 'returns the created list' do it 'returns the created feedback' do
allow(VulnerabilityFeedbackModule::CreateService)
.to receive(:new).with(project, user, create_params)
.and_call_original
create_feedback user: user, project: project, params: create_params create_feedback user: user, project: project, params: create_params
expect(response).to match_response_schema('vulnerability_feedback', dir: 'ee') expect(response).to match_response_schema('vulnerability_feedback', dir: 'ee')
...@@ -142,7 +161,7 @@ describe Projects::VulnerabilityFeedbackController do ...@@ -142,7 +161,7 @@ describe Projects::VulnerabilityFeedbackController do
def create_feedback(user:, project:, params:) def create_feedback(user:, project:, params:)
sign_in(user) sign_in(user)
post :create, namespace_id: project.namespace.to_param, project_id: project, vulnerability_feedback: params post :create, namespace_id: project.namespace.to_param, project_id: project, vulnerability_feedback: params, format: :json
end end
end end
......
...@@ -53,6 +53,12 @@ describe EpicsFinder do ...@@ -53,6 +53,12 @@ describe EpicsFinder do
expect(epics).to contain_exactly(epic1, epic2, epic3) expect(epics).to contain_exactly(epic1, epic2, epic3)
end end
it 'does not execute more than 7 SQL queries' do
amount = ActiveRecord::QueryRecorder.new { epics.to_a }.count
expect(amount).to be <= 7
end
context 'by created_at' do context 'by created_at' do
it 'returns all epics created before the given date' do it 'returns all epics created before the given date' do
expect(epics(created_before: 2.days.ago)).to contain_exactly(epic1, epic2) expect(epics(created_before: 2.days.ago)).to contain_exactly(epic1, epic2)
...@@ -97,6 +103,24 @@ describe EpicsFinder do ...@@ -97,6 +103,24 @@ describe EpicsFinder do
it 'returns all epics that belong to the given group and its subgroups' do it 'returns all epics that belong to the given group and its subgroups' do
expect(epics).to contain_exactly(epic1, epic2, epic3, subepic1, subepic2) expect(epics).to contain_exactly(epic1, epic2, epic3, subepic1, subepic2)
end end
it 'does not execute more than 9 SQL queries' do
amount = ActiveRecord::QueryRecorder.new { epics.to_a }.count
expect(amount).to be <= 9
end
it 'does not execute more than 11 SQL queries when checking namespace plans' do
allow(Gitlab::CurrentSettings)
.to receive(:should_check_namespace_plan?)
.and_return(true)
group.update(plan: create(:gold_plan))
amount = ActiveRecord::QueryRecorder.new { epics.to_a }.count
expect(amount).to be <= 10
end
end end
context 'by timeframe' do context 'by timeframe' do
......
...@@ -25,7 +25,7 @@ describe SearchHelper do ...@@ -25,7 +25,7 @@ describe SearchHelper do
options: { highlight: true } options: { highlight: true }
)[:blobs][:results][0] )[:blobs][:results][0]
parsed_result = helper.parse_search_result(result) _, parsed_result = helper.parse_search_result(result)
expect(parsed_result.ref). to eq('b83d6e391c22777fca1ed3012fce84f633d7fed0') expect(parsed_result.ref). to eq('b83d6e391c22777fca1ed3012fce84f633d7fed0')
expect(parsed_result.filename).to eq('files/ruby/popen.rb') expect(parsed_result.filename).to eq('files/ruby/popen.rb')
......
...@@ -35,6 +35,19 @@ describe Gitlab::Geo::LogCursor::Events::RepositoryDeletedEvent, :postgresql, :c ...@@ -35,6 +35,19 @@ describe Gitlab::Geo::LogCursor::Events::RepositoryDeletedEvent, :postgresql, :c
it 'removes the tracking entry' do it 'removes the tracking entry' do
expect { subject.process }.to change(Geo::ProjectRegistry, :count).by(-1) expect { subject.process }.to change(Geo::ProjectRegistry, :count).by(-1)
end end
context 'when selective sync is enabled' do
let(:secondary) { create(:geo_node, selective_sync_type: 'namespaces', namespaces: [project.namespace]) }
it 'replays delete events when project does not exist on primary' do
project.delete
expect(::GeoRepositoryDestroyWorker).to receive(:perform_async)
.with(project.id, deleted_project_name, deleted_path, project.repository_storage)
subject.process
end
end
end end
end end
end end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::GroupPlansPreloader do
describe '#preload' do
let!(:plan1) { create(:free_plan, name: 'plan-1') }
let!(:plan2) { create(:free_plan, name: 'plan-2') }
let(:preloaded_groups) do
# We don't use the factory objects here because they might have the plan
# loaded already (as we specify the plan when creating them).
described_class.new.preload(Group.order(id: :asc))
end
before do
group1 = create(:group, name: 'group-1', plan_id: plan1.id)
create(:group, name: 'group-2', plan_id: plan2.id)
create(:group, name: 'group-3', parent: group1)
end
it 'only executes three SQL queries to preload the data' do
amount = ActiveRecord::QueryRecorder
.new { preloaded_groups }
.count
# One query to get the groups and their ancestors, one query to get their
# plans, and one query to _just_ get the groups.
expect(amount).to eq(3)
end
it 'associates the correct plans with the correct groups' do
expect(preloaded_groups[0].plans).to eq([plan1])
expect(preloaded_groups[1].plans).to eq([plan2])
expect(preloaded_groups[2].plans).to eq([plan1])
end
it 'does not execute any queries for preloaded plans' do
preloaded_groups
amount = ActiveRecord::QueryRecorder
.new { preloaded_groups.each(&:plans) }
.count
expect(amount).to be_zero
end
end
end
require 'spec_helper'
describe Gitlab::SlashCommands::Command do
describe '.commands' do
it 'includes EE specific commands' do
expect(described_class.commands).to include(Gitlab::SlashCommands::Run)
end
end
end
require 'spec_helper'
describe Gitlab::SlashCommands::Presenters::IssueShow do
let(:project) { create(:project) }
let(:attachment) { subject[:attachments].first }
subject { described_class.new(issue).present }
context 'issue with issue weight' do
let(:issue) { create(:issue, project: project, weight: 3) }
let(:weight_attachment) { attachment[:fields].find { |a| a[:title] == "Weight" } }
it 'shows the weight' do
expect(weight_attachment).not_to be_nil
expect(weight_attachment[:value]).to be(3)
end
end
end
...@@ -7,5 +7,24 @@ describe Issuable::CommonSystemNotesService do ...@@ -7,5 +7,24 @@ describe Issuable::CommonSystemNotesService do
describe '#execute' do describe '#execute' do
it_behaves_like 'system note creation', { weight: 5 }, 'changed weight to **5**,' it_behaves_like 'system note creation', { weight: 5 }, 'changed weight to **5**,'
context 'when issuable is an epic' do
let(:timestamp) { Time.now }
let(:issuable) { create(:epic, end_date: timestamp) }
subject { described_class.new(nil, user).execute(issuable, [])}
before do
issuable.assign_attributes(start_date: timestamp, end_date: nil)
issuable.save
end
it 'creates 2 system notes with the correct content' do
expect { subject }.to change { Note.count }.from(0).to(2)
expect(Note.first.note).to match("changed start date to #{timestamp.strftime('%b %-d, %Y')}")
expect(Note.second.note).to match('removed the finish date')
end
end
end end
end end
...@@ -29,19 +29,34 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do ...@@ -29,19 +29,34 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do
let(:params) do let(:params) do
{ {
category: 'sast', category: 'sast',
priority: 'Low', line: '41',
severity: 'Low', confidence: 'High', severity: 'Low', confidence: 'High',
solution: 'Please do something!', solution: 'Please do something!',
file: 'subdir/src/main/java/com/gitlab/security_products/tests/App.java', file: 'subdir/src/main/java/com/gitlab/security_products/tests/App.java',
cve: '818bf5dacb291e15d9e6dc3c5ac32178:PREDICTABLE_RANDOM', cve: '818bf5dacb291e15d9e6dc3c5ac32178:PREDICTABLE_RANDOM',
name: 'Predictable pseudorandom number generator', title: 'Predictable pseudorandom number generator',
description: 'Description of Predictable pseudorandom number generator', description: 'Description of Predictable pseudorandom number generator',
tool: 'find_sec_bugs', tool: 'find_sec_bugs',
identifiers: [ identifiers: [{
{ name: 'CVE', value: 'CVE-2017-15650' }, type: 'CVE',
{ name: 'CWE', value: '16' }, name: 'CVE-2017-15650',
{ name: 'GAS_RULE_ID', value: 'G105' } value: 'CVE-2017-15650',
] url: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15650'
}, {
type: 'CWE',
name: 'CWE-16',
value: '16',
url: 'https://cwe.mitre.org/data/definitions/16.html'
}, {
type: 'GAS_RULE_ID',
name: 'GAS Rule ID G105',
value: 'G105'
}],
links: [{
name: 'Awesome-security blog post',
url: 'https;//example.com/blog-post'
}, {
url: 'https://example.com/another-link'
}]
} }
end end
...@@ -63,6 +78,12 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do ...@@ -63,6 +78,12 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do
* [CVE-2017-15650](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15650) * [CVE-2017-15650](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15650)
* [CWE-16](https://cwe.mitre.org/data/definitions/16.html) * [CWE-16](https://cwe.mitre.org/data/definitions/16.html)
* GAS Rule ID G105
### Links:
* [Awesome-security blog post](https;//example.com/blog-post)
* https://example.com/another-link
DESC DESC
end end
...@@ -73,16 +94,12 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do ...@@ -73,16 +94,12 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do
let(:params) do let(:params) do
{ {
category: 'sast', category: 'sast',
priority: 'Low', line: '41',
severity: 'Low', confidence: 'High', severity: 'Low', confidence: 'High',
solution: 'Please do something!', solution: 'Please do something!',
file: 'subdir/src/main/java/com/gitlab/security_products/tests/App.java', file: 'subdir/src/main/java/com/gitlab/security_products/tests/App.java',
cve: '818bf5dacb291e15d9e6dc3c5ac32178:PREDICTABLE_RANDOM', cve: '818bf5dacb291e15d9e6dc3c5ac32178:PREDICTABLE_RANDOM',
name: 'Predictable pseudorandom number generator', title: 'Predictable pseudorandom number generator',
tool: 'find_sec_bugs', tool: 'find_sec_bugs'
identifiers: [
{ name: 'CVE', value: 'CVE-2017-15650' }
]
} }
end end
let(:expected_title) { 'Investigate vulnerability: Predictable pseudorandom number generator' } let(:expected_title) { 'Investigate vulnerability: Predictable pseudorandom number generator' }
...@@ -98,10 +115,6 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do ...@@ -98,10 +115,6 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do
### Solution: ### Solution:
Please do something! Please do something!
### Identifiers:
* [CVE-2017-15650](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15650)
DESC DESC
end end
...@@ -114,19 +127,34 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do ...@@ -114,19 +127,34 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do
let(:params) do let(:params) do
{ {
category: 'dependency_scanning', category: 'dependency_scanning',
priority: 'Low', line: '41',
severity: 'Low', confidence: 'High', severity: 'Low', confidence: 'High',
solution: 'Please do something!', solution: 'Please do something!',
file: 'subdir/src/main/java/com/gitlab/security_products/tests/App.java', file: 'subdir/src/main/java/com/gitlab/security_products/tests/App.java',
cve: '818bf5dacb291e15d9e6dc3c5ac32178:PREDICTABLE_RANDOM', cve: '818bf5dacb291e15d9e6dc3c5ac32178:PREDICTABLE_RANDOM',
name: 'Predictable pseudorandom number generator', title: 'Predictable pseudorandom number generator',
description: 'Description of Predictable pseudorandom number generator', description: 'Description of Predictable pseudorandom number generator',
tool: 'find_sec_bugs', tool: 'find_sec_bugs',
identifiers: [ identifiers: [{
{ name: 'CVE', value: 'CVE-2017-15650' }, type: 'CVE',
{ name: 'CWE', value: '16' }, name: 'CVE-2017-15650',
{ name: 'GAS_RULE_ID', value: 'G105' } value: 'CVE-2017-15650',
] url: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15650'
}, {
type: 'CWE',
name: 'CWE-16',
value: '16',
url: 'https://cwe.mitre.org/data/definitions/16.html'
}, {
type: 'GAS_RULE_ID',
name: 'GAS Rule ID G105',
value: 'G105'
}],
links: [{
name: 'Awesome-security blog post',
url: 'https;//example.com/blog-post'
}, {
url: 'https://example.com/another-link'
}]
} }
end end
let(:expected_title) { 'Investigate vulnerability: Predictable pseudorandom number generator' } let(:expected_title) { 'Investigate vulnerability: Predictable pseudorandom number generator' }
...@@ -147,6 +175,12 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do ...@@ -147,6 +175,12 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do
* [CVE-2017-15650](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15650) * [CVE-2017-15650](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15650)
* [CWE-16](https://cwe.mitre.org/data/definitions/16.html) * [CWE-16](https://cwe.mitre.org/data/definitions/16.html)
* GAS Rule ID G105
### Links:
* [Awesome-security blog post](https;//example.com/blog-post)
* https://example.com/another-link
DESC DESC
end end
...@@ -162,11 +196,8 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do ...@@ -162,11 +196,8 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do
solution: 'Please do something!', solution: 'Please do something!',
file: 'subdir/src/main/java/com/gitlab/security_products/tests/App.java', file: 'subdir/src/main/java/com/gitlab/security_products/tests/App.java',
cve: '818bf5dacb291e15d9e6dc3c5ac32178:PREDICTABLE_RANDOM', cve: '818bf5dacb291e15d9e6dc3c5ac32178:PREDICTABLE_RANDOM',
name: 'Predictable pseudorandom number generator', title: 'Predictable pseudorandom number generator',
tool: 'find_sec_bugs', tool: 'find_sec_bugs'
identifiers: [
{ name: 'CVE', value: 'CVE-2017-15650' }
]
} }
end end
let(:expected_title) { 'Investigate vulnerability: Predictable pseudorandom number generator' } let(:expected_title) { 'Investigate vulnerability: Predictable pseudorandom number generator' }
...@@ -182,10 +213,6 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do ...@@ -182,10 +213,6 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do
### Solution: ### Solution:
Please do something! Please do something!
### Identifiers:
* [CVE-2017-15650](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15650)
DESC DESC
end end
...@@ -204,10 +231,16 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do ...@@ -204,10 +231,16 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do
featurename: 'musl', featurename: 'musl',
featureversion: '1.1.14-r15', featureversion: '1.1.14-r15',
fixedby: '1.1.14-r16', fixedby: '1.1.14-r16',
name: 'CVE-2017-15650', title: 'CVE-2017-15650',
vulnerability: 'CVE-2017-15650', vulnerability: 'CVE-2017-15650',
description: 'This is a description for CVE-2017-15650.', description: 'This is a description for CVE-2017-15650.',
tool: 'find_sec_bugs' tool: 'find_sec_bugs',
identifiers: [{
type: 'CVE',
name: 'CVE-2017-15650',
value: 'CVE-2017-15650',
url: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15650'
}]
} }
end end
let(:expected_title) { 'Investigate vulnerability: CVE-2017-15650 in alpine:v3.4' } let(:expected_title) { 'Investigate vulnerability: CVE-2017-15650 in alpine:v3.4' }
...@@ -241,10 +274,16 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do ...@@ -241,10 +274,16 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do
featurename: 'musl', featurename: 'musl',
featureversion: '1.1.14-r15', featureversion: '1.1.14-r15',
fixedby: '1.1.14-r16', fixedby: '1.1.14-r16',
name: 'CVE-2017-15650', title: 'CVE-2017-15650',
vulnerability: 'CVE-2017-15650', vulnerability: 'CVE-2017-15650',
description: '', description: '',
tool: 'find_sec_bugs' tool: 'find_sec_bugs',
identifiers: [{
type: 'CVE',
name: 'CVE-2017-15650',
value: 'CVE-2017-15650',
url: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15650'
}]
} }
end end
let(:expected_title) { 'Investigate vulnerability: CVE-2017-15650 in alpine:v3.4' } let(:expected_title) { 'Investigate vulnerability: CVE-2017-15650 in alpine:v3.4' }
...@@ -276,11 +315,22 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do ...@@ -276,11 +315,22 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do
category: 'dast', category: 'dast',
priority: 'Low', priority: 'Low',
severity: 'Low', severity: 'Low',
name: 'X-Content-Type-Options Header Missing', title: 'X-Content-Type-Options Header Missing',
desc: 'The Anti-MIME-Sniffing header X-Content-Type-Options was not set to nosniff.', desc: '<p>The Anti-MIME-Sniffing header X-Content-Type-Options was not set to nosniff.</p>',
description: 'The Anti-MIME-Sniffing header X-Content-Type-Options was not set to nosniff.',
cweid: '123', cweid: '123',
wascid: '456', wascid: '456',
solution: 'Ensure that the application/web server sets the Content-Type header appropriately, and that it sets the X-Content-Type-Options header to nosniff for all web pages.' solution: 'Ensure that the application/web server sets the Content-Type header appropriately, and that it sets the X-Content-Type-Options header to nosniff for all web pages.',
identifiers: [{
type: 'CWE',
name: 'CWE-123',
value: '123',
url: 'https://cwe.mitre.org/data/definitions/123.html'
}, {
type: 'WASC',
name: 'WASC-456',
value: '456'
}]
} }
end end
let(:expected_title) { 'Investigate vulnerability: X-Content-Type-Options Header Missing' } let(:expected_title) { 'Investigate vulnerability: X-Content-Type-Options Header Missing' }
...@@ -306,4 +356,16 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do ...@@ -306,4 +356,16 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do
it_behaves_like 'a created issue' it_behaves_like 'a created issue'
end end
end end
context 'when params are invalid' do
context 'when category is unknown' do
let(:params) { { category: 'foo' } }
let(:result) { described_class.new(project, user, params).execute }
it 'return expected error' do
expect(result[:status]).to eq(:error)
expect(result[:message]).to eq('Invalid vulnerability category')
end
end
end
end end
...@@ -21,20 +21,28 @@ describe SystemNoteService do ...@@ -21,20 +21,28 @@ describe SystemNoteService do
expect(subject).to be_system expect(subject).to be_system
expect(subject.noteable).to eq expected_noteable expect(subject.noteable).to eq expected_noteable
expect(subject.project).to eq project
expect(subject.author).to eq author expect(subject.author).to eq author
expect(subject.system_note_metadata.action).to eq(action) expect(subject.system_note_metadata.action).to eq(action)
expect(subject.system_note_metadata.commit_count).to eq(commit_count) expect(subject.system_note_metadata.commit_count).to eq(commit_count)
end end
end end
shared_examples_for 'a project system note' do
it 'has the project attribute set' do
expect(subject.project).to eq project
end
it_behaves_like 'a system note'
end
describe '.change_weight_note' do describe '.change_weight_note' do
context 'when weight changed' do context 'when weight changed' do
let(:noteable) { create(:issue, project: project, title: 'Lorem ipsum', weight: 4) } let(:noteable) { create(:issue, project: project, title: 'Lorem ipsum', weight: 4) }
subject { described_class.change_weight_note(noteable, project, author) } subject { described_class.change_weight_note(noteable, project, author) }
it_behaves_like 'a system note' do it_behaves_like 'a project system note' do
let(:action) { 'weight' } let(:action) { 'weight' }
end end
...@@ -48,7 +56,7 @@ describe SystemNoteService do ...@@ -48,7 +56,7 @@ describe SystemNoteService do
subject { described_class.change_weight_note(noteable, project, author) } subject { described_class.change_weight_note(noteable, project, author) }
it_behaves_like 'a system note' do it_behaves_like 'a project system note' do
let(:action) { 'weight' } let(:action) { 'weight' }
end end
...@@ -57,4 +65,36 @@ describe SystemNoteService do ...@@ -57,4 +65,36 @@ describe SystemNoteService do
end end
end end
end end
describe '.change_epic_date_note' do
let(:timestamp) { Time.now }
context 'when start date was changed' do
let(:noteable) { create(:epic) }
subject { described_class.change_epic_date_note(noteable, author, 'start date', timestamp) }
it_behaves_like 'a system note' do
let(:action) { 'epic_date_changed' }
end
it 'sets the note text' do
expect(subject.note).to eq "changed start date to #{timestamp.strftime('%b %-d, %Y')}"
end
end
context 'when start date was removed' do
let(:noteable) { create(:epic, start_date: timestamp) }
subject { described_class.change_epic_date_note(noteable, author, 'start date', nil) }
it_behaves_like 'a system note' do
let(:action) { 'epic_date_changed' }
end
it 'sets the note text' do
expect(subject.note).to eq 'removed the start date'
end
end
end
end end
...@@ -42,9 +42,7 @@ module API ...@@ -42,9 +42,7 @@ module API
end end
case params[:scope] case params[:scope]
when 'wiki_blobs' when 'blobs', 'wiki_blobs'
paginate(results).map { |blob| Gitlab::ProjectSearchResults.parse_search_result(blob, user_project) }
when 'blobs'
paginate(results).map { |blob| blob[1] } paginate(results).map { |blob| blob[1] }
else else
paginate(results) paginate(results)
......
...@@ -32,17 +32,13 @@ module Gitlab ...@@ -32,17 +32,13 @@ module Gitlab
end end
def find_by_filename(query, except: []) def find_by_filename(query, except: [])
filenames = repository.search_files_by_name(query, ref).first(BATCH_SIZE) filenames = search_filenames(query, except)
filenames.delete_if { |filename| except.include?(filename) } unless except.empty?
blob_refs = filenames.map { |filename| [ref, filename] } blobs(filenames).map do |blob|
blobs = Gitlab::Git::Blob.batch(repository, blob_refs, blob_size_limit: 1024)
blobs.map do |blob|
Gitlab::SearchResults::FoundBlob.new( Gitlab::SearchResults::FoundBlob.new(
id: blob.id, id: blob.id,
filename: blob.path, filename: blob.path,
basename: File.basename(blob.path), basename: File.basename(blob.path, File.extname(blob.path)),
ref: ref, ref: ref,
startline: 1, startline: 1,
data: blob.data, data: blob.data,
...@@ -50,5 +46,21 @@ module Gitlab ...@@ -50,5 +46,21 @@ module Gitlab
) )
end end
end end
def search_filenames(query, except)
filenames = repository.search_files_by_name(query, ref).first(BATCH_SIZE)
filenames.delete_if { |filename| except.include?(filename) } unless except.empty?
filenames
end
def blob_refs(filenames)
filenames.map { |filename| [ref, filename] }
end
def blobs(filenames)
Gitlab::Git::Blob.batch(repository, blob_refs(filenames), blob_size_limit: 1024)
end
end end
end end
...@@ -106,7 +106,8 @@ module Gitlab ...@@ -106,7 +106,8 @@ module Gitlab
project_wiki = ProjectWiki.new(project) project_wiki = ProjectWiki.new(project)
unless project_wiki.empty? unless project_wiki.empty?
project_wiki.search_files(query) ref = repository_ref || project.wiki.default_branch
Gitlab::WikiFileFinder.new(project, ref).find(query)
else else
[] []
end end
......
module Gitlab module Gitlab
module SlashCommands module SlashCommands
class Command < BaseCommand class Command < BaseCommand
COMMANDS = [ prepend EE::Gitlab::SlashCommands::Command
def self.commands
[
Gitlab::SlashCommands::IssueShow, Gitlab::SlashCommands::IssueShow,
Gitlab::SlashCommands::IssueNew, Gitlab::SlashCommands::IssueNew,
Gitlab::SlashCommands::IssueSearch, Gitlab::SlashCommands::IssueSearch,
Gitlab::SlashCommands::IssueMove, Gitlab::SlashCommands::IssueMove,
Gitlab::SlashCommands::Deploy, Gitlab::SlashCommands::Deploy
Gitlab::SlashCommands::Run ]
].freeze end
def execute def execute
command, match = match_command command, match = match_command
...@@ -38,7 +41,7 @@ module Gitlab ...@@ -38,7 +41,7 @@ module Gitlab
private private
def available_commands def available_commands
COMMANDS.select do |klass| self.class.commands.keep_if do |klass|
klass.available?(project) klass.available?(project)
end end
end end
......
...@@ -2,6 +2,8 @@ module Gitlab ...@@ -2,6 +2,8 @@ module Gitlab
module SlashCommands module SlashCommands
module Presenters module Presenters
module IssueBase module IssueBase
prepend EE::Gitlab::SlashCommands::Presenters::IssueBase
def color(issuable) def color(issuable)
issuable.open? ? '#38ae67' : '#d22852' issuable.open? ? '#38ae67' : '#d22852'
end end
...@@ -34,11 +36,6 @@ module Gitlab ...@@ -34,11 +36,6 @@ module Gitlab
title: "Labels", title: "Labels",
value: resource.labels.any? ? resource.label_names.join(', ') : "_None_", value: resource.labels.any? ? resource.label_names.join(', ') : "_None_",
short: true short: true
},
{
title: "Weight",
value: resource.weight? ? resource.weight : "_None_",
short: true
} }
] ]
end end
......
module Gitlab
class WikiFileFinder < FileFinder
attr_reader :repository
def initialize(project, ref)
@project = project
@ref = ref
@repository = project.wiki.repository
end
private
def search_filenames(query, except)
safe_query = Regexp.escape(query.tr(' ', '-'))
safe_query = Regexp.new(safe_query, Regexp::IGNORECASE)
filenames = repository.ls_files(ref)
filenames.delete_if { |filename| except.include?(filename) } unless except.empty?
filenames.grep(safe_query).first(BATCH_SIZE)
end
end
end
...@@ -5922,6 +5922,9 @@ msgstr "" ...@@ -5922,6 +5922,9 @@ msgstr ""
msgid "ciReport|%{linkStartTag}Learn more about SAST image %{linkEndTag}" msgid "ciReport|%{linkStartTag}Learn more about SAST image %{linkEndTag}"
msgstr "" msgstr ""
msgid "ciReport|%{namespace} is affected by %{vulnerability}."
msgstr ""
msgid "ciReport|%{reportName} is loading" msgid "ciReport|%{reportName} is loading"
msgstr "" msgstr ""
...@@ -5937,10 +5940,13 @@ msgstr "" ...@@ -5937,10 +5940,13 @@ msgstr ""
msgid "ciReport|%{type} detected no vulnerabilities" msgid "ciReport|%{type} detected no vulnerabilities"
msgstr "" msgstr ""
msgid "ciReport|Class"
msgstr ""
msgid "ciReport|Code quality" msgid "ciReport|Code quality"
msgstr "" msgstr ""
msgid "ciReport|Confidence Level" msgid "ciReport|Confidence"
msgstr "" msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images." msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
...@@ -5991,7 +5997,7 @@ msgstr "" ...@@ -5991,7 +5997,7 @@ msgstr ""
msgid "ciReport|Fixed:" msgid "ciReport|Fixed:"
msgstr "" msgstr ""
msgid "ciReport|Identifier" msgid "ciReport|Identifiers"
msgstr "" msgstr ""
msgid "ciReport|Instances" msgid "ciReport|Instances"
...@@ -6000,9 +6006,15 @@ msgstr "" ...@@ -6000,9 +6006,15 @@ msgstr ""
msgid "ciReport|Learn more about whitelisting" msgid "ciReport|Learn more about whitelisting"
msgstr "" msgstr ""
msgid "ciReport|Links"
msgstr ""
msgid "ciReport|Loading %{reportName} report" msgid "ciReport|Loading %{reportName} report"
msgstr "" msgstr ""
msgid "ciReport|Method"
msgstr ""
msgid "ciReport|Namespace" msgid "ciReport|Namespace"
msgstr "" msgstr ""
...@@ -6045,9 +6057,6 @@ msgstr "" ...@@ -6045,9 +6057,6 @@ msgstr ""
msgid "ciReport|Solution" msgid "ciReport|Solution"
msgstr "" msgstr ""
msgid "ciReport|Source"
msgstr ""
msgid "ciReport|Static Application Security Testing (SAST) detects known vulnerabilities in your source code." msgid "ciReport|Static Application Security Testing (SAST) detects known vulnerabilities in your source code."
msgstr "" msgstr ""
...@@ -6069,6 +6078,9 @@ msgstr "" ...@@ -6069,6 +6078,9 @@ msgstr ""
msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved." msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved."
msgstr "" msgstr ""
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr ""
msgid "ciReport|no vulnerabilities" msgid "ciReport|no vulnerabilities"
msgstr "" msgstr ""
......
...@@ -82,7 +82,7 @@ describe('DropdownValueComponent', () => { ...@@ -82,7 +82,7 @@ describe('DropdownValueComponent', () => {
}); });
it('renders label element with tooltip and styles based on label details', () => { it('renders label element with tooltip and styles based on label details', () => {
const labelEl = vm.$el.querySelector('a span.label.color-label'); const labelEl = vm.$el.querySelector('a span.badge.color-label');
expect(labelEl).not.toBeNull(); expect(labelEl).not.toBeNull();
expect(labelEl.dataset.placement).toBe('bottom'); expect(labelEl.dataset.placement).toBe('bottom');
expect(labelEl.dataset.container).toBe('body'); expect(labelEl.dataset.container).toBe('body');
......
...@@ -8,15 +8,13 @@ describe('dast issue body', () => { ...@@ -8,15 +8,13 @@ describe('dast issue body', () => {
const Component = Vue.extend(component); const Component = Vue.extend(component);
const dastIssue = { const dastIssue = {
alert: 'X-Content-Type-Options Header Missing', alert: 'X-Content-Type-Options Header Missing',
confidence: '2', severity: 'Low',
confidence: 'Medium',
count: '17', count: '17',
cweid: '16', cweid: '16',
desc: desc:
'<p>The Anti-MIME-Sniffing header X-Content-Type-Options was not set to "nosniff". </p>', '<p>The Anti-MIME-Sniffing header X-Content-Type-Options was not set to "nosniff". </p>',
name: 'X-Content-Type-Options Header Missing', title: 'X-Content-Type-Options Header Missing',
parsedDescription:
' The Anti-MIME-Sniffing header X-Content-Type-Options was not set to "nosniff". ',
priority: 'Low (Medium)',
reference: reference:
'<p>http://msdn.microsoft.com/en-us/library/ie/gg622941%28v=vs.85%29.aspx</p><p>https://www.owasp.org/index.php/List_of_useful_HTTP_headers</p>', '<p>http://msdn.microsoft.com/en-us/library/ie/gg622941%28v=vs.85%29.aspx</p><p>https://www.owasp.org/index.php/List_of_useful_HTTP_headers</p>',
riskcode: '1', riskcode: '1',
...@@ -27,34 +25,19 @@ describe('dast issue body', () => { ...@@ -27,34 +25,19 @@ describe('dast issue body', () => {
vm.$destroy(); vm.$destroy();
}); });
describe('with priority', () => { describe('severity and confidence ', () => {
it('renders priority key', () => { it('renders severity and confidence', () => {
vm = mountComponent(Component, { vm = mountComponent(Component, {
issue: dastIssue, issue: dastIssue,
issueIndex: 1, issueIndex: 1,
modalTargetId: '#modal-mrwidget-issue', modalTargetId: '#modal-mrwidget-issue',
}); });
expect(vm.$el.textContent.trim()).toContain(dastIssue.priority); expect(vm.$el.textContent.trim()).toContain(`${dastIssue.severity} (${dastIssue.confidence})`);
}); });
}); });
describe('without priority', () => { describe('issue title', () => {
it('does not rendere priority key', () => {
const issueCopy = Object.assign({}, dastIssue);
delete issueCopy.priority;
vm = mountComponent(Component, {
issue: issueCopy,
issueIndex: 1,
modalTargetId: '#modal-mrwidget-issue',
});
expect(vm.$el.textContent.trim()).not.toContain(dastIssue.priority);
});
});
describe('issue name', () => {
beforeEach(() => { beforeEach(() => {
vm = mountComponent(Component, { vm = mountComponent(Component, {
issue: dastIssue, issue: dastIssue,
...@@ -63,8 +46,8 @@ describe('dast issue body', () => { ...@@ -63,8 +46,8 @@ describe('dast issue body', () => {
}); });
}); });
it('renders button with issue name', () => { it('renders button with issue title', () => {
expect(vm.$el.textContent.trim()).toContain(dastIssue.name); expect(vm.$el.textContent.trim()).toContain(dastIssue.title);
}); });
}); });
}); });
...@@ -27,7 +27,7 @@ describe('Security Reports modal', () => { ...@@ -27,7 +27,7 @@ describe('Security Reports modal', () => {
cve: 'CVE-2014-9999', cve: 'CVE-2014-9999',
file: 'Gemfile.lock', file: 'Gemfile.lock',
solution: 'upgrade to ~> 3.2.21, ~> 4.0.11.1, ~> 4.0.12, ~> 4.1.7.1, >= 4.1.8', solution: 'upgrade to ~> 3.2.21, ~> 4.0.11.1, ~> 4.0.12, ~> 4.1.7.1, >= 4.1.8',
name: 'Arbitrary file existence disclosure in Action Pack', title: 'Arbitrary file existence disclosure in Action Pack',
path: 'Gemfile.lock', path: 'Gemfile.lock',
urlPath: 'path/Gemfile.lock', urlPath: 'path/Gemfile.lock',
isDismissed: true, isDismissed: true,
...@@ -83,7 +83,7 @@ describe('Security Reports modal', () => { ...@@ -83,7 +83,7 @@ describe('Security Reports modal', () => {
cve: 'CVE-2014-9999', cve: 'CVE-2014-9999',
file: 'Gemfile.lock', file: 'Gemfile.lock',
solution: 'upgrade to ~> 3.2.21, ~> 4.0.11.1, ~> 4.0.12, ~> 4.1.7.1, >= 4.1.8', solution: 'upgrade to ~> 3.2.21, ~> 4.0.11.1, ~> 4.0.12, ~> 4.1.7.1, >= 4.1.8',
name: 'Arbitrary file existence disclosure in Action Pack', title: 'Arbitrary file existence disclosure in Action Pack',
path: 'Gemfile.lock', path: 'Gemfile.lock',
urlPath: 'path/Gemfile.lock', urlPath: 'path/Gemfile.lock',
}); });
...@@ -112,12 +112,10 @@ describe('Security Reports modal', () => { ...@@ -112,12 +112,10 @@ describe('Security Reports modal', () => {
describe('with instances', () => { describe('with instances', () => {
beforeEach(() => { beforeEach(() => {
store.dispatch('setModalData', { store.dispatch('setModalData', {
name: 'Absence of Anti-CSRF Tokens', title: 'Absence of Anti-CSRF Tokens',
riskcode: '1', riskcode: '1',
riskdesc: 'Low (Medium)', riskdesc: 'Low (Medium)',
priority: 'Low (Medium)',
desc: '<p>No Anti-CSRF tokens were found in a HTML submission form.</p>', desc: '<p>No Anti-CSRF tokens were found in a HTML submission form.</p>',
parsedDescription: ' No Anti-CSRF tokens were found in a HTML submission form. ',
pluginid: '123', pluginid: '123',
instances: [ instances: [
{ {
...@@ -159,13 +157,17 @@ describe('Security Reports modal', () => { ...@@ -159,13 +157,17 @@ describe('Security Reports modal', () => {
store.dispatch('setModalData', { store.dispatch('setModalData', {
tool: 'bundler_audit', tool: 'bundler_audit',
message: 'Arbitrary file existence disclosure in Action Pack', message: 'Arbitrary file existence disclosure in Action Pack',
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/rMTQy4oRCGk',
cve: 'CVE-2014-9999', cve: 'CVE-2014-9999',
file: 'Gemfile.lock',
solution: 'upgrade to ~> 3.2.21, ~> 4.0.11.1, ~> 4.0.12, ~> 4.1.7.1, >= 4.1.8', solution: 'upgrade to ~> 3.2.21, ~> 4.0.11.1, ~> 4.0.12, ~> 4.1.7.1, >= 4.1.8',
name: 'Arbitrary file existence disclosure in Action Pack', title: 'Arbitrary file existence disclosure in Action Pack',
path: 'Gemfile.lock', path: 'Gemfile.lock',
urlPath: 'path/Gemfile.lock', urlPath: 'path/Gemfile.lock',
location: {
file: 'Gemfile.lock',
},
links: [{
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/rMTQy4oRCGk',
}],
}); });
vm = mountComponentWithStore(Component, { vm = mountComponentWithStore(Component, {
......
...@@ -95,7 +95,7 @@ describe('Report issues', () => { ...@@ -95,7 +95,7 @@ describe('Report issues', () => {
it('should not render location', () => { it('should not render location', () => {
vm = mountComponent(ReportIssues, { vm = mountComponent(ReportIssues, {
issues: [{ issues: [{
name: 'foo', title: 'foo',
}], }],
type: 'SAST', type: 'SAST',
status: 'failed', status: 'failed',
...@@ -106,7 +106,7 @@ describe('Report issues', () => { ...@@ -106,7 +106,7 @@ describe('Report issues', () => {
}); });
}); });
describe('for docker issues', () => { describe('for container scanning issues', () => {
beforeEach(() => { beforeEach(() => {
vm = mountComponent(ReportIssues, { vm = mountComponent(ReportIssues, {
issues: dockerReportParsed.unapproved, issues: dockerReportParsed.unapproved,
...@@ -115,16 +115,16 @@ describe('Report issues', () => { ...@@ -115,16 +115,16 @@ describe('Report issues', () => {
}); });
}); });
it('renders priority', () => { it('renders severity', () => {
expect( expect(
vm.$el.querySelector('.report-block-list li').textContent.trim(), vm.$el.querySelector('.report-block-list li').textContent.trim(),
).toContain(dockerReportParsed.unapproved[0].priority); ).toContain(dockerReportParsed.unapproved[0].severity);
}); });
it('renders CVE name', () => { it('renders CVE name', () => {
expect( expect(
vm.$el.querySelector('.report-block-list button').textContent.trim(), vm.$el.querySelector('.report-block-list button').textContent.trim(),
).toEqual(dockerReportParsed.unapproved[0].name); ).toEqual(dockerReportParsed.unapproved[0].title);
}); });
it('renders namespace', () => { it('renders namespace', () => {
...@@ -148,9 +148,9 @@ describe('Report issues', () => { ...@@ -148,9 +148,9 @@ describe('Report issues', () => {
}); });
}); });
it('renders priority and name', () => { it('renders severity (confidence) and title', () => {
expect(vm.$el.textContent).toContain(parsedDast[0].name); expect(vm.$el.textContent).toContain(parsedDast[0].title);
expect(vm.$el.textContent).toContain(parsedDast[0].priority); expect(vm.$el.textContent).toContain(`${parsedDast[0].severity} (${parsedDast[0].confidence})`);
}); });
}); });
}); });
...@@ -116,7 +116,7 @@ describe('Report section', () => { ...@@ -116,7 +116,7 @@ describe('Report section', () => {
cve: 'CVE-2016-9999', cve: 'CVE-2016-9999',
file: 'Gemfile.lock', file: 'Gemfile.lock',
message: 'Test Information Leak Vulnerability in Action View', message: 'Test Information Leak Vulnerability in Action View',
name: 'Test Information Leak Vulnerability in Action View', title: 'Test Information Leak Vulnerability in Action View',
path: 'Gemfile.lock', path: 'Gemfile.lock',
solution: 'upgrade to >= 5.0.0.beta1.1, >= 4.2.5.1, ~> 4.2.5, >= 4.1.14.1, ~> 4.1.14, ~> 3.2.22.1', solution: 'upgrade to >= 5.0.0.beta1.1, >= 4.2.5.1, ~> 4.2.5, >= 4.1.14.1, ~> 4.1.14, ~> 3.2.22.1',
tool: 'bundler_audit', tool: 'bundler_audit',
...@@ -127,7 +127,7 @@ describe('Report section', () => { ...@@ -127,7 +127,7 @@ describe('Report section', () => {
cve: 'CVE-2014-7829', cve: 'CVE-2014-7829',
file: 'Gemfile.lock', file: 'Gemfile.lock',
message: 'Arbitrary file existence disclosure in Action Pack', message: 'Arbitrary file existence disclosure in Action Pack',
name: 'Arbitrary file existence disclosure in Action Pack', title: 'Arbitrary file existence disclosure in Action Pack',
path: 'Gemfile.lock', path: 'Gemfile.lock',
solution: 'upgrade to ~> 3.2.21, ~> 4.0.11.1, ~> 4.0.12, ~> 4.1.7.1, >= 4.1.8', solution: 'upgrade to ~> 3.2.21, ~> 4.0.11.1, ~> 4.0.12, ~> 4.1.7.1, >= 4.1.8',
tool: 'bundler_audit', tool: 'bundler_audit',
...@@ -138,7 +138,7 @@ describe('Report section', () => { ...@@ -138,7 +138,7 @@ describe('Report section', () => {
cve: 'CVE-2016-0752', cve: 'CVE-2016-0752',
file: 'Gemfile.lock', file: 'Gemfile.lock',
message: 'Possible Information Leak Vulnerability in Action View', message: 'Possible Information Leak Vulnerability in Action View',
name: 'Possible Information Leak Vulnerability in Action View', title: 'Possible Information Leak Vulnerability in Action View',
path: 'Gemfile.lock', path: 'Gemfile.lock',
solution: 'upgrade to >= 5.0.0.beta1.1, >= 4.2.5.1, ~> 4.2.5, >= 4.1.14.1, ~> 4.1.14, ~> 3.2.22.1', solution: 'upgrade to >= 5.0.0.beta1.1, >= 4.2.5.1, ~> 4.2.5, >= 4.1.14.1, ~> 4.1.14, ~> 3.2.22.1',
tool: 'bundler_audit', tool: 'bundler_audit',
......
...@@ -8,11 +8,9 @@ describe('sast container issue body', () => { ...@@ -8,11 +8,9 @@ describe('sast container issue body', () => {
const Component = Vue.extend(component); const Component = Vue.extend(component);
const sastContainerIssue = { const sastContainerIssue = {
name: 'CVE-2017-11671', title: 'CVE-2017-11671',
nameLink: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-11671',
namespace: 'debian:8', namespace: 'debian:8',
path: 'debian:8', path: 'debian:8',
priority: 'Low',
severity: 'Low', severity: 'Low',
vulnerability: 'CVE-2017-11671', vulnerability: 'CVE-2017-11671',
}; };
...@@ -21,26 +19,26 @@ describe('sast container issue body', () => { ...@@ -21,26 +19,26 @@ describe('sast container issue body', () => {
vm.$destroy(); vm.$destroy();
}); });
describe('with priority', () => { describe('with severity', () => {
it('renders priority key', () => { it('renders severity key', () => {
vm = mountComponent(Component, { vm = mountComponent(Component, {
issue: sastContainerIssue, issue: sastContainerIssue,
}); });
expect(vm.$el.textContent.trim()).toContain(sastContainerIssue.priority); expect(vm.$el.textContent.trim()).toContain(sastContainerIssue.severity);
}); });
}); });
describe('without priority', () => { describe('without severity', () => {
it('does not rendere priority key', () => { it('does not render severity key', () => {
const issueCopy = Object.assign({}, sastContainerIssue); const issueCopy = Object.assign({}, sastContainerIssue);
delete issueCopy.priority; delete issueCopy.severity;
vm = mountComponent(Component, { vm = mountComponent(Component, {
issue: issueCopy, issue: issueCopy,
}); });
expect(vm.$el.textContent.trim()).not.toContain(sastContainerIssue.priority); expect(vm.$el.textContent.trim()).not.toContain(sastContainerIssue.severity);
}); });
}); });
...@@ -49,7 +47,7 @@ describe('sast container issue body', () => { ...@@ -49,7 +47,7 @@ describe('sast container issue body', () => {
issue: sastContainerIssue, issue: sastContainerIssue,
}); });
expect(vm.$el.querySelector('button').textContent.trim()).toEqual(sastContainerIssue.name); expect(vm.$el.querySelector('button').textContent.trim()).toEqual(sastContainerIssue.title);
}); });
describe('path', () => { describe('path', () => {
......
...@@ -11,7 +11,7 @@ describe('sast issue body', () => { ...@@ -11,7 +11,7 @@ describe('sast issue body', () => {
cve: 'CVE-2016-9999', cve: 'CVE-2016-9999',
file: 'Gemfile.lock', file: 'Gemfile.lock',
message: 'Test Information Leak Vulnerability in Action View', message: 'Test Information Leak Vulnerability in Action View',
name: 'Test Information Leak Vulnerability in Action View', title: 'Test Information Leak Vulnerability in Action View',
path: 'Gemfile.lock', path: 'Gemfile.lock',
solution: solution:
'upgrade to >= 5.0.0.beta1.1, >= 4.2.5.1, ~> 4.2.5, >= 4.1.14.1, ~> 4.1.14, ~> 3.2.22.1', 'upgrade to >= 5.0.0.beta1.1, >= 4.2.5.1, ~> 4.2.5, >= 4.1.14.1, ~> 4.1.14, ~> 3.2.22.1',
...@@ -19,27 +19,57 @@ describe('sast issue body', () => { ...@@ -19,27 +19,57 @@ describe('sast issue body', () => {
url: url:
'https://groups.google.com/forum/#!topic/rubyonrails-security/335P1DcLG00', 'https://groups.google.com/forum/#!topic/rubyonrails-security/335P1DcLG00',
urlPath: '/Gemfile.lock', urlPath: '/Gemfile.lock',
priority: 'Low', severity: 'Medium',
confidence: 'Low',
}; };
afterEach(() => { afterEach(() => {
vm.$destroy(); vm.$destroy();
}); });
describe('with priority', () => { describe('with severity and confidence (new json format)', () => {
it('renders priority key', () => { it('renders severity and confidence', () => {
vm = mountComponent(Component, { vm = mountComponent(Component, {
issue: sastIssue, issue: sastIssue,
}); });
expect(vm.$el.textContent.trim()).toContain(sastIssue.priority); expect(vm.$el.textContent.trim()).toContain(`${sastIssue.severity} (${sastIssue.confidence})`);
});
});
describe('without severity', () => {
it('does not render severity nor confidence', () => {
const issueCopy = Object.assign({}, sastIssue);
delete issueCopy.severity;
vm = mountComponent(Component, {
issue: issueCopy,
});
expect(vm.$el.textContent.trim()).not.toContain(sastIssue.severity);
expect(vm.$el.textContent.trim()).not.toContain(sastIssue.confidence);
});
});
describe('with priority (old json format)', () => {
it('renders priority key', () => {
const issueCopy = Object.assign({}, sastIssue);
delete issueCopy.severity;
delete issueCopy.confidence;
issueCopy.priority = 'Low';
vm = mountComponent(Component, {
issue: issueCopy,
});
expect(vm.$el.textContent.trim()).toContain(issueCopy.priority);
}); });
}); });
describe('without priority', () => { describe('without priority', () => {
it('does not rendere priority key', () => { it('does not render priority key', () => {
const issueCopy = Object.assign({}, sastIssue); const issueCopy = Object.assign({}, sastIssue);
delete issueCopy.priority; delete issueCopy.severity;
delete issueCopy.confidence;
vm = mountComponent(Component, { vm = mountComponent(Component, {
issue: issueCopy, issue: issueCopy,
...@@ -51,20 +81,20 @@ describe('sast issue body', () => { ...@@ -51,20 +81,20 @@ describe('sast issue body', () => {
}); });
}); });
describe('name', () => { describe('title', () => {
it('renders name', () => { it('renders title', () => {
vm = mountComponent(Component, { vm = mountComponent(Component, {
issue: sastIssue, issue: sastIssue,
}); });
expect(vm.$el.textContent.trim()).toContain( expect(vm.$el.textContent.trim()).toContain(
sastIssue.name, sastIssue.title,
); );
}); });
}); });
describe('path', () => { describe('path', () => {
it('renders name', () => { it('renders path', () => {
vm = mountComponent(Component, { vm = mountComponent(Component, {
issue: sastIssue, issue: sastIssue,
}); });
......
...@@ -316,21 +316,50 @@ describe('security reports mutations', () => { ...@@ -316,21 +316,50 @@ describe('security reports mutations', () => {
const issue = { const issue = {
tool: 'bundler_audit', tool: 'bundler_audit',
message: 'Arbitrary file existence disclosure in Action Pack', message: 'Arbitrary file existence disclosure in Action Pack',
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/rMTQy4oRCGk',
cve: 'CVE-2014-7829', cve: 'CVE-2014-7829',
file: 'Gemfile.lock',
solution: 'upgrade to ~> 3.2.21, ~> 4.0.11.1, ~> 4.0.12, ~> 4.1.7.1, >= 4.1.8', solution: 'upgrade to ~> 3.2.21, ~> 4.0.11.1, ~> 4.0.12, ~> 4.1.7.1, >= 4.1.8',
name: 'Arbitrary file existence disclosure in Action Pack', title: 'Arbitrary file existence disclosure in Action Pack',
path: 'Gemfile.lock', path: 'Gemfile.lock',
urlPath: 'path/Gemfile.lock', urlPath: 'path/Gemfile.lock',
namespace: 'debian:8',
location: {
file: 'Gemfile.lock',
class: 'User',
method: 'do_something',
},
links: [{
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/rMTQy4oRCGk',
}],
identifiers: [{
type: 'CVE',
name: 'CVE-2014-9999',
value: 'CVE-2014-9999',
url: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-9999',
}],
instances: [{
param: 'X-Content-Type-Options',
method: 'GET',
uri: 'http://example.com/some-path',
}],
isDismissed: true, isDismissed: true,
}; };
mutations[types.SET_ISSUE_MODAL_DATA](stateCopy, issue); mutations[types.SET_ISSUE_MODAL_DATA](stateCopy, issue);
expect(stateCopy.modal.title).toEqual(issue.name); expect(stateCopy.modal.title).toEqual(issue.title);
expect(stateCopy.modal.data.file.value).toEqual(issue.file); expect(stateCopy.modal.data.description.value).toEqual(issue.description);
expect(stateCopy.modal.data.file.value).toEqual(issue.location.file);
expect(stateCopy.modal.data.file.url).toEqual(issue.urlPath);
expect(stateCopy.modal.data.className.value).toEqual(issue.location.class);
expect(stateCopy.modal.data.methodName.value).toEqual(issue.location.method);
expect(stateCopy.modal.data.namespace.value).toEqual(issue.namespace);
expect(stateCopy.modal.data.identifiers.value).toEqual(issue.identifiers);
expect(stateCopy.modal.data.severity.value).toEqual(issue.severity);
expect(stateCopy.modal.data.confidence.value).toEqual(issue.confidence);
expect(stateCopy.modal.data.solution.value).toEqual(issue.solution); expect(stateCopy.modal.data.solution.value).toEqual(issue.solution);
expect(stateCopy.modal.data.links.value).toEqual(issue.links);
expect(stateCopy.modal.data.instances.value).toEqual(issue.instances);
expect(stateCopy.modal.vulnerability).toEqual(issue); expect(stateCopy.modal.vulnerability).toEqual(issue);
}); });
}); });
......
...@@ -11,6 +11,7 @@ import { ...@@ -11,6 +11,7 @@ import {
statusIcon, statusIcon,
} from 'ee/vue_shared/security_reports/store/utils'; } from 'ee/vue_shared/security_reports/store/utils';
import { import {
oldSastIssues,
sastIssues, sastIssues,
sastFeedbacks, sastFeedbacks,
dependencyScanningIssues, dependencyScanningIssues,
...@@ -52,10 +53,17 @@ describe('security reports utils', () => { ...@@ -52,10 +53,17 @@ describe('security reports utils', () => {
}); });
describe('parseSastIssues', () => { describe('parseSastIssues', () => {
it('should parse the received issues', () => { it('should parse the received issues with old JSON format', () => {
const parsed = parseSastIssues(oldSastIssues, [], 'path')[0];
expect(parsed.title).toEqual(sastIssues[0].message);
expect(parsed.path).toEqual(sastIssues[0].location.file);
expect(parsed.project_fingerprint).toEqual(sha1(sastIssues[0].cve));
});
it('should parse the received issues with new JSON format', () => {
const parsed = parseSastIssues(sastIssues, [], 'path')[0]; const parsed = parseSastIssues(sastIssues, [], 'path')[0];
expect(parsed.name).toEqual(sastIssues[0].message); expect(parsed.title).toEqual(sastIssues[0].message);
expect(parsed.path).toEqual(sastIssues[0].file); expect(parsed.path).toEqual(sastIssues[0].location.file);
expect(parsed.project_fingerprint).toEqual(sha1(sastIssues[0].cve)); expect(parsed.project_fingerprint).toEqual(sha1(sastIssues[0].cve));
}); });
...@@ -75,7 +83,7 @@ describe('security reports utils', () => { ...@@ -75,7 +83,7 @@ describe('security reports utils', () => {
describe('parseDependencyScanningIssues', () => { describe('parseDependencyScanningIssues', () => {
it('should parse the received issues', () => { it('should parse the received issues', () => {
const parsed = parseDependencyScanningIssues(dependencyScanningIssues, [], 'path')[0]; const parsed = parseDependencyScanningIssues(dependencyScanningIssues, [], 'path')[0];
expect(parsed.name).toEqual(dependencyScanningIssues[0].message); expect(parsed.title).toEqual(dependencyScanningIssues[0].message);
expect(parsed.path).toEqual(dependencyScanningIssues[0].file); expect(parsed.path).toEqual(dependencyScanningIssues[0].file);
expect(parsed.project_fingerprint).toEqual(sha1(dependencyScanningIssues[0].cve)); expect(parsed.project_fingerprint).toEqual(sha1(dependencyScanningIssues[0].cve));
}); });
...@@ -107,14 +115,14 @@ describe('security reports utils', () => { ...@@ -107,14 +115,14 @@ describe('security reports utils', () => {
const parsed = parseSastContainer(dockerReport.vulnerabilities)[0]; const parsed = parseSastContainer(dockerReport.vulnerabilities)[0];
const issue = dockerReport.vulnerabilities[0]; const issue = dockerReport.vulnerabilities[0];
expect(parsed.name).toEqual(dockerReport.vulnerabilities[0].vulnerability); expect(parsed.title).toEqual(issue.vulnerability);
expect(parsed.priority).toEqual(dockerReport.vulnerabilities[0].severity); expect(parsed.path).toEqual(issue.namespace);
expect(parsed.path).toEqual(dockerReport.vulnerabilities[0].namespace); expect(parsed.identifiers).toEqual([{
expect(parsed.nameLink).toEqual( type: 'CVE',
`https://cve.mitre.org/cgi-bin/cvename.cgi?name=${ name: issue.vulnerability,
dockerReport.vulnerabilities[0].vulnerability value: issue.vulnerability,
}`, url: `https://cve.mitre.org/cgi-bin/cvename.cgi?name=${issue.vulnerability}`,
); }]);
expect(parsed.project_fingerprint).toEqual( expect(parsed.project_fingerprint).toEqual(
sha1(`${issue.namespace}:${issue.vulnerability}:${issue.featurename}:${issue.featureversion}`)); sha1(`${issue.namespace}:${issue.vulnerability}:${issue.featurename}:${issue.featureversion}`));
}); });
......
...@@ -3,27 +3,11 @@ require 'spec_helper' ...@@ -3,27 +3,11 @@ require 'spec_helper'
describe Gitlab::FileFinder do describe Gitlab::FileFinder do
describe '#find' do describe '#find' do
let(:project) { create(:project, :public, :repository) } let(:project) { create(:project, :public, :repository) }
let(:finder) { described_class.new(project, project.default_branch) }
it 'finds by name' do it_behaves_like 'file finder' do
results = finder.find('files') subject { described_class.new(project, project.default_branch) }
let(:expected_file_by_name) { 'files/images/wm.svg' }
filename, blob = results.find { |_, blob| blob.filename == 'files/images/wm.svg' } let(:expected_file_by_content) { 'CHANGELOG' }
expect(filename).to eq('files/images/wm.svg')
expect(blob).to be_a(Gitlab::SearchResults::FoundBlob)
expect(blob.ref).to eq(finder.ref)
expect(blob.data).not_to be_empty
end
it 'finds by content' do
results = finder.find('files')
filename, blob = results.find { |_, blob| blob.filename == 'CHANGELOG' }
expect(filename).to eq('CHANGELOG')
expect(blob).to be_a(Gitlab::SearchResults::FoundBlob)
expect(blob.ref).to eq(finder.ref)
expect(blob.data).not_to be_empty
end end
end end
end end
...@@ -22,47 +22,57 @@ describe Gitlab::ProjectSearchResults do ...@@ -22,47 +22,57 @@ describe Gitlab::ProjectSearchResults do
it { expect(results.query).to eq('hello world') } it { expect(results.query).to eq('hello world') }
end end
describe 'blob search' do shared_examples 'general blob search' do |entity_type, blob_kind|
let(:project) { create(:project, :public, :repository) } let(:query) { 'files' }
subject(:results) { described_class.new(user, project, query).objects(blob_type) }
subject(:results) { described_class.new(user, project, 'files').objects('blobs') }
context 'when repository is disabled' do
let(:project) { create(:project, :public, :repository, :repository_disabled) }
it 'hides blobs from members' do context "when #{entity_type} is disabled" do
let(:project) { disabled_project }
it "hides #{blob_kind} from members" do
project.add_reporter(user) project.add_reporter(user)
is_expected.to be_empty is_expected.to be_empty
end end
it 'hides blobs from non-members' do it "hides #{blob_kind} from non-members" do
is_expected.to be_empty is_expected.to be_empty
end end
end end
context 'when repository is internal' do context "when #{entity_type} is internal" do
let(:project) { create(:project, :public, :repository, :repository_private) } let(:project) { private_project }
it 'finds blobs for members' do it "finds #{blob_kind} for members" do
project.add_reporter(user) project.add_reporter(user)
is_expected.not_to be_empty is_expected.not_to be_empty
end end
it 'hides blobs from non-members' do it "hides #{blob_kind} from non-members" do
is_expected.to be_empty is_expected.to be_empty
end end
end end
it 'finds by name' do it 'finds by name' do
expect(results.map(&:first)).to include('files/images/wm.svg') expect(results.map(&:first)).to include(expected_file_by_name)
end end
it 'finds by content' do it 'finds by content' do
blob = results.select { |result| result.first == 'CHANGELOG' }.flatten.last blob = results.select { |result| result.first == expected_file_by_content }.flatten.last
expect(blob.filename).to eq("CHANGELOG") expect(blob.filename).to eq(expected_file_by_content)
end
end
describe 'blob search' do
let(:project) { create(:project, :public, :repository) }
it_behaves_like 'general blob search', 'repository', 'blobs' do
let(:blob_type) { 'blobs' }
let(:disabled_project) { create(:project, :public, :repository, :repository_disabled) }
let(:private_project) { create(:project, :public, :repository, :repository_private) }
let(:expected_file_by_name) { 'files/images/wm.svg' }
let(:expected_file_by_content) { 'CHANGELOG' }
end end
describe 'parsing results' do describe 'parsing results' do
...@@ -189,40 +199,18 @@ describe Gitlab::ProjectSearchResults do ...@@ -189,40 +199,18 @@ describe Gitlab::ProjectSearchResults do
describe 'wiki search' do describe 'wiki search' do
let(:project) { create(:project, :public, :wiki_repo) } let(:project) { create(:project, :public, :wiki_repo) }
let(:wiki) { build(:project_wiki, project: project) } let(:wiki) { build(:project_wiki, project: project) }
let!(:wiki_page) { wiki.create_page('Title', 'Content') }
subject(:results) { described_class.new(user, project, 'Content').objects('wiki_blobs') }
context 'when wiki is disabled' do
let(:project) { create(:project, :public, :wiki_repo, :wiki_disabled) }
it 'hides wiki blobs from members' do
project.add_reporter(user)
is_expected.to be_empty
end
it 'hides wiki blobs from non-members' do
is_expected.to be_empty
end
end
context 'when wiki is internal' do
let(:project) { create(:project, :public, :wiki_repo, :wiki_private) }
it 'finds wiki blobs for guest' do
project.add_guest(user)
is_expected.not_to be_empty before do
end wiki.create_page('Files/Title', 'Content')
wiki.create_page('CHANGELOG', 'Files example')
it 'hides wiki blobs from non-members' do
is_expected.to be_empty
end
end end
it 'finds by content' do it_behaves_like 'general blob search', 'wiki', 'wiki blobs' do
expect(results).to include("master:Title.md\x001\x00Content\n") let(:blob_type) { 'wiki_blobs' }
let(:disabled_project) { create(:project, :public, :wiki_repo, :wiki_disabled) }
let(:private_project) { create(:project, :public, :wiki_repo, :wiki_private) }
let(:expected_file_by_name) { 'Files/Title.md' }
let(:expected_file_by_content) { 'CHANGELOG.md' }
end end
end end
......
...@@ -49,14 +49,4 @@ describe Gitlab::SlashCommands::Presenters::IssueShow do ...@@ -49,14 +49,4 @@ describe Gitlab::SlashCommands::Presenters::IssueShow do
expect(attachment[:text]).to start_with("**Open**") expect(attachment[:text]).to start_with("**Open**")
end end
end end
context 'issue with issue weight' do
let(:issue) { create(:issue, project: project, weight: 3) }
let(:weight_attachment) { subject[:attachments].first[:fields].find { |a| a[:title] == "Weight" } }
it 'shows the weight' do
expect(weight_attachment).not_to be_nil
expect(weight_attachment[:value]).to be(3)
end
end
end end
require 'spec_helper'
describe Gitlab::WikiFileFinder do
describe '#find' do
let(:project) { create(:project, :public, :wiki_repo) }
let(:wiki) { build(:project_wiki, project: project) }
before do
wiki.create_page('Files/Title', 'Content')
wiki.create_page('CHANGELOG', 'Files example')
end
it_behaves_like 'file finder' do
subject { described_class.new(project, project.wiki.default_branch) }
let(:expected_file_by_name) { 'Files/Title.md' }
let(:expected_file_by_content) { 'CHANGELOG.md' }
end
end
end
shared_examples 'file finder' do
let(:query) { 'files' }
let(:search_results) { subject.find(query) }
it 'finds by name' do
filename, blob = search_results.find { |_, blob| blob.filename == expected_file_by_name }
expect(filename).to eq(expected_file_by_name)
expect(blob).to be_a(Gitlab::SearchResults::FoundBlob)
expect(blob.ref).to eq(subject.ref)
expect(blob.data).not_to be_empty
end
it 'finds by content' do
filename, blob = search_results.find { |_, blob| blob.filename == expected_file_by_content }
expect(filename).to eq(expected_file_by_content)
expect(blob).to be_a(Gitlab::SearchResults::FoundBlob)
expect(blob.ref).to eq(subject.ref)
expect(blob.data).not_to be_empty
end
end
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