summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-01-28 13:31:50 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-01-28 13:31:50 +0000
commit1e8ef329735f06d3b3cfe1966b79fe55eff21f30 (patch)
tree0c03cc2618813b9eafafd2289cfbac16fd2c2766
parentefed756aa7fbe80f589edb613eda69f6c7a9a47a (diff)
downloadgitlab-ce-1e8ef329735f06d3b3cfe1966b79fe55eff21f30.tar.gz
Add latest changes from gitlab-org/security/gitlab@12-7-stable-ee
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock6
-rw-r--r--app/assets/javascripts/groups_select.js7
-rw-r--r--app/controllers/application_controller.rb8
-rw-r--r--app/graphql/types/grafana_integration_type.rb11
-rw-r--r--app/helpers/projects_helper.rb4
-rw-r--r--app/models/grafana_integration.rb28
-rw-r--r--app/models/note.rb3
-rw-r--r--app/models/project.rb4
-rw-r--r--app/services/projects/import_export/export_service.rb6
-rw-r--r--app/views/projects/settings/operations/_grafana_integration.html.haml2
-rw-r--r--changelogs/unreleased/security-13-update-ruby-zip-pages-master.yml5
-rw-r--r--changelogs/unreleased/security-dependency-proxy-path-traversal.yml5
-rw-r--r--changelogs/unreleased/security-dos-via-asciidoc-includes.yml5
-rw-r--r--changelogs/unreleased/security-fix-grafana-token-leaked-in-plain-to-other-maintainers.yml5
-rw-r--r--changelogs/unreleased/security-fix-xss-on-project-templates.yml5
-rw-r--r--changelogs/unreleased/security-project_export_service_permission_check.yml5
-rw-r--r--changelogs/unreleased/security-reference-check.yml5
-rw-r--r--changelogs/unreleased/security-remove-caching-from-api-project-raw-endpoint.yml5
-rw-r--r--changelogs/unreleased/security-update-excon-cve-2019-16779.yml5
-rw-r--r--changelogs/unreleased/security-workhorse-package-bypass.yml5
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql4
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json6
-rw-r--r--doc/api/graphql/reference/index.md2
-rw-r--r--doc/user/asciidoc.md5
-rw-r--r--lib/api/files.rb1
-rw-r--r--lib/api/helpers.rb10
-rw-r--r--lib/api/helpers/headers_helpers.rb8
-rw-r--r--lib/banzai/reference_parser/base_parser.rb8
-rw-r--r--lib/gitlab/asciidoc.rb2
-rw-r--r--lib/gitlab/asciidoc/include_processor.rb11
-rw-r--r--lib/gitlab/no_cache_headers.rb15
-rw-r--r--lib/gitlab/reference_extractor.rb11
-rw-r--r--package.json1
-rw-r--r--qa/qa/specs/features/api/3_create/repository/files_spec.rb43
-rw-r--r--spec/helpers/projects_helper_spec.rb4
-rw-r--r--spec/lib/banzai/reference_parser/mentioned_group_parser_spec.rb6
-rw-r--r--spec/lib/banzai/reference_parser/mentioned_project_parser_spec.rb6
-rw-r--r--spec/lib/banzai/reference_parser/mentioned_user_parser_spec.rb6
-rw-r--r--spec/lib/banzai/reference_parser/project_parser_spec.rb8
-rw-r--r--spec/lib/gitlab/asciidoc/include_processor_spec.rb44
-rw-r--r--spec/lib/gitlab/asciidoc_spec.rb18
-rw-r--r--spec/lib/gitlab/no_cache_headers_spec.rb17
-rw-r--r--spec/lib/gitlab/reference_extractor_spec.rb32
-rw-r--r--spec/models/grafana_integration_spec.rb22
-rw-r--r--spec/models/note_spec.rb26
-rw-r--r--spec/requests/api/files_spec.rb12
-rw-r--r--spec/requests/api/graphql/project/grafana_integration_spec.rb2
-rw-r--r--spec/requests/api/runner_spec.rb14
-rw-r--r--spec/services/projects/import_export/export_service_spec.rb17
-rw-r--r--spec/services/projects/operations/update_service_spec.rb4
-rw-r--r--spec/services/projects/update_pages_service_spec.rb28
-rw-r--r--spec/support/helpers/reference_parser_helpers.rb5
-rw-r--r--spec/support/shared_contexts/pages_zip_with_spoofed_size_shared_context.rb41
-rw-r--r--[-rwxr-xr-x]vendor/gitignore/C++.gitignore0
-rw-r--r--[-rwxr-xr-x]vendor/gitignore/Java.gitignore0
56 files changed, 512 insertions, 58 deletions
diff --git a/Gemfile b/Gemfile
index 951ae73a318..0c3974ceeba 100644
--- a/Gemfile
+++ b/Gemfile
@@ -65,7 +65,7 @@ gem 'u2f', '~> 0.2.1'
# GitLab Pages
gem 'validates_hostname', '~> 1.0.6'
-gem 'rubyzip', '~> 1.3.0', require: 'zip'
+gem 'rubyzip', '~> 2.0.0', require: 'zip'
# GitLab Pages letsencrypt support
gem 'acme-client', '~> 2.0.2'
diff --git a/Gemfile.lock b/Gemfile.lock
index 0bf630b42ef..d4dd0a37570 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -262,7 +262,7 @@ GEM
et-orbi (1.2.1)
tzinfo
eventmachine (1.2.7)
- excon (0.62.0)
+ excon (0.71.1)
execjs (2.6.0)
expression_parser (0.9.0)
extended-markdown-filter (0.6.0)
@@ -944,7 +944,7 @@ GEM
sexp_processor (~> 4.9)
rubyntlm (0.6.2)
rubypants (0.2.0)
- rubyzip (1.3.0)
+ rubyzip (2.0.0)
rugged (0.28.4.1)
safe_yaml (1.0.4)
sanitize (4.6.6)
@@ -1343,7 +1343,7 @@ DEPENDENCIES
ruby-prof (~> 1.0.0)
ruby-progressbar
ruby_parser (~> 3.8)
- rubyzip (~> 1.3.0)
+ rubyzip (~> 2.0.0)
rugged (~> 0.28)
sanitize (~> 4.6)
sassc-rails (~> 2.1.0)
diff --git a/app/assets/javascripts/groups_select.js b/app/assets/javascripts/groups_select.js
index a5e38022b8d..4daa8c60e58 100644
--- a/app/assets/javascripts/groups_select.js
+++ b/app/assets/javascripts/groups_select.js
@@ -1,6 +1,7 @@
import $ from 'jquery';
import axios from './lib/utils/axios_utils';
import Api from './api';
+import { escape } from 'lodash';
import { normalizeHeaders } from './lib/utils/common_utils';
import { __ } from '~/locale';
@@ -75,10 +76,12 @@ const groupsSelect = () => {
}
},
formatResult(object) {
- return `<div class='group-result'> <div class='group-name'>${object.full_name}</div> <div class='group-path'>${object.full_path}</div> </div>`;
+ return `<div class='group-result'> <div class='group-name'>${escape(
+ object.full_name,
+ )}</div> <div class='group-path'>${object.full_path}</div> </div>`;
},
formatSelection(object) {
- return object.full_name;
+ return escape(object.full_name);
},
dropdownCssClass: 'ajax-groups-dropdown select2-infinite',
// we do not want to escape markup since we are displaying html in results
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 60b5d9b6da8..acbc25220a0 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -5,6 +5,7 @@ require 'fogbugz'
class ApplicationController < ActionController::Base
include Gitlab::GonHelper
+ include Gitlab::NoCacheHeaders
include GitlabRoutingHelper
include PageLayoutHelper
include SafeParamsHelper
@@ -55,7 +56,6 @@ class ApplicationController < ActionController::Base
# Adds `no-store` to the DEFAULT_CACHE_CONTROL, to prevent security
# concerns due to caching private data.
DEFAULT_GITLAB_CACHE_CONTROL = "#{ActionDispatch::Http::Cache::Response::DEFAULT_CACHE_CONTROL}, no-store"
- DEFAULT_GITLAB_CONTROL_NO_CACHE = "#{DEFAULT_GITLAB_CACHE_CONTROL}, no-cache"
rescue_from Encoding::CompatibilityError do |exception|
log_exception(exception)
@@ -247,9 +247,9 @@ class ApplicationController < ActionController::Base
end
def no_cache_headers
- headers['Cache-Control'] = DEFAULT_GITLAB_CONTROL_NO_CACHE
- headers['Pragma'] = 'no-cache' # HTTP 1.0 compatibility
- headers['Expires'] = 'Fri, 01 Jan 1990 00:00:00 GMT'
+ DEFAULT_GITLAB_NO_CACHE_HEADERS.each do |k, v|
+ headers[k] = v
+ end
end
def default_headers
diff --git a/app/graphql/types/grafana_integration_type.rb b/app/graphql/types/grafana_integration_type.rb
index e6c865fea53..f234008ee0d 100644
--- a/app/graphql/types/grafana_integration_type.rb
+++ b/app/graphql/types/grafana_integration_type.rb
@@ -10,14 +10,19 @@ module Types
description: 'Internal ID of the Grafana integration'
field :grafana_url, GraphQL::STRING_TYPE, null: false,
description: 'Url for the Grafana host for the Grafana integration'
- field :token, GraphQL::STRING_TYPE, null: false,
- description: 'API token for the Grafana integration'
field :enabled, GraphQL::BOOLEAN_TYPE, null: false,
description: 'Indicates whether Grafana integration is enabled'
-
field :created_at, Types::TimeType, null: false,
description: 'Timestamp of the issue\'s creation'
field :updated_at, Types::TimeType, null: false,
description: 'Timestamp of the issue\'s last activity'
+
+ field :token, GraphQL::STRING_TYPE, null: false,
+ deprecation_reason: 'Plain text token has been masked for security reasons',
+ description: 'API token for the Grafana integration. Field is permanently masked.'
+
+ def token
+ object.masked_token
+ end
end
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 63f1f24b611..e2173140a08 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -368,8 +368,8 @@ module ProjectsHelper
@project.grafana_integration&.grafana_url
end
- def grafana_integration_token
- @project.grafana_integration&.token
+ def grafana_integration_masked_token
+ @project.grafana_integration&.masked_token
end
def grafana_integration_enabled?
diff --git a/app/models/grafana_integration.rb b/app/models/grafana_integration.rb
index ed4c279965a..00213732fee 100644
--- a/app/models/grafana_integration.rb
+++ b/app/models/grafana_integration.rb
@@ -8,11 +8,13 @@ class GrafanaIntegration < ApplicationRecord
algorithm: 'aes-256-gcm',
key: Settings.attr_encrypted_db_key_base_32
+ before_validation :check_token_changes
+
validates :grafana_url,
length: { maximum: 1024 },
addressable_url: { enforce_sanitization: true, ascii_only: true }
- validates :token, :project, presence: true
+ validates :encrypted_token, :project, presence: true
validates :enabled, inclusion: { in: [true, false] }
@@ -23,4 +25,28 @@ class GrafanaIntegration < ApplicationRecord
@client ||= ::Grafana::Client.new(api_url: grafana_url.chomp('/'), token: token)
end
+
+ def masked_token
+ mask(encrypted_token)
+ end
+
+ def masked_token_was
+ mask(encrypted_token_was)
+ end
+
+ private
+
+ def token
+ decrypt(:token, encrypted_token)
+ end
+
+ def check_token_changes
+ return unless [encrypted_token_was, masked_token_was].include?(token)
+
+ clear_attribute_changes [:token, :encrypted_token, :encrypted_token_iv]
+ end
+
+ def mask(token)
+ token&.squish&.gsub(/./, '*')
+ end
end
diff --git a/app/models/note.rb b/app/models/note.rb
index 7731b477ad0..11237a5049d 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -545,7 +545,8 @@ class Note < ApplicationRecord
# if they are not equal, then there are private/confidential references as well
user_visible_reference_count > 0 && user_visible_reference_count == total_reference_count
else
- referenced_mentionables(user).any?
+ refs = all_references(user)
+ refs.all.any? && refs.stateful_not_visible_counter == 0
end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index c48360290c7..b2f20731c65 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -2341,6 +2341,10 @@ class Project < ApplicationRecord
end
end
+ def template_source?
+ false
+ end
+
private
def closest_namespace_setting(name)
diff --git a/app/services/projects/import_export/export_service.rb b/app/services/projects/import_export/export_service.rb
index 8344397f67d..38859c1efa4 100644
--- a/app/services/projects/import_export/export_service.rb
+++ b/app/services/projects/import_export/export_service.rb
@@ -4,6 +4,12 @@ module Projects
module ImportExport
class ExportService < BaseService
def execute(after_export_strategy = nil, options = {})
+ unless project.template_source? || can?(current_user, :admin_project, project)
+ raise ::Gitlab::ImportExport::Error.new(
+ "User with ID: %s does not have permission to Project %s with ID: %s." %
+ [current_user.id, project.name, project.id])
+ end
+
@shared = project.import_export_shared
save_all!
diff --git a/app/views/projects/settings/operations/_grafana_integration.html.haml b/app/views/projects/settings/operations/_grafana_integration.html.haml
index cd5b5abd9ce..69e42a6c4fb 100644
--- a/app/views/projects/settings/operations/_grafana_integration.html.haml
+++ b/app/views/projects/settings/operations/_grafana_integration.html.haml
@@ -1,2 +1,2 @@
.js-grafana-integration{ data: { operations_settings_endpoint: project_settings_operations_path(@project),
- grafana_integration: { url: grafana_integration_url, token: grafana_integration_token, enabled: grafana_integration_enabled?.to_s } } }
+ grafana_integration: { url: grafana_integration_url, token: grafana_integration_masked_token, enabled: grafana_integration_enabled?.to_s } } }
diff --git a/changelogs/unreleased/security-13-update-ruby-zip-pages-master.yml b/changelogs/unreleased/security-13-update-ruby-zip-pages-master.yml
new file mode 100644
index 00000000000..976ce6f90b3
--- /dev/null
+++ b/changelogs/unreleased/security-13-update-ruby-zip-pages-master.yml
@@ -0,0 +1,5 @@
+---
+title: Bump rubyzip to 2.0.0
+merge_request:
+author: Utkarsh Gupta
+type: security
diff --git a/changelogs/unreleased/security-dependency-proxy-path-traversal.yml b/changelogs/unreleased/security-dependency-proxy-path-traversal.yml
new file mode 100644
index 00000000000..ca0a03e36ab
--- /dev/null
+++ b/changelogs/unreleased/security-dependency-proxy-path-traversal.yml
@@ -0,0 +1,5 @@
+---
+title: Add constraint to group dependency proxy endpoint param
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-dos-via-asciidoc-includes.yml b/changelogs/unreleased/security-dos-via-asciidoc-includes.yml
new file mode 100644
index 00000000000..8fc3bd32316
--- /dev/null
+++ b/changelogs/unreleased/security-dos-via-asciidoc-includes.yml
@@ -0,0 +1,5 @@
+---
+title: Limit number of AsciiDoc includes per document
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-fix-grafana-token-leaked-in-plain-to-other-maintainers.yml b/changelogs/unreleased/security-fix-grafana-token-leaked-in-plain-to-other-maintainers.yml
new file mode 100644
index 00000000000..0b8de350393
--- /dev/null
+++ b/changelogs/unreleased/security-fix-grafana-token-leaked-in-plain-to-other-maintainers.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent gafana integration token from being displayed as a plain text to other project maintainers, by only displaying a masked version of it. GraphQL api deprecate token field in GrafanaIntegration type.
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-fix-xss-on-project-templates.yml b/changelogs/unreleased/security-fix-xss-on-project-templates.yml
new file mode 100644
index 00000000000..2930bbaff87
--- /dev/null
+++ b/changelogs/unreleased/security-fix-xss-on-project-templates.yml
@@ -0,0 +1,5 @@
+---
+title: Fix XSS vulnerability on custom project templates form
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-project_export_service_permission_check.yml b/changelogs/unreleased/security-project_export_service_permission_check.yml
new file mode 100644
index 00000000000..a38aaabfc9b
--- /dev/null
+++ b/changelogs/unreleased/security-project_export_service_permission_check.yml
@@ -0,0 +1,5 @@
+---
+title: ImportExport::ExportService to require admin_project permission
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-reference-check.yml b/changelogs/unreleased/security-reference-check.yml
new file mode 100644
index 00000000000..f33cea66eb1
--- /dev/null
+++ b/changelogs/unreleased/security-reference-check.yml
@@ -0,0 +1,5 @@
+---
+title: Make sure that only system notes where all references are visible to user are exposed in GraphQL API.
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-remove-caching-from-api-project-raw-endpoint.yml b/changelogs/unreleased/security-remove-caching-from-api-project-raw-endpoint.yml
new file mode 100644
index 00000000000..308a618da89
--- /dev/null
+++ b/changelogs/unreleased/security-remove-caching-from-api-project-raw-endpoint.yml
@@ -0,0 +1,5 @@
+---
+title: Disable caching of repository/files/:file_path/raw API endpoint
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-update-excon-cve-2019-16779.yml b/changelogs/unreleased/security-update-excon-cve-2019-16779.yml
new file mode 100644
index 00000000000..e849dc92848
--- /dev/null
+++ b/changelogs/unreleased/security-update-excon-cve-2019-16779.yml
@@ -0,0 +1,5 @@
+---
+title: Update excon to 0.71.1 to fix CVE-2019-16779
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-workhorse-package-bypass.yml b/changelogs/unreleased/security-workhorse-package-bypass.yml
new file mode 100644
index 00000000000..bb9aa0a2bf1
--- /dev/null
+++ b/changelogs/unreleased/security-workhorse-package-bypass.yml
@@ -0,0 +1,5 @@
+---
+title: Add workhorse request verification to package upload endpoints
+merge_request:
+author:
+type: security
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index 23ffae3b097..0884f8948f0 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -2414,9 +2414,9 @@ type GrafanaIntegration {
id: ID!
"""
- API token for the Grafana integration
+ API token for the Grafana integration. Field is permanently masked.
"""
- token: String!
+ token: String! @deprecated(reason: "Plain text token has been masked for security reasons")
"""
Timestamp of the issue's last activity
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index 6239a398c7e..b1617edbfbb 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -16249,7 +16249,7 @@
},
{
"name": "token",
- "description": "API token for the Grafana integration",
+ "description": "API token for the Grafana integration. Field is permanently masked.",
"args": [
],
@@ -16262,8 +16262,8 @@
"ofType": null
}
},
- "isDeprecated": false,
- "deprecationReason": null
+ "isDeprecated": true,
+ "deprecationReason": "Plain text token has been masked for security reasons"
},
{
"name": "updatedAt",
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 72fc82444ca..5adc68209b0 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -370,10 +370,10 @@ Autogenerated return type of EpicTreeReorder
| --- | ---- | ---------- |
| `id` | ID! | Internal ID of the Grafana integration |
| `grafanaUrl` | String! | Url for the Grafana host for the Grafana integration |
-| `token` | String! | API token for the Grafana integration |
| `enabled` | Boolean! | Indicates whether Grafana integration is enabled |
| `createdAt` | Time! | Timestamp of the issue's creation |
| `updatedAt` | Time! | Timestamp of the issue's last activity |
+| `token` | String! | API token for the Grafana integration. Field is permanently masked. |
## Group
diff --git a/doc/user/asciidoc.md b/doc/user/asciidoc.md
index b4d3cb58e97..da6bf287955 100644
--- a/doc/user/asciidoc.md
+++ b/doc/user/asciidoc.md
@@ -221,6 +221,11 @@ include::basics.adoc[]
include::https://example.org/installation.adoc[]
```
+To guarantee good system performance and prevent malicious documents causing
+problems, GitLab enforces a **maximum limit** on the number of include directives
+processed in any one document. Currently a total of 32 documents can be
+included, a number that is inclusive of transitive dependencies.
+
### Blocks
```asciidoc
diff --git a/lib/api/files.rb b/lib/api/files.rb
index 0b438fb5bbc..feed22d188c 100644
--- a/lib/api/files.rb
+++ b/lib/api/files.rb
@@ -127,6 +127,7 @@ module API
get ":id/repository/files/:file_path/raw", requirements: FILE_ENDPOINT_REQUIREMENTS do
assign_file_vars!
+ no_cache_headers
set_http_headers(blob_data)
send_git_blob @repo, @blob
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index b2f5def4048..7d9a91cd360 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -258,11 +258,21 @@ module API
end
def require_gitlab_workhorse!
+ verify_workhorse_api!
+
unless env['HTTP_GITLAB_WORKHORSE'].present?
forbidden!('Request should be executed via GitLab Workhorse')
end
end
+ def verify_workhorse_api!
+ Gitlab::Workhorse.verify_api_request!(request.headers)
+ rescue => e
+ Gitlab::ErrorTracking.track_exception(e)
+
+ forbidden!
+ end
+
def require_pages_enabled!
not_found! unless user_project.pages_available?
end
diff --git a/lib/api/helpers/headers_helpers.rb b/lib/api/helpers/headers_helpers.rb
index 7553af9d156..908c57bb04e 100644
--- a/lib/api/helpers/headers_helpers.rb
+++ b/lib/api/helpers/headers_helpers.rb
@@ -3,6 +3,8 @@
module API
module Helpers
module HeadersHelpers
+ include Gitlab::NoCacheHeaders
+
def set_http_headers(header_data)
header_data.each do |key, value|
if value.is_a?(Enumerable)
@@ -12,6 +14,12 @@ module API
header "X-Gitlab-#{key.to_s.split('_').collect(&:capitalize).join('-')}", value.to_s
end
end
+
+ def no_cache_headers
+ DEFAULT_GITLAB_NO_CACHE_HEADERS.each do |k, v|
+ header k, v
+ end
+ end
end
end
end
diff --git a/lib/banzai/reference_parser/base_parser.rb b/lib/banzai/reference_parser/base_parser.rb
index 9160c0e14cf..9ecbc3ecec2 100644
--- a/lib/banzai/reference_parser/base_parser.rb
+++ b/lib/banzai/reference_parser/base_parser.rb
@@ -201,12 +201,14 @@ module Banzai
gather_references(nodes)
end
- # Gathers the references for the given HTML nodes.
+ # Gathers the references for the given HTML nodes. Returns visible
+ # references and a list of nodes which are not visible to the user
def gather_references(nodes)
nodes = nodes_user_can_reference(current_user, nodes)
- nodes = nodes_visible_to_user(current_user, nodes)
+ visible = nodes_visible_to_user(current_user, nodes)
+ not_visible = nodes - visible
- referenced_by(nodes)
+ { visible: referenced_by(visible), not_visible: not_visible }
end
# Returns a Hash containing the projects for a given list of HTML nodes.
diff --git a/lib/gitlab/asciidoc.rb b/lib/gitlab/asciidoc.rb
index da65caa6c9c..8d072422e17 100644
--- a/lib/gitlab/asciidoc.rb
+++ b/lib/gitlab/asciidoc.rb
@@ -11,6 +11,7 @@ module Gitlab
# the resulting HTML through HTML pipeline filters.
module Asciidoc
MAX_INCLUDE_DEPTH = 5
+ MAX_INCLUDES = 32
DEFAULT_ADOC_ATTRS = {
'showtitle' => true,
'sectanchors' => true,
@@ -40,6 +41,7 @@ module Gitlab
extensions: extensions }
context[:pipeline] = :ascii_doc
+ context[:max_includes] = [MAX_INCLUDES, context[:max_includes]].compact.min
plantuml_setup
diff --git a/lib/gitlab/asciidoc/include_processor.rb b/lib/gitlab/asciidoc/include_processor.rb
index 6e0b7ce60ba..53d1135a2d7 100644
--- a/lib/gitlab/asciidoc/include_processor.rb
+++ b/lib/gitlab/asciidoc/include_processor.rb
@@ -14,6 +14,8 @@ module Gitlab
@context = context
@repository = context[:repository] || context[:project].try(:repository)
+ @max_includes = context[:max_includes].to_i
+ @included = []
# Note: Asciidoctor calls #freeze on extensions, so we can't set new
# instance variables after initialization.
@@ -28,8 +30,11 @@ module Gitlab
def include_allowed?(target, reader)
doc = reader.document
- return false if doc.attributes.fetch('max-include-depth').to_i < 1
+ max_include_depth = doc.attributes.fetch('max-include-depth').to_i
+
+ return false if max_include_depth < 1
return false if target_uri?(target)
+ return false if included.size >= max_includes
true
end
@@ -62,7 +67,7 @@ module Gitlab
private
- attr_accessor :context, :repository, :cache
+ attr_reader :context, :repository, :cache, :max_includes, :included
# Gets a Blob at a path for a specific revision.
# This method will check that the Blob exists and contains readable text.
@@ -77,6 +82,8 @@ module Gitlab
raise 'Blob not found' unless blob
raise 'File is not readable' unless blob.readable_text?
+ included << filename
+
blob
end
diff --git a/lib/gitlab/no_cache_headers.rb b/lib/gitlab/no_cache_headers.rb
new file mode 100644
index 00000000000..f80ca2c1369
--- /dev/null
+++ b/lib/gitlab/no_cache_headers.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module NoCacheHeaders
+ DEFAULT_GITLAB_NO_CACHE_HEADERS = {
+ 'Cache-Control' => "#{ActionDispatch::Http::Cache::Response::DEFAULT_CACHE_CONTROL}, no-store, no-cache",
+ 'Pragma' => 'no-cache', # HTTP 1.0 compatibility
+ 'Expires' => 'Fri, 01 Jan 1990 00:00:00 GMT'
+ }.freeze
+
+ def no_cache_headers
+ raise "#no_cache_headers is not implemented for this object"
+ end
+ end
+end
diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb
index f095ac9ffd1..519eb49658a 100644
--- a/lib/gitlab/reference_extractor.rb
+++ b/lib/gitlab/reference_extractor.rb
@@ -6,11 +6,16 @@ module Gitlab
REFERABLES = %i(user issue label milestone mentioned_user mentioned_group mentioned_project
merge_request snippet commit commit_range directly_addressed_user epic).freeze
attr_accessor :project, :current_user, :author
+ # This counter is increased by a number of references filtered out by
+ # banzai reference exctractor. Note that this counter is stateful and
+ # not idempotent and is increased whenever you call `references`.
+ attr_reader :stateful_not_visible_counter
def initialize(project, current_user = nil)
@project = project
@current_user = current_user
@references = {}
+ @stateful_not_visible_counter = 0
super()
end
@@ -20,11 +25,15 @@ module Gitlab
end
def references(type)
- super(type, project, current_user)
+ refs = super(type, project, current_user)
+ @stateful_not_visible_counter += refs[:not_visible].count
+
+ refs[:visible]
end
def reset_memoized_values
@references = {}
+ @stateful_not_visible_counter = 0
super()
end
diff --git a/package.json b/package.json
index df3c28ada2a..22908519da4 100644
--- a/package.json
+++ b/package.json
@@ -94,6 +94,7 @@
"jszip": "^3.1.3",
"jszip-utils": "^0.0.2",
"katex": "^0.10.0",
+ "lodash": "^4.17.15",
"marked": "^0.3.12",
"mermaid": "^8.4.5",
"monaco-editor": "^0.18.1",
diff --git a/qa/qa/specs/features/api/3_create/repository/files_spec.rb b/qa/qa/specs/features/api/3_create/repository/files_spec.rb
index f6f020da472..dc471128dae 100644
--- a/qa/qa/specs/features/api/3_create/repository/files_spec.rb
+++ b/qa/qa/specs/features/api/3_create/repository/files_spec.rb
@@ -59,5 +59,48 @@ module QA
a_hash_including(message: '202 Accepted')
)
end
+
+ describe 'raw file access' do
+ let(:svg_file) do
+ <<-SVG
+ <?xml version="1.0" standalone="no"?>
+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+
+ <svg version="1.1" baseProfile="full" xmlns="http://www.w3.org/2000/svg">
+ <polygon id="triangle" points="0,0 0,50 50,0" fill="#009900" stroke="#004400"/>
+ <script type="text/javascript">
+ alert("surprise");
+ </script>
+ </svg>
+ SVG
+ end
+
+ it 'sets no-cache headers as expected' do
+ create_project_request = Runtime::API::Request.new(@api_client, '/projects')
+ post create_project_request.url, path: project_name, name: project_name
+
+ create_file_request = Runtime::API::Request.new(@api_client, "/projects/#{sanitized_project_path}/repository/files/test.svg")
+ post create_file_request.url, branch: 'master', content: svg_file, commit_message: 'Add test.svg'
+
+ get_file_request = Runtime::API::Request.new(@api_client, "/projects/#{sanitized_project_path}/repository/files/test.svg/raw", ref: 'master')
+
+ 3.times do
+ response = get get_file_request.url
+
+ # Subsequent responses aren't cached, so headers should match from
+ # request to request, especially a 200 response rather than a 304
+ # (indicating a cached response.) Further, :content_disposition
+ # should include `attachment` for all responses.
+ #
+ expect(response.headers[:cache_control]).to include("no-store")
+ expect(response.headers[:cache_control]).to include("no-cache")
+ expect(response.headers[:pragma]).to eq("no-cache")
+ expect(response.headers[:expires]).to eq("Fri, 01 Jan 1990 00:00:00 GMT")
+ expect(response.headers[:content_disposition]).to include("attachment")
+ expect(response.headers[:content_disposition]).not_to include("inline")
+ expect(response.headers[:content_type]).to include("image/svg+xml")
+ end
+ end
+ end
end
end
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index c7e454771bb..7fc568bb960 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -935,14 +935,14 @@ describe ProjectsHelper do
helper.instance_variable_set(:@project, project)
end
- subject { helper.grafana_integration_token }
+ subject { helper.grafana_integration_masked_token }
it { is_expected.to eq(nil) }
context 'grafana integration exists' do
let!(:grafana_integration) { create(:grafana_integration, project: project) }
- it { is_expected.to eq(grafana_integration.token) }
+ it { is_expected.to eq(grafana_integration.masked_token) }
end
end
diff --git a/spec/lib/banzai/reference_parser/mentioned_group_parser_spec.rb b/spec/lib/banzai/reference_parser/mentioned_group_parser_spec.rb
index 30b99f3eda7..8346ba93f88 100644
--- a/spec/lib/banzai/reference_parser/mentioned_group_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/mentioned_group_parser_spec.rb
@@ -19,7 +19,7 @@ describe Banzai::ReferenceParser::MentionedGroupParser do
it 'returns empty array' do
link['data-group'] = project.group.id.to_s
- expect(subject.gather_references([link])).to eq([])
+ expect_gathered_references(subject.gather_references([link]), [], 1)
end
end
@@ -30,7 +30,7 @@ describe Banzai::ReferenceParser::MentionedGroupParser do
end
it 'returns groups' do
- expect(subject.gather_references([link])).to eq([group])
+ expect_gathered_references(subject.gather_references([link]), [group], 0)
end
end
@@ -38,7 +38,7 @@ describe Banzai::ReferenceParser::MentionedGroupParser do
it 'returns an empty Array' do
link['data-group'] = 'test-non-existing'
- expect(subject.gather_references([link])).to eq([])
+ expect_gathered_references(subject.gather_references([link]), [], 1)
end
end
end
diff --git a/spec/lib/banzai/reference_parser/mentioned_project_parser_spec.rb b/spec/lib/banzai/reference_parser/mentioned_project_parser_spec.rb
index 154f7c4dc36..b99c02351d0 100644
--- a/spec/lib/banzai/reference_parser/mentioned_project_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/mentioned_project_parser_spec.rb
@@ -19,7 +19,7 @@ describe Banzai::ReferenceParser::MentionedProjectParser do
it 'returns empty Array' do
link['data-project'] = project.id.to_s
- expect(subject.gather_references([link])).to eq([])
+ expect_gathered_references(subject.gather_references([link]), [], 1)
end
end
@@ -30,7 +30,7 @@ describe Banzai::ReferenceParser::MentionedProjectParser do
end
it 'returns an Array of referenced projects' do
- expect(subject.gather_references([link])).to eq([project])
+ expect_gathered_references(subject.gather_references([link]), [project], 0)
end
end
@@ -38,7 +38,7 @@ describe Banzai::ReferenceParser::MentionedProjectParser do
it 'returns an empty Array' do
link['data-project'] = 'inexisting-project-id'
- expect(subject.gather_references([link])).to eq([])
+ expect_gathered_references(subject.gather_references([link]), [], 1)
end
end
end
diff --git a/spec/lib/banzai/reference_parser/mentioned_user_parser_spec.rb b/spec/lib/banzai/reference_parser/mentioned_user_parser_spec.rb
index 1be279375bd..b10e5d19828 100644
--- a/spec/lib/banzai/reference_parser/mentioned_user_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/mentioned_user_parser_spec.rb
@@ -22,7 +22,7 @@ describe Banzai::ReferenceParser::MentionedUserParser do
end
it 'returns empty list of users' do
- expect(subject.gather_references([link])).to eq([])
+ expect_gathered_references(subject.gather_references([link]), [], 0)
end
end
end
@@ -35,7 +35,7 @@ describe Banzai::ReferenceParser::MentionedUserParser do
end
it 'returns empty list of users' do
- expect(subject.gather_references([link])).to eq([])
+ expect_gathered_references(subject.gather_references([link]), [], 0)
end
end
end
@@ -44,7 +44,7 @@ describe Banzai::ReferenceParser::MentionedUserParser do
it 'returns an Array of users' do
link['data-user'] = user.id.to_s
- expect(subject.referenced_by([link])).to eq([user])
+ expect_gathered_references(subject.gather_references([link]), [user], 0)
end
end
end
diff --git a/spec/lib/banzai/reference_parser/project_parser_spec.rb b/spec/lib/banzai/reference_parser/project_parser_spec.rb
index 356dde1e9c2..e87fa3e8767 100644
--- a/spec/lib/banzai/reference_parser/project_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/project_parser_spec.rb
@@ -17,7 +17,7 @@ describe Banzai::ReferenceParser::ProjectParser do
it 'returns an Array of projects' do
link['data-project'] = project.id.to_s
- expect(subject.gather_references([link])).to eq([project])
+ expect_gathered_references(subject.gather_references([link]), [project], 0)
end
end
@@ -25,7 +25,7 @@ describe Banzai::ReferenceParser::ProjectParser do
it 'returns an empty Array' do
link['data-project'] = ''
- expect(subject.gather_references([link])).to eq([])
+ expect_gathered_references(subject.gather_references([link]), [], 1)
end
end
@@ -35,7 +35,7 @@ describe Banzai::ReferenceParser::ProjectParser do
link['data-project'] = private_project.id.to_s
- expect(subject.gather_references([link])).to eq([])
+ expect_gathered_references(subject.gather_references([link]), [], 1)
end
it 'returns an Array when authorized' do
@@ -43,7 +43,7 @@ describe Banzai::ReferenceParser::ProjectParser do
link['data-project'] = private_project.id.to_s
- expect(subject.gather_references([link])).to eq([private_project])
+ expect_gathered_references(subject.gather_references([link]), [private_project], 0)
end
end
end
diff --git a/spec/lib/gitlab/asciidoc/include_processor_spec.rb b/spec/lib/gitlab/asciidoc/include_processor_spec.rb
new file mode 100644
index 00000000000..5fec4d9e208
--- /dev/null
+++ b/spec/lib/gitlab/asciidoc/include_processor_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'nokogiri'
+
+describe Gitlab::Asciidoc::IncludeProcessor do
+ let_it_be(:project) { create(:project, :repository) }
+
+ let(:processor_context) do
+ {
+ project: project,
+ max_includes: max_includes,
+ ref: ref
+ }
+ end
+ let(:ref) { project.repository.root_ref }
+ let(:max_includes) { 10 }
+
+ let(:reader) { Asciidoctor::PreprocessorReader.new(document, lines, 'file.adoc') }
+ let(:document) { Asciidoctor::Document.new(lines) }
+
+ subject(:processor) { described_class.new(processor_context) }
+
+ let(:a_blob) { double(:Blob, readable_text?: true, data: a_data) }
+ let(:a_data) { StringIO.new('include::b.adoc[]') }
+
+ let(:lines) { [':max-include-depth: 1000'] + Array.new(10, 'include::a.adoc[]') }
+
+ before do
+ allow(project.repository).to receive(:blob_at).with(ref, 'a.adoc').and_return(a_blob)
+ end
+
+ describe '#include_allowed?' do
+ it 'allows the first include' do
+ expect(processor.send(:include_allowed?, 'foo.adoc', reader)).to be_truthy
+ end
+
+ it 'disallows the Nth + 1 include' do
+ max_includes.times { processor.send(:read_blob, ref, 'a.adoc') }
+
+ expect(processor.send(:include_allowed?, 'foo.adoc', reader)).to be_falsey
+ end
+ end
+end
diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb
index c8d159d1e84..c7156a500d0 100644
--- a/spec/lib/gitlab/asciidoc_spec.rb
+++ b/spec/lib/gitlab/asciidoc_spec.rb
@@ -425,6 +425,24 @@ module Gitlab
create_file(current_file, "= AsciiDoc\n")
end
+ def many_includes(target)
+ Array.new(10, "include::#{target}[]").join("\n")
+ end
+
+ context 'cyclic imports' do
+ before do
+ create_file('doc/api/a.adoc', many_includes('b.adoc'))
+ create_file('doc/api/b.adoc', many_includes('a.adoc'))
+ end
+
+ let(:include_path) { 'a.adoc' }
+ let(:requested_path) { 'doc/api/README.md' }
+
+ it 'completes successfully' do
+ is_expected.to include('<p>Include this:</p>')
+ end
+ end
+
context 'with path to non-existing file' do
let(:include_path) { 'not-exists.adoc' }
diff --git a/spec/lib/gitlab/no_cache_headers_spec.rb b/spec/lib/gitlab/no_cache_headers_spec.rb
new file mode 100644
index 00000000000..f011b55006e
--- /dev/null
+++ b/spec/lib/gitlab/no_cache_headers_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::NoCacheHeaders do
+ class NoCacheTester
+ include Gitlab::NoCacheHeaders
+ end
+
+ describe "#no_cache_headers" do
+ subject { NoCacheTester.new }
+
+ it "raises a RuntimeError" do
+ expect { subject.no_cache_headers }.to raise_error(RuntimeError)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb
index 6bc9b6365d1..0faaaa50621 100644
--- a/spec/lib/gitlab/reference_extractor_spec.rb
+++ b/spec/lib/gitlab/reference_extractor_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
describe Gitlab::ReferenceExtractor do
- let(:project) { create(:project) }
+ let_it_be(:project) { create(:project) }
before do
project.add_developer(project.creator)
@@ -293,4 +293,34 @@ describe Gitlab::ReferenceExtractor do
end
end
end
+
+ describe '#references' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:issue) { create(:issue, project: project) }
+ let(:text) { "Ref. #{issue.to_reference}" }
+
+ subject { described_class.new(project, user) }
+
+ before do
+ subject.analyze(text)
+ end
+
+ context 'when references are visible' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'returns visible references of given type' do
+ expect(subject.references(:issue)).to eq([issue])
+ end
+
+ it 'does not increase stateful_not_visible_counter' do
+ expect { subject.references(:issue) }.not_to change { subject.stateful_not_visible_counter }
+ end
+ end
+
+ it 'increases stateful_not_visible_counter' do
+ expect { subject.references(:issue) }.to change { subject.stateful_not_visible_counter }.by(1)
+ end
+ end
end
diff --git a/spec/models/grafana_integration_spec.rb b/spec/models/grafana_integration_spec.rb
index 615865e17b9..662e8b1dd61 100644
--- a/spec/models/grafana_integration_spec.rb
+++ b/spec/models/grafana_integration_spec.rb
@@ -9,7 +9,7 @@ describe GrafanaIntegration do
describe 'validations' do
it { is_expected.to validate_presence_of(:project) }
- it { is_expected.to validate_presence_of(:token) }
+ it { is_expected.to validate_presence_of(:encrypted_token) }
it 'disallows invalid urls for grafana_url' do
unsafe_url = %{https://replaceme.com/'><script>alert(document.cookie)</script>}
@@ -66,4 +66,24 @@ describe GrafanaIntegration do
end
end
end
+
+ describe 'attribute encryption' do
+ subject(:grafana_integration) { create(:grafana_integration, token: 'super-secret') }
+
+ context 'token' do
+ it 'encrypts original value into encrypted_token attribute' do
+ expect(grafana_integration.encrypted_token).not_to be_nil
+ end
+
+ it 'locks access to raw value in private method', :aggregate_failures do
+ expect { grafana_integration.token }.to raise_error(NoMethodError, /private method .token. called/)
+ expect(grafana_integration.send(:token)).to eql('super-secret')
+ end
+
+ it 'prevents overriding token value with its encrypted or masked version', :aggregate_failures do
+ expect { grafana_integration.update(token: grafana_integration.encrypted_token) }.not_to change { grafana_integration.reload.send(:token) }
+ expect { grafana_integration.update(token: grafana_integration.masked_token) }.not_to change { grafana_integration.reload.send(:token) }
+ end
+ end
+ end
end
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index a6d9ecaa7c5..2fa3f426da4 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -350,12 +350,12 @@ describe Note do
end
describe "cross_reference_not_visible_for?" do
- let(:private_user) { create(:user) }
- let(:private_project) { create(:project, namespace: private_user.namespace) { |p| p.add_maintainer(private_user) } }
- let(:private_issue) { create(:issue, project: private_project) }
+ let_it_be(:private_user) { create(:user) }
+ let_it_be(:private_project) { create(:project, namespace: private_user.namespace) { |p| p.add_maintainer(private_user) } }
+ let_it_be(:private_issue) { create(:issue, project: private_project) }
- let(:ext_proj) { create(:project, :public) }
- let(:ext_issue) { create(:issue, project: ext_proj) }
+ let_it_be(:ext_proj) { create(:project, :public) }
+ let_it_be(:ext_issue) { create(:issue, project: ext_proj) }
shared_examples "checks references" do
it "returns true" do
@@ -393,10 +393,24 @@ describe Note do
it_behaves_like "checks references"
end
- context "when there are two references in note" do
+ context "when there is a reference to a label" do
+ let_it_be(:private_label) { create(:label, project: private_project) }
let(:note) do
create :note,
noteable: ext_issue, project: ext_proj,
+ note: "added label #{private_label.to_reference(ext_proj)}",
+ system: true
+ end
+ let!(:system_note_metadata) { create(:system_note_metadata, note: note, action: :label) }
+
+ it_behaves_like "checks references"
+ end
+
+ context "when there are two references in note" do
+ let_it_be(:ext_issue2) { create(:issue, project: ext_proj) }
+ let(:note) do
+ create :note,
+ noteable: ext_issue2, project: ext_proj,
note: "mentioned in issue #{private_issue.to_reference(ext_proj)} and " \
"public issue #{ext_issue.to_reference(ext_proj)}",
system: true
diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb
index ab915af8ab0..efad443de3f 100644
--- a/spec/requests/api/files_spec.rb
+++ b/spec/requests/api/files_spec.rb
@@ -447,6 +447,18 @@ describe API::Files do
expect(response).to have_gitlab_http_status(200)
end
+ it 'sets no-cache headers' do
+ url = route('.gitignore') + "/raw"
+ expect(Gitlab::Workhorse).to receive(:send_git_blob)
+
+ get api(url, current_user), params: params
+
+ expect(response.headers["Cache-Control"]).to include("no-store")
+ expect(response.headers["Cache-Control"]).to include("no-cache")
+ expect(response.headers["Pragma"]).to eq("no-cache")
+ expect(response.headers["Expires"]).to eq("Fri, 01 Jan 1990 00:00:00 GMT")
+ end
+
context 'when mandatory params are not given' do
it_behaves_like '400 response' do
let(:request) { get api(route("any%2Ffile"), current_user) }
diff --git a/spec/requests/api/graphql/project/grafana_integration_spec.rb b/spec/requests/api/graphql/project/grafana_integration_spec.rb
index 6075efb0cbd..e7155934b3a 100644
--- a/spec/requests/api/graphql/project/grafana_integration_spec.rb
+++ b/spec/requests/api/graphql/project/grafana_integration_spec.rb
@@ -45,7 +45,7 @@ describe 'Getting Grafana Integration' do
it_behaves_like 'a working graphql query'
- it { expect(integration_data['token']).to eql grafana_integration.token }
+ it { expect(integration_data['token']).to eql grafana_integration.masked_token }
it { expect(integration_data['grafanaUrl']).to eql grafana_integration.grafana_url }
it do
diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb
index a313f75e3ec..e3ba366dfcc 100644
--- a/spec/requests/api/runner_spec.rb
+++ b/spec/requests/api/runner_spec.rb
@@ -1545,7 +1545,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
authorize_artifacts
- expect(response).to have_gitlab_http_status(500)
+ expect(response).to have_gitlab_http_status(:forbidden)
end
context 'authorization token is invalid' do
@@ -1675,6 +1675,18 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
end
end
+ context 'Is missing GitLab Workhorse token headers' do
+ let(:jwt_token) { JWT.encode({ 'iss' => 'invalid-header' }, Gitlab::Workhorse.secret, 'HS256') }
+
+ it 'fails to post artifacts without GitLab-Workhorse' do
+ expect(Gitlab::ErrorTracking).to receive(:track_exception).once
+
+ upload_artifacts(file_upload, headers_with_token)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
context 'when setting an expire date' do
let(:default_artifacts_expire_in) {}
let(:post_data) do
diff --git a/spec/services/projects/import_export/export_service_spec.rb b/spec/services/projects/import_export/export_service_spec.rb
index c7ac07fc524..906fef6edf5 100644
--- a/spec/services/projects/import_export/export_service_spec.rb
+++ b/spec/services/projects/import_export/export_service_spec.rb
@@ -10,6 +10,10 @@ describe Projects::ImportExport::ExportService do
let(:service) { described_class.new(project, user) }
let!(:after_export_strategy) { Gitlab::ImportExport::AfterExportStrategies::DownloadNotificationStrategy.new }
+ before do
+ project.add_maintainer(user)
+ end
+
it 'saves the version' do
expect(Gitlab::ImportExport::VersionSaver).to receive(:new).and_call_original
@@ -137,5 +141,18 @@ describe Projects::ImportExport::ExportService do
expect(service).not_to receive(:execute_after_export_action)
end
end
+
+ context 'when user does not have admin_project permission' do
+ let!(:another_user) { create(:user) }
+
+ subject(:service) { described_class.new(project, another_user) }
+
+ it 'fails' do
+ expected_message =
+ "User with ID: %s does not have permission to Project %s with ID: %s." %
+ [another_user.id, project.name, project.id]
+ expect { service.execute }.to raise_error(Gitlab::ImportExport::Error).with_message(expected_message)
+ end
+ end
end
end
diff --git a/spec/services/projects/operations/update_service_spec.rb b/spec/services/projects/operations/update_service_spec.rb
index 93cd5c43e86..d20ec0b818b 100644
--- a/spec/services/projects/operations/update_service_spec.rb
+++ b/spec/services/projects/operations/update_service_spec.rb
@@ -210,7 +210,7 @@ describe Projects::Operations::UpdateService do
integration = project.reload.grafana_integration
expect(integration.grafana_url).to eq(expected_attrs[:grafana_url])
- expect(integration.token).to eq(expected_attrs[:token])
+ expect(integration.send(:token)).to eq(expected_attrs[:token])
end
end
@@ -226,7 +226,7 @@ describe Projects::Operations::UpdateService do
integration = project.reload.grafana_integration
expect(integration.grafana_url).to eq(expected_attrs[:grafana_url])
- expect(integration.token).to eq(expected_attrs[:token])
+ expect(integration.send(:token)).to eq(expected_attrs[:token])
end
context 'with all grafana attributes blank in params' do
diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb
index 714256d9b08..52ec80c252b 100644
--- a/spec/services/projects/update_pages_service_spec.rb
+++ b/spec/services/projects/update_pages_service_spec.rb
@@ -5,7 +5,7 @@ require "spec_helper"
describe Projects::UpdatePagesService do
let_it_be(:project, refind: true) { create(:project, :repository) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project, sha: project.commit('HEAD').sha) }
- let_it_be(:build) { create(:ci_build, pipeline: pipeline, ref: 'HEAD') }
+ let(:build) { create(:ci_build, pipeline: pipeline, ref: 'HEAD') }
let(:invalid_file) { fixture_file_upload('spec/fixtures/dk.png') }
let(:file) { fixture_file_upload("spec/fixtures/pages.zip") }
@@ -204,6 +204,32 @@ describe Projects::UpdatePagesService do
end
end
+ context 'when file size is spoofed' do
+ let(:metadata) { spy('metadata') }
+
+ include_context 'pages zip with spoofed size'
+
+ before do
+ file = fixture_file_upload(fake_zip_path, 'pages.zip')
+ metafile = fixture_file_upload('spec/fixtures/pages.zip.meta')
+
+ create(:ci_job_artifact, :archive, file: file, job: build)
+ create(:ci_job_artifact, :metadata, file: metafile, job: build)
+
+ allow(build).to receive(:artifacts_metadata_entry)
+ .and_return(metadata)
+ allow(metadata).to receive(:total_size).and_return(100)
+ end
+
+ it 'raises an error' do
+ expect do
+ subject.execute
+ end.to raise_error(Projects::UpdatePagesService::FailedToExtractError,
+ 'Entry public/index.html should be 1B but is larger when inflated')
+ expect(deploy_status).to be_script_failure
+ end
+ end
+
def deploy_status
GenericCommitStatus.find_by(name: 'pages:deploy')
end
diff --git a/spec/support/helpers/reference_parser_helpers.rb b/spec/support/helpers/reference_parser_helpers.rb
index f96a01d15b5..9084265b587 100644
--- a/spec/support/helpers/reference_parser_helpers.rb
+++ b/spec/support/helpers/reference_parser_helpers.rb
@@ -5,6 +5,11 @@ module ReferenceParserHelpers
Nokogiri::HTML.fragment('<a></a>').children[0]
end
+ def expect_gathered_references(result, visible, not_visible_count)
+ expect(result[:visible]).to eq(visible)
+ expect(result[:not_visible].count).to eq(not_visible_count)
+ end
+
shared_examples 'no project N+1 queries' do
it 'avoids N+1 queries in #nodes_visible_to_user', :request_store do
context = Banzai::RenderContext.new(project, user)
diff --git a/spec/support/shared_contexts/pages_zip_with_spoofed_size_shared_context.rb b/spec/support/shared_contexts/pages_zip_with_spoofed_size_shared_context.rb
new file mode 100644
index 00000000000..4cec5ab3b74
--- /dev/null
+++ b/spec/support/shared_contexts/pages_zip_with_spoofed_size_shared_context.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+# the idea of creating zip archive with spoofed size is borrowed from
+# https://github.com/rubyzip/rubyzip/pull/403/files#diff-118213fb4baa6404a40f89e1147661ebR88
+RSpec.shared_context 'pages zip with spoofed size' do
+ let(:real_zip_path) { Tempfile.new(['real', '.zip']).path }
+ let(:fake_zip_path) { Tempfile.new(['fake', '.zip']).path }
+
+ before do
+ full_file_name = 'public/index.html'
+ true_size = 500_000
+ fake_size = 1
+
+ ::Zip::File.open(real_zip_path, ::Zip::File::CREATE) do |zf|
+ zf.get_output_stream(full_file_name) do |os|
+ os.write 'a' * true_size
+ end
+ end
+
+ compressed_size = nil
+ ::Zip::File.open(real_zip_path) do |zf|
+ a_entry = zf.find_entry(full_file_name)
+ compressed_size = a_entry.compressed_size
+ end
+
+ true_size_bytes = [compressed_size, true_size, full_file_name.size].pack('LLS')
+ fake_size_bytes = [compressed_size, fake_size, full_file_name.size].pack('LLS')
+
+ data = File.binread(real_zip_path)
+ data.gsub! true_size_bytes, fake_size_bytes
+
+ File.open(fake_zip_path, 'wb') do |file|
+ file.write data
+ end
+ end
+
+ after do
+ File.delete(real_zip_path) if File.exist?(real_zip_path)
+ File.delete(fake_zip_path) if File.exist?(fake_zip_path)
+ end
+end
diff --git a/vendor/gitignore/C++.gitignore b/vendor/gitignore/C++.gitignore
index 259148fa18f..259148fa18f 100755..100644
--- a/vendor/gitignore/C++.gitignore
+++ b/vendor/gitignore/C++.gitignore
diff --git a/vendor/gitignore/Java.gitignore b/vendor/gitignore/Java.gitignore
index a1c2a238a96..a1c2a238a96 100755..100644
--- a/vendor/gitignore/Java.gitignore
+++ b/vendor/gitignore/Java.gitignore