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
gem 'capybara', '~> 2.15'
gem 'capybara-screenshot', '~> 1.0.0'
gem 'selenium-webdriver', '~> 3.5'
gem 'selenium-webdriver', '~> 3.12'
gem 'spring', '~> 2.0.0'
gem 'spring-commands-rspec', '~> 1.0.4'
......@@ -396,7 +396,7 @@ group :test do
gem 'test-prof', '~> 0.2.5'
end
gem 'octokit', '~> 4.8'
gem 'octokit', '~> 4.9'
gem 'mail_room', '~> 0.9.1'
......
......@@ -123,7 +123,7 @@ GEM
mime-types (>= 1.16)
cause (0.1)
charlock_holmes (0.7.6)
childprocess (0.7.0)
childprocess (0.9.0)
ffi (~> 1.0, >= 1.0.11)
chronic (0.10.2)
chronic_duration (0.10.6)
......@@ -546,7 +546,7 @@ GEM
multi_json (~> 1.3)
multi_xml (~> 0.5)
rack (>= 1.2, < 3)
octokit (4.8.0)
octokit (4.9.0)
sawyer (~> 0.8.0, >= 0.5.3)
omniauth (1.8.1)
hashie (>= 3.4.6, < 3.6.0)
......@@ -857,9 +857,9 @@ GEM
activesupport (>= 3.1)
select2-rails (3.5.9.3)
thor (~> 0.14)
selenium-webdriver (3.5.0)
selenium-webdriver (3.12.0)
childprocess (~> 0.5)
rubyzip (~> 1.0)
rubyzip (~> 1.2)
sentry-raven (2.7.2)
faraday (>= 0.7.6, < 1.0)
settingslogic (2.0.9)
......@@ -1121,7 +1121,7 @@ DEPENDENCIES
net-ssh (~> 4.2.0)
nokogiri (~> 1.8.2)
oauth2 (~> 1.4)
octokit (~> 4.8)
octokit (~> 4.9)
omniauth (~> 1.8)
omniauth-auth0 (~> 2.0.0)
omniauth-authentiq (~> 0.3.3)
......@@ -1191,7 +1191,7 @@ DEPENDENCIES
scss_lint (~> 0.56.0)
seed-fu (~> 2.3.7)
select2-rails (~> 3.5.9)
selenium-webdriver (~> 3.5)
selenium-webdriver (~> 3.12)
sentry-raven (~> 2.7)
settingslogic (~> 2.0.9)
sham_rack (~> 1.3.6)
......
......@@ -35,7 +35,12 @@ export default {
</script>
<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
v-if="isEmpty"
class="text-secondary"
......@@ -50,7 +55,7 @@ export default {
>
<span
v-tooltip
class="label color-label"
class="badge color-label"
data-placement="bottom"
data-container="body"
:style="labelStyle(label)"
......
......@@ -61,8 +61,18 @@ a {
code {
padding: 2px 4px;
color: $red-600;
background-color: $red-100;
border-radius: 3px;
.code & {
background-color: inherit;
padding: unset;
}
}
.code {
padding: 9.5px;
}
table {
......
......@@ -33,16 +33,16 @@ module SearchHelper
"Showing #{from} - #{to} of #{count} #{scope.humanize(capitalize: false)} for \"#{term}\""
end
def find_project_for_result_blob(result)
@project
end
def parse_search_result(result)
if result.is_a?(String)
Gitlab::ProjectSearchResults.parse_search_result(result)
else
Gitlab::Elastic::SearchResults.parse_search_result(result)
end
result
end
def find_project_for_blob(blob)
Project.find(blob['_parent'])
def search_blob_title(project, filename)
filename
end
private
......
......@@ -13,12 +13,12 @@ module Clusters
attr_encrypted :password,
mode: :per_attribute_iv,
key: Settings.attr_encrypted_db_key_base,
key: Settings.attr_encrypted_db_key_base_truncated,
algorithm: 'aes-256-cbc'
attr_encrypted :token,
mode: :per_attribute_iv,
key: Settings.attr_encrypted_db_key_base,
key: Settings.attr_encrypted_db_key_base_truncated,
algorithm: 'aes-256-cbc'
before_validation :enforce_namespace_to_lower_case
......
......@@ -11,7 +11,7 @@ module Clusters
attr_encrypted :access_token,
mode: :per_attribute_iv,
key: Settings.attr_encrypted_db_key_base,
key: Settings.attr_encrypted_db_key_base_truncated,
algorithm: 'aes-256-cbc'
validates :gcp_project_id,
......
......@@ -154,10 +154,6 @@ class ProjectWiki
[title, title_array.join("/")]
end
def search_files(query)
repository.search_files_by_content(query, default_branch)
end
def repository
@repository ||= Repository.new(full_path, @project, disk_path: disk_path, is_wiki: true)
end
......
- project = @project || find_project_for_blob(blob)
- if blob.is_a?(Array)
- file_name, blob = blob
- else
- blob = parse_search_result(blob)
- file_name = blob.filename
- project = find_project_for_result_blob(blob)
- file_name, blob = parse_search_result(blob)
- blob_link = project_blob_path(project, tree_join(blob.ref, file_name))
.blob-result
.file-holder
.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
= render partial: 'search/results/blob_data', locals: { blob: blob, project: project, file_name: file_name, 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)
- wiki_blob = parse_search_result(wiki_blob)
- project = find_project_for_result_blob(wiki_blob)
- file_name, wiki_blob = parse_search_result(wiki_blob)
- wiki_blob_link = project_wiki_path(project, wiki_blob.basename)
.blob-result
.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
= render partial: 'search/results/blob_data', locals: { blob: wiki_blob, project: project, file_name: file_name, blob_link: wiki_blob_link }
---
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
File.expand_path(path, Rails.root)
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
# (https://github.com/ruby/ruby/commit/ce635262f53b760284d56bb1027baebaaec175d1).
# Previous versions quietly truncated the input.
#
# The default mode for the attr_encrypted gem is to use a 256-bit key.
# We truncate the 128-byte string to 32 bytes.
# Ruby 2.4+ requires passing in the exact required length for OpenSSL keys
# (https://github.com/ruby/ruby/commit/ce635262f53b760284d56bb1027baebaaec175d1).
# Previous versions quietly truncated the input.
#
# Use this when using :per_attribute_iv mode for attr_encrypted.
# 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]
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
def base_url(config)
......
......@@ -48,7 +48,7 @@ class MigrateKubernetesServiceToNewClustersArchitectures < ActiveRecord::Migrati
attr_encrypted :token,
mode: :per_attribute_iv,
key: Settings.attr_encrypted_db_key_base,
key: Settings.attr_encrypted_db_key_base_truncated,
algorithm: 'aes-256-cbc'
end
......
......@@ -425,6 +425,18 @@ Role | Name | Upstream | Connection String
If the 'Role' column for any node says "FAILED", check the
[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
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
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`
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
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
```
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.
> **Note:**
......@@ -193,13 +193,13 @@ world. Custom domains are supported, but no TLS.
```shell
pages_external_url "http://example.io"
nginx['listen_addresses'] = ['1.1.1.1']
nginx['listen_addresses'] = ['192.0.2.1']
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
`1.1.1.2` and `2001::2` are the secondary IPs the GitLab Pages daemon
where `192.0.2.1` is the primary IP address that GitLab is listening to and
`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.
1. [Reconfigure GitLab][reconfigure]
......@@ -228,16 +228,16 @@ world. Custom domains and TLS are supported.
```shell
pages_external_url "https://example.io"
nginx['listen_addresses'] = ['1.1.1.1']
nginx['listen_addresses'] = ['192.0.2.1']
pages_nginx['enable'] = false
gitlab_pages['cert'] = "/etc/gitlab/ssl/example.io.crt"
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_https'] = ['1.1.1.2:443', '[2001::2]:443']
gitlab_pages['external_http'] = ['192.0.2.2:80', '[2001::2]:80']
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
`1.1.1.2` and `2001::2` are the secondary IPs where the GitLab Pages daemon
where `192.0.2.1` is the primary IP address that GitLab is listening to and
`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.
1. [Reconfigure GitLab][reconfigure]
......
......@@ -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:
```
*.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
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:**
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.
port: 80
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
......@@ -263,7 +263,7 @@ world. Custom domains are supported, but no TLS.
```
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:
......@@ -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
`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.
1. Restart NGINX
1. [Restart GitLab][restart]
......@@ -320,8 +320,8 @@ world. Custom domains and TLS are supported.
port: 443
https: true
external_http: 1.1.1.2:80
external_https: 1.1.1.2:443
external_http: 192.0.2.2:80
external_https: 192.0.2.2:443
```
1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in
......@@ -333,7 +333,7 @@ world. Custom domains and TLS are supported.
```
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:
......@@ -344,7 +344,7 @@ world. Custom domains and TLS are supported.
```
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.
1. Restart NGINX
1. [Restart GitLab][restart]
......
......@@ -144,7 +144,7 @@ helm install --name gitlab -f values.yaml gitlab/gitlab-omnibus
or passing them on the command line:
```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
......
......@@ -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.
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
certificate and the imports are failing, you will need to disable SSL verification.
......
# Import your project from GitHub to GitLab
Import your projects from GitHub to GitLab with minimal effort.
Using the importer, you can import your GitHub repositories to GitLab.com or to
your self-hosted GitLab instance.
## Overview
>**Note:**
If you are an administrator you can enable the [GitHub integration][gh-import]
in your GitLab instance sitewide. This configuration is optional, users will
still be able to import their GitHub repositories with a
[personal access token][gh-token].
>**Note:**
Administrators of a GitLab instance (Community or Enterprise Edition) can also
use the [GitHub rake task][gh-rake] to import projects from GitHub without the
constrains of a Sidekiq worker.
- At its current state, GitHub importer can import:
- the repository description (GitLab 7.7+)
- the Git repository data (GitLab 7.7+)
- the issues (GitLab 7.7+)
- the pull requests (GitLab 8.4+)
- the wiki pages (GitLab 8.4+)
- the milestones (GitLab 8.7+)
- the labels (GitLab 8.7+)
- the release note descriptions (GitLab 8.12+)
- the pull request review comments (GitLab 10.2+)
- the regular issue and pull request comments
- References to pull requests and issues are preserved (GitLab 8.7+)
- Repository public access is retained. If a repository is private in GitHub
it will be created as private in GitLab as well.
NOTE: **Note:**
While these instructions will always work for users on GitLab.com, if you are an
administrator of a self-hosted GitLab instance, you will need to enable the
[GitHub integration][gh-import] in order for users to follow the preferred
import method described on this page. If this is not enabled, users can alternatively import their
GitHub repositories using a [personal access token](#using-a-github-token) from GitHub,
but this method will not be able to associate all user activity (such as issues and pull requests)
with matching GitLab users. As an administrator of a self-hosted GitLab instance, you can also use
the [GitHub rake task](../../../administration/raketasks/github_import.md) to import projects from
GitHub without the constraints of a Sidekiq worker.
The following aspects of a project are imported:
* Repository description (GitLab.com & 7.7+)
* Git repository data (GitLab.com & 7.7+)
* Issues (GitLab.com & 7.7+)
* Pull requests (GitLab.com & 8.4+)
* Wiki pages (GitLab.com & 8.4+)
* Milestones (GitLab.com & 8.7+)
* Labels (GitLab.com & 8.7+)
* Release note descriptions (GitLab.com & 8.12+)
* Pull request review comments (GitLab.com & 10.2+)
* Regular issue and pull request comments
References to pull requests and issues are preserved (GitLab.com & 8.7+), and
each imported repository defaults to `private` but [can be made public](../settings/index.md#sharing-and-permissions), as needed.
## How it works
When issues/pull requests are being imported, the GitHub importer tries to find
the GitHub author/assignee in GitLab's database using the GitHub ID. For this
to work, the GitHub author/assignee should have signed in beforehand in GitLab
and **associated their GitHub account**. If the user is not
found in GitLab's database, the project creator (most of the times the current
user that started the import process) is set as the author, but a reference on
the issue about the original GitHub author is kept.
When issues and pull requests are being imported, the importer attempts to find their GitHub authors and
assignees in the database of the GitLab instance (note that pull requests are called "merge requests" in GitLab).
The importer will create any new namespaces (groups) if they don't exist or in
the case the namespace is taken, the repository will be imported under the user's
namespace that started the import process.
For this association to succeed, prior to the import, each GitHub author and assignee in the repository must
have either previously logged in to a GitLab account using the GitHub icon **or** have a GitHub account with
a [public email address](https://help.github.com/articles/setting-your-commit-email-address-on-github/) that
matches their GitLab account's email address.
The importer will also import branches on forks of projects related to open pull
requests. These branches will be imported with a naming scheme similar to
GH-SHA-Username/Pull-Request-number/fork-name/branch. This may lead to a discrepancy
in branches compared to the GitHub Repository.
If a user referenced in the project is not found in GitLab's database, the project creator (typically the user
that initiated the import process) is set as the author/assignee, but a note on the issue mentioning the original
GitHub author is added.
For a more technical description and an overview of the architecture you can
refer to [Working with the GitHub importer][gh-import-dev-docs].
The importer creates any new namespaces (groups) if they do not exist, or, if the namespace is taken, the
repository is imported under the namespace of the user who initiated the import process. The namespace/repository
name can also be edited, with the proper permissions.
## Importing your GitHub repositories
The importer will also import branches on forks of projects related to open pull requests. These branches will be
imported with a naming scheme similar to `GH-SHA-username/pull-request-number/fork-name/branch`. This may lead to
a discrepancy in branches compared to those of the GitHub repository.
The importer page is visible when you create a new project.
For additional technical details, you can refer to the
[GitHub Importer](../../../development/github_importer.md "Working with the GitHub importer")
developer documentation.
![New project page on GitLab](img/import_projects_from_new_project_page.png)
## Import your GitHub repository into GitLab
Click on the **GitHub** link and the import authorization process will start.
There are two ways to authorize access to your GitHub repositories:
### Using the GitHub integration
1. [Using the GitHub integration][gh-integration] (if it's enabled by your
GitLab administrator). This is the preferred way as it's possible to
preserve the GitHub authors/assignees. Read more in the [How it works](#how-it-works)
section.
1. [Using a personal access token][gh-token] provided by GitHub.
Before you begin, ensure that any GitHub users who you want to map to GitLab users have either:
![Select authentication method](img/import_projects_from_github_select_auth_method.png)
1. A GitLab account that has logged in using the GitHub icon
\- or -
2. A GitLab account with an email address that matches the [public email address](https://help.github.com/articles/setting-your-commit-email-address-on-github/) of the GitHub user
### Authorize access to your repositories using the GitHub integration
User-matching attempts occur in that order, and if a user is not identified either way, the activity is associated with
the user account that is performing the import.
If the [GitHub integration][gh-import] is enabled by your GitLab administrator,
you can use it instead of the personal access token.
NOTE: **Note:**
If you are using a self-hosted GitLab instance, this process requires that you have configured the
[GitHub integration][gh-import].
1. First you may want to connect your GitHub account to GitLab in order for
the username mapping to be correct.
1. Once you connect GitHub, click the **List your GitHub repositories** button
and you will be redirected to GitHub for permission to access your projects.
1. After accepting, you'll be automatically redirected to the importer.
1. From the top navigation bar, click **+** and select **New project**.
2. Select the **Import project** tab and then select **GitHub**.
3. Select the first button to **List your GitHub repositories**. You are redirected to a page on github.com to authorize the GitLab application.
4. Click **Authorize gitlabhq**. You are redirected back to GitLab's Import page and all of your GitHub repositories are listed.
5. Continue on to [selecting which repositories to import](#selecting-which-repositories-to-import).
You can now go on and [select which repositories to import](#select-which-repositories-to-import).
### Using a GitHub token
### Authorize access to your repositories using a personal access token
NOTE: **Note:**
For a proper author/assignee mapping for issues and pull requests, the [GitHub integration method (above)](#using-the-github-integration)
should be used instead of the personal access token. If you are using GitLab.com or a self-hosted GitLab instance with the GitHub
integration enabled, that should be the preferred method to import your repositories. Read more in the [How it works](#how-it-works) section.
>**Note:**
For a proper author/assignee mapping for issues and pull requests, the
[GitHub integration][gh-integration] should be used instead of the
[personal access token][gh-token]. If the GitHub integration is enabled by your
GitLab administrator, it should be the preferred method to import your repositories.
Read more in the [How it works](#how-it-works) section.
If you are not using the GitHub integration, you can still perform an authorization with GitHub to grant GitLab access your repositories:
If you are not using the GitHub integration, you can still perform a one-off
authorization with GitHub to grant GitLab access your repositories:
1. Go to https://github.com/settings/tokens/new
2. Enter a token description.
3. Select the repo scope.
4. Click **Generate token**.
5. Copy the token hash.
6. Go back to GitLab and provide the token to the GitHub importer.
7. Hit the **List Your GitHub Repositories** button and wait while GitLab reads your repositories' information.
Once done, you'll be taken to the importer page to select the repositories to import.
1. Go to <https://github.com/settings/tokens/new>.
1. Enter a token description.
1. Check the `repo` scope.
1. Click **Generate token**.
1. Copy the token hash.
1. Go back to GitLab and provide the token to the GitHub importer.
1. Hit the **List Your GitHub Repositories** button and wait while GitLab reads
your repositories' information. Once done, you'll be taken to the importer
page to select the repositories to import.
### Selecting which repositories to import
### Select which repositories to import
After you have authorized access to your GitHub repositories, you are redirected to the GitHub importer page and
your GitHub repositories are listed.
After you've authorized access to your GitHub repositories, you will be
redirected to the GitHub importer page.
1. By default, the proposed repository namespaces match the names as they exist in GitHub, but based on your permissions,
you can choose to edit these names before you proceed to import any of them.
2. Select the **Import** button next to any number of repositories, or select **Import all repositories**.
3. The **Status** column shows the import status of each repository. You can choose to leave the page open and it will
update in realtime or you can return to it later.
4. Once a repository has been imported, click its GitLab path to open its GitLab URL.
From there, you can see the import statuses of your GitHub repositories.
## Mirroring and pipeline status sharing
- Those that are being imported will show a _started_ status,
- those already successfully imported will be green with a _done_ status,
- whereas those that are not yet imported will have an **Import** button on the
right side of the table.
Depending your GitLab tier, [project mirroring](../../../workflow/repository_mirroring.md) can be set up to keep
your imported project in sync with its GitHub copy.
If you want, you can import all your GitHub projects in one go by hitting
**Import all projects** in the upper left corner.
Additionally, you can configure GitLab to send pipeline status updates back GitHub with the
[GitHub Project Integration](../integrations/github.md). **[PREMIUM]**
![GitHub importer page](img/import_projects_from_github_importer.png)
If you import your project using [CI/CD for external repo](../../../ci/ci_cd_for_external_repos/index.md), then both
of the above are automatically configured. **[PREMIUM]**
---
## Improving the speed of imports on self-hosted instances
You can also choose a different name for the project and a different namespace,
if you have the privileges to do so.
NOTE: **Note:**
Admin access to the GitLab server is required.
## Mirroring
[Project mirroring](../../../workflow/repository_mirroring.md) can be set up to keep your imported project in sync. Additionally you can configure GitLab to send pipeline status updates back GitHub with the [GitHub Project Integration](../integrations/github.md).
If you import you project using "CI/CD for external repo" then both of the above will be automatically configured.
## Making the import process go faster
For large projects it may take a while to import all data. To reduce the time
necessary you can increase the number of Sidekiq workers that process the
following queues:
For large projects it may take a while to import all data. To reduce the time necessary, you can increase the number of
Sidekiq workers that process the following queues:
* `github_importer`
* `github_importer_advance_stage`
For an optimal experience we recommend having at least 4 Sidekiq processes (each
running a number of threads equal to the number of CPU cores) that _only_
process these queues. We also recommend that these processes run on separate
servers. For 4 servers with 8 cores this means you can import up to 32 objects
(e.g. issues) in parallel.
For an optimal experience, it's recommended having at least 4 Sidekiq processes (each running a number of threads equal
to the number of CPU cores) that *only* process these queues. It's also recommended that these processes run on separate
servers. For 4 servers with 8 cores this means you can import up to 32 objects (e.g., issues) in parallel.
Reducing the time spent in cloning a repository can be done by increasing
network throughput, CPU capacity, and disk performance (e.g. by using high
performance SSDs) of the disks that store the Git repositories (for your GitLab
instance). Increasing the number of Sidekiq workers will _not_ reduce the time
spent cloning repositories.
Reducing the time spent in cloning a repository can be done by increasing network throughput, CPU capacity, and disk
performance (e.g., by using high performance SSDs) of the disks that store the Git repositories (for your GitLab instance).
Increasing the number of Sidekiq workers will *not* reduce the time spent cloning repositories.
[gh-import]: ../../../integration/github.md "GitHub integration"
[gh-rake]: ../../../administration/raketasks/github_import.md "GitHub rake task"
[gh-integration]: #authorize-access-to-your-repositories-using-the-github-integration
[gh-token]: #authorize-access-to-your-repositories-using-a-personal-access-token
[gh-import-dev-docs]: ../../../development/github_importer.md "Working with the GitHub importer"
# 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)
......@@ -10,14 +14,14 @@ This project integration is separate from the [instance wide GitHub integration]
### 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. Click "Generate New Token"
1. Ensure that `repo:status` is checked and click "Generate token"
1. Copy the generated token to use on GitLab
### Complete these steps on GitLab
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
1. Click "GitHub".
1. Select the "Active" checkbox.
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".
![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.
| Language / framework | Scan tool |
|-----------------------|----------------------------------------------------------------------------------------|
| 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) |
| Java (Maven & Gradle) | [find-sec-bugs](https://find-sec-bugs.github.io/) |
| Go (experimental) | [Go AST Scanner](https://github.com/GoASTScanner/gas) |
......
# 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
in your repository.
......@@ -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
manually.
## Use-cases
## Use cases
- 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
......@@ -294,11 +294,12 @@ by using the **Update now** button which is exposed in various places:
## Bidirectional mirroring
> **Warning:** There is no bidirectional support without conflicts. If you
> configure a repository to pull and push to a second remote, there is no
> guarantee that it will update correctly on both remotes. If you configure
> a repository for bidirectional mirroring, you should consider when conflicts
> occur who and how they will be resolved.
CAUTION: **Warning:**
There is no bidirectional support without conflicts. If you
configure a repository to pull and push to a second remote, there is no
guarantee that it will update correctly on both remotes. If you configure
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
mirroring to fail. This can be prevented by [only pulling protected branches](
......@@ -318,10 +319,11 @@ custom Git hooks][hooks] on the GitLab server.
### Mirroring with Perforce via GitFusion
> **Warning:** Bidirectional mirroring should not be used as a permanent
> configuration. There is no bidirectional mirroring without conflicts.
> Refer to [Migrating from Perforce Helix][perforce] for alternative migration
> approaches.
CAUTION: **Warning:**
Bidirectional mirroring should not be used as a permanent
configuration. There is no bidirectional mirroring without conflicts.
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
bidirectionally mirror projects with GitLab. This may be useful in some
......@@ -339,10 +341,11 @@ limitations of GitFusion.
[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-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
[perms]: ../user/permissions.md
[hooks]: ../administration/custom_hooks.html
[hooks]: ../administration/custom_hooks.md
[deploy-key]: ../ssh/README.md#deploy-keys
[webhook]: ../user/project/integrations/webhooks.html#push-events
[pull-api]: ../api/projects.html#start-the-pull-mirroring-process-for-a-project
[perforce]: ../user/project/import/perforce.html
[webhook]: ../user/project/integrations/webhooks.md#push-events
[pull-api]: ../api/projects.md#start-the-pull-mirroring-process-for-a-project
[perforce]: ../user/project/import/perforce.md
......@@ -23,7 +23,10 @@ export default {
<template>
<div
class="flex"
:class="{ 'issue-info-container': !canReorder }"
:class="{
'issue-info-container': !canReorder,
'card-body': canReorder,
}"
>
<div class="block-truncated append-right-10">
<a
......
......@@ -93,10 +93,13 @@ export default {
},
mounted() {
if (this.canReorder) {
this.sortable = Sortable.create(this.$refs.list, Object.assign({}, sortableConfig, {
onStart: this.addDraggingCursor,
onEnd: this.reordered,
}));
this.sortable = Sortable.create(
this.$refs.list,
Object.assign({}, sortableConfig, {
onStart: this.addDraggingCursor,
onEnd: this.reordered,
}),
);
}
},
methods: {
......@@ -225,7 +228,7 @@ issue-count-badge-add-button btn btn-sm btn-default"
:class="{
'user-can-drag': canReorder,
'sortable-row': canReorder,
card: canReorder
'card-slim': canReorder
}"
:data-key="issue.id"
:data-epic-issue-id="issue.epic_issue_id"
......
<script>
/**
* Renders DAST body text
* [priority]: [name]
* [severity] ([confidence]): [name]
*/
import ModalOpenName from './modal_open_name.vue';
......@@ -27,7 +27,7 @@ export default {
<template>
<div class="report-block-list-issue-description prepend-top-5 append-bottom-5">
<div class="report-block-list-issue-description-text">
<template v-if="issue.priority">{{ issue.priority }}:</template>
{{ issue.severity }} ({{ issue.confidence }}):
<modal-open-name
:issue="issue"
......
......@@ -35,9 +35,20 @@ export default {
this.dismissIssue();
}
},
isLastValue(index, values) {
return index < values.length - 1;
},
hasValue(field) {
return field.value && field.value.length > 0;
},
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 {
<slot>
<div
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"
:key="index"
>
......@@ -99,6 +110,42 @@ export default {
</li>
</ul>
</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>
<a
:class="`js-link-${key}`"
......
......@@ -22,6 +22,6 @@ export default {
@click="handleIssueClick()"
class="btn-link btn-blank text-left break-link vulnerability-name-button"
>
{{ issue.name }}
{{ issue.title }}
</button>
</template>
......@@ -23,7 +23,7 @@ export default {
<template>
<div class="report-block-list-issue-description prepend-top-5 append-bottom-5">
<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" />
</div>
......
<script>
/**
* Renders SAST body text
* [priority]: [name] in [link] : [line]
* [severity] ([confidence]): [name] in [link] : [line]
*/
import ReportLink from './report_link.vue';
import ModalOpenName from './modal_open_name.vue';
......@@ -25,7 +25,10 @@ export default {
<template>
<div class="report-block-list-issue-description prepend-top-5 append-bottom-5">
<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" />
</div>
......
......@@ -248,28 +248,30 @@ export default {
},
[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.file.value = issue.file;
state.modal.data.file.value = issue.location && issue.location.file;
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;
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.confidence.value = issue.confidence;
state.modal.data.solution.value = issue.solution;
state.modal.data.confidenceLevel.value = issue.confidence;
state.modal.data.source.value = issue.source;
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;
if (issue.links && issue.links.length > 0) {
state.modal.data.links.value = issue.links;
} else {
state.modal.data.identifier.value = issue.identifier;
state.modal.data.identifier.isLink = false;
state.modal.data.identifier.url = null;
// Force a null value for links to avoid showing an empty array
state.modal.data.links.value = null;
}
state.modal.data.instances.value = issue.instances;
state.modal.vulnerability = issue;
// clear previous state
state.modal.error = null;
......
......@@ -77,21 +77,30 @@ export default () => ({
text: s__('ciReport|Description'),
isLink: false,
},
identifiers: {
value: [],
text: s__('ciReport|Identifiers'),
isLink: false,
},
file: {
value: null,
url: null,
text: s__('ciReport|File'),
isLink: true,
},
namespace: {
className: {
value: null,
text: s__('ciReport|Namespace'),
text: s__('ciReport|Class'),
isLink: false,
},
identifier: {
methodName: {
value: null,
url: null,
text: s__('ciReport|Identifier'),
text: s__('ciReport|Method'),
isLink: false,
},
namespace: {
value: null,
text: s__('ciReport|Namespace'),
isLink: false,
},
severity: {
......@@ -99,20 +108,20 @@ export default () => ({
text: s__('ciReport|Severity'),
isLink: false,
},
solution: {
confidence: {
value: null,
text: s__('ciReport|Solution'),
text: s__('ciReport|Confidence'),
isLink: false,
},
confidenceLevel: {
solution: {
value: null,
text: s__('ciReport|Confidence Level'),
text: s__('ciReport|Solution'),
isLink: false,
},
source: {
value: null,
text: s__('ciReport|Source'),
isLink: true,
links: {
value: [],
text: s__('ciReport|Links'),
isLink: false,
},
instances: {
value: [],
......
import sha1 from 'sha1';
import _ from 'underscore';
import { stripHtml } from '~/lib/utils/text_utility';
import { n__, s__, sprintf } from '~/locale';
......@@ -12,25 +13,25 @@ export const findIssueIndex = (issues, issue) =>
/**
* Returns given vulnerability enriched with the corresponding
* feedbacks (`dismissal` or `issue` type)
* feedback (`dismissal` or `issue` type)
* @param {Object} vulnerability
* @param {Array} feedbacks
* @param {Array} feedback
*/
function enrichVulnerabilityWithfeedbacks(vulnerability, feedbacks = []) {
return feedbacks.filter(
feedback => feedback.project_fingerprint === vulnerability.project_fingerprint,
).reduce((vuln, feedback) => {
if (feedback.feedback_type === 'dismissal') {
function enrichVulnerabilityWithfeedback(vulnerability, feedback = []) {
return feedback.filter(
fb => fb.project_fingerprint === vulnerability.project_fingerprint,
).reduce((vuln, fb) => {
if (fb.feedback_type === 'dismissal') {
return {
...vuln,
isDismissed: true,
dismissalFeedback: feedback,
dismissalFeedback: fb,
};
} else if (feedback.feedback_type === 'issue') {
} else if (fb.feedback_type === 'issue') {
return {
...vuln,
hasIssue: true,
issueFeedback: feedback,
issueFeedback: fb,
};
}
return vuln;
......@@ -38,107 +39,192 @@ function enrichVulnerabilityWithfeedbacks(vulnerability, feedbacks = []) {
}
/**
* Maps SAST issues:
* { 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 }
* Generates url to repository file and highlight section between start and end lines.
*
* @param {Object} location
* @param {String} pathPrefix
* @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} feedback
* @param {String} path
* @returns {Array}
*/
export const parseSastIssues = (issues = [], feedbacks = [], path = '') =>
export const parseSastIssues = (issues = [], feedback = [], path = '') =>
issues.map(issue => {
const parsed = {
...issue,
...adaptDeprecatedFormat(issue),
category: 'sast',
// TODO: replace with issue.project_fingerprint
project_fingerprint: sha1(issue.cve),
name: issue.message,
path: issue.file,
urlPath: issue.line ? `${path}/${issue.file}#L${issue.line}` : `${path}/${issue.file}`,
title: issue.message,
};
return {
...parsed,
...enrichVulnerabilityWithfeedbacks(parsed, feedbacks),
path: parsed.location.file,
urlPath: fileUrl(parsed.location, path),
...enrichVulnerabilityWithfeedback(parsed, feedback),
};
});
/**
* Maps Dependency scanning issues:
* { 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 }
* Parses Dependency Scanning results into a common format to allow to use the same Vue component.
*
* @param {Array} issues
* @param {Array} feedback
* @param {String} path
* @returns {Array}
*/
export const parseDependencyScanningIssues = (issues = [], feedbacks = [], path = '') =>
export const parseDependencyScanningIssues = (issues = [], feedback = [], path = '') =>
issues.map(issue => {
const parsed = {
...issue,
...adaptDeprecatedFormat(issue),
category: 'dependency_scanning',
// TODO: replace with issue.project_fingerprint
project_fingerprint: sha1(issue.cve || issue.message),
name: issue.message,
path: issue.file,
urlPath: issue.line ? `${path}/${issue.file}#L${issue.line}` : `${path}/${issue.file}`,
title: issue.message,
};
return {
...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
* And adds an external link
* Parses Container Scanning results into a common format to allow to use the same Vue component.
* 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}
*/
export const parseSastContainer = (issues = [], feedbacks = []) =>
export const parseSastContainer = (issues = [], feedback = []) =>
issues.map(issue => {
const parsed = {
...issue,
category: 'container_scanning',
// TODO: replace with issue.project_fingerprint
project_fingerprint: sha1(`${issue.namespace}:${issue.vulnerability}:${issue.featurename}:${issue.featureversion}`),
name: issue.vulnerability,
priority: issue.severity,
title: issue.vulnerability,
description: !_.isEmpty(issue.description) ? issue.description :
sprintf(s__('ciReport|%{namespace} is affected by %{vulnerability}.'), {
namespace: issue.namespace,
vulnerability: issue.vulnerability,
}),
path: issue.namespace,
// external link to provide better description
nameLink: `https://cve.mitre.org/cgi-bin/cvename.cgi?name=${issue.vulnerability}`,
identifiers: [{
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 {
...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 => {
const parsed = {
...issue,
category: 'dast',
// TODO: replace with issue.project_fingerprint
project_fingerprint: sha1(issue.pluginid),
parsedDescription: stripHtml(issue.desc, ' '),
priority: issue.riskdesc,
solution: stripHtml(issue.solution, ' '),
title: issue.name,
description: stripHtml(issue.desc, ' '),
solution: stripHtml(issue.solution, ' '),
};
if (issue.cweid && issue.cweid !== '') {
if (!_.isEmpty(issue.cweid)) {
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 !== '') {
// Split 'severity (confidence)'
// Split riskdesc into severity and confidence.
// Riskdesc format is: "severity (confidence)"
const [, severity, confidence] = issue.riskdesc.match(/(.*) \((.*)\)/);
Object.assign(parsed, {
severity,
......@@ -148,7 +234,7 @@ export const parseDastIssues = (issues = [], feedbacks = []) =>
return {
...parsed,
...enrichVulnerabilityWithfeedbacks(parsed, feedbacks),
...enrichVulnerabilityWithfeedback(parsed, feedback),
};
});
......
......@@ -109,9 +109,22 @@ class Projects::VulnerabilityFeedbackController < Projects::ApplicationControlle
method
uri
],
location: %i[
file
start_line
end_line
class
method
],
identifiers: %i[
type
name
value
url
],
links: %i[
name
url
]
]
end
......
......@@ -52,6 +52,8 @@ class EpicsFinder < IssuableFinder
private
def groups_user_can_read_epics(groups)
groups = Gitlab::GroupPlansPreloader.new.preload(groups)
DeclarativePolicy.user_scope do
groups.select { |g| Ability.allowed?(current_user, :read_epic, g) }
end
......
module EE
module SearchHelper
extend ::Gitlab::Utils::Override
def search_filter_input_options(type)
options = super
options[:data][:'multiple-assignees'] = 'true' if search_multiple_assignees?(type)
......@@ -7,6 +9,29 @@ module EE
options
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
def search_multiple_assignees?(type)
......
......@@ -8,7 +8,8 @@ module EE
'epic_issue_moved' => 'issues',
'issue_added_to_epic' => 'epic',
'issue_removed_from_epic' => 'epic',
'issue_changed_epic' => 'epic'
'issue_changed_epic' => 'epic',
'epic_date_changed' => 'calendar'
}.freeze
override :system_note_icon_name
......
......@@ -70,13 +70,13 @@ module EE
attr_encrypted :external_auth_client_key,
mode: :per_attribute_iv,
key: Settings.attr_encrypted_db_key_base,
key: Settings.attr_encrypted_db_key_base_truncated,
algorithm: 'aes-256-gcm',
encode: true
attr_encrypted :external_auth_client_key_pass,
mode: :per_attribute_iv,
key: Settings.attr_encrypted_db_key_base,
key: Settings.attr_encrypted_db_key_base_truncated,
algorithm: 'aes-256-gcm',
encode: true
end
......
......@@ -155,6 +155,19 @@ module EE
actual_plan&.pipeline_size_limit.to_i
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
def validate_plan_name
......@@ -180,14 +193,5 @@ module EE
globally_available
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
......@@ -5,7 +5,7 @@ module EE
EE_ICON_TYPES = %w[
weight approved unapproved relate unrelate
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
override :icon_types
......
......@@ -42,7 +42,7 @@ class GeoNode < ActiveRecord::Base
scope :with_url_prefix, ->(prefix) { where('url LIKE ?', "#{prefix}%") }
attr_encrypted :secret_access_key,
key: Settings.attr_encrypted_db_key_base,
key: Settings.attr_encrypted_db_key_base_truncated,
algorithm: 'aes-256-gcm',
mode: :per_attribute_iv,
encode: true
......
......@@ -8,10 +8,21 @@ module EE
def execute(_issuable, _old_labels)
super
handle_weight_change_note
handle_date_change_note
end
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
if issuable.previous_changes.include?('weight')
create_weight_change_note
......
......@@ -24,5 +24,27 @@ module EE
body = noteable.weight ? "changed weight to **#{noteable.weight}**," : 'removed the weight'
create_note(NoteSummary.new(noteable, project, author, body, action: 'weight'))
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
......@@ -2,14 +2,14 @@ module Issues
class CreateFromVulnerabilityDataService < ::BaseService
def execute
vulnerability = case @params[:category]
when 'sast', 'dependency_scanning'
when 'sast', 'dependency_scanning', 'dast'
Gitlab::Vulnerabilities::StandardVulnerability.new(params)
when 'container_scanning'
Gitlab::Vulnerabilities::ContainerScanningVulnerability.new(params)
when 'dast'
Gitlab::Vulnerabilities::DastVulnerability.new(params)
end
return error('Invalid vulnerability category') unless vulnerability
issue_params = {
title: "Investigate vulnerability: #{vulnerability.title}",
description: render_description(vulnerability)
......
......@@ -7,8 +7,8 @@
- ineligible_approver = issuable.author || current_user
- can_update_approvers = can?(current_user, :update_approvers, issuable)
.form-group
= form.label :approver_ids, class: 'col-form-label' do
.form-group.row
= form.label :approver_ids, class: 'col-form-label col-sm-2' do
Approvers
.col-sm-10
- if can_update_approvers
......@@ -61,7 +61,7 @@
Remove
.col-sm-12
.form-group
.form-group.row
= form.label :approvals_before_merge, class: 'label-light' do
Approvals required
= form.number_field :approvals_before_merge, class: 'form-control', value: issuable.approvals_required, readonly: !can_update_approvers
......
......@@ -19,11 +19,22 @@
### Identifiers:
<% vulnerability.identifiers.each do |identifier| %>
<% if identifier[:link].present? %>
* [<%= identifier[:value] %>](<%= identifier[:link] %>)
<% if identifier[:url].present? %>
* [<%= identifier[:name] %>](<%= identifier[:url] %>)
<% else %>
* <%= identifier[:value] %>
* <%= identifier[:name] %>
<% 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 %>
---
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
def can_replay?(event_log)
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)
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
end
def commands
Gitlab::SlashCommands::Command::COMMANDS
Gitlab::SlashCommands::Command.commands
end
end
end
......
......@@ -15,23 +15,12 @@ module Gitlab
confidence
solution
identifiers
links
].each do |method_name|
define_method(method_name) do
raise NotImplementedError
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
......@@ -2,24 +2,24 @@ module Gitlab
module Vulnerabilities
class ContainerScanningVulnerability < BaseVulnerability
def title
"#{@data[:name]} in #{@data[:namespace]}"
"#{@data[:vulnerability]} in #{@data[:namespace]}"
end
# Passthrough properties
%i[
confidence
severity
identifiers
links
].each do |method_name|
define_method(method_name) do
@data[method_name]
end
end
def confidence
end
def description
@data[:description].presence ||
"**#{@data[:namespace]}** is affected by #{@data[:name]}"
"**#{@data[:namespace]}** is affected by #{@data[:vulnerability]}"
end
def solution
......@@ -30,13 +30,6 @@ module Gitlab
"Upgrade **#{@data[:featurename]}** from `#{@data[:featureversion]}` to `#{@data[:fixedby]}`"
end
end
def identifiers
[{
value: @data[:name],
link: cve_link(@data[:name])
}]
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 Vulnerabilities
class StandardVulnerability < BaseVulnerability
def title
@data[:name]
end
# Passthrough properties
%i[
title
severity
confidence
solution
identifiers
links
].each do |method_name|
define_method(method_name) do
@data[method_name]
......@@ -17,30 +16,7 @@ module Gitlab
end
def description
@data[:description].presence || @data[:name]
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
@data[:description].presence || @data[:title]
end
end
end
......
......@@ -86,18 +86,37 @@ describe Projects::VulnerabilityFeedbackController do
feedback_type: 'dismissal', pipeline_id: pipeline.id, category: 'sast',
project_fingerprint: '418291a26024a1445b23fe64de9380cdcdfd1fa8',
vulnerability_data: {
priority: 'Low', line: '41',
file: 'subdir/src/main/java/com/gitlab/security_products/tests/App.java',
category: 'sast',
severity: 'Low',
confidence: 'Medium',
cve: '818bf5dacb291e15d9e6dc3c5ac32178:PREDICTABLE_RANDOM',
name: 'Predictable pseudorandom number generator',
title: '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
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
expect(response).to match_response_schema('vulnerability_feedback', dir: 'ee')
......@@ -142,7 +161,7 @@ describe Projects::VulnerabilityFeedbackController do
def create_feedback(user:, project:, params:)
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
......
......@@ -53,6 +53,12 @@ describe EpicsFinder do
expect(epics).to contain_exactly(epic1, epic2, epic3)
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
it 'returns all epics created before the given date' do
expect(epics(created_before: 2.days.ago)).to contain_exactly(epic1, epic2)
......@@ -97,6 +103,24 @@ describe EpicsFinder 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)
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
context 'by timeframe' do
......
......@@ -25,7 +25,7 @@ describe SearchHelper do
options: { highlight: true }
)[: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.filename).to eq('files/ruby/popen.rb')
......
......@@ -35,6 +35,19 @@ describe Gitlab::Geo::LogCursor::Events::RepositoryDeletedEvent, :postgresql, :c
it 'removes the tracking entry' do
expect { subject.process }.to change(Geo::ProjectRegistry, :count).by(-1)
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
# 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
describe '#execute' do
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
......@@ -29,19 +29,34 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do
let(:params) do
{
category: 'sast',
priority: 'Low', line: '41',
severity: 'Low', confidence: 'High',
solution: 'Please do something!',
file: 'subdir/src/main/java/com/gitlab/security_products/tests/App.java',
cve: '818bf5dacb291e15d9e6dc3c5ac32178:PREDICTABLE_RANDOM',
name: 'Predictable pseudorandom number generator',
title: 'Predictable pseudorandom number generator',
description: 'Description of Predictable pseudorandom number generator',
tool: 'find_sec_bugs',
identifiers: [
{ name: 'CVE', value: 'CVE-2017-15650' },
{ name: 'CWE', value: '16' },
{ name: 'GAS_RULE_ID', value: 'G105' }
]
identifiers: [{
type: 'CVE',
name: 'CVE-2017-15650',
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
......@@ -63,6 +78,12 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do
* [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)
* GAS Rule ID G105
### Links:
* [Awesome-security blog post](https;//example.com/blog-post)
* https://example.com/another-link
DESC
end
......@@ -73,16 +94,12 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do
let(:params) do
{
category: 'sast',
priority: 'Low', line: '41',
severity: 'Low', confidence: 'High',
solution: 'Please do something!',
file: 'subdir/src/main/java/com/gitlab/security_products/tests/App.java',
cve: '818bf5dacb291e15d9e6dc3c5ac32178:PREDICTABLE_RANDOM',
name: 'Predictable pseudorandom number generator',
tool: 'find_sec_bugs',
identifiers: [
{ name: 'CVE', value: 'CVE-2017-15650' }
]
title: 'Predictable pseudorandom number generator',
tool: 'find_sec_bugs'
}
end
let(:expected_title) { 'Investigate vulnerability: Predictable pseudorandom number generator' }
......@@ -98,10 +115,6 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do
### Solution:
Please do something!
### Identifiers:
* [CVE-2017-15650](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15650)
DESC
end
......@@ -114,19 +127,34 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do
let(:params) do
{
category: 'dependency_scanning',
priority: 'Low', line: '41',
severity: 'Low', confidence: 'High',
solution: 'Please do something!',
file: 'subdir/src/main/java/com/gitlab/security_products/tests/App.java',
cve: '818bf5dacb291e15d9e6dc3c5ac32178:PREDICTABLE_RANDOM',
name: 'Predictable pseudorandom number generator',
title: 'Predictable pseudorandom number generator',
description: 'Description of Predictable pseudorandom number generator',
tool: 'find_sec_bugs',
identifiers: [
{ name: 'CVE', value: 'CVE-2017-15650' },
{ name: 'CWE', value: '16' },
{ name: 'GAS_RULE_ID', value: 'G105' }
]
identifiers: [{
type: 'CVE',
name: 'CVE-2017-15650',
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
let(:expected_title) { 'Investigate vulnerability: Predictable pseudorandom number generator' }
......@@ -147,6 +175,12 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do
* [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)
* GAS Rule ID G105
### Links:
* [Awesome-security blog post](https;//example.com/blog-post)
* https://example.com/another-link
DESC
end
......@@ -162,11 +196,8 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do
solution: 'Please do something!',
file: 'subdir/src/main/java/com/gitlab/security_products/tests/App.java',
cve: '818bf5dacb291e15d9e6dc3c5ac32178:PREDICTABLE_RANDOM',
name: 'Predictable pseudorandom number generator',
tool: 'find_sec_bugs',
identifiers: [
{ name: 'CVE', value: 'CVE-2017-15650' }
]
title: 'Predictable pseudorandom number generator',
tool: 'find_sec_bugs'
}
end
let(:expected_title) { 'Investigate vulnerability: Predictable pseudorandom number generator' }
......@@ -182,10 +213,6 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do
### Solution:
Please do something!
### Identifiers:
* [CVE-2017-15650](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15650)
DESC
end
......@@ -204,10 +231,16 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do
featurename: 'musl',
featureversion: '1.1.14-r15',
fixedby: '1.1.14-r16',
name: 'CVE-2017-15650',
title: 'CVE-2017-15650',
vulnerability: '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
let(:expected_title) { 'Investigate vulnerability: CVE-2017-15650 in alpine:v3.4' }
......@@ -241,10 +274,16 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do
featurename: 'musl',
featureversion: '1.1.14-r15',
fixedby: '1.1.14-r16',
name: 'CVE-2017-15650',
title: 'CVE-2017-15650',
vulnerability: 'CVE-2017-15650',
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
let(:expected_title) { 'Investigate vulnerability: CVE-2017-15650 in alpine:v3.4' }
......@@ -276,11 +315,22 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do
category: 'dast',
priority: 'Low',
severity: 'Low',
name: 'X-Content-Type-Options Header Missing',
desc: 'The Anti-MIME-Sniffing header X-Content-Type-Options was not set to nosniff.',
title: 'X-Content-Type-Options Header Missing',
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',
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
let(:expected_title) { 'Investigate vulnerability: X-Content-Type-Options Header Missing' }
......@@ -306,4 +356,16 @@ describe Issues::CreateFromVulnerabilityDataService, '#execute' do
it_behaves_like 'a created issue'
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
......@@ -21,20 +21,28 @@ describe SystemNoteService do
expect(subject).to be_system
expect(subject.noteable).to eq expected_noteable
expect(subject.project).to eq project
expect(subject.author).to eq author
expect(subject.system_note_metadata.action).to eq(action)
expect(subject.system_note_metadata.commit_count).to eq(commit_count)
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
context 'when weight changed' do
let(:noteable) { create(:issue, project: project, title: 'Lorem ipsum', weight: 4) }
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' }
end
......@@ -48,7 +56,7 @@ describe SystemNoteService do
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' }
end
......@@ -57,4 +65,36 @@ describe SystemNoteService do
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
......@@ -42,9 +42,7 @@ module API
end
case params[:scope]
when 'wiki_blobs'
paginate(results).map { |blob| Gitlab::ProjectSearchResults.parse_search_result(blob, user_project) }
when 'blobs'
when 'blobs', 'wiki_blobs'
paginate(results).map { |blob| blob[1] }
else
paginate(results)
......
......@@ -32,17 +32,13 @@ module Gitlab
end
def find_by_filename(query, except: [])
filenames = repository.search_files_by_name(query, ref).first(BATCH_SIZE)
filenames.delete_if { |filename| except.include?(filename) } unless except.empty?
filenames = search_filenames(query, except)
blob_refs = filenames.map { |filename| [ref, filename] }
blobs = Gitlab::Git::Blob.batch(repository, blob_refs, blob_size_limit: 1024)
blobs.map do |blob|
blobs(filenames).map do |blob|
Gitlab::SearchResults::FoundBlob.new(
id: blob.id,
filename: blob.path,
basename: File.basename(blob.path),
basename: File.basename(blob.path, File.extname(blob.path)),
ref: ref,
startline: 1,
data: blob.data,
......@@ -50,5 +46,21 @@ module Gitlab
)
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
......@@ -106,7 +106,8 @@ module Gitlab
project_wiki = ProjectWiki.new(project)
unless project_wiki.empty?
project_wiki.search_files(query)
ref = repository_ref || project.wiki.default_branch
Gitlab::WikiFileFinder.new(project, ref).find(query)
else
[]
end
......
module Gitlab
module SlashCommands
class Command < BaseCommand
COMMANDS = [
Gitlab::SlashCommands::IssueShow,
Gitlab::SlashCommands::IssueNew,
Gitlab::SlashCommands::IssueSearch,
Gitlab::SlashCommands::IssueMove,
Gitlab::SlashCommands::Deploy,
Gitlab::SlashCommands::Run
].freeze
prepend EE::Gitlab::SlashCommands::Command
def self.commands
[
Gitlab::SlashCommands::IssueShow,
Gitlab::SlashCommands::IssueNew,
Gitlab::SlashCommands::IssueSearch,
Gitlab::SlashCommands::IssueMove,
Gitlab::SlashCommands::Deploy
]
end
def execute
command, match = match_command
......@@ -38,7 +41,7 @@ module Gitlab
private
def available_commands
COMMANDS.select do |klass|
self.class.commands.keep_if do |klass|
klass.available?(project)
end
end
......
......@@ -2,6 +2,8 @@ module Gitlab
module SlashCommands
module Presenters
module IssueBase
prepend EE::Gitlab::SlashCommands::Presenters::IssueBase
def color(issuable)
issuable.open? ? '#38ae67' : '#d22852'
end
......@@ -34,11 +36,6 @@ module Gitlab
title: "Labels",
value: resource.labels.any? ? resource.label_names.join(', ') : "_None_",
short: true
},
{
title: "Weight",
value: resource.weight? ? resource.weight : "_None_",
short: true
}
]
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 ""
msgid "ciReport|%{linkStartTag}Learn more about SAST image %{linkEndTag}"
msgstr ""
msgid "ciReport|%{namespace} is affected by %{vulnerability}."
msgstr ""
msgid "ciReport|%{reportName} is loading"
msgstr ""
......@@ -5937,10 +5940,13 @@ msgstr ""
msgid "ciReport|%{type} detected no vulnerabilities"
msgstr ""
msgid "ciReport|Class"
msgstr ""
msgid "ciReport|Code quality"
msgstr ""
msgid "ciReport|Confidence Level"
msgid "ciReport|Confidence"
msgstr ""
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
......@@ -5991,7 +5997,7 @@ msgstr ""
msgid "ciReport|Fixed:"
msgstr ""
msgid "ciReport|Identifier"
msgid "ciReport|Identifiers"
msgstr ""
msgid "ciReport|Instances"
......@@ -6000,9 +6006,15 @@ msgstr ""
msgid "ciReport|Learn more about whitelisting"
msgstr ""
msgid "ciReport|Links"
msgstr ""
msgid "ciReport|Loading %{reportName} report"
msgstr ""
msgid "ciReport|Method"
msgstr ""
msgid "ciReport|Namespace"
msgstr ""
......@@ -6045,9 +6057,6 @@ msgstr ""
msgid "ciReport|Solution"
msgstr ""
msgid "ciReport|Source"
msgstr ""
msgid "ciReport|Static Application Security Testing (SAST) detects known vulnerabilities in your source code."
msgstr ""
......@@ -6069,6 +6078,9 @@ msgstr ""
msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved."
msgstr ""
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr ""
msgid "ciReport|no vulnerabilities"
msgstr ""
......
......@@ -82,7 +82,7 @@ describe('DropdownValueComponent', () => {
});
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.dataset.placement).toBe('bottom');
expect(labelEl.dataset.container).toBe('body');
......
......@@ -8,15 +8,13 @@ describe('dast issue body', () => {
const Component = Vue.extend(component);
const dastIssue = {
alert: 'X-Content-Type-Options Header Missing',
confidence: '2',
severity: 'Low',
confidence: 'Medium',
count: '17',
cweid: '16',
desc:
'<p>The Anti-MIME-Sniffing header X-Content-Type-Options was not set to "nosniff". </p>',
name: 'X-Content-Type-Options Header Missing',
parsedDescription:
' The Anti-MIME-Sniffing header X-Content-Type-Options was not set to "nosniff". ',
priority: 'Low (Medium)',
title: 'X-Content-Type-Options Header Missing',
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>',
riskcode: '1',
......@@ -27,34 +25,19 @@ describe('dast issue body', () => {
vm.$destroy();
});
describe('with priority', () => {
it('renders priority key', () => {
describe('severity and confidence ', () => {
it('renders severity and confidence', () => {
vm = mountComponent(Component, {
issue: dastIssue,
issueIndex: 1,
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', () => {
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', () => {
describe('issue title', () => {
beforeEach(() => {
vm = mountComponent(Component, {
issue: dastIssue,
......@@ -63,8 +46,8 @@ describe('dast issue body', () => {
});
});
it('renders button with issue name', () => {
expect(vm.$el.textContent.trim()).toContain(dastIssue.name);
it('renders button with issue title', () => {
expect(vm.$el.textContent.trim()).toContain(dastIssue.title);
});
});
});
......@@ -27,7 +27,7 @@ describe('Security Reports modal', () => {
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',
name: 'Arbitrary file existence disclosure in Action Pack',
title: 'Arbitrary file existence disclosure in Action Pack',
path: 'Gemfile.lock',
urlPath: 'path/Gemfile.lock',
isDismissed: true,
......@@ -83,7 +83,7 @@ describe('Security Reports modal', () => {
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',
name: 'Arbitrary file existence disclosure in Action Pack',
title: 'Arbitrary file existence disclosure in Action Pack',
path: 'Gemfile.lock',
urlPath: 'path/Gemfile.lock',
});
......@@ -112,12 +112,10 @@ describe('Security Reports modal', () => {
describe('with instances', () => {
beforeEach(() => {
store.dispatch('setModalData', {
name: 'Absence of Anti-CSRF Tokens',
title: 'Absence of Anti-CSRF Tokens',
riskcode: '1',
riskdesc: 'Low (Medium)',
priority: 'Low (Medium)',
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',
instances: [
{
......@@ -159,13 +157,17 @@ describe('Security Reports modal', () => {
store.dispatch('setModalData', {
tool: 'bundler_audit',
message: 'Arbitrary file existence disclosure in Action Pack',
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/rMTQy4oRCGk',
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',
name: 'Arbitrary file existence disclosure in Action Pack',
title: 'Arbitrary file existence disclosure in Action Pack',
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, {
......
......@@ -95,7 +95,7 @@ describe('Report issues', () => {
it('should not render location', () => {
vm = mountComponent(ReportIssues, {
issues: [{
name: 'foo',
title: 'foo',
}],
type: 'SAST',
status: 'failed',
......@@ -106,7 +106,7 @@ describe('Report issues', () => {
});
});
describe('for docker issues', () => {
describe('for container scanning issues', () => {
beforeEach(() => {
vm = mountComponent(ReportIssues, {
issues: dockerReportParsed.unapproved,
......@@ -115,16 +115,16 @@ describe('Report issues', () => {
});
});
it('renders priority', () => {
it('renders severity', () => {
expect(
vm.$el.querySelector('.report-block-list li').textContent.trim(),
).toContain(dockerReportParsed.unapproved[0].priority);
).toContain(dockerReportParsed.unapproved[0].severity);
});
it('renders CVE name', () => {
expect(
vm.$el.querySelector('.report-block-list button').textContent.trim(),
).toEqual(dockerReportParsed.unapproved[0].name);
).toEqual(dockerReportParsed.unapproved[0].title);
});
it('renders namespace', () => {
......@@ -148,9 +148,9 @@ describe('Report issues', () => {
});
});
it('renders priority and name', () => {
expect(vm.$el.textContent).toContain(parsedDast[0].name);
expect(vm.$el.textContent).toContain(parsedDast[0].priority);
it('renders severity (confidence) and title', () => {
expect(vm.$el.textContent).toContain(parsedDast[0].title);
expect(vm.$el.textContent).toContain(`${parsedDast[0].severity} (${parsedDast[0].confidence})`);
});
});
});
......@@ -116,7 +116,7 @@ describe('Report section', () => {
cve: 'CVE-2016-9999',
file: 'Gemfile.lock',
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',
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',
......@@ -127,7 +127,7 @@ describe('Report section', () => {
cve: 'CVE-2014-7829',
file: 'Gemfile.lock',
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',
solution: 'upgrade to ~> 3.2.21, ~> 4.0.11.1, ~> 4.0.12, ~> 4.1.7.1, >= 4.1.8',
tool: 'bundler_audit',
......@@ -138,7 +138,7 @@ describe('Report section', () => {
cve: 'CVE-2016-0752',
file: 'Gemfile.lock',
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',
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',
......
......@@ -8,11 +8,9 @@ describe('sast container issue body', () => {
const Component = Vue.extend(component);
const sastContainerIssue = {
name: 'CVE-2017-11671',
nameLink: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-11671',
title: 'CVE-2017-11671',
namespace: 'debian:8',
path: 'debian:8',
priority: 'Low',
severity: 'Low',
vulnerability: 'CVE-2017-11671',
};
......@@ -21,26 +19,26 @@ describe('sast container issue body', () => {
vm.$destroy();
});
describe('with priority', () => {
it('renders priority key', () => {
describe('with severity', () => {
it('renders severity key', () => {
vm = mountComponent(Component, {
issue: sastContainerIssue,
});
expect(vm.$el.textContent.trim()).toContain(sastContainerIssue.priority);
expect(vm.$el.textContent.trim()).toContain(sastContainerIssue.severity);
});
});
describe('without priority', () => {
it('does not rendere priority key', () => {
describe('without severity', () => {
it('does not render severity key', () => {
const issueCopy = Object.assign({}, sastContainerIssue);
delete issueCopy.priority;
delete issueCopy.severity;
vm = mountComponent(Component, {
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', () => {
issue: sastContainerIssue,
});
expect(vm.$el.querySelector('button').textContent.trim()).toEqual(sastContainerIssue.name);
expect(vm.$el.querySelector('button').textContent.trim()).toEqual(sastContainerIssue.title);
});
describe('path', () => {
......
......@@ -11,7 +11,7 @@ describe('sast issue body', () => {
cve: 'CVE-2016-9999',
file: 'Gemfile.lock',
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',
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',
......@@ -19,27 +19,57 @@ describe('sast issue body', () => {
url:
'https://groups.google.com/forum/#!topic/rubyonrails-security/335P1DcLG00',
urlPath: '/Gemfile.lock',
priority: 'Low',
severity: 'Medium',
confidence: 'Low',
};
afterEach(() => {
vm.$destroy();
});
describe('with priority', () => {
it('renders priority key', () => {
describe('with severity and confidence (new json format)', () => {
it('renders severity and confidence', () => {
vm = mountComponent(Component, {
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', () => {
it('does not rendere priority key', () => {
it('does not render priority key', () => {
const issueCopy = Object.assign({}, sastIssue);
delete issueCopy.priority;
delete issueCopy.severity;
delete issueCopy.confidence;
vm = mountComponent(Component, {
issue: issueCopy,
......@@ -51,20 +81,20 @@ describe('sast issue body', () => {
});
});
describe('name', () => {
it('renders name', () => {
describe('title', () => {
it('renders title', () => {
vm = mountComponent(Component, {
issue: sastIssue,
});
expect(vm.$el.textContent.trim()).toContain(
sastIssue.name,
sastIssue.title,
);
});
});
describe('path', () => {
it('renders name', () => {
it('renders path', () => {
vm = mountComponent(Component, {
issue: sastIssue,
});
......
......@@ -29,7 +29,7 @@ export const baseIssues = [
export const sastParsedIssues = [
{
name: 'Arbitrary file existence disclosure in Action Pack',
title: 'Arbitrary file existence disclosure in Action Pack',
path: 'Gemfile.lock',
line: 12,
priority: 'High',
......@@ -40,50 +40,116 @@ export const sastParsedIssues = [
export const sastIssues = [
{
tool: 'bundler_audit',
category: 'sast',
message: 'Arbitrary file existence disclosure in Action Pack',
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/rMTQy4oRCGk',
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',
location: {
file: 'Gemfile.lock',
},
links: [{
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/rMTQy4oRCGk',
}],
identifiers: [{
type: 'CVE',
name: 'CVE-2014-7829',
value: 'CVE-2014-7829',
link: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-7829',
}],
},
{
tool: 'bundler_audit',
category: 'sast',
message: 'Possible Information Leak Vulnerability in Action View',
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/335P1DcLG00',
cve: 'CVE-2016-0752',
file: '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',
location: {
file: 'Gemfile.lock',
},
links: [{
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/335P1DcLG00',
}],
identifiers: [{
type: 'CVE',
name: 'CVE-2016-0752',
value: 'CVE-2016-0752',
link: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-0752',
}],
},
{
tool: 'bundler_audit',
category: 'sast',
message: 'Possible Object Leak and Denial of Service attack in Action Pack',
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/9oLY_FCzvoc',
cve: 'CVE-2016-0751',
file: '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',
location: {
file: 'Gemfile.lock',
},
links: [{
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/9oLY_FCzvoc',
}],
identifiers: [{
type: 'CVE',
name: 'CVE-2016-0751',
value: 'CVE-2016-0751',
link: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-0751',
}],
},
];
export const oldSastIssues = [
{
tool: 'bundler_audit',
message: 'Arbitrary file existence disclosure in Action Pack',
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/rMTQy4oRCGk',
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',
},
];
export const sastIssuesBase = [
{
tool: 'bundler_audit',
category: 'sast',
message: 'Test Information Leak Vulnerability in Action View',
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/335P1DcLG00',
cve: 'CVE-2016-9999',
file: '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',
location: {
file: 'Gemfile.lock',
},
links: [{
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/335P1DcLG00',
}],
identifiers: [{
type: 'CVE',
name: 'CVE-2016-9999',
value: 'CVE-2016-9999',
link: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-9999',
}],
},
{
tool: 'bundler_audit',
category: 'sast',
message: 'Possible Information Leak Vulnerability in Action View',
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/335P1DcLG00',
cve: 'CVE-2016-0752',
file: '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',
location: {
file: 'Gemfile.lock',
},
links: [{
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/335P1DcLG00',
}],
identifiers: [{
type: 'CVE',
name: 'CVE-2016-0752',
value: 'CVE-2016-0752',
link: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-0752',
}],
},
];
......@@ -91,43 +157,73 @@ export const parsedSastIssuesStore = [
{
tool: 'bundler_audit',
message: 'Arbitrary file existence disclosure in Action Pack',
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/rMTQy4oRCGk',
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',
name: 'Arbitrary file existence disclosure in Action Pack',
title: 'Arbitrary file existence disclosure in Action Pack',
path: 'Gemfile.lock',
urlPath: 'path/Gemfile.lock',
category: 'sast',
project_fingerprint: 'f55331d66fd4f3bfb4237d48e9c9fa8704bd33c6',
location: {
file: 'Gemfile.lock',
},
links: [{
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/rMTQy4oRCGk',
}],
identifiers: [{
type: 'CVE',
name: 'CVE-2014-7829',
value: 'CVE-2014-7829',
link: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-7829',
}],
},
{
tool: 'bundler_audit',
message: 'Possible Information Leak Vulnerability in Action View',
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/335P1DcLG00',
cve: 'CVE-2016-0752',
file: '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',
name: 'Possible Information Leak Vulnerability in Action View',
title: 'Possible Information Leak Vulnerability in Action View',
path: 'Gemfile.lock',
urlPath: 'path/Gemfile.lock',
category: 'sast',
project_fingerprint: 'a6b61a2eba59071178d5899b26dd699fb880de1e',
location: {
file: 'Gemfile.lock',
},
links: [{
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/335P1DcLG00',
}],
identifiers: [{
type: 'CVE',
name: 'CVE-2016-0752',
value: 'CVE-2016-0752',
link: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-0752',
}],
},
{
tool: 'bundler_audit',
message: 'Possible Object Leak and Denial of Service attack in Action Pack',
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/9oLY_FCzvoc',
cve: 'CVE-2016-0751',
file: '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',
name: 'Possible Object Leak and Denial of Service attack in Action Pack',
title: 'Possible Object Leak and Denial of Service attack in Action Pack',
path: 'Gemfile.lock',
urlPath: 'path/Gemfile.lock',
category: 'sast',
project_fingerprint: '830f85e5fb011408bab365eb809cd97a45b0aa17',
location: {
file: 'Gemfile.lock',
},
links: [{
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/9oLY_FCzvoc',
}],
identifiers: [{
type: 'CVE',
name: 'CVE-2016-0751',
value: 'CVE-2016-0751',
link: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-0751',
}],
},
];
......@@ -135,46 +231,76 @@ export const parsedSastIssuesHead = [
{
tool: 'bundler_audit',
message: 'Arbitrary file existence disclosure in Action Pack',
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/rMTQy4oRCGk',
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',
name: 'Arbitrary file existence disclosure in Action Pack',
title: 'Arbitrary file existence disclosure in Action Pack',
path: 'Gemfile.lock',
urlPath: 'path/Gemfile.lock',
category: 'sast',
project_fingerprint: 'f55331d66fd4f3bfb4237d48e9c9fa8704bd33c6',
location: {
file: 'Gemfile.lock',
},
links: [{
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/rMTQy4oRCGk',
}],
identifiers: [{
type: 'CVE',
name: 'CVE-2014-7829',
value: 'CVE-2014-7829',
link: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-7829',
}],
},
{
tool: 'bundler_audit',
message: 'Possible Object Leak and Denial of Service attack in Action Pack',
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/9oLY_FCzvoc',
cve: 'CVE-2016-0751',
file: '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',
name: 'Possible Object Leak and Denial of Service attack in Action Pack',
title: 'Possible Object Leak and Denial of Service attack in Action Pack',
path: 'Gemfile.lock',
urlPath: 'path/Gemfile.lock',
category: 'sast',
project_fingerprint: '830f85e5fb011408bab365eb809cd97a45b0aa17',
location: {
file: 'Gemfile.lock',
},
links: [{
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/9oLY_FCzvoc',
}],
identifiers: [{
type: 'CVE',
name: 'CVE-2016-0751',
value: 'CVE-2016-0751',
link: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-0751',
}],
},
];
export const parsedSastBaseStore = [
{
name: 'Test Information Leak Vulnerability in Action View',
title: 'Test Information Leak Vulnerability in Action View',
tool: 'bundler_audit',
message: 'Test Information Leak Vulnerability in Action View',
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/335P1DcLG00',
cve: 'CVE-2016-9999',
file: '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',
path: 'Gemfile.lock',
urlPath: 'path/Gemfile.lock',
category: 'sast',
project_fingerprint: '3f5608c99f0c7442ba59bc6c0c1864d0000f8e1a',
location: {
file: 'Gemfile.lock',
},
links: [{
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/335P1DcLG00',
}],
identifiers: [{
type: 'CVE',
name: 'CVE-2016-9999',
value: 'CVE-2016-9999',
link: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-9999',
}],
},
];
......@@ -236,11 +362,18 @@ export const parsedDependencyScanningIssuesStore = [
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',
name: 'Arbitrary file existence disclosure in Action Pack',
title: 'Arbitrary file existence disclosure in Action Pack',
path: 'Gemfile.lock',
urlPath: 'path/Gemfile.lock',
category: 'dependency_scanning',
project_fingerprint: 'f55331d66fd4f3bfb4237d48e9c9fa8704bd33c6',
location: {
file: 'Gemfile.lock',
start_line: undefined,
},
links: [{
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/rMTQy4oRCGk',
}],
},
{
tool: 'bundler_audit',
......@@ -250,11 +383,18 @@ export const parsedDependencyScanningIssuesStore = [
file: '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',
name: 'Possible Information Leak Vulnerability in Action View',
title: 'Possible Information Leak Vulnerability in Action View',
path: 'Gemfile.lock',
urlPath: 'path/Gemfile.lock',
category: 'dependency_scanning',
project_fingerprint: 'a6b61a2eba59071178d5899b26dd699fb880de1e',
location: {
file: 'Gemfile.lock',
start_line: undefined,
},
links: [{
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/335P1DcLG00',
}],
},
{
tool: 'bundler_audit',
......@@ -264,11 +404,18 @@ export const parsedDependencyScanningIssuesStore = [
file: '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',
name: 'Possible Object Leak and Denial of Service attack in Action Pack',
title: 'Possible Object Leak and Denial of Service attack in Action Pack',
path: 'Gemfile.lock',
urlPath: 'path/Gemfile.lock',
category: 'dependency_scanning',
project_fingerprint: '830f85e5fb011408bab365eb809cd97a45b0aa17',
location: {
file: 'Gemfile.lock',
start_line: undefined,
},
links: [{
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/9oLY_FCzvoc',
}],
},
];
......@@ -280,11 +427,18 @@ export const parsedDependencyScanningIssuesHead = [
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',
name: 'Arbitrary file existence disclosure in Action Pack',
title: 'Arbitrary file existence disclosure in Action Pack',
path: 'Gemfile.lock',
urlPath: 'path/Gemfile.lock',
category: 'dependency_scanning',
project_fingerprint: 'f55331d66fd4f3bfb4237d48e9c9fa8704bd33c6',
location: {
file: 'Gemfile.lock',
start_line: undefined,
},
links: [{
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/rMTQy4oRCGk',
}],
},
{
tool: 'bundler_audit',
......@@ -294,17 +448,24 @@ export const parsedDependencyScanningIssuesHead = [
file: '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',
name: 'Possible Object Leak and Denial of Service attack in Action Pack',
title: 'Possible Object Leak and Denial of Service attack in Action Pack',
path: 'Gemfile.lock',
urlPath: 'path/Gemfile.lock',
category: 'dependency_scanning',
project_fingerprint: '830f85e5fb011408bab365eb809cd97a45b0aa17',
location: {
file: 'Gemfile.lock',
start_line: undefined,
},
links: [{
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/9oLY_FCzvoc',
}],
},
];
export const parsedDependencyScanningBaseStore = [
{
name: 'Test Information Leak Vulnerability in Action View',
title: 'Test Information Leak Vulnerability in Action View',
tool: 'bundler_audit',
message: 'Test Information Leak Vulnerability in Action View',
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/335P1DcLG00',
......@@ -316,12 +477,19 @@ export const parsedDependencyScanningBaseStore = [
urlPath: 'path/Gemfile.lock',
category: 'dependency_scanning',
project_fingerprint: '3f5608c99f0c7442ba59bc6c0c1864d0000f8e1a',
location: {
file: 'Gemfile.lock',
start_line: undefined,
},
links: [{
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/335P1DcLG00',
}],
},
];
export const allIssuesParsed = [
{
name: 'Possible Information Leak Vulnerability in Action View',
title: 'Possible Information Leak Vulnerability in Action View',
tool: 'bundler_audit',
message: 'Possible Information Leak Vulnerability in Action View',
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/335P1DcLG00',
......@@ -381,12 +549,17 @@ export const dockerNewIssues = [
vulnerability: 'CVE-2017-16232',
namespace: 'debian:8',
severity: 'Negligible',
name: 'CVE-2017-16232',
priority: 'Negligible',
title: 'CVE-2017-16232',
path: 'debian:8',
nameLink: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-16232',
identifiers: [{
type: 'CVE',
name: 'CVE-2017-16232',
value: 'CVE-2017-16232',
url: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-16232',
}],
category: 'container_scanning',
project_fingerprint: '4e010f6d292364a42c6bb05dbd2cc788c2e5e408',
description: 'debian:8 is affected by CVE-2017-16232.',
},
];
......@@ -395,23 +568,33 @@ export const dockerOnlyHeadParsed = [
vulnerability: 'CVE-2017-12944',
namespace: 'debian:8',
severity: 'Medium',
name: 'CVE-2017-12944',
priority: 'Medium',
title: 'CVE-2017-12944',
path: 'debian:8',
nameLink: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-12944',
identifiers: [{
type: 'CVE',
name: 'CVE-2017-12944',
value: 'CVE-2017-12944',
url: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-12944',
}],
category: 'container_scanning',
project_fingerprint: '0693a82ef93c5e9d98c23a35ddcd8ed2cbd047d9',
description: 'debian:8 is affected by CVE-2017-12944.',
},
{
vulnerability: 'CVE-2017-16232',
namespace: 'debian:8',
severity: 'Negligible',
name: 'CVE-2017-16232',
priority: 'Negligible',
title: 'CVE-2017-16232',
path: 'debian:8',
nameLink: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-16232',
identifiers: [{
type: 'CVE',
name: 'CVE-2017-16232',
value: 'CVE-2017-16232',
url: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-16232',
}],
category: 'container_scanning',
project_fingerprint: '4e010f6d292364a42c6bb05dbd2cc788c2e5e408',
description: 'debian:8 is affected by CVE-2017-16232.',
},
];
......@@ -421,19 +604,27 @@ export const dockerReportParsed = {
vulnerability: 'CVE-2017-12944',
namespace: 'debian:8',
severity: 'Medium',
name: 'CVE-2017-12944',
priority: 'Medium',
title: 'CVE-2017-12944',
path: 'debian:8',
nameLink: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-12944',
identifiers: [{
type: 'CVE',
name: 'CVE-2017-12944',
value: 'CVE-2017-12944',
url: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-12944',
}],
},
{
vulnerability: 'CVE-2017-16232',
namespace: 'debian:8',
severity: 'Negligible',
name: 'CVE-2017-16232',
priority: 'Negligible',
title: 'CVE-2017-16232',
path: 'debian:8',
nameLink: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-16232',
identifiers: [{
type: 'CVE',
name: 'CVE-2017-16232',
value: 'CVE-2017-16232',
url: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-16232',
}],
},
],
approved: [
......@@ -441,10 +632,14 @@ export const dockerReportParsed = {
vulnerability: 'CVE-2014-8130',
namespace: 'debian:8',
severity: 'Negligible',
name: 'CVE-2014-8130',
priority: 'Negligible',
title: 'CVE-2014-8130',
path: 'debian:8',
nameLink: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-8130',
identifiers: [{
type: 'CVE',
name: 'CVE-2017-8130',
value: 'CVE-2017-8130',
url: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-8130',
}],
},
],
vulnerabilities: [
......@@ -452,28 +647,40 @@ export const dockerReportParsed = {
vulnerability: 'CVE-2017-12944',
namespace: 'debian:8',
severity: 'Medium',
name: 'CVE-2017-12944',
priority: 'Medium',
title: 'CVE-2017-12944',
path: 'debian:8',
nameLink: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-12944',
identifiers: [{
type: 'CVE',
name: 'CVE-2017-12944',
value: 'CVE-2017-12944',
url: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-v',
}],
},
{
vulnerability: 'CVE-2017-16232',
namespace: 'debian:8',
severity: 'Negligible',
name: 'CVE-2017-16232',
priority: 'Negligible',
title: 'CVE-2017-16232',
path: 'debian:8',
nameLink: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-16232',
identifiers: [{
type: 'CVE',
name: 'CVE-2017-16232',
value: 'CVE-2017-16232',
url: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-16232',
}],
},
{
vulnerability: 'CVE-2014-8130',
namespace: 'debian:8',
severity: 'Negligible',
name: 'CVE-2014-8130',
priority: 'Negligible',
title: 'CVE-2014-8130',
path: 'debian:8',
nameLink: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-8130',
identifiers: [{
type: 'CVE',
name: 'CVE-2017-8130',
value: 'CVE-2017-8130',
url: 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-8130',
}],
},
],
};
......@@ -485,7 +692,7 @@ export const dast = {
name: 'Absence of Anti-CSRF Tokens',
riskcode: '1',
riskdesc: 'Low (Medium)',
cweid: '03',
cweid: '3',
desc: '<p>No Anti-CSRF tokens were found in a HTML submission form.</p>',
pluginid: '123',
solution: '<p>Update to latest</p>',
......@@ -508,7 +715,7 @@ export const dast = {
alert: 'X-Content-Type-Options Header Missing',
name: 'X-Content-Type-Options Header Missing',
riskdesc: 'Low (Medium)',
cweid: '04',
cweid: '4',
desc:
'<p>The Anti-MIME-Sniffing header X-Content-Type-Options was not set to "nosniff".</p>',
pluginid: '3456',
......@@ -560,16 +767,20 @@ export const parsedDast = [
category: 'dast',
project_fingerprint: '40bd001563085fc35165329ea1ff5c5ecbdbbeef',
name: 'Absence of Anti-CSRF Tokens',
title: 'Absence of Anti-CSRF Tokens',
riskcode: '1',
riskdesc: 'Low (Medium)',
priority: 'Low (Medium)',
identifier: 'CWE-03',
severity: 'Low',
confidence: 'Medium',
cweid: '03',
cweid: '3',
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',
identifiers: [{
type: 'CWE',
name: 'CWE-3',
value: '3',
url: 'https://cwe.mitre.org/data/definitions/3.html',
}],
instances: [
{
uri: 'http://192.168.32.236:3001/explore?sort=latest_activity_desc',
......@@ -590,16 +801,19 @@ export const parsedDast = [
project_fingerprint: 'ae8fe380dd9aa5a7a956d9085fe7cf6b87d0d028',
alert: 'X-Content-Type-Options Header Missing',
name: 'X-Content-Type-Options Header Missing',
title: 'X-Content-Type-Options Header Missing',
riskdesc: 'Low (Medium)',
priority: 'Low (Medium)',
identifier: 'CWE-04',
identifiers: [{
type: 'CWE',
name: 'CWE-4',
value: '4',
url: 'https://cwe.mitre.org/data/definitions/4.html',
}],
severity: 'Low',
confidence: 'Medium',
cweid: '04',
cweid: '4',
desc: '<p>The Anti-MIME-Sniffing header X-Content-Type-Options was not set to "nosniff".</p>',
pluginid: '3456',
parsedDescription:
' The Anti-MIME-Sniffing header X-Content-Type-Options was not set to "nosniff". ',
instances: [
{
uri: 'http://192.168.32.236:3001/assets/webpack/main.bundle.js',
......@@ -618,16 +832,19 @@ export const parsedDastNewIssues = [
project_fingerprint: 'ae8fe380dd9aa5a7a956d9085fe7cf6b87d0d028',
alert: 'X-Content-Type-Options Header Missing',
name: 'X-Content-Type-Options Header Missing',
title: 'X-Content-Type-Options Header Missing',
riskdesc: 'Low (Medium)',
priority: 'Low (Medium)',
identifier: 'CWE-04',
identifiers: [{
type: 'CWE',
name: 'CWE-4',
value: '4',
url: 'https://cwe.mitre.org/data/definitions/4.html',
}],
severity: 'Low',
confidence: 'Medium',
cweid: '04',
cweid: '4',
desc: '<p>The Anti-MIME-Sniffing header X-Content-Type-Options was not set to "nosniff".</p>',
pluginid: '3456',
parsedDescription:
' The Anti-MIME-Sniffing header X-Content-Type-Options was not set to "nosniff". ',
instances: [
{
uri: 'http://192.168.32.236:3001/assets/webpack/main.bundle.js',
......
......@@ -316,21 +316,50 @@ describe('security reports mutations', () => {
const issue = {
tool: 'bundler_audit',
message: 'Arbitrary file existence disclosure in Action Pack',
url: 'https://groups.google.com/forum/#!topic/rubyonrails-security/rMTQy4oRCGk',
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',
name: 'Arbitrary file existence disclosure in Action Pack',
title: 'Arbitrary file existence disclosure in Action Pack',
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,
};
mutations[types.SET_ISSUE_MODAL_DATA](stateCopy, issue);
expect(stateCopy.modal.title).toEqual(issue.name);
expect(stateCopy.modal.data.file.value).toEqual(issue.file);
expect(stateCopy.modal.title).toEqual(issue.title);
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.links.value).toEqual(issue.links);
expect(stateCopy.modal.data.instances.value).toEqual(issue.instances);
expect(stateCopy.modal.vulnerability).toEqual(issue);
});
});
......
......@@ -11,6 +11,7 @@ import {
statusIcon,
} from 'ee/vue_shared/security_reports/store/utils';
import {
oldSastIssues,
sastIssues,
sastFeedbacks,
dependencyScanningIssues,
......@@ -52,10 +53,17 @@ describe('security reports utils', () => {
});
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];
expect(parsed.name).toEqual(sastIssues[0].message);
expect(parsed.path).toEqual(sastIssues[0].file);
expect(parsed.title).toEqual(sastIssues[0].message);
expect(parsed.path).toEqual(sastIssues[0].location.file);
expect(parsed.project_fingerprint).toEqual(sha1(sastIssues[0].cve));
});
......@@ -75,7 +83,7 @@ describe('security reports utils', () => {
describe('parseDependencyScanningIssues', () => {
it('should parse the received issues', () => {
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.project_fingerprint).toEqual(sha1(dependencyScanningIssues[0].cve));
});
......@@ -107,14 +115,14 @@ describe('security reports utils', () => {
const parsed = parseSastContainer(dockerReport.vulnerabilities)[0];
const issue = dockerReport.vulnerabilities[0];
expect(parsed.name).toEqual(dockerReport.vulnerabilities[0].vulnerability);
expect(parsed.priority).toEqual(dockerReport.vulnerabilities[0].severity);
expect(parsed.path).toEqual(dockerReport.vulnerabilities[0].namespace);
expect(parsed.nameLink).toEqual(
`https://cve.mitre.org/cgi-bin/cvename.cgi?name=${
dockerReport.vulnerabilities[0].vulnerability
}`,
);
expect(parsed.title).toEqual(issue.vulnerability);
expect(parsed.path).toEqual(issue.namespace);
expect(parsed.identifiers).toEqual([{
type: 'CVE',
name: issue.vulnerability,
value: issue.vulnerability,
url: `https://cve.mitre.org/cgi-bin/cvename.cgi?name=${issue.vulnerability}`,
}]);
expect(parsed.project_fingerprint).toEqual(
sha1(`${issue.namespace}:${issue.vulnerability}:${issue.featurename}:${issue.featureversion}`));
});
......
......@@ -3,27 +3,11 @@ require 'spec_helper'
describe Gitlab::FileFinder do
describe '#find' do
let(:project) { create(:project, :public, :repository) }
let(:finder) { described_class.new(project, project.default_branch) }
it 'finds by name' do
results = finder.find('files')
filename, blob = results.find { |_, blob| blob.filename == 'files/images/wm.svg' }
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
it_behaves_like 'file finder' do
subject { described_class.new(project, project.default_branch) }
let(:expected_file_by_name) { 'files/images/wm.svg' }
let(:expected_file_by_content) { 'CHANGELOG' }
end
end
end
......@@ -22,47 +22,57 @@ describe Gitlab::ProjectSearchResults do
it { expect(results.query).to eq('hello world') }
end
describe 'blob search' do
let(:project) { create(:project, :public, :repository) }
subject(:results) { described_class.new(user, project, 'files').objects('blobs') }
context 'when repository is disabled' do
let(:project) { create(:project, :public, :repository, :repository_disabled) }
shared_examples 'general blob search' do |entity_type, blob_kind|
let(:query) { 'files' }
subject(:results) { described_class.new(user, project, query).objects(blob_type) }
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)
is_expected.to be_empty
end
it 'hides blobs from non-members' do
it "hides #{blob_kind} from non-members" do
is_expected.to be_empty
end
end
context 'when repository is internal' do
let(:project) { create(:project, :public, :repository, :repository_private) }
context "when #{entity_type} is internal" do
let(:project) { private_project }
it 'finds blobs for members' do
it "finds #{blob_kind} for members" do
project.add_reporter(user)
is_expected.not_to be_empty
end
it 'hides blobs from non-members' do
it "hides #{blob_kind} from non-members" do
is_expected.to be_empty
end
end
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
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
describe 'parsing results' do
......@@ -189,40 +199,18 @@ describe Gitlab::ProjectSearchResults do
describe 'wiki search' do
let(:project) { create(:project, :public, :wiki_repo) }
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
end
it 'hides wiki blobs from non-members' do
is_expected.to be_empty
end
before do
wiki.create_page('Files/Title', 'Content')
wiki.create_page('CHANGELOG', 'Files example')
end
it 'finds by content' do
expect(results).to include("master:Title.md\x001\x00Content\n")
it_behaves_like 'general blob search', 'wiki', 'wiki blobs' do
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
......
......@@ -49,14 +49,4 @@ describe Gitlab::SlashCommands::Presenters::IssueShow do
expect(attachment[:text]).to start_with("**Open**")
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
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