From 9d54184f308893338967b18874dedebf38acf89e Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Thu, 2 Jan 2020 21:07:38 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .gitlab/ci/releases.gitlab-ci.yml | 2 +- CHANGELOG-EE.md | 8 ++ .../profiles/notifications_controller.rb | 1 + .../projects/releases_controller.rb | 7 +- app/helpers/notifications_helper.rb | 4 + app/models/evidence.rb | 15 +++ app/models/user.rb | 2 +- app/policies/release_policy.rb | 27 ++++ app/uploaders/avatar_uploader.rb | 3 + app/uploaders/favicon_uploader.rb | 4 + app/uploaders/upload_type_check.rb | 98 ++++++++++++++ .../sent_notifications/unsubscribe.html.haml | 7 +- .../unreleased/ap-33785-file-integrity.yml | 5 + config/initializers/graphql.rb | 4 + lib/api/entities.rb | 6 +- lib/banzai/filter/relative_link_filter.rb | 12 +- locale/gitlab.pot | 3 + scripts/sync-stable-branch.sh | 16 +++ .../profiles/notifications_controller_spec.rb | 29 ++++ .../projects/releases_controller_spec.rb | 81 +++++++++++- .../sent_notifications_controller_spec.rb | 35 +++-- .../api/schemas/evidences/milestone.json | 9 +- spec/fixtures/not_a_png.png | Bin 0 -> 72642 bytes .../filter/relative_link_filter_spec.rb | 9 ++ spec/models/user_spec.rb | 26 +++- .../api/graphql/gitlab_schema_spec.rb | 12 ++ spec/requests/api/releases_spec.rb | 34 +++++ spec/requests/api/runners_spec.rb | 16 +++ .../upload_type_check_shared_context.rb | 33 +++++ .../uploaders/upload_type_shared_examples.rb | 65 +++++++++ spec/uploaders/avatar_uploader_spec.rb | 12 ++ spec/uploaders/favicon_uploader_spec.rb | 24 ++++ spec/uploaders/upload_type_check_spec.rb | 124 ++++++++++++++++++ 33 files changed, 701 insertions(+), 32 deletions(-) create mode 100644 app/uploaders/upload_type_check.rb create mode 100644 changelogs/unreleased/ap-33785-file-integrity.yml create mode 100644 spec/fixtures/not_a_png.png create mode 100644 spec/support/shared_contexts/upload_type_check_shared_context.rb create mode 100644 spec/support/shared_examples/uploaders/upload_type_shared_examples.rb create mode 100644 spec/uploaders/favicon_uploader_spec.rb create mode 100644 spec/uploaders/upload_type_check_spec.rb diff --git a/.gitlab/ci/releases.gitlab-ci.yml b/.gitlab/ci/releases.gitlab-ci.yml index d4e0236f3a8..8ca4041e6be 100644 --- a/.gitlab/ci/releases.gitlab-ci.yml +++ b/.gitlab/ci/releases.gitlab-ci.yml @@ -9,7 +9,7 @@ image: alpine:edge stage: sync before_script: - - apk add --no-cache --update curl bash + - apk add --no-cache --update curl bash jq after_script: [] script: - bash scripts/sync-stable-branch.sh diff --git a/CHANGELOG-EE.md b/CHANGELOG-EE.md index dc4e390ebc8..b52bdaef27d 100644 --- a/CHANGELOG-EE.md +++ b/CHANGELOG-EE.md @@ -1,5 +1,13 @@ Please view this file on the master branch, on stable branches it's out of date. +## 12.6.2 + +### Security (2 changes) + +- Don't publish drafts if user can't create notes. +- Remove protected tag access when group is removed. + + ## 12.6.1 - No changes. diff --git a/app/controllers/profiles/notifications_controller.rb b/app/controllers/profiles/notifications_controller.rb index 5f44e55f3ef..d295b64082c 100644 --- a/app/controllers/profiles/notifications_controller.rb +++ b/app/controllers/profiles/notifications_controller.rb @@ -11,6 +11,7 @@ class Profiles::NotificationsController < Profiles::ApplicationController exclude_group_ids: @group_notifications.select(:source_id) ).execute.map { |group| current_user.notification_settings_for(group, inherit: true) } @project_notifications = current_user.notification_settings.for_projects.order(:id) + .select { |notification| current_user.can?(:read_project, notification.source) } @global_notification_setting = current_user.global_notification_setting end # rubocop: enable CodeReuse/ActiveRecord diff --git a/app/controllers/projects/releases_controller.rb b/app/controllers/projects/releases_controller.rb index ffe69fe97e4..d6030a9e455 100644 --- a/app/controllers/projects/releases_controller.rb +++ b/app/controllers/projects/releases_controller.rb @@ -10,7 +10,7 @@ class Projects::ReleasesController < Projects::ApplicationController push_frontend_feature_flag(:release_evidence_collection, project) end before_action :authorize_update_release!, only: %i[edit update] - before_action :authorize_download_code!, only: [:evidence] + before_action :authorize_read_release_evidence!, only: [:evidence] def index respond_to do |format| @@ -47,6 +47,11 @@ class Projects::ReleasesController < Projects::ApplicationController access_denied! unless can?(current_user, :update_release, release) end + def authorize_read_release_evidence! + access_denied! unless Feature.enabled?(:release_evidence, project, default_enabled: true) + access_denied! unless can?(current_user, :read_release_evidence, release) + end + def release @release ||= project.releases.find_by_tag!(sanitized_tag_name) end diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb index 8855e0cdd70..9a64fe98f86 100644 --- a/app/helpers/notifications_helper.rb +++ b/app/helpers/notifications_helper.rb @@ -116,4 +116,8 @@ module NotificationsHelper def show_unsubscribe_title?(noteable) can?(current_user, "read_#{noteable.to_ability_name}".to_sym, noteable) end + + def can_read_project?(project) + can?(current_user, :read_project, project) + end end diff --git a/app/models/evidence.rb b/app/models/evidence.rb index 69a00f1cb3f..55149ab0dfa 100644 --- a/app/models/evidence.rb +++ b/app/models/evidence.rb @@ -15,6 +15,21 @@ class Evidence < ApplicationRecord @milestones ||= release.milestones.includes(:issues) end + ## + # Return `summary` without sensitive information. + # + # Removing issues from summary in order to prevent leaking confidential ones. + # See more https://gitlab.com/gitlab-org/gitlab/issues/121930 + def summary + safe_summary = read_attribute(:summary) + + safe_summary.dig('release', 'milestones')&.each do |milestone| + milestone.delete('issues') + end + + safe_summary + end + private def generate_summary_and_sha diff --git a/app/models/user.rb b/app/models/user.rb index 18bf5ceaa0e..ee42a987939 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1327,7 +1327,7 @@ class User < ApplicationRecord .select('ci_runners.*') group_runners = Ci::RunnerNamespace - .where(namespace_id: owned_or_maintainers_groups.select(:id)) + .where(namespace_id: owned_groups.select(:id)) .joins(:runner) .select('ci_runners.*') diff --git a/app/policies/release_policy.rb b/app/policies/release_policy.rb index d7f9e5d7445..0fd1312c511 100644 --- a/app/policies/release_policy.rb +++ b/app/policies/release_policy.rb @@ -2,4 +2,31 @@ class ReleasePolicy < BasePolicy delegate { @subject.project } + + rule { allowed_to_read_evidence & external_authorization_service_disabled }.policy do + enable :read_release_evidence + end + + ## + # evidence.summary includes the following entities: + # - Release + # - git-tag (Repository) + # - Project + # - Milestones + # - Issues + condition(:allowed_to_read_evidence) do + can?(:read_release) && + can?(:download_code) && + can?(:read_project) && + can?(:read_milestone) && + can?(:read_issue) + end + + ## + # Currently, we don't support release evidence for the GitLab instances + # that enables external authorization services. + # See https://gitlab.com/gitlab-org/gitlab/issues/121930. + condition(:external_authorization_service_disabled) do + !Gitlab::ExternalAuthorization::Config.enabled? + end end diff --git a/app/uploaders/avatar_uploader.rb b/app/uploaders/avatar_uploader.rb index d42c9dbedf4..b79a5deb9c0 100644 --- a/app/uploaders/avatar_uploader.rb +++ b/app/uploaders/avatar_uploader.rb @@ -5,6 +5,9 @@ class AvatarUploader < GitlabUploader include RecordsUploads::Concern include ObjectStorage::Concern prepend ObjectStorage::Extension::RecordsUploads + include UploadTypeCheck::Concern + + check_upload_type extensions: AvatarUploader::SAFE_IMAGE_EXT def exists? model.avatar.file && model.avatar.file.present? diff --git a/app/uploaders/favicon_uploader.rb b/app/uploaders/favicon_uploader.rb index a0b275b56a9..f393fdf0d84 100644 --- a/app/uploaders/favicon_uploader.rb +++ b/app/uploaders/favicon_uploader.rb @@ -1,8 +1,12 @@ # frozen_string_literal: true class FaviconUploader < AttachmentUploader + include UploadTypeCheck::Concern + EXTENSION_WHITELIST = %w[png ico].freeze + check_upload_type extensions: EXTENSION_WHITELIST + def extension_whitelist EXTENSION_WHITELIST end diff --git a/app/uploaders/upload_type_check.rb b/app/uploaders/upload_type_check.rb new file mode 100644 index 00000000000..2837b001660 --- /dev/null +++ b/app/uploaders/upload_type_check.rb @@ -0,0 +1,98 @@ +# frozen_string_literal: true + +# Ensure that uploaded files are what they say they are for security and +# handling purposes. The checks are not 100% reliable so we err on the side of +# caution and allow by default, and deny when we're confident of a fail state. +# +# Include this concern, then call `check_upload_type` to check all +# uploads. Attach a `mime_type` or `extensions` parameter to only check +# specific upload types. Both parameters will be normalized to a MIME type and +# checked against the inferred MIME type of the upload content and filename +# extension. +# +# class YourUploader +# include UploadTypeCheck::Concern +# check_upload_type mime_types: ['image/png', /image\/jpe?g/] +# +# # or... +# +# check_upload_type extensions: ['png', 'jpg', 'jpeg'] +# end +# +# The mime_types parameter can accept `NilClass`, `String`, `Regexp`, +# `Array[String, Regexp]`. This matches the CarrierWave `extension_whitelist` +# and `content_type_whitelist` family of behavior. +# +# The extensions parameter can accept `NilClass`, `String`, `Array[String]`. +module UploadTypeCheck + module Concern + extend ActiveSupport::Concern + + class_methods do + def check_upload_type(mime_types: nil, extensions: nil) + define_method :check_upload_type_callback do |file| + magic_file = MagicFile.new(file.to_file) + + # Map file extensions back to mime types. + if extensions + mime_types = Array(mime_types) + + Array(extensions).map { |e| MimeMagic::EXTENSIONS[e] } + end + + if mime_types.nil? || magic_file.matches_mime_types?(mime_types) + check_content_matches_extension!(magic_file) + end + end + before :cache, :check_upload_type_callback + end + end + + def check_content_matches_extension!(magic_file) + return if magic_file.ambiguous_type? + + if magic_file.magic_type != magic_file.ext_type + raise CarrierWave::IntegrityError, 'Content type does not match file extension' + end + end + end + + # Convenience class to wrap MagicMime objects. + class MagicFile + attr_reader :file + + def initialize(file) + @file = file + end + + def magic_type + @magic_type ||= MimeMagic.by_magic(file) + end + + def ext_type + @ext_type ||= MimeMagic.by_path(file.path) + end + + def magic_type_type + magic_type&.type + end + + def ext_type_type + ext_type&.type + end + + def matches_mime_types?(mime_types) + Array(mime_types).any? do |mt| + magic_type_type =~ /\A#{mt}\z/ || ext_type_type =~ /\A#{mt}\z/ + end + end + + # - Both types unknown or text/plain. + # - Ambiguous magic type with text extension. Plain text file. + # - Text magic type with ambiguous extension. TeX file missing extension. + def ambiguous_type? + (ext_type.to_s.blank? && magic_type.to_s.blank?) || + (magic_type.to_s.blank? && ext_type_type == 'text/plain') || + (ext_type.to_s.blank? && magic_type_type == 'text/plain') + end + end +end diff --git a/app/views/sent_notifications/unsubscribe.html.haml b/app/views/sent_notifications/unsubscribe.html.haml index 22fcfcda297..1eecbe3bc0e 100644 --- a/app/views/sent_notifications/unsubscribe.html.haml +++ b/app/views/sent_notifications/unsubscribe.html.haml @@ -1,13 +1,16 @@ - noteable = @sent_notification.noteable - noteable_type = @sent_notification.noteable_type.titleize.downcase - noteable_text = show_unsubscribe_title?(noteable) ? %(#{noteable.title} (#{noteable.to_reference})) : %(#{noteable.to_reference}) -- page_title _("Unsubscribe"), noteable_text, noteable_type.pluralize, @sent_notification.project.full_name +- show_project_path = can_read_project?(@sent_notification.project) +- project_path = show_project_path ? @sent_notification.project.full_name : _("GitLab / Unsubscribe") +- noteable_url = show_project_path ? url_for([@sent_notification.project.namespace.becomes(Namespace), @sent_notification.project, noteable]) : breadcrumb_title_link +- page_title _('Unsubscribe'), noteable_text, noteable_type.pluralize, project_path %h3.page-title = _("Unsubscribe from %{type}") % { type: noteable_type } %p - - link_to_noteable_text = link_to(noteable_text, url_for([@sent_notification.project.namespace.becomes(Namespace), @sent_notification.project, noteable])) + - link_to_noteable_text = link_to(noteable_text, noteable_url) = _("Are you sure you want to unsubscribe from the %{type}: %{link_to_noteable_text}?").html_safe % { type: noteable_type, link_to_noteable_text: link_to_noteable_text } %p diff --git a/changelogs/unreleased/ap-33785-file-integrity.yml b/changelogs/unreleased/ap-33785-file-integrity.yml new file mode 100644 index 00000000000..36ff617a252 --- /dev/null +++ b/changelogs/unreleased/ap-33785-file-integrity.yml @@ -0,0 +1,5 @@ +--- +title: Ensure content matches extension on image uploads +merge_request: 20697 +author: +type: security diff --git a/config/initializers/graphql.rb b/config/initializers/graphql.rb index f1bc289f1f0..2b21c9d9729 100644 --- a/config/initializers/graphql.rb +++ b/config/initializers/graphql.rb @@ -5,3 +5,7 @@ GraphQL::Field.accepts_definitions(authorize: GraphQL::Define.assign_metadata_ke GraphQL::Schema::Object.accepts_definition(:authorize) GraphQL::Schema::Field.accepts_definition(:authorize) + +GitlabSchema.middleware << GraphQL::Schema::TimeoutMiddleware.new(max_seconds: ENV.fetch('GITLAB_RAILS_GRAPHQL_TIMEOUT', 30).to_i) do |timeout_error, query| + Gitlab::GraphqlLogger.error(message: timeout_error.to_s, query: query.query_string, query_variables: query.provided_variables) +end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index a86fb44caa1..0240fc1539f 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -1363,7 +1363,7 @@ module API expose :author, using: Entities::UserBasic, if: -> (release, _) { release.author.present? } expose :commit, using: Entities::Commit, if: ->(_, _) { can_download_code? } expose :upcoming_release?, as: :upcoming_release - expose :milestones, using: Entities::Milestone, if: -> (release, _) { release.milestones.present? } + expose :milestones, using: Entities::Milestone, if: -> (release, _) { release.milestones.present? && can_read_milestone? } expose :commit_path, expose_nil: false expose :tag_path, expose_nil: false expose :evidence_sha, expose_nil: false, if: ->(_, _) { can_download_code? } @@ -1389,6 +1389,10 @@ module API def can_download_code? Ability.allowed?(options[:current_user], :download_code, object.project) end + + def can_read_milestone? + Ability.allowed?(options[:current_user], :read_milestone, object.project) + end end class Tag < Grape::Entity diff --git a/lib/banzai/filter/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb index 583b0081319..4f257189f8e 100644 --- a/lib/banzai/filter/relative_link_filter.rb +++ b/lib/banzai/filter/relative_link_filter.rb @@ -116,7 +116,7 @@ module Banzai end def process_link_to_upload_attr(html_attr) - path_parts = [Addressable::URI.unescape(html_attr.value)] + path_parts = [unescape_and_scrub_uri(html_attr.value)] if project path_parts.unshift(relative_url_root, project.full_path) @@ -172,7 +172,7 @@ module Banzai end def cleaned_file_path(uri) - Addressable::URI.unescape(uri.path).scrub.delete("\0").chomp("/") + unescape_and_scrub_uri(uri.path).delete("\0").chomp("/") end def relative_file_path(uri) @@ -184,7 +184,7 @@ module Banzai def request_path return unless context[:requested_path] - Addressable::URI.unescape(context[:requested_path]).chomp("/") + unescape_and_scrub_uri(context[:requested_path]).chomp("/") end # Convert a relative path into its correct location based on the currently @@ -266,6 +266,12 @@ module Banzai def repository @repository ||= project&.repository end + + private + + def unescape_and_scrub_uri(uri) + Addressable::URI.unescape(uri).scrub + end end end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index fa5560755a9..b573205a85f 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -8579,6 +8579,9 @@ msgstr "" msgid "GitHub import" msgstr "" +msgid "GitLab / Unsubscribe" +msgstr "" + msgid "GitLab CI Linter has been moved" msgstr "" diff --git a/scripts/sync-stable-branch.sh b/scripts/sync-stable-branch.sh index b44bf26a151..1eb416bf4f5 100644 --- a/scripts/sync-stable-branch.sh +++ b/scripts/sync-stable-branch.sh @@ -35,6 +35,22 @@ then exit 1 fi +if [[ "$TARGET_PROJECT" != "gitlab-org/gitlab-foss" ]] +then + echo 'This is a security FOSS merge train' + echo "Checking if $CI_COMMIT_SHA is available on canonical" + + gitlab_com_commit_status=$(curl -s "https://gitlab.com/api/v4/projects/278964/repository/commits/$CI_COMMIT_SHA" | jq -M .status) + + if [[ "$gitlab_com_commit_status" != "null" ]] + then + echo 'Commit available on canonical, skipping merge train' + exit 0 + fi + + echo 'Commit not available, triggering a merge train' +fi + curl -X POST \ -F token="$MERGE_TRAIN_TRIGGER_TOKEN" \ -F ref=master \ diff --git a/spec/controllers/profiles/notifications_controller_spec.rb b/spec/controllers/profiles/notifications_controller_spec.rb index dbc408bcdd9..ede68744ac6 100644 --- a/spec/controllers/profiles/notifications_controller_spec.rb +++ b/spec/controllers/profiles/notifications_controller_spec.rb @@ -52,6 +52,35 @@ describe Profiles::NotificationsController do end.to exceed_query_limit(control) end end + + context 'with project notifications' do + let!(:notification_setting) { create(:notification_setting, source: project, user: user, level: :watch) } + + before do + sign_in(user) + get :show + end + + context 'when project is public' do + let(:project) { create(:project, :public) } + + it 'shows notification setting for project' do + expect(assigns(:project_notifications).map(&:source_id)).to include(project.id) + end + end + + context 'when project is public' do + let(:project) { create(:project, :private) } + + it 'shows notification setting for project' do + # notification settings for given project were created before project was set to private + expect(user.notification_settings.for_projects.map(&:source_id)).to include(project.id) + + # check that notification settings for project where user does not have access are filtered + expect(assigns(:project_notifications)).to be_empty + end + end + end end describe 'POST update' do diff --git a/spec/controllers/projects/releases_controller_spec.rb b/spec/controllers/projects/releases_controller_spec.rb index e9fa3764117..750e9aabef0 100644 --- a/spec/controllers/projects/releases_controller_spec.rb +++ b/spec/controllers/projects/releases_controller_spec.rb @@ -167,7 +167,7 @@ describe Projects::ReleasesController do end describe 'GET #evidence' do - let(:tag_name) { "v1.1.0-evidence" } + let_it_be(:tag_name) { "v1.1.0-evidence" } let!(:release) { create(:release, :with_evidence, project: project, tag: tag_name) } let(:tag) { CGI.escape(release.tag) } let(:format) { :json } @@ -220,6 +220,85 @@ describe Projects::ReleasesController do it_behaves_like 'successful request' end end + + context 'when release is associated to a milestone which includes an issue' do + let_it_be(:project) { create(:project, :repository, :public) } + let_it_be(:issue) { create(:issue, project: project) } + let_it_be(:milestone) { create(:milestone, project: project, issues: [issue]) } + let_it_be(:release) { create(:release, project: project, tag: tag_name, milestones: [milestone]) } + + before do + create(:evidence, release: release) + end + + shared_examples_for 'does not show the issue in evidence' do + it do + subject + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['release']['milestones'] + .all? { |milestone| milestone['issues'].nil? }).to eq(true) + end + end + + shared_examples_for 'evidence not found' do + it do + subject + + expect(response).to have_gitlab_http_status(:not_found) + end + end + + shared_examples_for 'safely expose evidence' do + it_behaves_like 'does not show the issue in evidence' + + context 'when the issue is confidential' do + let(:issue) { create(:issue, :confidential, project: project) } + + it_behaves_like 'does not show the issue in evidence' + end + + context 'when the user is the author of the confidential issue' do + let(:issue) { create(:issue, :confidential, project: project, author: user) } + + it_behaves_like 'does not show the issue in evidence' + end + + context 'when project is private' do + let!(:project) { create(:project, :repository, :private) } + + it_behaves_like 'evidence not found' + end + + context 'when project restricts the visibility of issues to project members only' do + let!(:project) { create(:project, :repository, :issues_private) } + + it_behaves_like 'evidence not found' + end + end + + context 'when user is non-project member' do + let(:user) { create(:user) } + + it_behaves_like 'safely expose evidence' + end + + context 'when user is auditor', if: Gitlab.ee? do + let(:user) { create(:user, :auditor) } + + it_behaves_like 'safely expose evidence' + end + + context 'when external authorization control is enabled' do + let(:user) { create(:user) } + + before do + stub_application_setting(external_authorization_service_enabled: true) + end + + it_behaves_like 'evidence not found' + end + end end private diff --git a/spec/controllers/sent_notifications_controller_spec.rb b/spec/controllers/sent_notifications_controller_spec.rb index 0e634d8ba99..4dd4f49dcf1 100644 --- a/spec/controllers/sent_notifications_controller_spec.rb +++ b/spec/controllers/sent_notifications_controller_spec.rb @@ -56,7 +56,7 @@ describe SentNotificationsController do get(:unsubscribe, params: { id: sent_notification.reply_key }) end - shared_examples 'unsubscribing as anonymous' do + shared_examples 'unsubscribing as anonymous' do |project_visibility| it 'does not unsubscribe the user' do expect(noteable.subscribed?(user, target_project)).to be_truthy end @@ -69,6 +69,18 @@ describe SentNotificationsController do expect(response.status).to eq(200) expect(response).to render_template :unsubscribe end + + if project_visibility == :private + it 'does not show project name or path' do + expect(response.body).not_to include(noteable.project.name) + expect(response.body).not_to include(noteable.project.full_name) + end + else + it 'shows project name or path' do + expect(response.body).to include(noteable.project.name) + expect(response.body).to include(noteable.project.full_name) + end + end end context 'when project is public' do @@ -79,7 +91,7 @@ describe SentNotificationsController do expect(response.body).to include(issue.title) end - it_behaves_like 'unsubscribing as anonymous' + it_behaves_like 'unsubscribing as anonymous', :public end context 'when unsubscribing from confidential issue' do @@ -90,7 +102,7 @@ describe SentNotificationsController do expect(response.body).to include(confidential_issue.to_reference) end - it_behaves_like 'unsubscribing as anonymous' + it_behaves_like 'unsubscribing as anonymous', :public end context 'when unsubscribing from merge request' do @@ -100,7 +112,12 @@ describe SentNotificationsController do expect(response.body).to include(merge_request.title) end - it_behaves_like 'unsubscribing as anonymous' + it 'shows project name or path' do + expect(response.body).to include(issue.project.name) + expect(response.body).to include(issue.project.full_name) + end + + it_behaves_like 'unsubscribing as anonymous', :public end end @@ -110,11 +127,11 @@ describe SentNotificationsController do context 'when unsubscribing from issue' do let(:noteable) { issue } - it 'shows issue title' do + it 'does not show issue title' do expect(response.body).not_to include(issue.title) end - it_behaves_like 'unsubscribing as anonymous' + it_behaves_like 'unsubscribing as anonymous', :private end context 'when unsubscribing from confidential issue' do @@ -125,17 +142,17 @@ describe SentNotificationsController do expect(response.body).to include(confidential_issue.to_reference) end - it_behaves_like 'unsubscribing as anonymous' + it_behaves_like 'unsubscribing as anonymous', :private end context 'when unsubscribing from merge request' do let(:noteable) { merge_request } - it 'shows merge request title' do + it 'dos not show merge request title' do expect(response.body).not_to include(merge_request.title) end - it_behaves_like 'unsubscribing as anonymous' + it_behaves_like 'unsubscribing as anonymous', :private end end end diff --git a/spec/fixtures/api/schemas/evidences/milestone.json b/spec/fixtures/api/schemas/evidences/milestone.json index ab27fdecde2..3ce0644225b 100644 --- a/spec/fixtures/api/schemas/evidences/milestone.json +++ b/spec/fixtures/api/schemas/evidences/milestone.json @@ -7,8 +7,7 @@ "state", "iid", "created_at", - "due_date", - "issues" + "due_date" ], "properties": { "id": { "type": "integer" }, @@ -17,11 +16,7 @@ "state": { "type": "string" }, "iid": { "type": "integer" }, "created_at": { "type": "date" }, - "due_date": { "type": ["date", "null"] }, - "issues": { - "type": "array", - "items": { "$ref": "issue.json" } - } + "due_date": { "type": ["date", "null"] } }, "additionalProperties": false } diff --git a/spec/fixtures/not_a_png.png b/spec/fixtures/not_a_png.png new file mode 100644 index 0000000000000000000000000000000000000000..932f9efaed97c5d7ffa468c7b05fc74eb9f57dd2 GIT binary patch literal 72642 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

-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 literal 0 HcmV?d00001 diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb index a17a645d4d0..9f467d7a6fd 100644 --- a/spec/lib/banzai/filter/relative_link_filter_spec.rb +++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb @@ -128,6 +128,15 @@ describe Banzai::Filter::RelativeLinkFilter do expect { filter(act) }.not_to raise_error end + it 'does not raise an exception on URIs containing invalid utf-8 byte sequences in uploads' do + act = link("/uploads/%FF") + expect { filter(act) }.not_to raise_error + end + + it 'does not raise an exception on URIs containing invalid utf-8 byte sequences in context requested path' do + expect { filter(link("files/test.md"), requested_path: '%FF') }.not_to raise_error + end + it 'does not raise an exception with a garbled path' do act = link("open(/var/tmp/):%20/location%0Afrom:%20/test") expect { filter(act) }.not_to raise_error diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 314cc4e1d3c..b0f708bc0e7 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -2638,8 +2638,8 @@ describe User, :do_not_mock_admin_mode do add_user(:maintainer) end - it 'loads' do - expect(user.ci_owned_runners).to contain_exactly(runner) + it 'does not load' do + expect(user.ci_owned_runners).to be_empty end end @@ -2654,6 +2654,20 @@ describe User, :do_not_mock_admin_mode do end end + shared_examples :group_member do + context 'when the user is owner' do + before do + add_user(:owner) + end + + it 'loads' do + expect(user.ci_owned_runners).to contain_exactly(runner) + end + end + + it_behaves_like :member + end + context 'with groups projects runners' do let(:group) { create(:group) } let!(:project) { create(:project, group: group) } @@ -2662,7 +2676,7 @@ describe User, :do_not_mock_admin_mode do group.add_user(user, access) end - it_behaves_like :member + it_behaves_like :group_member end context 'with groups runners' do @@ -2673,14 +2687,14 @@ describe User, :do_not_mock_admin_mode do group.add_user(user, access) end - it_behaves_like :member + it_behaves_like :group_member end context 'with other projects runners' do let!(:project) { create(:project) } def add_user(access) - project.add_role(user, access) + project.add_user(user, access) end it_behaves_like :member @@ -2698,7 +2712,7 @@ describe User, :do_not_mock_admin_mode do subgroup.add_user(another_user, :owner) end - it_behaves_like :member + it_behaves_like :group_member end end diff --git a/spec/requests/api/graphql/gitlab_schema_spec.rb b/spec/requests/api/graphql/gitlab_schema_spec.rb index 2aeb75a10b4..2cb8436662b 100644 --- a/spec/requests/api/graphql/gitlab_schema_spec.rb +++ b/spec/requests/api/graphql/gitlab_schema_spec.rb @@ -8,6 +8,18 @@ describe 'GitlabSchema configurations' do set(:project) { create(:project) } shared_examples 'imposing query limits' do + describe 'timeouts' do + context 'when timeout is reached' do + it 'shows an error' do + Timecop.scale(50000000) do # ludicrously large number because the timeout has to happen before the query even begins + subject + + expect_graphql_errors_to_include /Timeout/ + end + end + end + end + describe '#max_complexity' do context 'when complexity is too high' do it 'shows an error' do diff --git a/spec/requests/api/releases_spec.rb b/spec/requests/api/releases_spec.rb index da04e852795..233f0497b7f 100644 --- a/spec/requests/api/releases_spec.rb +++ b/spec/requests/api/releases_spec.rb @@ -340,6 +340,40 @@ describe API::Releases do expect(response).to have_gitlab_http_status(:ok) end + + context 'when release is associated to a milestone' do + let!(:release) do + create(:release, tag: 'v0.1', project: project, milestones: [milestone]) + end + + let(:milestone) { create(:milestone, project: project) } + + it 'exposes milestones' do + get api("/projects/#{project.id}/releases/v0.1", non_project_member) + + expect(json_response['milestones'].first['title']).to eq(milestone.title) + end + + context 'when project restricts visibility of issues and merge requests' do + let!(:project) { create(:project, :repository, :public, :issues_private, :merge_requests_private) } + + it 'does not expose milestones' do + get api("/projects/#{project.id}/releases/v0.1", non_project_member) + + expect(json_response['milestones']).to be_nil + end + end + + context 'when project restricts visibility of issues' do + let!(:project) { create(:project, :repository, :public, :issues_private) } + + it 'exposes milestones' do + get api("/projects/#{project.id}/releases/v0.1", non_project_member) + + expect(json_response['milestones'].first['title']).to eq(milestone.title) + end + end + end end end end diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb index 8daba204d50..7bad30d107d 100644 --- a/spec/requests/api/runners_spec.rb +++ b/spec/requests/api/runners_spec.rb @@ -6,6 +6,7 @@ describe API::Runners do let(:admin) { create(:user, :admin) } let(:user) { create(:user) } let(:user2) { create(:user) } + let(:group_maintainer) { create(:user) } let(:project) { create(:project, creator_id: user.id) } let(:project2) { create(:project, creator_id: user.id) } @@ -20,6 +21,7 @@ describe API::Runners do before do # Set project access for users + create(:group_member, :maintainer, user: group_maintainer, group: group) create(:project_member, :maintainer, user: user, project: project) create(:project_member, :maintainer, user: user, project: project2) create(:project_member, :reporter, user: user2, project: project) @@ -525,6 +527,20 @@ describe API::Runners do end.to change { Ci::Runner.project_type.count }.by(-1) end + it 'does not delete group runner with maintainer access' do + delete api("/runners/#{group_runner.id}", group_maintainer) + + expect(response).to have_http_status(403) + end + + it 'deletes group runner with owner access' do + expect do + delete api("/runners/#{group_runner.id}", user) + + expect(response).to have_http_status(204) + end.to change { Ci::Runner.group_type.count }.by(-1) + end + it_behaves_like '412 response' do let(:request) { api("/runners/#{project_runner.id}", user) } end diff --git a/spec/support/shared_contexts/upload_type_check_shared_context.rb b/spec/support/shared_contexts/upload_type_check_shared_context.rb new file mode 100644 index 00000000000..04c97500dd6 --- /dev/null +++ b/spec/support/shared_contexts/upload_type_check_shared_context.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +# Construct an `uploader` variable that is configured to `check_upload_type` +# with `mime_types` and `extensions`. +shared_context 'uploader with type check' do + let(:uploader_class) do + Class.new(GitlabUploader) do + include UploadTypeCheck::Concern + storage :file + end + end + + let(:mime_types) { nil } + let(:extensions) { nil } + let(:uploader) do + uploader_class.class_exec(mime_types, extensions) do |mime_types, extensions| + check_upload_type mime_types: mime_types, extensions: extensions + end + uploader_class.new(build_stubbed(:user)) + end +end + +shared_context 'stubbed MimeMagic mime type detection' do + let(:mime_type) { '' } + let(:magic_mime) { mime_type } + let(:ext_mime) { mime_type } + before do + magic_mime_obj = MimeMagic.new(magic_mime) + ext_mime_obj = MimeMagic.new(ext_mime) + allow(MimeMagic).to receive(:by_magic).with(anything).and_return(magic_mime_obj) + allow(MimeMagic).to receive(:by_path).with(anything).and_return(ext_mime_obj) + end +end diff --git a/spec/support/shared_examples/uploaders/upload_type_shared_examples.rb b/spec/support/shared_examples/uploaders/upload_type_shared_examples.rb new file mode 100644 index 00000000000..91d2526cde2 --- /dev/null +++ b/spec/support/shared_examples/uploaders/upload_type_shared_examples.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +def check_content_matches_extension!(file = double(read: nil, path: '')) + magic_file = UploadTypeCheck::MagicFile.new(file) + uploader.check_content_matches_extension!(magic_file) +end + +shared_examples 'upload passes content type check' do + it 'does not raise error' do + expect { check_content_matches_extension! }.not_to raise_error + end +end + +shared_examples 'upload fails content type check' do + it 'raises error' do + expect { check_content_matches_extension! }.to raise_error(CarrierWave::IntegrityError) + end +end + +def upload_type_checked_filenames(filenames) + Array(filenames).each do |filename| + # Feed the uploader "some" content. + path = File.join('spec', 'fixtures', 'dk.png') + file = File.new(path, 'r') + # Rename the file with what we want. + allow(file).to receive(:path).and_return(filename) + + # Force the content type to match the extension type. + mime_type = MimeMagic.by_path(filename) + allow(MimeMagic).to receive(:by_magic).and_return(mime_type) + + uploaded_file = Rack::Test::UploadedFile.new(file, original_filename: filename) + uploader.cache!(uploaded_file) + end +end + +def upload_type_checked_fixtures(upload_fixtures) + upload_fixtures = Array(upload_fixtures) + upload_fixtures.each do |upload_fixture| + path = File.join('spec', 'fixtures', upload_fixture) + uploader.cache!(fixture_file_upload(path)) + end +end + +shared_examples 'type checked uploads' do |upload_fixtures = nil, filenames: nil| + it 'check type' do + upload_fixtures = Array(upload_fixtures) + filenames = Array(filenames) + + times = upload_fixtures.length + filenames.length + expect(uploader).to receive(:check_content_matches_extension!).exactly(times).times + + upload_type_checked_fixtures(upload_fixtures) unless upload_fixtures.empty? + upload_type_checked_filenames(filenames) unless filenames.empty? + end +end + +shared_examples 'skipped type checked uploads' do |upload_fixtures = nil, filenames: nil| + it 'skip type check' do + expect(uploader).not_to receive(:check_content_matches_extension!) + + upload_type_checked_fixtures(upload_fixtures) if upload_fixtures + upload_type_checked_filenames(filenames) if filenames + end +end diff --git a/spec/uploaders/avatar_uploader_spec.rb b/spec/uploaders/avatar_uploader_spec.rb index c0844360589..669f75b2ee8 100644 --- a/spec/uploaders/avatar_uploader_spec.rb +++ b/spec/uploaders/avatar_uploader_spec.rb @@ -46,4 +46,16 @@ describe AvatarUploader do expect(uploader.absolute_path).to eq(absolute_path) end end + + context 'upload type check' do + AvatarUploader::SAFE_IMAGE_EXT.each do |ext| + context "#{ext} extension" do + it_behaves_like 'type checked uploads', filenames: "image.#{ext}" + end + end + + context 'skip image/svg+xml integrity check' do + it_behaves_like 'skipped type checked uploads', filenames: 'image.svg' + end + end end diff --git a/spec/uploaders/favicon_uploader_spec.rb b/spec/uploaders/favicon_uploader_spec.rb new file mode 100644 index 00000000000..4d6c849883a --- /dev/null +++ b/spec/uploaders/favicon_uploader_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe FaviconUploader do + let_it_be(:model) { build_stubbed(:user) } + let_it_be(:uploader) { described_class.new(model, :favicon) } + + context 'upload type check' do + FaviconUploader::EXTENSION_WHITELIST.each do |ext| + context "#{ext} extension" do + it_behaves_like 'type checked uploads', filenames: "image.#{ext}" + end + end + end + + context 'upload non-whitelisted file extensions' do + it 'will deny upload' do + path = File.join('spec', 'fixtures', 'banana_sample.gif') + fixture_file = fixture_file_upload(path) + expect { uploader.cache!(fixture_file) }.to raise_exception(CarrierWave::IntegrityError) + end + end +end diff --git a/spec/uploaders/upload_type_check_spec.rb b/spec/uploaders/upload_type_check_spec.rb new file mode 100644 index 00000000000..a4895f6a956 --- /dev/null +++ b/spec/uploaders/upload_type_check_spec.rb @@ -0,0 +1,124 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe UploadTypeCheck do + include_context 'uploader with type check' + + def upload_fixture(filename) + fixture_file_upload(File.join('spec', 'fixtures', filename)) + end + + describe '#check_content_matches_extension! callback using file upload' do + context 'when extension matches contents' do + it 'not raise error on upload' do + expect { uploader.cache!(upload_fixture('banana_sample.gif')) }.not_to raise_error + end + end + + context 'when extension does not match contents' do + it 'raise error' do + expect { uploader.cache!(upload_fixture('not_a_png.png')) }.to raise_error(CarrierWave::IntegrityError) + end + end + end + + describe '#check_content_matches_extension! callback using stubs' do + include_context 'stubbed MimeMagic mime type detection' + + context 'when no extension and with ambiguous/text content' do + let(:magic_mime) { '' } + let(:ext_mime) { '' } + + it_behaves_like 'upload passes content type check' + end + + context 'when no extension and with non-text content' do + let(:magic_mime) { 'image/gif' } + let(:ext_mime) { '' } + + it_behaves_like 'upload fails content type check' + end + + # Most text files will exhibit this behaviour. + context 'when ambiguous content with text extension' do + let(:magic_mime) { '' } + let(:ext_mime) { 'text/plain' } + + it_behaves_like 'upload passes content type check' + end + + context 'when text content with text extension' do + let(:magic_mime) { 'text/plain' } + let(:ext_mime) { 'text/plain' } + + it_behaves_like 'upload passes content type check' + end + + context 'when ambiguous content with non-text extension' do + let(:magic_mime) { '' } + let(:ext_mime) { 'application/zip' } + + it_behaves_like 'upload fails content type check' + end + + # These are the types when uploading a .dmg + context 'when content and extension do not match' do + let(:magic_mime) { 'application/x-bzip' } + let(:ext_mime) { 'application/x-apple-diskimage' } + + it_behaves_like 'upload fails content type check' + end + end + + describe '#check_content_matches_extension! mime_type filtering' do + context 'without mime types' do + let(:mime_types) { nil } + + it_behaves_like 'type checked uploads', %w[doc_sample.txt rails_sample.jpg] + end + + context 'with mime types string' do + let(:mime_types) { 'text/plain' } + + it_behaves_like 'type checked uploads', %w[doc_sample.txt] + it_behaves_like 'skipped type checked uploads', %w[dk.png] + end + + context 'with mime types regex' do + let(:mime_types) { [/image\/(gif|png)/] } + + it_behaves_like 'type checked uploads', %w[banana_sample.gif dk.png] + it_behaves_like 'skipped type checked uploads', %w[doc_sample.txt] + end + + context 'with mime types array' do + let(:mime_types) { ['text/plain', /image\/png/] } + + it_behaves_like 'type checked uploads', %w[doc_sample.txt dk.png] + it_behaves_like 'skipped type checked uploads', %w[audio_sample.wav] + end + end + + describe '#check_content_matches_extension! extensions filtering' do + context 'without extensions' do + let(:extensions) { nil } + + it_behaves_like 'type checked uploads', %w[doc_sample.txt dk.png] + end + + context 'with extensions string' do + let(:extensions) { 'txt' } + + it_behaves_like 'type checked uploads', %w[doc_sample.txt] + it_behaves_like 'skipped type checked uploads', %w[rails_sample.jpg] + end + + context 'with extensions array of strings' do + let(:extensions) { %w[txt png] } + + it_behaves_like 'type checked uploads', %w[doc_sample.txt dk.png] + it_behaves_like 'skipped type checked uploads', %w[audio_sample.wav] + end + end +end