summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Release Tools Bot <delivery-team+release-tools@gitlab.com>2022-01-11 20:34:56 +0000
committerGitLab Release Tools Bot <delivery-team+release-tools@gitlab.com>2022-01-11 20:34:56 +0000
commitab5b519cca2356dd2b84a7fffc6ddbd6dc43a0ab (patch)
treea4e2d538a2f9d82590614d5661dbbe9996c8eab8
parent661d663ab2b7c69977ba8a0db02ef4afc2427e39 (diff)
parent0a901d60f8ae4a60c04ae82e6e9c3a03e9321417 (diff)
downloadgitlab-ce-ab5b519cca2356dd2b84a7fffc6ddbd6dc43a0ab.tar.gz
Merge remote-tracking branch 'dev/14-6-stable' into 14-6-stable
-rw-r--r--CHANGELOG.md4
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--VERSION2
-rw-r--r--app/assets/javascripts/behaviors/gl_emoji.js6
-rw-r--r--app/assets/javascripts/emoji/index.js15
-rw-r--r--app/controllers/concerns/sessionless_authentication.rb2
-rw-r--r--app/controllers/import/github_controller.rb21
-rw-r--r--app/graphql/resolvers/base_issues_resolver.rb2
-rw-r--r--app/models/concerns/integrations/slack_mattermost_notifier.rb7
-rw-r--r--app/models/integrations/chat_message/alert_message.rb4
-rw-r--r--app/models/integrations/chat_message/base_message.rb20
-rw-r--r--app/models/integrations/chat_message/deployment_message.rb6
-rw-r--r--app/models/integrations/chat_message/issue_message.rb6
-rw-r--r--app/models/integrations/chat_message/merge_message.rb8
-rw-r--r--app/models/integrations/chat_message/note_message.rb6
-rw-r--r--app/models/integrations/chat_message/pipeline_message.rb18
-rw-r--r--app/models/integrations/chat_message/push_message.rb10
-rw-r--r--app/models/integrations/chat_message/wiki_page_message.rb6
-rw-r--r--app/services/bulk_imports/archive_extraction_service.rb74
-rw-r--r--app/services/packages/npm/create_package_service.rb18
-rw-r--r--app/workers/all_queues.yml9
-rw-r--r--app/workers/concerns/dependency_proxy/expireable.rb17
-rw-r--r--app/workers/dependency_proxy/cleanup_dependency_proxy_worker.rb28
-rw-r--r--app/workers/dependency_proxy/image_ttl_group_policy_worker.rb26
-rw-r--r--app/workers/purge_dependency_proxy_cache_worker.rb12
-rw-r--r--config/initializers/1_settings.rb3
-rw-r--r--doc/api/dependency_proxy.md3
-rw-r--r--lib/api/projects.rb1
-rw-r--r--lib/bulk_imports/common/pipelines/uploads_pipeline.rb39
-rw-r--r--lib/gitlab/import_export/command_line_util.rb4
-rw-r--r--lib/gitlab/legacy_github_import/client.rb5
-rw-r--r--lib/gitlab/url_blocker.rb4
-rw-r--r--spec/controllers/dashboard/projects_controller_spec.rb4
-rw-r--r--spec/controllers/dashboard_controller_spec.rb3
-rw-r--r--spec/controllers/groups_controller_spec.rb20
-rw-r--r--spec/controllers/import/github_controller_spec.rb57
-rw-r--r--spec/controllers/projects/commits_controller_spec.rb23
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb36
-rw-r--r--spec/controllers/projects/raw_controller_spec.rb71
-rw-r--r--spec/controllers/projects/repositories_controller_spec.rb48
-rw-r--r--spec/controllers/projects/tags_controller_spec.rb22
-rw-r--r--spec/controllers/projects_controller_spec.rb22
-rw-r--r--spec/fixtures/symlink_export.tarbin0 -> 10240 bytes
-rw-r--r--spec/frontend/behaviors/gl_emoji_spec.js12
-rw-r--r--spec/graphql/resolvers/issues_resolver_spec.rb6
-rw-r--r--spec/initializers/action_cable_subscription_adapter_identifier_spec.rb6
-rw-r--r--spec/lib/bulk_imports/common/pipelines/uploads_pipeline_spec.rb24
-rw-r--r--spec/lib/gitlab/import_export/command_line_util_spec.rb35
-rw-r--r--spec/lib/gitlab/url_blocker_spec.rb56
-rw-r--r--spec/models/integrations/chat_message/alert_message_spec.rb2
-rw-r--r--spec/models/integrations/chat_message/base_message_spec.rb18
-rw-r--r--spec/models/integrations/chat_message/deployment_message_spec.rb90
-rw-r--r--spec/models/integrations/chat_message/issue_message_spec.rb2
-rw-r--r--spec/models/integrations/chat_message/merge_message_spec.rb2
-rw-r--r--spec/models/integrations/chat_message/note_message_spec.rb15
-rw-r--r--spec/models/integrations/chat_message/pipeline_message_spec.rb2
-rw-r--r--spec/models/integrations/chat_message/push_message_spec.rb2
-rw-r--r--spec/models/integrations/chat_message/wiki_page_message_spec.rb2
-rw-r--r--spec/requests/api/graphql_spec.rb28
-rw-r--r--spec/requests/api/projects_spec.rb23
-rw-r--r--spec/requests/dashboard/projects_controller_spec.rb11
-rw-r--r--spec/requests/dashboard_controller_spec.rb15
-rw-r--r--spec/requests/groups_controller_spec.rb51
-rw-r--r--spec/requests/projects/commits_controller_spec.rb27
-rw-r--r--spec/requests/projects/issues_controller_spec.rb42
-rw-r--r--spec/requests/projects/merge_requests_controller_spec.rb27
-rw-r--r--spec/requests/projects/tags_controller_spec.rb27
-rw-r--r--spec/requests/projects_controller_spec.rb27
-rw-r--r--spec/requests/users_controller_spec.rb6
-rw-r--r--spec/services/bulk_imports/archive_extraction_service_spec.rb60
-rw-r--r--spec/services/packages/npm/create_package_service_spec.rb52
-rw-r--r--spec/support/shared_examples/controllers/sessionless_auth_controller_shared_examples.rb112
-rw-r--r--spec/support/shared_examples/models/integrations/chat_message_shared_examples.rb56
-rw-r--r--spec/support/shared_examples/requests/sessionless_auth_request_shared_examples.rb158
-rw-r--r--spec/workers/dependency_proxy/cleanup_dependency_proxy_worker_spec.rb34
-rw-r--r--spec/workers/dependency_proxy/image_ttl_group_policy_worker_spec.rb16
76 files changed, 1235 insertions, 505 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7f94a6ed393..35287206474 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,10 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 14.6.2 (2022-01-10)
+
+No changes.
+
## 14.6.1 (2022-01-04)
### Fixed (2 changes)
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 9b87fcc4a48..bf8aaa2ef91 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-14.6.1 \ No newline at end of file
+14.6.2 \ No newline at end of file
diff --git a/VERSION b/VERSION
index 9b87fcc4a48..bf8aaa2ef91 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-14.6.1 \ No newline at end of file
+14.6.2 \ No newline at end of file
diff --git a/app/assets/javascripts/behaviors/gl_emoji.js b/app/assets/javascripts/behaviors/gl_emoji.js
index 8fe90b6bb15..8fd03d3132d 100644
--- a/app/assets/javascripts/behaviors/gl_emoji.js
+++ b/app/assets/javascripts/behaviors/gl_emoji.js
@@ -64,10 +64,12 @@ class GlEmoji extends HTMLElement {
this.classList.add('emoji-icon');
this.classList.add(fallbackSpriteClass);
} else if (hasImageFallback) {
- this.innerHTML = emojiImageTag(name, fallbackSrc);
+ this.innerHTML = '';
+ this.appendChild(emojiImageTag(name, fallbackSrc));
} else {
const src = emojiFallbackImageSrc(name);
- this.innerHTML = emojiImageTag(name, src);
+ this.innerHTML = '';
+ this.appendChild(emojiImageTag(name, src));
}
}
});
diff --git a/app/assets/javascripts/emoji/index.js b/app/assets/javascripts/emoji/index.js
index b507792cc91..aaae1624bee 100644
--- a/app/assets/javascripts/emoji/index.js
+++ b/app/assets/javascripts/emoji/index.js
@@ -1,6 +1,7 @@
import { escape, minBy } from 'lodash';
import emojiRegexFactory from 'emoji-regex';
import emojiAliases from 'emojis/aliases.json';
+import { setAttributes } from '~/lib/utils/dom_utils';
import AccessorUtilities from '../lib/utils/accessor';
import axios from '../lib/utils/axios_utils';
import { CACHE_KEY, CACHE_VERSION_KEY, CATEGORY_ICON_MAP, FREQUENTLY_USED_KEY } from './constants';
@@ -220,7 +221,19 @@ export function emojiFallbackImageSrc(inputName) {
}
export function emojiImageTag(name, src) {
- return `<img class="emoji" title=":${name}:" alt=":${name}:" src="${src}" width="20" height="20" align="absmiddle" />`;
+ const img = document.createElement('img');
+
+ img.className = 'emoji';
+ setAttributes(img, {
+ title: `:${name}:`,
+ alt: `:${name}:`,
+ src,
+ width: '20',
+ height: '20',
+ align: 'absmiddle',
+ });
+
+ return img;
}
export function glEmojiTag(inputName, options) {
diff --git a/app/controllers/concerns/sessionless_authentication.rb b/app/controllers/concerns/sessionless_authentication.rb
index 58e65ba20e2..c6d926c8a8d 100644
--- a/app/controllers/concerns/sessionless_authentication.rb
+++ b/app/controllers/concerns/sessionless_authentication.rb
@@ -20,7 +20,7 @@ module SessionlessAuthentication
end
def sessionless_sign_in(user)
- if user && can?(user, :log_in)
+ if can?(user, :log_in) && !user.password_expired_if_applicable?
# Notice we are passing store false, so the user is not
# actually stored in the session and a token is needed
# for every request. If you want the token to work as a
diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb
index d7aebd25432..55f4563285d 100644
--- a/app/controllers/import/github_controller.rb
+++ b/app/controllers/import/github_controller.rb
@@ -28,8 +28,14 @@ class Import::GithubController < Import::BaseController
end
def callback
- session[access_token_key] = get_token(params[:code])
- redirect_to status_import_url
+ auth_state = session[auth_state_key]
+ session[auth_state_key] = nil
+ if auth_state.blank? || !ActiveSupport::SecurityUtils.secure_compare(auth_state, params[:state])
+ provider_unauthorized
+ else
+ session[access_token_key] = get_token(params[:code])
+ redirect_to status_import_url
+ end
end
def personal_access_token
@@ -154,13 +160,16 @@ class Import::GithubController < Import::BaseController
end
def authorize_url
+ state = SecureRandom.base64(64)
+ session[auth_state_key] = state
if Feature.enabled?(:remove_legacy_github_client)
oauth_client.auth_code.authorize_url(
redirect_uri: callback_import_url,
- scope: 'repo, user, user:email'
+ scope: 'repo, user, user:email',
+ state: state
)
else
- client.authorize_url(callback_import_url)
+ client.authorize_url(callback_import_url, state)
end
end
@@ -219,6 +228,10 @@ class Import::GithubController < Import::BaseController
alert: _('Missing OAuth configuration for GitHub.')
end
+ def auth_state_key
+ :"#{provider_name}_auth_state_key"
+ end
+
def access_token_key
:"#{provider_name}_access_token"
end
diff --git a/app/graphql/resolvers/base_issues_resolver.rb b/app/graphql/resolvers/base_issues_resolver.rb
index dca93444907..3983a3697cb 100644
--- a/app/graphql/resolvers/base_issues_resolver.rb
+++ b/app/graphql/resolvers/base_issues_resolver.rb
@@ -36,7 +36,7 @@ module Resolvers
def unconditional_includes
[
{
- project: [:project_feature]
+ project: [:project_feature, :group]
},
:author
]
diff --git a/app/models/concerns/integrations/slack_mattermost_notifier.rb b/app/models/concerns/integrations/slack_mattermost_notifier.rb
index cb6fafa8de0..be13701289a 100644
--- a/app/models/concerns/integrations/slack_mattermost_notifier.rb
+++ b/app/models/concerns/integrations/slack_mattermost_notifier.rb
@@ -6,6 +6,13 @@ module Integrations
def notify(message, opts)
# See https://gitlab.com/gitlab-org/slack-notifier/#custom-http-client
+ #
+ # TODO: By default both Markdown and HTML links are converted into Slack "mrkdwn" syntax,
+ # but it seems we only need to support Markdown and could disable HTML.
+ #
+ # See:
+ # - https://gitlab.com/gitlab-org/slack-notifier#middleware
+ # - https://gitlab.com/gitlab-org/gitlab/-/issues/347048
notifier = ::Slack::Messenger.new(webhook, opts.merge(http_client: HTTPClient))
notifier.ping(
message.pretext,
diff --git a/app/models/integrations/chat_message/alert_message.rb b/app/models/integrations/chat_message/alert_message.rb
index ef0579124fe..e2c689f9435 100644
--- a/app/models/integrations/chat_message/alert_message.rb
+++ b/app/models/integrations/chat_message/alert_message.rb
@@ -23,7 +23,7 @@ module Integrations
def attachments
[{
- title: title,
+ title: strip_markup(title),
title_link: alert_url,
color: attachment_color,
fields: attachment_fields
@@ -31,7 +31,7 @@ module Integrations
end
def message
- "Alert firing in #{project_name}"
+ "Alert firing in #{strip_markup(project_name)}"
end
private
diff --git a/app/models/integrations/chat_message/base_message.rb b/app/models/integrations/chat_message/base_message.rb
index afe3ffc45a0..ab213f4b43f 100644
--- a/app/models/integrations/chat_message/base_message.rb
+++ b/app/models/integrations/chat_message/base_message.rb
@@ -5,6 +5,10 @@ module Integrations
class BaseMessage
RELATIVE_LINK_REGEX = %r{!\[[^\]]*\]\((/uploads/[^\)]*)\)}.freeze
+ # Markup characters which are used for links in HTML, Markdown,
+ # and Slack "mrkdwn" syntax (`<http://example.com|Label>`).
+ UNSAFE_MARKUP_CHARACTERS = '<>[]|'
+
attr_reader :markdown
attr_reader :user_full_name
attr_reader :user_name
@@ -65,12 +69,26 @@ module Integrations
string.gsub(RELATIVE_LINK_REGEX, "#{project_url}\\1")
end
+ # Remove unsafe markup from user input, which can be used to hijack links in our own markup,
+ # or insert new ones.
+ #
+ # This currently removes Markdown and Slack "mrkdwn" links (keeping the link label),
+ # and all HTML markup (keeping the text nodes).
+ # We can't just escape the markup characters, because each chat app handles this differently.
+ #
+ # See:
+ # - https://api.slack.com/reference/surfaces/formatting#escaping
+ # - https://gitlab.com/gitlab-org/slack-notifier#escaping
+ def strip_markup(string)
+ string&.delete(UNSAFE_MARKUP_CHARACTERS)
+ end
+
def attachment_color
'#345'
end
def link(text, url)
- "[#{text}](#{url})"
+ "[#{strip_markup(text)}](#{url})"
end
def pretty_duration(seconds)
diff --git a/app/models/integrations/chat_message/deployment_message.rb b/app/models/integrations/chat_message/deployment_message.rb
index c4f3bf9610d..b28edeecb4d 100644
--- a/app/models/integrations/chat_message/deployment_message.rb
+++ b/app/models/integrations/chat_message/deployment_message.rb
@@ -27,7 +27,7 @@ module Integrations
def attachments
[{
- text: "#{project_link} with job #{deployment_link} by #{user_link}\n#{commit_link}: #{commit_title}",
+ text: "#{project_link} with job #{deployment_link} by #{user_link}\n#{commit_link}: #{strip_markup(commit_title)}",
color: color
}]
end
@@ -40,9 +40,9 @@ module Integrations
def message
if running?
- "Starting deploy to #{environment}"
+ "Starting deploy to #{strip_markup(environment)}"
else
- "Deploy to #{environment} #{humanized_status}"
+ "Deploy to #{strip_markup(environment)} #{humanized_status}"
end
end
diff --git a/app/models/integrations/chat_message/issue_message.rb b/app/models/integrations/chat_message/issue_message.rb
index 5fa6bd4090f..ca8ef670e67 100644
--- a/app/models/integrations/chat_message/issue_message.rb
+++ b/app/models/integrations/chat_message/issue_message.rb
@@ -32,7 +32,7 @@ module Integrations
def activity
{
- title: "Issue #{state} by #{user_combined_name}",
+ title: "Issue #{state} by #{strip_markup(user_combined_name)}",
subtitle: "in #{project_link}",
text: issue_link,
image: user_avatar
@@ -42,7 +42,7 @@ module Integrations
private
def message
- "[#{project_link}] Issue #{issue_link} #{state} by #{user_combined_name}"
+ "[#{project_link}] Issue #{issue_link} #{state} by #{strip_markup(user_combined_name)}"
end
def opened_issue?
@@ -67,7 +67,7 @@ module Integrations
end
def issue_title
- "#{Issue.reference_prefix}#{issue_iid} #{title}"
+ "#{Issue.reference_prefix}#{issue_iid} #{strip_markup(title)}"
end
end
end
diff --git a/app/models/integrations/chat_message/merge_message.rb b/app/models/integrations/chat_message/merge_message.rb
index d2f48699f50..98da38de27c 100644
--- a/app/models/integrations/chat_message/merge_message.rb
+++ b/app/models/integrations/chat_message/merge_message.rb
@@ -29,7 +29,7 @@ module Integrations
def activity
{
- title: "Merge request #{state_or_action_text} by #{user_combined_name}",
+ title: "Merge request #{state_or_action_text} by #{strip_markup(user_combined_name)}",
subtitle: "in #{project_link}",
text: merge_request_link,
image: user_avatar
@@ -39,7 +39,7 @@ module Integrations
private
def format_title(title)
- '*' + title.lines.first.chomp + '*'
+ '*' + strip_markup(title.lines.first.chomp) + '*'
end
def message
@@ -51,7 +51,7 @@ module Integrations
end
def merge_request_message
- "#{user_combined_name} #{state_or_action_text} merge request #{merge_request_link} in #{project_link}"
+ "#{strip_markup(user_combined_name)} #{state_or_action_text} merge request #{merge_request_link} in #{project_link}"
end
def merge_request_link
@@ -59,7 +59,7 @@ module Integrations
end
def merge_request_title
- "#{MergeRequest.reference_prefix}#{merge_request_iid} #{title}"
+ "#{MergeRequest.reference_prefix}#{merge_request_iid} #{strip_markup(title)}"
end
def merge_request_url
diff --git a/app/models/integrations/chat_message/note_message.rb b/app/models/integrations/chat_message/note_message.rb
index 96675d2b27c..b2b2059536a 100644
--- a/app/models/integrations/chat_message/note_message.rb
+++ b/app/models/integrations/chat_message/note_message.rb
@@ -35,9 +35,9 @@ module Integrations
def activity
{
- title: "#{user_combined_name} #{link('commented on ' + target, note_url)}",
+ title: "#{strip_markup(user_combined_name)} #{link('commented on ' + target, note_url)}",
subtitle: "in #{project_link}",
- text: formatted_title,
+ text: strip_markup(formatted_title),
image: user_avatar
}
end
@@ -45,7 +45,7 @@ module Integrations
private
def message
- "#{user_combined_name} #{link('commented on ' + target, note_url)} in #{project_link}: *#{formatted_title}*"
+ "#{strip_markup(user_combined_name)} #{link('commented on ' + target, note_url)} in #{project_link}: *#{strip_markup(formatted_title)}*"
end
def format_title(title)
diff --git a/app/models/integrations/chat_message/pipeline_message.rb b/app/models/integrations/chat_message/pipeline_message.rb
index a3f68d34035..b3502905bf7 100644
--- a/app/models/integrations/chat_message/pipeline_message.rb
+++ b/app/models/integrations/chat_message/pipeline_message.rb
@@ -56,7 +56,7 @@ module Integrations
[{
fallback: format(message),
color: attachment_color,
- author_name: user_combined_name,
+ author_name: strip_markup(user_combined_name),
author_icon: user_avatar,
author_link: author_url,
title: s_("ChatMessage|Pipeline #%{pipeline_id} %{humanized_status} in %{duration}") %
@@ -80,7 +80,7 @@ module Integrations
pipeline_link: pipeline_link,
ref_type: ref_type,
ref_link: ref_link,
- user_combined_name: user_combined_name,
+ user_combined_name: strip_markup(user_combined_name),
humanized_status: humanized_status
},
subtitle: s_("ChatMessage|in %{project_link}") % { project_link: project_link },
@@ -154,7 +154,7 @@ module Integrations
pipeline_link: pipeline_link,
ref_type: ref_type,
ref_link: ref_link,
- user_combined_name: user_combined_name,
+ user_combined_name: strip_markup(user_combined_name),
humanized_status: humanized_status,
duration: pretty_duration(duration)
}
@@ -189,7 +189,7 @@ module Integrations
end
def ref_link
- "[#{ref}](#{ref_url})"
+ link(ref, ref_url)
end
def project_url
@@ -197,7 +197,7 @@ module Integrations
end
def project_link
- "[#{project.name}](#{project_url})"
+ link(project.name, project_url)
end
def pipeline_failed_jobs_url
@@ -213,7 +213,7 @@ module Integrations
end
def pipeline_link
- "[##{pipeline_id}](#{pipeline_url})"
+ link("##{pipeline_id}", pipeline_url)
end
def job_url(job)
@@ -221,7 +221,7 @@ module Integrations
end
def job_link(job)
- "[#{job[:name]}](#{job_url(job)})"
+ link(job[:name], job_url(job))
end
def failed_jobs_links
@@ -242,7 +242,7 @@ module Integrations
def stage_link(stage)
# All stages link to the pipeline page
- "[#{stage}](#{pipeline_url})"
+ link(stage, pipeline_url)
end
def failed_stages_links
@@ -254,7 +254,7 @@ module Integrations
end
def commit_link
- "[#{commit.title}](#{commit_url})"
+ link(commit.title, commit_url)
end
def author_url
diff --git a/app/models/integrations/chat_message/push_message.rb b/app/models/integrations/chat_message/push_message.rb
index fabd214633b..60a3105d1c0 100644
--- a/app/models/integrations/chat_message/push_message.rb
+++ b/app/models/integrations/chat_message/push_message.rb
@@ -39,7 +39,7 @@ module Integrations
def humanized_action(short: false)
action, ref_link, target_link = compose_action_details
- text = [user_combined_name, action, ref_type, ref_link]
+ text = [strip_markup(user_combined_name), action, ref_type, ref_link]
text << target_link unless short
text.join(' ')
end
@@ -67,7 +67,7 @@ module Integrations
url = commit[:url]
- "[#{id}](#{url}): #{title} - #{author}"
+ "#{link(id, url)}: #{strip_markup(title)} - #{strip_markup(author)}"
end
def new_branch?
@@ -91,15 +91,15 @@ module Integrations
end
def ref_link
- "[#{ref}](#{ref_url})"
+ link(ref, ref_url)
end
def project_link
- "[#{project_name}](#{project_url})"
+ link(project_name, project_url)
end
def compare_link
- "[Compare changes](#{compare_url})"
+ link('Compare changes', compare_url)
end
def compose_action_details
diff --git a/app/models/integrations/chat_message/wiki_page_message.rb b/app/models/integrations/chat_message/wiki_page_message.rb
index 00f0f911b0e..e7299696856 100644
--- a/app/models/integrations/chat_message/wiki_page_message.rb
+++ b/app/models/integrations/chat_message/wiki_page_message.rb
@@ -36,9 +36,9 @@ module Integrations
def activity
{
- title: "#{user_combined_name} #{action} #{wiki_page_link}",
+ title: "#{strip_markup(user_combined_name)} #{action} #{wiki_page_link}",
subtitle: "in #{project_link}",
- text: title,
+ text: strip_markup(title),
image: user_avatar
}
end
@@ -46,7 +46,7 @@ module Integrations
private
def message
- "#{user_combined_name} #{action} #{wiki_page_link} (#{diff_link}) in #{project_link}: *#{title}*"
+ "#{strip_markup(user_combined_name)} #{action} #{wiki_page_link} (#{diff_link}) in #{project_link}: *#{strip_markup(title)}*"
end
def description_message
diff --git a/app/services/bulk_imports/archive_extraction_service.rb b/app/services/bulk_imports/archive_extraction_service.rb
new file mode 100644
index 00000000000..9fc828b8e34
--- /dev/null
+++ b/app/services/bulk_imports/archive_extraction_service.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+# Archive Extraction Service allows extraction of contents
+# from `tar` archives with an additional handling (removal)
+# of file symlinks.
+#
+# @param tmpdir [String] A path where archive is located
+# and where its contents are extracted.
+# Tmpdir directory must be located under `Dir.tmpdir`.
+# `BulkImports::Error` is raised if any other directory path is used.
+#
+# @param filename [String] Name of the file to extract contents from.
+#
+# @example
+# dir = Dir.mktmpdir
+# filename = 'things.tar'
+# BulkImports::ArchiveExtractionService.new(tmpdir: dir, filename: filename).execute
+# Dir.glob(File.join(dir, '**', '*'))
+# => ['/path/to/tmp/dir/extracted_file_1', '/path/to/tmp/dir/extracted_file_2', '/path/to/tmp/dir/extracted_file_3']
+module BulkImports
+ class ArchiveExtractionService
+ include Gitlab::ImportExport::CommandLineUtil
+
+ def initialize(tmpdir:, filename:)
+ @tmpdir = tmpdir
+ @filename = filename
+ @filepath = File.join(@tmpdir, @filename)
+ end
+
+ def execute
+ validate_filepath
+ validate_tmpdir
+ validate_symlink
+
+ extract_archive
+ remove_symlinks
+ tmpdir
+ end
+
+ private
+
+ attr_reader :tmpdir, :filename, :filepath
+
+ def validate_filepath
+ Gitlab::Utils.check_path_traversal!(filepath)
+ end
+
+ def validate_tmpdir
+ raise(BulkImports::Error, 'Invalid target directory') unless File.expand_path(tmpdir).start_with?(Dir.tmpdir)
+ end
+
+ def validate_symlink
+ raise(BulkImports::Error, 'Invalid file') if symlink?(filepath)
+ end
+
+ def symlink?(filepath)
+ File.lstat(filepath).symlink?
+ end
+
+ def extract_archive
+ untar_xf(archive: filepath, dir: tmpdir)
+ end
+
+ def extracted_files
+ Dir.glob(File.join(tmpdir, '**', '*'))
+ end
+
+ def remove_symlinks
+ extracted_files.each do |path|
+ FileUtils.rm(path) if symlink?(path)
+ end
+ end
+ end
+end
diff --git a/app/services/packages/npm/create_package_service.rb b/app/services/packages/npm/create_package_service.rb
index 655616c3a28..76a7f3bdc72 100644
--- a/app/services/packages/npm/create_package_service.rb
+++ b/app/services/packages/npm/create_package_service.rb
@@ -70,10 +70,24 @@ module Packages
end
end
+ # TODO (technical debt): Extract the package size calculation to its own component and unit test it separately.
+ def calculated_package_file_size
+ strong_memoize(:calculated_package_file_size) do
+ # This calculation is based on:
+ # 1. 4 chars in a Base64 encoded string are 3 bytes in the original string. Meaning 1 char is 0.75 bytes.
+ # 2. The encoded string may have 1 or 2 extra '=' chars used for padding. Each padding char means 1 byte less in the original string.
+ # Reference:
+ # - https://blog.aaronlenoir.com/2017/11/10/get-original-length-from-base-64-string/
+ # - https://en.wikipedia.org/wiki/Base64#Decoding_Base64_with_padding
+ encoded_data = attachment['data']
+ ((encoded_data.length * 0.75 ) - encoded_data[-2..].count('=')).to_i
+ end
+ end
+
def file_params
{
file: CarrierWaveStringFile.new(Base64.decode64(attachment['data'])),
- size: attachment['length'],
+ size: calculated_package_file_size,
file_sha1: version_data[:dist][:shasum],
file_name: package_file_name,
build: params[:build]
@@ -86,7 +100,7 @@ module Packages
end
def file_size_exceeded?
- project.actual_limits.exceeded?(:npm_max_file_size, attachment['length'].to_i)
+ project.actual_limits.exceeded?(:npm_max_file_size, calculated_package_file_size)
end
end
end
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index e5ac9da37c6..f2961d825a0 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -291,6 +291,15 @@
:weight: 1
:idempotent: true
:tags: []
+- :name: cronjob:dependency_proxy_cleanup_dependency_proxy
+ :worker_name: DependencyProxy::CleanupDependencyProxyWorker
+ :feature_category: :dependency_proxy
+ :has_external_dependencies:
+ :urgency: :low
+ :resource_boundary: :unknown
+ :weight: 1
+ :idempotent: true
+ :tags: []
- :name: cronjob:dependency_proxy_image_ttl_group_policy
:worker_name: DependencyProxy::ImageTtlGroupPolicyWorker
:feature_category: :dependency_proxy
diff --git a/app/workers/concerns/dependency_proxy/expireable.rb b/app/workers/concerns/dependency_proxy/expireable.rb
new file mode 100644
index 00000000000..9650ac85c6c
--- /dev/null
+++ b/app/workers/concerns/dependency_proxy/expireable.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module DependencyProxy
+ module Expireable
+ extend ActiveSupport::Concern
+
+ UPDATE_BATCH_SIZE = 100
+
+ private
+
+ def expire_artifacts(collection)
+ collection.each_batch(of: UPDATE_BATCH_SIZE) do |batch|
+ batch.update_all(status: :expired)
+ end
+ end
+ end
+end
diff --git a/app/workers/dependency_proxy/cleanup_dependency_proxy_worker.rb b/app/workers/dependency_proxy/cleanup_dependency_proxy_worker.rb
new file mode 100644
index 00000000000..d77c782267a
--- /dev/null
+++ b/app/workers/dependency_proxy/cleanup_dependency_proxy_worker.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module DependencyProxy
+ class CleanupDependencyProxyWorker
+ include ApplicationWorker
+ include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
+
+ data_consistency :always
+ idempotent!
+
+ feature_category :dependency_proxy
+
+ def perform
+ enqueue_blob_cleanup_job if DependencyProxy::Blob.expired.any?
+ enqueue_manifest_cleanup_job if DependencyProxy::Manifest.expired.any?
+ end
+
+ private
+
+ def enqueue_blob_cleanup_job
+ DependencyProxy::CleanupBlobWorker.perform_with_capacity
+ end
+
+ def enqueue_manifest_cleanup_job
+ DependencyProxy::CleanupManifestWorker.perform_with_capacity
+ end
+ end
+end
diff --git a/app/workers/dependency_proxy/image_ttl_group_policy_worker.rb b/app/workers/dependency_proxy/image_ttl_group_policy_worker.rb
index 6a1de00ce80..3de2364fc71 100644
--- a/app/workers/dependency_proxy/image_ttl_group_policy_worker.rb
+++ b/app/workers/dependency_proxy/image_ttl_group_policy_worker.rb
@@ -4,20 +4,19 @@ module DependencyProxy
class ImageTtlGroupPolicyWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
+ include DependencyProxy::Expireable
data_consistency :always
feature_category :dependency_proxy
- UPDATE_BATCH_SIZE = 100
-
def perform
DependencyProxy::ImageTtlGroupPolicy.enabled.each do |policy|
qualified_blobs = policy.group.dependency_proxy_blobs.active.read_before(policy.ttl)
qualified_manifests = policy.group.dependency_proxy_manifests.active.read_before(policy.ttl)
- enqueue_blob_cleanup_job if expire_artifacts(qualified_blobs, DependencyProxy::Blob)
- enqueue_manifest_cleanup_job if expire_artifacts(qualified_manifests, DependencyProxy::Manifest)
+ expire_artifacts(qualified_blobs)
+ expire_artifacts(qualified_manifests)
end
log_counts
@@ -25,25 +24,6 @@ module DependencyProxy
private
- def expire_artifacts(artifacts, model)
- rows_updated = false
-
- artifacts.each_batch(of: UPDATE_BATCH_SIZE) do |batch|
- rows = batch.update_all(status: :expired)
- rows_updated ||= rows > 0
- end
-
- rows_updated
- end
-
- def enqueue_blob_cleanup_job
- DependencyProxy::CleanupBlobWorker.perform_with_capacity
- end
-
- def enqueue_manifest_cleanup_job
- DependencyProxy::CleanupManifestWorker.perform_with_capacity
- end
-
def log_counts
use_replica_if_available do
expired_blob_count = DependencyProxy::Blob.expired.count
diff --git a/app/workers/purge_dependency_proxy_cache_worker.rb b/app/workers/purge_dependency_proxy_cache_worker.rb
index 615fa81f28e..c0ddf190210 100644
--- a/app/workers/purge_dependency_proxy_cache_worker.rb
+++ b/app/workers/purge_dependency_proxy_cache_worker.rb
@@ -2,6 +2,7 @@
class PurgeDependencyProxyCacheWorker
include ApplicationWorker
+ include DependencyProxy::Expireable
data_consistency :always
@@ -12,21 +13,14 @@ class PurgeDependencyProxyCacheWorker
queue_namespace :dependency_proxy
feature_category :dependency_proxy
- UPDATE_BATCH_SIZE = 100
-
def perform(current_user_id, group_id)
@current_user = User.find_by_id(current_user_id)
@group = Group.find_by_id(group_id)
return unless valid?
- @group.dependency_proxy_blobs.each_batch(of: UPDATE_BATCH_SIZE) do |batch|
- batch.update_all(status: :expired)
- end
-
- @group.dependency_proxy_manifests.each_batch(of: UPDATE_BATCH_SIZE) do |batch|
- batch.update_all(status: :expired)
- end
+ expire_artifacts(@group.dependency_proxy_blobs)
+ expire_artifacts(@group.dependency_proxy_manifests)
end
private
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 6444215421d..2587347719a 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -534,6 +534,9 @@ Settings.cron_jobs['container_expiration_policy_worker']['job_class'] = 'Contain
Settings.cron_jobs['image_ttl_group_policy_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['image_ttl_group_policy_worker']['cron'] ||= '40 0 * * *'
Settings.cron_jobs['image_ttl_group_policy_worker']['job_class'] = 'DependencyProxy::ImageTtlGroupPolicyWorker'
+Settings.cron_jobs['cleanup_dependency_proxy_worker'] ||= Settingslogic.new({})
+Settings.cron_jobs['cleanup_dependency_proxy_worker']['cron'] ||= '20 3,15 * * *'
+Settings.cron_jobs['cleanup_dependency_proxy_worker']['job_class'] = 'DependencyProxy::CleanupDependencyProxyWorker'
Settings.cron_jobs['x509_issuer_crl_check_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['x509_issuer_crl_check_worker']['cron'] ||= '30 1 * * *'
Settings.cron_jobs['x509_issuer_crl_check_worker']['job_class'] = 'X509IssuerCrlCheckWorker'
diff --git a/doc/api/dependency_proxy.md b/doc/api/dependency_proxy.md
index 535c6607cad..5401c007c0d 100644
--- a/doc/api/dependency_proxy.md
+++ b/doc/api/dependency_proxy.md
@@ -11,7 +11,8 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/11631) in GitLab 12.10.
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/273655) from GitLab Premium to GitLab Free in 13.6.
-Deletes the cached manifests and blobs for a group. This endpoint requires the [Owner role](../user/permissions.md)
+Schedules for deletion the cached manifests and blobs for a group. This endpoint requires the
+[Owner role](../user/permissions.md)
for the group.
```plaintext
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 67f0b7af7a9..887c76941cf 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -613,6 +613,7 @@ module API
source_project = Project.find_by_id(params[:project_id])
not_found!('Project') unless source_project && can?(current_user, :read_project, source_project)
+ forbidden!('Project') unless source_project && can?(current_user, :admin_project_member, source_project)
result = ::Members::ImportProjectTeamService.new(current_user, params).execute
diff --git a/lib/bulk_imports/common/pipelines/uploads_pipeline.rb b/lib/bulk_imports/common/pipelines/uploads_pipeline.rb
index 49c16209661..2ac4e533c1d 100644
--- a/lib/bulk_imports/common/pipelines/uploads_pipeline.rb
+++ b/lib/bulk_imports/common/pipelines/uploads_pipeline.rb
@@ -5,16 +5,16 @@ module BulkImports
module Pipelines
class UploadsPipeline
include Pipeline
- include Gitlab::ImportExport::CommandLineUtil
- FILENAME = 'uploads.tar.gz'
AVATAR_PATTERN = %r{.*\/#{BulkImports::UploadsExportService::AVATAR_PATH}\/(?<identifier>.*)}.freeze
AvatarLoadingError = Class.new(StandardError)
- def extract(context)
- download_service(tmp_dir, context).execute
- untar_zxf(archive: File.join(tmp_dir, FILENAME), dir: tmp_dir)
+ def extract(_context)
+ download_service.execute
+ decompression_service.execute
+ extraction_service.execute
+
upload_file_paths = Dir.glob(File.join(tmp_dir, '**', '*'))
BulkImports::Pipeline::ExtractedData.new(data: upload_file_paths)
@@ -29,6 +29,7 @@ module BulkImports
return unless dynamic_path
return if File.directory?(file_path)
+ return if File.lstat(file_path).symlink?
named_captures = dynamic_path.named_captures.symbolize_keys
@@ -36,20 +37,40 @@ module BulkImports
end
def after_run(_)
- FileUtils.remove_entry(tmp_dir)
+ FileUtils.remove_entry(tmp_dir) if Dir.exist?(tmp_dir)
end
private
- def download_service(tmp_dir, context)
+ def download_service
BulkImports::FileDownloadService.new(
configuration: context.configuration,
- relative_url: context.entity.relation_download_url_path('uploads'),
+ relative_url: context.entity.relation_download_url_path(relation),
dir: tmp_dir,
- filename: FILENAME
+ filename: targz_filename
)
end
+ def decompression_service
+ BulkImports::FileDecompressionService.new(dir: tmp_dir, filename: targz_filename)
+ end
+
+ def extraction_service
+ BulkImports::ArchiveExtractionService.new(tmpdir: tmp_dir, filename: tar_filename)
+ end
+
+ def relation
+ BulkImports::FileTransfer::BaseConfig::UPLOADS_RELATION
+ end
+
+ def tar_filename
+ "#{relation}.tar"
+ end
+
+ def targz_filename
+ "#{tar_filename}.gz"
+ end
+
def tmp_dir
@tmp_dir ||= Dir.mktmpdir('bulk_imports')
end
diff --git a/lib/gitlab/import_export/command_line_util.rb b/lib/gitlab/import_export/command_line_util.rb
index fdc4c22001f..3da9083e743 100644
--- a/lib/gitlab/import_export/command_line_util.rb
+++ b/lib/gitlab/import_export/command_line_util.rb
@@ -18,6 +18,10 @@ module Gitlab
tar_with_options(archive: archive, dir: dir, options: 'cf')
end
+ def untar_xf(archive:, dir:)
+ untar_with_options(archive: archive, dir: dir, options: 'xf')
+ end
+
def gzip(dir:, filename:)
gzip_with_options(dir: dir, filename: filename)
end
diff --git a/lib/gitlab/legacy_github_import/client.rb b/lib/gitlab/legacy_github_import/client.rb
index 48a8e0ce6d7..7a9dae3a3de 100644
--- a/lib/gitlab/legacy_github_import/client.rb
+++ b/lib/gitlab/legacy_github_import/client.rb
@@ -48,10 +48,11 @@ module Gitlab
)
end
- def authorize_url(redirect_uri)
+ def authorize_url(redirect_uri, state = nil)
client.auth_code.authorize_url({
redirect_uri: redirect_uri,
- scope: "repo, user, user:email"
+ scope: "repo, user, user:email",
+ state: state
})
end
diff --git a/lib/gitlab/url_blocker.rb b/lib/gitlab/url_blocker.rb
index 2c5d76ba41d..f092e03046a 100644
--- a/lib/gitlab/url_blocker.rb
+++ b/lib/gitlab/url_blocker.rb
@@ -252,13 +252,13 @@ module Gitlab
def internal_web?(uri)
uri.scheme == config.gitlab.protocol &&
uri.hostname == config.gitlab.host &&
- (uri.port.blank? || uri.port == config.gitlab.port)
+ get_port(uri) == config.gitlab.port
end
def internal_shell?(uri)
uri.scheme == 'ssh' &&
uri.hostname == config.gitlab_shell.ssh_host &&
- (uri.port.blank? || uri.port == config.gitlab_shell.ssh_port)
+ get_port(uri) == config.gitlab_shell.ssh_port
end
def domain_allowed?(uri)
diff --git a/spec/controllers/dashboard/projects_controller_spec.rb b/spec/controllers/dashboard/projects_controller_spec.rb
index 9b13025cbe3..743759d5023 100644
--- a/spec/controllers/dashboard/projects_controller_spec.rb
+++ b/spec/controllers/dashboard/projects_controller_spec.rb
@@ -8,10 +8,6 @@ RSpec.describe Dashboard::ProjectsController, :aggregate_failures do
let_it_be(:user) { create(:user) }
describe '#index' do
- context 'user not logged in' do
- it_behaves_like 'authenticates sessionless user', :index, :atom
- end
-
context 'user logged in' do
let_it_be(:project) { create(:project, name: 'Project 1') }
let_it_be(:project2) { create(:project, name: 'Project Two') }
diff --git a/spec/controllers/dashboard_controller_spec.rb b/spec/controllers/dashboard_controller_spec.rb
index c838affa239..8fae617ea65 100644
--- a/spec/controllers/dashboard_controller_spec.rb
+++ b/spec/controllers/dashboard_controller_spec.rb
@@ -72,9 +72,6 @@ RSpec.describe DashboardController do
end
end
- it_behaves_like 'authenticates sessionless user', :issues, :atom, author_id: User.first
- it_behaves_like 'authenticates sessionless user', :issues_calendar, :ics
-
describe "#check_filters_presence!" do
let(:user) { create(:user) }
diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb
index a7625e65603..62171528695 100644
--- a/spec/controllers/groups_controller_spec.rb
+++ b/spec/controllers/groups_controller_spec.rb
@@ -1209,26 +1209,6 @@ RSpec.describe GroupsController, factory_default: :keep do
end
end
- context 'token authentication' do
- it_behaves_like 'authenticates sessionless user', :show, :atom, public: true do
- before do
- default_params.merge!(id: group)
- end
- end
-
- it_behaves_like 'authenticates sessionless user', :issues, :atom, public: true do
- before do
- default_params.merge!(id: group, author_id: user.id)
- end
- end
-
- it_behaves_like 'authenticates sessionless user', :issues_calendar, :ics, public: true do
- before do
- default_params.merge!(id: group)
- end
- end
- end
-
describe 'external authorization' do
before do
group.add_owner(user)
diff --git a/spec/controllers/import/github_controller_spec.rb b/spec/controllers/import/github_controller_spec.rb
index d82fff1f7ae..fd380f9b763 100644
--- a/spec/controllers/import/github_controller_spec.rb
+++ b/spec/controllers/import/github_controller_spec.rb
@@ -6,6 +6,7 @@ RSpec.describe Import::GithubController do
include ImportSpecHelper
let(:provider) { :github }
+ let(:new_import_url) { public_send("new_import_#{provider}_url") }
include_context 'a GitHub-ish import controller'
@@ -50,13 +51,37 @@ RSpec.describe Import::GithubController do
stub_omniauth_provider('github')
end
- it "updates access token" do
- token = "asdasd12345"
+ context "when auth state param is missing from session" do
+ it "reports an error" do
+ get :callback
- get :callback
+ expect(controller).to redirect_to(new_import_url)
+ expect(flash[:alert]).to eq('Access denied to your GitHub account.')
+ end
+ end
+
+ context "when auth state param is present in session" do
+ let(:valid_auth_state) { "secret-state" }
+
+ before do
+ session[:github_auth_state_key] = valid_auth_state
+ end
- expect(session[:github_access_token]).to eq(token)
- expect(controller).to redirect_to(status_import_github_url)
+ it "updates access token if state param is valid" do
+ token = "asdasd12345"
+
+ get :callback, params: { state: valid_auth_state }
+
+ expect(session[:github_access_token]).to eq(token)
+ expect(controller).to redirect_to(status_import_github_url)
+ end
+
+ it "reports an error if state param is invalid" do
+ get :callback, params: { state: "different-state" }
+
+ expect(controller).to redirect_to(new_import_url)
+ expect(flash[:alert]).to eq('Access denied to your GitHub account.')
+ end
end
end
@@ -71,8 +96,6 @@ RSpec.describe Import::GithubController do
end
context 'when OAuth config is missing' do
- let(:new_import_url) { public_send("new_import_#{provider}_url") }
-
before do
allow(controller).to receive(:oauth_config).and_return(nil)
end
@@ -108,6 +131,16 @@ RSpec.describe Import::GithubController do
get :status
end
+
+ it 'gets authorization url using legacy client' do
+ allow(controller).to receive(:logged_in_with_provider?).and_return(true)
+ expect(controller).to receive(:go_to_provider_for_permissions).and_call_original
+ expect_next_instance_of(Gitlab::LegacyGithubImport::Client) do |client|
+ expect(client).to receive(:authorize_url).and_call_original
+ end
+
+ get :new
+ end
end
context 'when feature remove_legacy_github_client is enabled' do
@@ -130,6 +163,16 @@ RSpec.describe Import::GithubController do
get :status
end
+ it 'gets authorization url using oauth client' do
+ allow(controller).to receive(:logged_in_with_provider?).and_return(true)
+ expect(controller).to receive(:go_to_provider_for_permissions).and_call_original
+ expect_next_instance_of(OAuth2::Client) do |client|
+ expect(client.auth_code).to receive(:authorize_url).and_call_original
+ end
+
+ get :new
+ end
+
context 'pagination' do
context 'when no page is specified' do
it 'requests first page' do
diff --git a/spec/controllers/projects/commits_controller_spec.rb b/spec/controllers/projects/commits_controller_spec.rb
index a8e71d73beb..fd840fafa61 100644
--- a/spec/controllers/projects/commits_controller_spec.rb
+++ b/spec/controllers/projects/commits_controller_spec.rb
@@ -162,27 +162,4 @@ RSpec.describe Projects::CommitsController do
end
end
end
-
- context 'token authentication' do
- context 'public project' do
- it_behaves_like 'authenticates sessionless user', :show, :atom, { public: true, ignore_incrementing: true } do
- before do
- public_project = create(:project, :repository, :public)
-
- default_params.merge!(namespace_id: public_project.namespace, project_id: public_project, id: "master.atom")
- end
- end
- end
-
- context 'private project' do
- it_behaves_like 'authenticates sessionless user', :show, :atom, { public: false, ignore_incrementing: true } do
- before do
- private_project = create(:project, :repository, :private)
- private_project.add_maintainer(user)
-
- default_params.merge!(namespace_id: private_project.namespace, project_id: private_project, id: "master.atom")
- end
- end
- end
- end
end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 763c3e43e27..d91c1b0d29a 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -1948,40 +1948,4 @@ RSpec.describe Projects::IssuesController do
end
end
end
-
- context 'private project with token authentication' do
- let_it_be(:private_project) { create(:project, :private) }
-
- it_behaves_like 'authenticates sessionless user', :index, :atom, ignore_incrementing: true do
- before do
- default_params.merge!(project_id: private_project, namespace_id: private_project.namespace)
-
- private_project.add_maintainer(user)
- end
- end
-
- it_behaves_like 'authenticates sessionless user', :calendar, :ics, ignore_incrementing: true do
- before do
- default_params.merge!(project_id: private_project, namespace_id: private_project.namespace)
-
- private_project.add_maintainer(user)
- end
- end
- end
-
- context 'public project with token authentication' do
- let_it_be(:public_project) { create(:project, :public) }
-
- it_behaves_like 'authenticates sessionless user', :index, :atom, public: true do
- before do
- default_params.merge!(project_id: public_project, namespace_id: public_project.namespace)
- end
- end
-
- it_behaves_like 'authenticates sessionless user', :calendar, :ics, public: true do
- before do
- default_params.merge!(project_id: public_project, namespace_id: public_project.namespace)
- end
- end
- end
end
diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb
index 79da18f2d6d..4d99afb6b1f 100644
--- a/spec/controllers/projects/raw_controller_spec.rb
+++ b/spec/controllers/projects/raw_controller_spec.rb
@@ -193,6 +193,8 @@ RSpec.describe Projects::RawController do
let_it_be(:user) { create(:user, static_object_token: 'very-secure-token') }
let_it_be(:file_path) { 'master/README.md' }
+ let(:token) { user.static_object_token }
+
before do
project.add_developer(user)
end
@@ -207,17 +209,46 @@ RSpec.describe Projects::RawController do
end
context 'when a token param is present' do
+ subject(:execute_raw_request_with_token_in_params) do
+ execute_raw_requests(requests: 1, project: project, file_path: file_path, token: token)
+ end
+
context 'when token is correct' do
it 'calls the action normally' do
- execute_raw_requests(requests: 1, project: project, file_path: file_path, token: user.static_object_token)
+ execute_raw_request_with_token_in_params
expect(response).to have_gitlab_http_status(:ok)
end
+
+ context 'when user with expired password' do
+ let_it_be(:user) { create(:user, password_expires_at: 2.minutes.ago) }
+
+ it 'redirects to sign in page' do
+ execute_raw_request_with_token_in_params
+
+ expect(response).to have_gitlab_http_status(:found)
+ expect(response.location).to end_with('/users/sign_in')
+ end
+ end
+
+ context 'when password expiration is not applicable' do
+ context 'when ldap user' do
+ let_it_be(:user) { create(:omniauth_user, provider: 'ldap', password_expires_at: 2.minutes.ago) }
+
+ it 'calls the action normally' do
+ execute_raw_request_with_token_in_params
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
end
context 'when token is incorrect' do
+ let(:token) { 'foobar' }
+
it 'redirects to sign in page' do
- execute_raw_requests(requests: 1, project: project, file_path: file_path, token: 'foobar')
+ execute_raw_request_with_token_in_params
expect(response).to have_gitlab_http_status(:found)
expect(response.location).to end_with('/users/sign_in')
@@ -226,19 +257,47 @@ RSpec.describe Projects::RawController do
end
context 'when a token header is present' do
+ subject(:execute_raw_request_with_token_in_headers) do
+ request.headers['X-Gitlab-Static-Object-Token'] = token
+ execute_raw_requests(requests: 1, project: project, file_path: file_path)
+ end
+
context 'when token is correct' do
it 'calls the action normally' do
- request.headers['X-Gitlab-Static-Object-Token'] = user.static_object_token
- execute_raw_requests(requests: 1, project: project, file_path: file_path)
+ execute_raw_request_with_token_in_headers
expect(response).to have_gitlab_http_status(:ok)
end
+
+ context 'when user with expired password' do
+ let_it_be(:user) { create(:user, password_expires_at: 2.minutes.ago) }
+
+ it 'redirects to sign in page' do
+ execute_raw_request_with_token_in_headers
+
+ expect(response).to have_gitlab_http_status(:found)
+ expect(response.location).to end_with('/users/sign_in')
+ end
+ end
+
+ context 'when password expiration is not applicable' do
+ context 'when ldap user' do
+ let_it_be(:user) { create(:omniauth_user, provider: 'ldap', password_expires_at: 2.minutes.ago) }
+
+ it 'calls the action normally' do
+ execute_raw_request_with_token_in_headers
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
end
context 'when token is incorrect' do
+ let(:token) { 'foobar' }
+
it 'redirects to sign in page' do
- request.headers['X-Gitlab-Static-Object-Token'] = 'foobar'
- execute_raw_requests(requests: 1, project: project, file_path: file_path)
+ execute_raw_request_with_token_in_headers
expect(response).to have_gitlab_http_status(:found)
expect(response.location).to end_with('/users/sign_in')
diff --git a/spec/controllers/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb
index b7eef3812a4..f7cf55d8a95 100644
--- a/spec/controllers/projects/repositories_controller_spec.rb
+++ b/spec/controllers/projects/repositories_controller_spec.rb
@@ -178,6 +178,29 @@ RSpec.describe Projects::RepositoriesController do
expect(response).to have_gitlab_http_status(:ok)
end
+
+ context 'when user with expired password' do
+ let_it_be(:user) { create(:user, password_expires_at: 2.minutes.ago) }
+
+ it 'redirects to sign in page' do
+ get :archive, params: { namespace_id: project.namespace, project_id: project, id: 'master', token: user.static_object_token }, format: 'zip'
+
+ expect(response).to have_gitlab_http_status(:found)
+ expect(response.location).to end_with('/users/sign_in')
+ end
+ end
+
+ context 'when password expiration is not applicable' do
+ context 'when ldap user' do
+ let_it_be(:user) { create(:omniauth_user, provider: 'ldap', password_expires_at: 2.minutes.ago) }
+
+ it 'calls the action normally' do
+ get :archive, params: { namespace_id: project.namespace, project_id: project, id: 'master', token: user.static_object_token }, format: 'zip'
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
end
context 'when token is incorrect' do
@@ -197,6 +220,31 @@ RSpec.describe Projects::RepositoriesController do
expect(response).to have_gitlab_http_status(:ok)
end
+
+ context 'when user with expired password' do
+ let_it_be(:user) { create(:user, password_expires_at: 2.minutes.ago) }
+
+ it 'redirects to sign in page' do
+ request.headers['X-Gitlab-Static-Object-Token'] = user.static_object_token
+ get :archive, params: { namespace_id: project.namespace, project_id: project, id: 'master' }, format: 'zip'
+
+ expect(response).to have_gitlab_http_status(:found)
+ expect(response.location).to end_with('/users/sign_in')
+ end
+ end
+
+ context 'when password expiration is not applicable' do
+ context 'when ldap user' do
+ let_it_be(:user) { create(:omniauth_user, provider: 'ldap', password_expires_at: 2.minutes.ago) }
+
+ it 'calls the action normally' do
+ request.headers['X-Gitlab-Static-Object-Token'] = user.static_object_token
+ get :archive, params: { namespace_id: project.namespace, project_id: project, id: 'master' }, format: 'zip'
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
end
context 'when token is incorrect' do
diff --git a/spec/controllers/projects/tags_controller_spec.rb b/spec/controllers/projects/tags_controller_spec.rb
index 0045c0a484b..9823c36cb86 100644
--- a/spec/controllers/projects/tags_controller_spec.rb
+++ b/spec/controllers/projects/tags_controller_spec.rb
@@ -117,28 +117,6 @@ RSpec.describe Projects::TagsController do
end
end
- context 'private project with token authentication' do
- let(:private_project) { create(:project, :repository, :private) }
-
- it_behaves_like 'authenticates sessionless user', :index, :atom, ignore_incrementing: true do
- before do
- default_params.merge!(project_id: private_project, namespace_id: private_project.namespace)
-
- private_project.add_maintainer(user)
- end
- end
- end
-
- context 'public project with token authentication' do
- let(:public_project) { create(:project, :repository, :public) }
-
- it_behaves_like 'authenticates sessionless user', :index, :atom, public: true do
- before do
- default_params.merge!(project_id: public_project, namespace_id: public_project.namespace)
- end
- end
- end
-
describe 'POST #create' do
before do
project.add_developer(user)
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index fd0f9985392..7ebd86640ad 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -1568,28 +1568,6 @@ RSpec.describe ProjectsController do
end
end
- context 'private project with token authentication' do
- let_it_be(:private_project) { create(:project, :private) }
-
- it_behaves_like 'authenticates sessionless user', :show, :atom, ignore_incrementing: true do
- before do
- default_params.merge!(id: private_project, namespace_id: private_project.namespace)
-
- private_project.add_maintainer(user)
- end
- end
- end
-
- context 'public project with token authentication' do
- let_it_be(:public_project) { create(:project, :public) }
-
- it_behaves_like 'authenticates sessionless user', :show, :atom, public: true do
- before do
- default_params.merge!(id: public_project, namespace_id: public_project.namespace)
- end
- end
- end
-
context 'GET show.atom' do
let_it_be(:public_project) { create(:project, :public) }
let_it_be(:event) { create(:event, :commented, project: public_project, target: create(:note, project: public_project)) }
diff --git a/spec/fixtures/symlink_export.tar b/spec/fixtures/symlink_export.tar
new file mode 100644
index 00000000000..111874c729c
--- /dev/null
+++ b/spec/fixtures/symlink_export.tar
Binary files differ
diff --git a/spec/frontend/behaviors/gl_emoji_spec.js b/spec/frontend/behaviors/gl_emoji_spec.js
index 0f4e2e08dbd..cac1ea67cf5 100644
--- a/spec/frontend/behaviors/gl_emoji_spec.js
+++ b/spec/frontend/behaviors/gl_emoji_spec.js
@@ -97,6 +97,18 @@ describe('gl_emoji', () => {
});
});
+ it('escapes gl-emoji name', async () => {
+ const glEmojiElement = markupToDomElement(
+ "<gl-emoji data-name='&#34;x=&#34y&#34 onload=&#34;alert(document.location.href)&#34;' data-unicode-version='x'>abc</gl-emoji>",
+ );
+
+ await waitForPromises();
+
+ expect(glEmojiElement.outerHTML).toBe(
+ '<gl-emoji data-name="&quot;x=&quot;y&quot; onload=&quot;alert(document.location.href)&quot;" data-unicode-version="x"><img class="emoji" title=":&quot;x=&quot;y&quot; onload=&quot;alert(document.location.href)&quot;:" alt=":&quot;x=&quot;y&quot; onload=&quot;alert(document.location.href)&quot;:" src="/-/emojis/2/grey_question.png" width="20" height="20" align="absmiddle"></gl-emoji>',
+ );
+ });
+
it('Adds sprite CSS if emojis are not supported', async () => {
const testPath = '/test-path.css';
jest.spyOn(EmojiUnicodeSupport, 'default').mockReturnValue(false);
diff --git a/spec/graphql/resolvers/issues_resolver_spec.rb b/spec/graphql/resolvers/issues_resolver_spec.rb
index 3c892214aaf..dc717b113c1 100644
--- a/spec/graphql/resolvers/issues_resolver_spec.rb
+++ b/spec/graphql/resolvers/issues_resolver_spec.rb
@@ -560,13 +560,13 @@ RSpec.describe Resolvers::IssuesResolver do
end
it 'finds a specific issue with iid', :request_store do
- result = batch_sync(max_queries: 5) { resolve_issues(iid: issue1.iid).to_a }
+ result = batch_sync(max_queries: 6) { resolve_issues(iid: issue1.iid).to_a }
expect(result).to contain_exactly(issue1)
end
it 'batches queries that only include IIDs', :request_store do
- result = batch_sync(max_queries: 5) do
+ result = batch_sync(max_queries: 6) do
[issue1, issue2]
.map { |issue| resolve_issues(iid: issue.iid.to_s) }
.flat_map(&:to_a)
@@ -576,7 +576,7 @@ RSpec.describe Resolvers::IssuesResolver do
end
it 'finds a specific issue with iids', :request_store do
- result = batch_sync(max_queries: 5) do
+ result = batch_sync(max_queries: 6) do
resolve_issues(iids: [issue1.iid]).to_a
end
diff --git a/spec/initializers/action_cable_subscription_adapter_identifier_spec.rb b/spec/initializers/action_cable_subscription_adapter_identifier_spec.rb
index 12988b851ef..074df9adc21 100644
--- a/spec/initializers/action_cable_subscription_adapter_identifier_spec.rb
+++ b/spec/initializers/action_cable_subscription_adapter_identifier_spec.rb
@@ -4,6 +4,12 @@ require 'spec_helper'
RSpec.describe 'ActionCableSubscriptionAdapterIdentifier override' do
describe '#identifier' do
+ let!(:original_config) { ::ActionCable::Server::Base.config.cable }
+
+ after do
+ ::ActionCable::Server::Base.config.cable = original_config
+ end
+
context 'when id key is nil on cable.yml' do
it 'does not override server config id with action cable pid' do
config = {
diff --git a/spec/lib/bulk_imports/common/pipelines/uploads_pipeline_spec.rb b/spec/lib/bulk_imports/common/pipelines/uploads_pipeline_spec.rb
index 0f6238e10dc..3b5ea131d0d 100644
--- a/spec/lib/bulk_imports/common/pipelines/uploads_pipeline_spec.rb
+++ b/spec/lib/bulk_imports/common/pipelines/uploads_pipeline_spec.rb
@@ -70,8 +70,10 @@ RSpec.describe BulkImports::Common::Pipelines::UploadsPipeline do
describe '#extract' do
it 'downloads & extracts upload paths' do
allow(Dir).to receive(:mktmpdir).and_return(tmpdir)
- expect(pipeline).to receive(:untar_zxf)
- file_download_service = instance_double("BulkImports::FileDownloadService")
+
+ download_service = instance_double("BulkImports::FileDownloadService")
+ decompression_service = instance_double("BulkImports::FileDecompressionService")
+ extraction_service = instance_double("BulkImports::ArchiveExtractionService")
expect(BulkImports::FileDownloadService)
.to receive(:new)
@@ -80,9 +82,13 @@ RSpec.describe BulkImports::Common::Pipelines::UploadsPipeline do
relative_url: "/#{entity.pluralized_name}/test/export_relations/download?relation=uploads",
dir: tmpdir,
filename: 'uploads.tar.gz')
- .and_return(file_download_service)
+ .and_return(download_service)
+ expect(BulkImports::FileDecompressionService).to receive(:new).with(dir: tmpdir, filename: 'uploads.tar.gz').and_return(decompression_service)
+ expect(BulkImports::ArchiveExtractionService).to receive(:new).with(tmpdir: tmpdir, filename: 'uploads.tar').and_return(extraction_service)
- expect(file_download_service).to receive(:execute)
+ expect(download_service).to receive(:execute)
+ expect(decompression_service).to receive(:execute)
+ expect(extraction_service).to receive(:execute)
extracted_data = pipeline.extract(context)
@@ -106,6 +112,16 @@ RSpec.describe BulkImports::Common::Pipelines::UploadsPipeline do
expect { pipeline.load(context, uploads_dir_path) }.not_to change { portable.uploads.count }
end
end
+
+ context 'when path is a symlink' do
+ it 'does not upload the file' do
+ symlink = File.join(tmpdir, 'symlink')
+
+ FileUtils.ln_s(File.join(tmpdir, upload_file_path), symlink)
+
+ expect { pipeline.load(context, symlink) }.not_to change { portable.uploads.count }
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/import_export/command_line_util_spec.rb b/spec/lib/gitlab/import_export/command_line_util_spec.rb
index 59c4e1083ae..31512259bb1 100644
--- a/spec/lib/gitlab/import_export/command_line_util_spec.rb
+++ b/spec/lib/gitlab/import_export/command_line_util_spec.rb
@@ -101,4 +101,39 @@ RSpec.describe Gitlab::ImportExport::CommandLineUtil do
end
end
end
+
+ describe '#untar_xf' do
+ let(:archive_dir) { Dir.mktmpdir }
+
+ after do
+ FileUtils.remove_entry(archive_dir)
+ end
+
+ it 'extracts archive without decompression' do
+ filename = 'archive.tar.gz'
+ archive_file = File.join(archive_dir, 'archive.tar')
+
+ FileUtils.copy_file(archive, File.join(archive_dir, filename))
+ subject.gunzip(dir: archive_dir, filename: filename)
+
+ result = subject.untar_xf(archive: archive_file, dir: archive_dir)
+
+ expect(result).to eq(true)
+ expect(File.exist?(archive_file)).to eq(true)
+ expect(File.exist?(File.join(archive_dir, 'project.json'))).to eq(true)
+ expect(Dir.exist?(File.join(archive_dir, 'uploads'))).to eq(true)
+ end
+
+ context 'when something goes wrong' do
+ it 'raises an error' do
+ expect(Gitlab::Popen).to receive(:popen).and_return(['Error', 1])
+
+ klass = Class.new do
+ include Gitlab::ImportExport::CommandLineUtil
+ end.new
+
+ expect { klass.untar_xf(archive: 'test', dir: 'test') }.to raise_error(Gitlab::ImportExport::Error, 'System call failed')
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/url_blocker_spec.rb b/spec/lib/gitlab/url_blocker_spec.rb
index e076815c4f6..0713475d59b 100644
--- a/spec/lib/gitlab/url_blocker_spec.rb
+++ b/spec/lib/gitlab/url_blocker_spec.rb
@@ -531,24 +531,6 @@ RSpec.describe Gitlab::UrlBlocker, :stub_invalid_dns_only do
end
end
end
-
- def stub_domain_resolv(domain, ip, port = 80, &block)
- address = instance_double(Addrinfo,
- ip_address: ip,
- ipv4_private?: true,
- ipv6_linklocal?: false,
- ipv4_loopback?: false,
- ipv6_loopback?: false,
- ipv4?: false,
- ip_port: port
- )
- allow(Addrinfo).to receive(:getaddrinfo).with(domain, port, any_args).and_return([address])
- allow(address).to receive(:ipv6_v4mapped?).and_return(false)
-
- yield
-
- allow(Addrinfo).to receive(:getaddrinfo).and_call_original
- end
end
context 'when enforce_user is' do
@@ -611,6 +593,44 @@ RSpec.describe Gitlab::UrlBlocker, :stub_invalid_dns_only do
expect(described_class).to be_blocked_url('http://foobar.x')
end
+
+ context 'when gitlab is running on a non-default port' do
+ let(:gitlab_port) { 3000 }
+
+ before do
+ stub_config(gitlab: { protocol: 'http', host: 'gitlab.local', port: gitlab_port })
+ end
+
+ it 'returns true for url targeting the wrong port' do
+ stub_domain_resolv('gitlab.local', '127.0.0.1') do
+ expect(described_class).to be_blocked_url("http://gitlab.local/foo")
+ end
+ end
+
+ it 'does not block url on gitlab port' do
+ stub_domain_resolv('gitlab.local', '127.0.0.1') do
+ expect(described_class).not_to be_blocked_url("http://gitlab.local:#{gitlab_port}/foo")
+ end
+ end
+ end
+
+ def stub_domain_resolv(domain, ip, port = 80, &block)
+ address = instance_double(Addrinfo,
+ ip_address: ip,
+ ipv4_private?: true,
+ ipv6_linklocal?: false,
+ ipv4_loopback?: false,
+ ipv6_loopback?: false,
+ ipv4?: false,
+ ip_port: port
+ )
+ allow(Addrinfo).to receive(:getaddrinfo).with(domain, port, any_args).and_return([address])
+ allow(address).to receive(:ipv6_v4mapped?).and_return(false)
+
+ yield
+
+ allow(Addrinfo).to receive(:getaddrinfo).and_call_original
+ end
end
describe '#validate_hostname' do
diff --git a/spec/models/integrations/chat_message/alert_message_spec.rb b/spec/models/integrations/chat_message/alert_message_spec.rb
index 9866b2d9185..162df1a774c 100644
--- a/spec/models/integrations/chat_message/alert_message_spec.rb
+++ b/spec/models/integrations/chat_message/alert_message_spec.rb
@@ -16,6 +16,8 @@ RSpec.describe Integrations::ChatMessage::AlertMessage do
}.merge(Gitlab::DataBuilder::Alert.build(alert))
end
+ it_behaves_like Integrations::ChatMessage
+
describe '#message' do
it 'returns the correct message' do
expect(subject.message).to eq("Alert firing in #{args[:project_name]}")
diff --git a/spec/models/integrations/chat_message/base_message_spec.rb b/spec/models/integrations/chat_message/base_message_spec.rb
index eada5d1031d..0f0ab11f2ac 100644
--- a/spec/models/integrations/chat_message/base_message_spec.rb
+++ b/spec/models/integrations/chat_message/base_message_spec.rb
@@ -31,4 +31,22 @@ RSpec.describe Integrations::ChatMessage::BaseMessage do
it { is_expected.to eq('Check this out https://gitlab-domain.com/uploads/Screenshot1.png. And this https://gitlab-domain.com/uploads/Screenshot2.png') }
end
end
+
+ describe '#strip_markup' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:input, :output) do
+ nil | nil
+ '' | ''
+ '[label](url)' | 'label(url)'
+ '<url|label>' | 'urllabel'
+ '<a href="url">label</a>' | 'a href="url"label/a'
+ end
+
+ with_them do
+ it 'returns the expected output' do
+ expect(base_message.send(:strip_markup, input)).to eq(output)
+ end
+ end
+ end
end
diff --git a/spec/models/integrations/chat_message/deployment_message_spec.rb b/spec/models/integrations/chat_message/deployment_message_spec.rb
index ff255af11a3..6bcd29c0a00 100644
--- a/spec/models/integrations/chat_message/deployment_message_spec.rb
+++ b/spec/models/integrations/chat_message/deployment_message_spec.rb
@@ -3,83 +3,79 @@
require 'spec_helper'
RSpec.describe Integrations::ChatMessage::DeploymentMessage do
- describe '#pretext' do
- it 'returns a message with the data returned by the deployment data builder' do
- environment = create(:environment, name: "myenvironment")
- project = create(:project, :repository)
- commit = project.commit('HEAD')
- deployment = create(:deployment, status: :success, environment: environment, project: project, sha: commit.sha)
- data = Gitlab::DataBuilder::Deployment.build(deployment, Time.current)
+ subject { described_class.new(args) }
+
+ let_it_be(:user) { create(:user, name: 'John Smith', username: 'smith') }
+ let_it_be(:namespace) { create(:namespace, name: 'myspace') }
+ let_it_be(:project) { create(:project, :repository, namespace: namespace, name: 'myproject') }
+ let_it_be(:commit) { project.commit('HEAD') }
+ let_it_be(:ci_build) { create(:ci_build, project: project) }
+ let_it_be(:environment) { create(:environment, name: 'myenvironment', project: project) }
+ let_it_be(:deployment) { create(:deployment, status: :success, deployable: ci_build, environment: environment, project: project, user: user, sha: commit.sha) }
+
+ let(:args) do
+ Gitlab::DataBuilder::Deployment.build(deployment, Time.current)
+ end
- message = described_class.new(data)
+ it_behaves_like Integrations::ChatMessage
- expect(message.pretext).to eq("Deploy to myenvironment succeeded")
+ describe '#pretext' do
+ it 'returns a message with the data returned by the deployment data builder' do
+ expect(subject.pretext).to eq("Deploy to myenvironment succeeded")
end
it 'returns a message for a successful deployment' do
- data = {
+ args.merge!(
status: 'success',
environment: 'production'
- }
+ )
- message = described_class.new(data)
-
- expect(message.pretext).to eq('Deploy to production succeeded')
+ expect(subject.pretext).to eq('Deploy to production succeeded')
end
it 'returns a message for a failed deployment' do
- data = {
+ args.merge!(
status: 'failed',
environment: 'production'
- }
+ )
- message = described_class.new(data)
-
- expect(message.pretext).to eq('Deploy to production failed')
+ expect(subject.pretext).to eq('Deploy to production failed')
end
it 'returns a message for a canceled deployment' do
- data = {
+ args.merge!(
status: 'canceled',
environment: 'production'
- }
-
- message = described_class.new(data)
+ )
- expect(message.pretext).to eq('Deploy to production canceled')
+ expect(subject.pretext).to eq('Deploy to production canceled')
end
it 'returns a message for a deployment to another environment' do
- data = {
+ args.merge!(
status: 'success',
environment: 'staging'
- }
-
- message = described_class.new(data)
+ )
- expect(message.pretext).to eq('Deploy to staging succeeded')
+ expect(subject.pretext).to eq('Deploy to staging succeeded')
end
it 'returns a message for a deployment with any other status' do
- data = {
+ args.merge!(
status: 'unknown',
environment: 'staging'
- }
+ )
- message = described_class.new(data)
-
- expect(message.pretext).to eq('Deploy to staging unknown')
+ expect(subject.pretext).to eq('Deploy to staging unknown')
end
it 'returns a message for a running deployment' do
- data = {
- status: 'running',
- environment: 'production'
- }
-
- message = described_class.new(data)
+ args.merge!(
+ status: 'running',
+ environment: 'production'
+ )
- expect(message.pretext).to eq('Starting deploy to production')
+ expect(subject.pretext).to eq('Starting deploy to production')
end
end
@@ -108,21 +104,11 @@ RSpec.describe Integrations::ChatMessage::DeploymentMessage do
end
it 'returns attachments with the data returned by the deployment data builder' do
- user = create(:user, name: "John Smith", username: "smith")
- namespace = create(:namespace, name: "myspace")
- project = create(:project, :repository, namespace: namespace, name: "myproject")
- commit = project.commit('HEAD')
- environment = create(:environment, name: "myenvironment", project: project)
- ci_build = create(:ci_build, project: project)
- deployment = create(:deployment, :success, deployable: ci_build, environment: environment, project: project, user: user, sha: commit.sha)
job_url = Gitlab::Routing.url_helpers.project_job_url(project, ci_build)
commit_url = Gitlab::UrlBuilder.build(deployment.commit)
user_url = Gitlab::Routing.url_helpers.user_url(user)
- data = Gitlab::DataBuilder::Deployment.build(deployment, Time.current)
- message = described_class.new(data)
-
- expect(message.attachments).to eq([{
+ expect(subject.attachments).to eq([{
text: "[myspace/myproject](#{project.web_url}) with job [##{ci_build.id}](#{job_url}) by [John Smith (smith)](#{user_url})\n[#{deployment.short_sha}](#{commit_url}): #{commit.title}",
color: "good"
}])
diff --git a/spec/models/integrations/chat_message/issue_message_spec.rb b/spec/models/integrations/chat_message/issue_message_spec.rb
index 31b80ad3169..7026a314b78 100644
--- a/spec/models/integrations/chat_message/issue_message_spec.rb
+++ b/spec/models/integrations/chat_message/issue_message_spec.rb
@@ -28,6 +28,8 @@ RSpec.describe Integrations::ChatMessage::IssueMessage do
}
end
+ it_behaves_like Integrations::ChatMessage
+
context 'without markdown' do
let(:color) { '#C95823' }
diff --git a/spec/models/integrations/chat_message/merge_message_spec.rb b/spec/models/integrations/chat_message/merge_message_spec.rb
index ed1ad6837e2..52f15667b03 100644
--- a/spec/models/integrations/chat_message/merge_message_spec.rb
+++ b/spec/models/integrations/chat_message/merge_message_spec.rb
@@ -29,6 +29,8 @@ RSpec.describe Integrations::ChatMessage::MergeMessage do
}
end
+ it_behaves_like Integrations::ChatMessage
+
context 'without markdown' do
let(:color) { '#345' }
diff --git a/spec/models/integrations/chat_message/note_message_spec.rb b/spec/models/integrations/chat_message/note_message_spec.rb
index 668c0da26ae..197df216814 100644
--- a/spec/models/integrations/chat_message/note_message_spec.rb
+++ b/spec/models/integrations/chat_message/note_message_spec.rb
@@ -19,6 +19,10 @@ RSpec.describe Integrations::ChatMessage::NoteMessage do
name: 'project_name',
url: 'http://somewhere.com'
},
+ commit: {
+ id: '5f163b2b95e6f53cbd428f5f0b103702a52b9a23',
+ message: "Added a commit message\ndetails\n123\n"
+ },
object_attributes: {
id: 10,
note: 'comment on a commit',
@@ -28,16 +32,9 @@ RSpec.describe Integrations::ChatMessage::NoteMessage do
}
end
- context 'commit notes' do
- before do
- args[:object_attributes][:note] = 'comment on a commit'
- args[:object_attributes][:noteable_type] = 'Commit'
- args[:commit] = {
- id: '5f163b2b95e6f53cbd428f5f0b103702a52b9a23',
- message: "Added a commit message\ndetails\n123\n"
- }
- end
+ it_behaves_like Integrations::ChatMessage
+ context 'commit notes' do
context 'without markdown' do
it 'returns a message regarding notes on commits' do
expect(subject.pretext).to eq("Test User (test.user) <http://url.com|commented on " \
diff --git a/spec/models/integrations/chat_message/pipeline_message_spec.rb b/spec/models/integrations/chat_message/pipeline_message_spec.rb
index a80d13d7f5d..68ef0ccb2e4 100644
--- a/spec/models/integrations/chat_message/pipeline_message_spec.rb
+++ b/spec/models/integrations/chat_message/pipeline_message_spec.rb
@@ -40,6 +40,8 @@ RSpec.describe Integrations::ChatMessage::PipelineMessage do
let(:has_yaml_errors) { false }
+ it_behaves_like Integrations::ChatMessage
+
before do
test_commit = double("A test commit", committer: args[:user], title: "A test commit message")
test_project = double("A test project", commit_by: test_commit, name: args[:project][:name], web_url: args[:project][:web_url])
diff --git a/spec/models/integrations/chat_message/push_message_spec.rb b/spec/models/integrations/chat_message/push_message_spec.rb
index 167487449c3..8d2d0f9f9a8 100644
--- a/spec/models/integrations/chat_message/push_message_spec.rb
+++ b/spec/models/integrations/chat_message/push_message_spec.rb
@@ -19,6 +19,8 @@ RSpec.describe Integrations::ChatMessage::PushMessage do
let(:color) { '#345' }
+ it_behaves_like Integrations::ChatMessage
+
context 'push' do
before do
args[:commits] = [
diff --git a/spec/models/integrations/chat_message/wiki_page_message_spec.rb b/spec/models/integrations/chat_message/wiki_page_message_spec.rb
index ded467dc910..4aa96c7e031 100644
--- a/spec/models/integrations/chat_message/wiki_page_message_spec.rb
+++ b/spec/models/integrations/chat_message/wiki_page_message_spec.rb
@@ -33,6 +33,8 @@ RSpec.describe Integrations::ChatMessage::WikiPageMessage do
}
end
+ it_behaves_like Integrations::ChatMessage
+
context 'without markdown' do
describe '#pretext' do
context 'when :action == "create"' do
diff --git a/spec/requests/api/graphql_spec.rb b/spec/requests/api/graphql_spec.rb
index b8f7af29a9f..da0c87fcefe 100644
--- a/spec/requests/api/graphql_spec.rb
+++ b/spec/requests/api/graphql_spec.rb
@@ -253,7 +253,7 @@ RSpec.describe 'GraphQL' do
end
context 'with token authentication' do
- let(:token) { create(:personal_access_token) }
+ let(:token) { create(:personal_access_token, user: user) }
it 'authenticates users with a PAT' do
stub_authentication_activity_metrics(debug: false)
@@ -276,6 +276,32 @@ RSpec.describe 'GraphQL' do
expect(graphql_errors).to include({ 'message' => /API not accessible/ })
end
+ context 'when user with expired password' do
+ let_it_be(:user) { create(:user, password_expires_at: 2.minutes.ago) }
+
+ it 'does not authenticate user' do
+ post_graphql(query, headers: { 'PRIVATE-TOKEN' => token.token })
+
+ expect(response).to have_gitlab_http_status(:ok)
+
+ expect(graphql_data['echo']).to eq('nil says: Hello world')
+ end
+ end
+
+ context 'when password expiration is not applicable' do
+ context 'when ldap user' do
+ let_it_be(:user) { create(:omniauth_user, provider: 'ldap', password_expires_at: 2.minutes.ago) }
+
+ it 'authenticates user' do
+ post_graphql(query, headers: { 'PRIVATE-TOKEN' => token.token })
+
+ expect(response).to have_gitlab_http_status(:ok)
+
+ expect(graphql_data['echo']).to eq("\"#{token.user.username}\" says: Hello world")
+ end
+ end
+ end
+
context 'when the personal access token has no api scope' do
it 'does not log the user in' do
token.update!(scopes: [:read_user])
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 79dbbd20d83..8406ded85d8 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -3138,6 +3138,29 @@ RSpec.describe API::Projects do
expect(json_response['message']).to eq('404 Project Not Found')
end
+ it 'returns 404 if the source project members cannot be viewed by the requester' do
+ private_project = create(:project, :private)
+
+ expect do
+ post api("/projects/#{project.id}/import_project_members/#{private_project.id}", user)
+ end.not_to change { project.members.count }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ expect(json_response['message']).to eq('404 Project Not Found')
+ end
+
+ it 'returns 403 if the source project members cannot be administered by the requester' do
+ project.add_maintainer(user2)
+ project2.add_developer(user2)
+
+ expect do
+ post api("/projects/#{project.id}/import_project_members/#{project2.id}", user2)
+ end.not_to change { project.members.count }
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ expect(json_response['message']).to eq('403 Forbidden - Project')
+ end
+
it 'returns 422 if the import failed for valid projects' do
allow_next_instance_of(::ProjectTeam) do |project_team|
allow(project_team).to receive(:import).and_return(false)
diff --git a/spec/requests/dashboard/projects_controller_spec.rb b/spec/requests/dashboard/projects_controller_spec.rb
new file mode 100644
index 00000000000..4cd3b6c4f9e
--- /dev/null
+++ b/spec/requests/dashboard/projects_controller_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Dashboard::ProjectsController do
+ context 'token authentication' do
+ it_behaves_like 'authenticates sessionless user for the request spec', 'index atom', public_resource: false do
+ let(:url) { dashboard_projects_url(:atom) }
+ end
+ end
+end
diff --git a/spec/requests/dashboard_controller_spec.rb b/spec/requests/dashboard_controller_spec.rb
new file mode 100644
index 00000000000..62655d720c5
--- /dev/null
+++ b/spec/requests/dashboard_controller_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe DashboardController do
+ context 'token authentication' do
+ it_behaves_like 'authenticates sessionless user for the request spec', 'issues atom', public_resource: false do
+ let(:url) { issues_dashboard_url(:atom, assignee_username: user.username) }
+ end
+
+ it_behaves_like 'authenticates sessionless user for the request spec', 'issues_calendar ics', public_resource: false do
+ let(:url) { issues_dashboard_url(:ics, assignee_username: user.username) }
+ end
+ end
+end
diff --git a/spec/requests/groups_controller_spec.rb b/spec/requests/groups_controller_spec.rb
new file mode 100644
index 00000000000..422c108f2ad
--- /dev/null
+++ b/spec/requests/groups_controller_spec.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GroupsController do
+ context 'token authentication' do
+ context 'when public group' do
+ let_it_be(:public_group) { create(:group, :public) }
+
+ it_behaves_like 'authenticates sessionless user for the request spec', 'show atom', public_resource: true do
+ let(:url) { group_path(public_group, format: :atom) }
+ end
+
+ it_behaves_like 'authenticates sessionless user for the request spec', 'issues atom', public_resource: true do
+ let(:url) { issues_group_path(public_group, format: :atom) }
+ end
+
+ it_behaves_like 'authenticates sessionless user for the request spec', 'issues_calendar ics', public_resource: true do
+ let(:url) { issues_group_calendar_url(public_group, format: :ics) }
+ end
+ end
+
+ context 'when private project' do
+ let_it_be(:private_group) { create(:group, :private) }
+
+ it_behaves_like 'authenticates sessionless user for the request spec', 'show atom', public_resource: false, ignore_metrics: true do
+ let(:url) { group_path(private_group, format: :atom) }
+
+ before do
+ private_group.add_maintainer(user)
+ end
+ end
+
+ it_behaves_like 'authenticates sessionless user for the request spec', 'issues atom', public_resource: false, ignore_metrics: true do
+ let(:url) { issues_group_path(private_group, format: :atom) }
+
+ before do
+ private_group.add_maintainer(user)
+ end
+ end
+
+ it_behaves_like 'authenticates sessionless user for the request spec', 'issues_calendar ics', public_resource: false, ignore_metrics: true do
+ let(:url) { issues_group_calendar_url(private_group, format: :ics) }
+
+ before do
+ private_group.add_maintainer(user)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/projects/commits_controller_spec.rb b/spec/requests/projects/commits_controller_spec.rb
new file mode 100644
index 00000000000..158902c0ffd
--- /dev/null
+++ b/spec/requests/projects/commits_controller_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Projects::CommitsController do
+ context 'token authentication' do
+ context 'when public project' do
+ let_it_be(:public_project) { create(:project, :repository, :public) }
+
+ it_behaves_like 'authenticates sessionless user for the request spec', 'show atom', public_resource: true do
+ let(:url) { project_commits_url(public_project, public_project.default_branch, format: :atom) }
+ end
+ end
+
+ context 'when private project' do
+ let_it_be(:private_project) { create(:project, :repository, :private) }
+
+ it_behaves_like 'authenticates sessionless user for the request spec', 'show atom', public_resource: false, ignore_metrics: true do
+ let(:url) { project_commits_url(private_project, private_project.default_branch, format: :atom) }
+
+ before do
+ private_project.add_maintainer(user)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/projects/issues_controller_spec.rb b/spec/requests/projects/issues_controller_spec.rb
index f44b1f4d502..248e3e3a92b 100644
--- a/spec/requests/projects/issues_controller_spec.rb
+++ b/spec/requests/projects/issues_controller_spec.rb
@@ -8,11 +8,11 @@ RSpec.describe Projects::IssuesController do
let_it_be(:project) { issue.project }
let_it_be(:user) { issue.author }
- before do
- login_as(user)
- end
-
describe 'GET #discussions' do
+ before do
+ login_as(user)
+ end
+
let_it_be(:discussion) { create(:discussion_note_on_issue, noteable: issue, project: issue.project) }
let_it_be(:discussion_reply) { create(:discussion_note_on_issue, noteable: issue, project: issue.project, in_reply_to: discussion) }
let_it_be(:state_event) { create(:resource_state_event, issue: issue) }
@@ -68,4 +68,38 @@ RSpec.describe Projects::IssuesController do
end
end
end
+
+ context 'token authentication' do
+ context 'when public project' do
+ let_it_be(:public_project) { create(:project, :public) }
+
+ it_behaves_like 'authenticates sessionless user for the request spec', 'index atom', public_resource: true do
+ let(:url) { project_issues_url(public_project, format: :atom) }
+ end
+
+ it_behaves_like 'authenticates sessionless user for the request spec', 'calendar ics', public_resource: true do
+ let(:url) { project_issues_url(public_project, format: :ics) }
+ end
+ end
+
+ context 'when private project' do
+ let_it_be(:private_project) { create(:project, :private) }
+
+ it_behaves_like 'authenticates sessionless user for the request spec', 'index atom', public_resource: false, ignore_metrics: true do
+ let(:url) { project_issues_url(private_project, format: :atom) }
+
+ before do
+ private_project.add_maintainer(user)
+ end
+ end
+
+ it_behaves_like 'authenticates sessionless user for the request spec', 'calendar ics', public_resource: false, ignore_metrics: true do
+ let(:url) { project_issues_url(private_project, format: :ics) }
+
+ before do
+ private_project.add_maintainer(user)
+ end
+ end
+ end
+ end
end
diff --git a/spec/requests/projects/merge_requests_controller_spec.rb b/spec/requests/projects/merge_requests_controller_spec.rb
new file mode 100644
index 00000000000..3b1ce569033
--- /dev/null
+++ b/spec/requests/projects/merge_requests_controller_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Projects::MergeRequestsController do
+ context 'token authentication' do
+ context 'when public project' do
+ let_it_be(:public_project) { create(:project, :public) }
+
+ it_behaves_like 'authenticates sessionless user for the request spec', 'index atom', public_resource: true do
+ let(:url) { project_merge_requests_url(public_project, format: :atom) }
+ end
+ end
+
+ context 'when private project' do
+ let_it_be(:private_project) { create(:project, :private) }
+
+ it_behaves_like 'authenticates sessionless user for the request spec', 'index atom', public_resource: false, ignore_metrics: true do
+ let(:url) { project_merge_requests_url(private_project, format: :atom) }
+
+ before do
+ private_project.add_maintainer(user)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/projects/tags_controller_spec.rb b/spec/requests/projects/tags_controller_spec.rb
new file mode 100644
index 00000000000..b9531a2739c
--- /dev/null
+++ b/spec/requests/projects/tags_controller_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Projects::TagsController do
+ context 'token authentication' do
+ context 'when public project' do
+ let_it_be(:public_project) { create(:project, :repository, :public) }
+
+ it_behaves_like 'authenticates sessionless user for the request spec', 'index atom', public_resource: true do
+ let(:url) { project_tags_url(public_project, format: :atom) }
+ end
+ end
+
+ context 'when private project' do
+ let_it_be(:private_project) { create(:project, :repository, :private) }
+
+ it_behaves_like 'authenticates sessionless user for the request spec', 'index atom', public_resource: false, ignore_metrics: true do
+ let(:url) { project_tags_url(private_project, format: :atom) }
+
+ before do
+ private_project.add_maintainer(user)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/projects_controller_spec.rb b/spec/requests/projects_controller_spec.rb
new file mode 100644
index 00000000000..d2200d5a4ec
--- /dev/null
+++ b/spec/requests/projects_controller_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ProjectsController do
+ context 'token authentication' do
+ context 'when public project' do
+ let_it_be(:public_project) { create(:project, :public) }
+
+ it_behaves_like 'authenticates sessionless user for the request spec', 'show atom', public_resource: true do
+ let(:url) { project_url(public_project, format: :atom) }
+ end
+ end
+
+ context 'when private project' do
+ let_it_be(:private_project) { create(:project, :private) }
+
+ it_behaves_like 'authenticates sessionless user for the request spec', 'show atom', public_resource: false, ignore_metrics: true do
+ let(:url) { project_url(private_project, format: :atom) }
+
+ before do
+ private_project.add_maintainer(user)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/users_controller_spec.rb b/spec/requests/users_controller_spec.rb
index 701a73761fd..eefc24f7824 100644
--- a/spec/requests/users_controller_spec.rb
+++ b/spec/requests/users_controller_spec.rb
@@ -792,9 +792,9 @@ RSpec.describe UsersController do
end
context 'token authentication' do
- let(:url) { user_url(user.username, format: :atom) }
-
- it_behaves_like 'authenticates sessionless user for the request spec', public: true
+ it_behaves_like 'authenticates sessionless user for the request spec', 'show atom', public_resource: true do
+ let(:url) { user_url(user, format: :atom) }
+ end
end
def user_moved_message(redirect_route, user)
diff --git a/spec/services/bulk_imports/archive_extraction_service_spec.rb b/spec/services/bulk_imports/archive_extraction_service_spec.rb
new file mode 100644
index 00000000000..aa823d88010
--- /dev/null
+++ b/spec/services/bulk_imports/archive_extraction_service_spec.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe BulkImports::ArchiveExtractionService do
+ let_it_be(:tmpdir) { Dir.mktmpdir }
+ let_it_be(:filename) { 'symlink_export.tar' }
+ let_it_be(:filepath) { File.join(tmpdir, filename) }
+
+ before do
+ FileUtils.copy_file(File.join('spec', 'fixtures', filename), filepath)
+ end
+
+ after(:all) do
+ FileUtils.remove_entry(tmpdir)
+ end
+
+ subject(:service) { described_class.new(tmpdir: tmpdir, filename: filename) }
+
+ describe '#execute' do
+ it 'extracts files from archive and removes symlinks' do
+ file = File.join(tmpdir, 'project.json')
+ folder = File.join(tmpdir, 'uploads')
+ symlink = File.join(tmpdir, 'uploads', 'link.gitignore')
+
+ expect(service).to receive(:untar_xf).with(archive: filepath, dir: tmpdir).and_call_original
+
+ service.execute
+
+ expect(File.exist?(file)).to eq(true)
+ expect(Dir.exist?(folder)).to eq(true)
+ expect(File.exist?(symlink)).to eq(false)
+ end
+
+ context 'when dir is not in tmpdir' do
+ it 'raises an error' do
+ ['/etc', '/usr', '/', '/home', '', '/some/other/path', Rails.root].each do |path|
+ expect { described_class.new(tmpdir: path, filename: 'filename').execute }
+ .to raise_error(BulkImports::Error, 'Invalid target directory')
+ end
+ end
+ end
+
+ context 'when archive file is a symlink' do
+ it 'raises an error' do
+ FileUtils.ln_s(File.join(tmpdir, filename), File.join(tmpdir, 'symlink'))
+
+ expect { described_class.new(tmpdir: tmpdir, filename: 'symlink').execute }
+ .to raise_error(BulkImports::Error, 'Invalid file')
+ end
+ end
+
+ context 'when filepath is being traversed' do
+ it 'raises an error' do
+ expect { described_class.new(tmpdir: File.join(tmpdir, '../../../'), filename: 'name').execute }
+ .to raise_error(Gitlab::Utils::PathTraversalAttackError, 'Invalid path')
+ end
+ end
+ end
+end
diff --git a/spec/services/packages/npm/create_package_service_spec.rb b/spec/services/packages/npm/create_package_service_spec.rb
index 3bb675058df..a5b36527565 100644
--- a/spec/services/packages/npm/create_package_service_spec.rb
+++ b/spec/services/packages/npm/create_package_service_spec.rb
@@ -11,10 +11,8 @@ RSpec.describe Packages::Npm::CreatePackageService do
Gitlab::Json.parse(fixture_file('packages/npm/payload.json')
.gsub('@root/npm-test', package_name)
.gsub('1.0.1', version)).with_indifferent_access
- .merge!(override)
end
- let(:override) { {} }
let(:package_name) { "@#{namespace.path}/my-app" }
let(:version_data) { params.dig('versions', '1.0.1') }
@@ -116,13 +114,53 @@ RSpec.describe Packages::Npm::CreatePackageService do
it { expect(subject[:message]).to be 'Package already exists.' }
end
- context 'file size above maximum limit' do
+ describe 'max file size validation' do
+ let(:max_file_size) { 5.bytes}
+
+ shared_examples_for 'max file size validation failure' do
+ it 'returns a 400 error', :aggregate_failures do
+ expect(subject[:http_status]).to eq 400
+ expect(subject[:message]).to be 'File is too large.'
+ end
+ end
+
before do
- params['_attachments']["#{package_name}-#{version}.tgz"]['length'] = project.actual_limits.npm_max_file_size + 1
+ project.actual_limits.update!(npm_max_file_size: max_file_size)
end
- it { expect(subject[:http_status]).to eq 400 }
- it { expect(subject[:message]).to be 'File is too large.' }
+ context 'when max file size is exceeded' do
+ # NOTE: The base64 encoded package data in the fixture file is the "hello\n" string, whose byte size is 6.
+ it_behaves_like 'max file size validation failure'
+ end
+
+ context 'when file size is faked by setting the attachment length param to a lower size' do
+ let(:params) { super().deep_merge!( { _attachments: { "#{package_name}-#{version}.tgz" => { data: encoded_package_data, length: 1 } } }) }
+
+ # TODO (technical debt): Extract the package size calculation outside the service and add separate specs for it.
+ # Right now we have several contexts here to test the calculation's different scenarios.
+ context "when encoded package data is not padded" do
+ # 'Hello!' (size = 6 bytes) => 'SGVsbG8h'
+ let(:encoded_package_data) { 'SGVsbG8h' }
+
+ it_behaves_like 'max file size validation failure'
+ end
+
+ context "when encoded package data is padded with '='" do
+ let(:max_file_size) { 4.bytes}
+ # 'Hello' (size = 5 bytes) => 'SGVsbG8='
+ let(:encoded_package_data) { 'SGVsbG8=' }
+
+ it_behaves_like 'max file size validation failure'
+ end
+
+ context "when encoded package data is padded with '=='" do
+ let(:max_file_size) { 3.bytes}
+ # 'Hell' (size = 4 bytes) => 'SGVsbA=='
+ let(:encoded_package_data) { 'SGVsbA==' }
+
+ it_behaves_like 'max file size validation failure'
+ end
+ end
end
[
@@ -141,7 +179,7 @@ RSpec.describe Packages::Npm::CreatePackageService do
end
context 'with empty versions' do
- let(:override) { { versions: {} } }
+ let(:params) { super().merge!({ versions: {} } ) }
it { expect(subject[:http_status]).to eq 400 }
it { expect(subject[:message]).to eq 'Version is empty.' }
diff --git a/spec/support/shared_examples/controllers/sessionless_auth_controller_shared_examples.rb b/spec/support/shared_examples/controllers/sessionless_auth_controller_shared_examples.rb
deleted file mode 100644
index 041695d8111..00000000000
--- a/spec/support/shared_examples/controllers/sessionless_auth_controller_shared_examples.rb
+++ /dev/null
@@ -1,112 +0,0 @@
-# frozen_string_literal: true
-
-# This controller shared examples will be migrated to
-# spec/support/shared_examples/requests/sessionless_auth_request_shared_examples.rb
-# See also https://gitlab.com/groups/gitlab-org/-/epics/5076
-
-RSpec.shared_examples 'authenticates sessionless user' do |path, format, params|
- params ||= {}
-
- before do
- stub_authentication_activity_metrics(debug: false)
- end
-
- let(:user) { create(:user) }
- let(:personal_access_token) { create(:personal_access_token, user: user) }
- let(:default_params) { { format: format }.merge(params.except(:public) || {}) }
-
- context "when the 'personal_access_token' param is populated with the personal access token" do
- it 'logs the user in' do
- expect(authentication_metrics)
- .to increment(:user_authenticated_counter)
- .and increment(:user_session_override_counter)
- .and increment(:user_sessionless_authentication_counter)
-
- get path, params: default_params.merge(private_token: personal_access_token.token)
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(controller.current_user).to eq(user)
- end
-
- it 'does not log the user in if page is public', if: params[:public] do
- get path, params: default_params
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(controller.current_user).to be_nil
- end
- end
-
- context 'when the personal access token has no api scope', unless: params[:public] do
- it 'does not log the user in' do
- # Several instances of where these specs are shared route the request
- # through ApplicationController#route_not_found which does not involve
- # the usual auth code from Devise, so does not increment the
- # :user_unauthenticated_counter
- #
- unless params[:ignore_incrementing]
- expect(authentication_metrics)
- .to increment(:user_unauthenticated_counter)
- end
-
- personal_access_token.update!(scopes: [:read_user])
-
- get path, params: default_params.merge(private_token: personal_access_token.token)
-
- expect(response).not_to have_gitlab_http_status(:ok)
- end
- end
-
- context "when the 'PERSONAL_ACCESS_TOKEN' header is populated with the personal access token" do
- it 'logs the user in' do
- expect(authentication_metrics)
- .to increment(:user_authenticated_counter)
- .and increment(:user_session_override_counter)
- .and increment(:user_sessionless_authentication_counter)
-
- @request.headers['PRIVATE-TOKEN'] = personal_access_token.token
- get path, params: default_params
-
- expect(response).to have_gitlab_http_status(:ok)
- end
- end
-
- context "when the 'feed_token' param is populated with the feed token", if: format == :rss do
- it "logs the user in" do
- expect(authentication_metrics)
- .to increment(:user_authenticated_counter)
- .and increment(:user_session_override_counter)
- .and increment(:user_sessionless_authentication_counter)
-
- get path, params: default_params.merge(feed_token: user.feed_token)
-
- expect(response).to have_gitlab_http_status(:ok)
- end
- end
-
- context "when the 'feed_token' param is populated with an invalid feed token", if: format == :rss, unless: params[:public] do
- it "logs the user" do
- expect(authentication_metrics)
- .to increment(:user_unauthenticated_counter)
-
- get path, params: default_params.merge(feed_token: 'token')
-
- expect(response).not_to have_gitlab_http_status(:ok)
- end
- end
-
- it "doesn't log the user in otherwise", unless: params[:public] do
- # Several instances of where these specs are shared route the request
- # through ApplicationController#route_not_found which does not involve
- # the usual auth code from Devise, so does not increment the
- # :user_unauthenticated_counter
- #
- unless params[:ignore_incrementing]
- expect(authentication_metrics)
- .to increment(:user_unauthenticated_counter)
- end
-
- get path, params: default_params.merge(private_token: 'token')
-
- expect(response).not_to have_gitlab_http_status(:ok)
- end
-end
diff --git a/spec/support/shared_examples/models/integrations/chat_message_shared_examples.rb b/spec/support/shared_examples/models/integrations/chat_message_shared_examples.rb
new file mode 100644
index 00000000000..2665f249ded
--- /dev/null
+++ b/spec/support/shared_examples/models/integrations/chat_message_shared_examples.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples Integrations::ChatMessage do
+ context 'when input contains link markup' do
+ let(:evil_input) { '[Markdown](http://evil.com) <a href="http://evil.com">HTML</a> <http://evil.com|Slack>' }
+
+ # Attributes returned from #activity and #attributes which should be sanitized.
+ let(:sanitized_attributes) do
+ %i[title subtitle text fallback author_name]
+ end
+
+ # Attributes passed to #initialize which can contain user input.
+ before do
+ args.deep_merge!(
+ project_name: evil_input,
+ user_name: evil_input,
+ user_full_name: evil_input,
+ commit_title: evil_input,
+ environment: evil_input,
+ project: {
+ name: evil_input
+ },
+ user: {
+ name: evil_input,
+ username: evil_input
+ },
+ object_attributes: {
+ title: evil_input
+ }
+ )
+ end
+
+ # NOTE: The `include` matcher is used here so the RSpec error messages will tell us
+ # which method or attribute is failing, even though it makes the spec a bit less readable.
+ it 'strips all link markup characters', :aggregate_failures do
+ expect(subject).not_to have_attributes(
+ pretext: include(evil_input),
+ summary: include(evil_input)
+ )
+
+ begin
+ sanitized_attributes.each do |attribute|
+ expect(subject.activity).not_to include(attribute => include(evil_input))
+ end
+ rescue NotImplementedError
+ end
+
+ begin
+ sanitized_attributes.each do |attribute|
+ expect(subject.attachments).not_to include(include(attribute => include(evil_input)))
+ end
+ rescue NotImplementedError
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/sessionless_auth_request_shared_examples.rb b/spec/support/shared_examples/requests/sessionless_auth_request_shared_examples.rb
index d82da1b01e1..56e90a6ec34 100644
--- a/spec/support/shared_examples/requests/sessionless_auth_request_shared_examples.rb
+++ b/spec/support/shared_examples/requests/sessionless_auth_request_shared_examples.rb
@@ -1,84 +1,160 @@
# frozen_string_literal: true
-RSpec.shared_examples 'authenticates sessionless user for the request spec' do |params|
- params ||= {}
-
+RSpec.shared_examples 'authenticates sessionless user for the request spec' do |name, public_resource:, ignore_metrics: false, params: {}|
before do
stub_authentication_activity_metrics(debug: false)
end
- let(:user) { create(:user) }
+ let_it_be(:user) { create(:user) }
let(:personal_access_token) { create(:personal_access_token, user: user) }
- let(:default_params) { params.except(:public) || {} }
- context "when the 'personal_access_token' param is populated with the personal access token" do
- it 'logs the user in' do
+ shared_examples 'authenticates user and returns response with ok status' do
+ it 'authenticates user and returns response with ok status' do
expect(authentication_metrics)
.to increment(:user_authenticated_counter)
- .and increment(:user_session_override_counter)
- .and increment(:user_sessionless_authentication_counter)
+ .and increment(:user_session_override_counter)
+ .and increment(:user_sessionless_authentication_counter)
- get url, params: default_params.merge(private_token: personal_access_token.token)
+ subject
- expect(response).to have_gitlab_http_status(:ok)
expect(controller.current_user).to eq(user)
+ expect(response).to have_gitlab_http_status(:ok)
end
+ end
- it 'does not log the user in if page is public', if: params[:public] do
- get url, params: default_params
+ shared_examples 'does not authenticate user and returns response with ok status' do
+ it 'does not authenticate user and returns response with ok status' do
+ subject
- expect(response).to have_gitlab_http_status(:ok)
expect(controller.current_user).to be_nil
+ expect(response).to have_gitlab_http_status(:ok)
end
end
- context 'when the personal access token has no api scope', unless: params[:public] do
- it 'does not log the user in' do
+ shared_examples 'does not return response with ok status' do
+ it 'does not return response with ok status' do
# Several instances of where these specs are shared route the request
# through ApplicationController#route_not_found which does not involve
# the usual auth code from Devise, so does not increment the
# :user_unauthenticated_counter
- #
- unless params[:ignore_incrementing]
+ unless ignore_metrics
expect(authentication_metrics)
.to increment(:user_unauthenticated_counter)
end
- personal_access_token.update!(scopes: [:read_user])
-
- get url, params: default_params.merge(private_token: personal_access_token.token)
+ subject
expect(response).not_to have_gitlab_http_status(:ok)
end
end
- context "when the 'PERSONAL_ACCESS_TOKEN' header is populated with the personal access token" do
- it 'logs the user in' do
- expect(authentication_metrics)
- .to increment(:user_authenticated_counter)
- .and increment(:user_session_override_counter)
- .and increment(:user_sessionless_authentication_counter)
+ shared_examples 'using valid token' do
+ context 'when resource is private', unless: public_resource do
+ include_examples 'authenticates user and returns response with ok status'
- headers = { 'PRIVATE-TOKEN': personal_access_token.token }
- get url, params: default_params, headers: headers
+ context 'when user with expired password' do
+ let_it_be(:user) { create(:user, password_expires_at: 2.minutes.ago) }
- expect(response).to have_gitlab_http_status(:ok)
+ include_examples 'does not return response with ok status'
+ end
+
+ context 'when password expiration is not applicable' do
+ context 'when ldap user' do
+ let_it_be(:user) { create(:omniauth_user, provider: 'ldap', password_expires_at: 2.minutes.ago) }
+
+ include_examples 'authenticates user and returns response with ok status'
+ end
+ end
+ end
+
+ context 'when resource is public', if: public_resource do
+ include_examples 'authenticates user and returns response with ok status'
+
+ context 'when user with expired password' do
+ let_it_be(:user) { create(:user, password_expires_at: 2.minutes.ago) }
+
+ include_examples 'does not authenticate user and returns response with ok status'
+ end
end
end
- it "doesn't log the user in otherwise", unless: params[:public] do
- # Several instances of where these specs are shared route the request
- # through ApplicationController#route_not_found which does not involve
- # the usual auth code from Devise, so does not increment the
- # :user_unauthenticated_counter
- #
- unless params[:ignore_incrementing]
- expect(authentication_metrics)
- .to increment(:user_unauthenticated_counter)
+ shared_examples 'using invalid token' do
+ context 'when resource is private', unless: public_resource do
+ include_examples 'does not return response with ok status'
+ end
+
+ context 'when resource is public', if: public_resource do
+ include_examples 'does not authenticate user and returns response with ok status'
+ end
+ end
+
+ shared_examples 'personal access token has no api scope' do
+ context 'when the personal access token has no api scope' do
+ before do
+ personal_access_token.update!(scopes: [:read_user])
+ end
+
+ context 'when resource is private', unless: public_resource do
+ include_examples 'does not return response with ok status'
+ end
+
+ context 'when resource is public', if: public_resource do
+ include_examples 'does not authenticate user and returns response with ok status'
+ end
+ end
+ end
+
+ describe name do
+ context "when the 'private_token' param is populated with the personal access token" do
+ context 'when valid token' do
+ subject { get url, params: params.merge(private_token: personal_access_token.token) }
+
+ include_examples 'using valid token'
+
+ include_examples 'personal access token has no api scope'
+ end
+
+ context 'when invalid token' do
+ subject { get url, params: params.merge(private_token: 'invalid token') }
+
+ include_examples 'using invalid token'
+ end
end
- get url, params: default_params.merge(private_token: 'token')
+ context "when the 'PRIVATE-TOKEN' header is populated with the personal access token" do
+ context 'when valid token' do
+ subject do
+ headers = { 'PRIVATE-TOKEN': personal_access_token.token }
+ get url, params: params, headers: headers
+ end
- expect(response).not_to have_gitlab_http_status(:ok)
+ include_examples 'using valid token'
+
+ include_examples 'personal access token has no api scope'
+ end
+
+ context 'when invalid token' do
+ subject do
+ headers = { 'PRIVATE-TOKEN': 'invalid token' }
+ get url, params: params, headers: headers
+ end
+
+ include_examples 'using invalid token'
+ end
+ end
+
+ context "when the 'feed_token' param is populated with the feed token" do
+ context 'when valid token' do
+ subject { get url, params: params.merge(feed_token: user.feed_token) }
+
+ include_examples 'using valid token'
+ end
+
+ context 'when invalid token' do
+ subject { get url, params: params.merge(feed_token: 'invalid token') }
+
+ include_examples 'using invalid token'
+ end
+ end
end
end
diff --git a/spec/workers/dependency_proxy/cleanup_dependency_proxy_worker_spec.rb b/spec/workers/dependency_proxy/cleanup_dependency_proxy_worker_spec.rb
new file mode 100644
index 00000000000..ed0bdefbdb8
--- /dev/null
+++ b/spec/workers/dependency_proxy/cleanup_dependency_proxy_worker_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe DependencyProxy::CleanupDependencyProxyWorker do
+ describe '#perform' do
+ subject { described_class.new.perform }
+
+ context 'when there are records to be deleted' do
+ it_behaves_like 'an idempotent worker' do
+ it 'queues the cleanup jobs', :aggregate_failures do
+ create(:dependency_proxy_blob, :expired)
+ create(:dependency_proxy_manifest, :expired)
+
+ expect(DependencyProxy::CleanupBlobWorker).to receive(:perform_with_capacity).twice
+ expect(DependencyProxy::CleanupManifestWorker).to receive(:perform_with_capacity).twice
+
+ subject
+ end
+ end
+ end
+
+ context 'when there are not records to be deleted' do
+ it_behaves_like 'an idempotent worker' do
+ it 'does not queue the cleanup jobs', :aggregate_failures do
+ expect(DependencyProxy::CleanupBlobWorker).not_to receive(:perform_with_capacity)
+ expect(DependencyProxy::CleanupManifestWorker).not_to receive(:perform_with_capacity)
+
+ subject
+ end
+ end
+ end
+ end
+end
diff --git a/spec/workers/dependency_proxy/image_ttl_group_policy_worker_spec.rb b/spec/workers/dependency_proxy/image_ttl_group_policy_worker_spec.rb
index ae0cb097ebf..b035a2ec0b7 100644
--- a/spec/workers/dependency_proxy/image_ttl_group_policy_worker_spec.rb
+++ b/spec/workers/dependency_proxy/image_ttl_group_policy_worker_spec.rb
@@ -17,13 +17,6 @@ RSpec.describe DependencyProxy::ImageTtlGroupPolicyWorker do
let_it_be_with_reload(:new_blob) { create(:dependency_proxy_blob, group: group) }
let_it_be_with_reload(:new_manifest) { create(:dependency_proxy_manifest, group: group) }
- it 'calls the limited capacity workers', :aggregate_failures do
- expect(DependencyProxy::CleanupBlobWorker).to receive(:perform_with_capacity)
- expect(DependencyProxy::CleanupManifestWorker).to receive(:perform_with_capacity)
-
- subject
- end
-
it 'updates the old images to expired' do
expect { subject }
.to change { old_blob.reload.status }.from('default').to('expired')
@@ -33,15 +26,6 @@ RSpec.describe DependencyProxy::ImageTtlGroupPolicyWorker do
end
end
- context 'when there are no images to expire' do
- it 'does not do anything', :aggregate_failures do
- expect(DependencyProxy::CleanupBlobWorker).not_to receive(:perform_with_capacity)
- expect(DependencyProxy::CleanupManifestWorker).not_to receive(:perform_with_capacity)
-
- subject
- end
- end
-
context 'counts logging' do
let_it_be(:expired_blob) { create(:dependency_proxy_blob, :expired, group: group) }
let_it_be(:expired_blob2) { create(:dependency_proxy_blob, :expired, group: group) }