diff options
author | John Jarvis <jarv@gitlab.com> | 2018-12-27 12:53:06 +0100 |
---|---|---|
committer | John Jarvis <jarv@gitlab.com> | 2018-12-27 12:53:06 +0100 |
commit | 278490c8e951f3052b28852e8bd0314a59bd6555 (patch) | |
tree | 2caabd032bbd63f5ffffc406b36fa05aa6349cd2 /app | |
parent | 88fb4b7502bd50e40674235f4315bc54be287a29 (diff) | |
parent | 6e793e16cb0f9195f9bb60ceeaf30244c9d4cb7e (diff) | |
download | gitlab-ce-278490c8e951f3052b28852e8bd0314a59bd6555.tar.gz |
Merge branch 'security-11-4' of dev.gitlab.org:gitlab/gitlabhq into 11-4-stable
Diffstat (limited to 'app')
24 files changed, 166 insertions, 64 deletions
diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js index 337a70a270a..51f05be7c84 100644 --- a/app/assets/javascripts/gfm_auto_complete.js +++ b/app/assets/javascripts/gfm_auto_complete.js @@ -242,7 +242,7 @@ class GfmAutoComplete { displayTpl(value) { let tmpl = GfmAutoComplete.Loading.template; if (value.title != null) { - tmpl = GfmAutoComplete.Milestones.template; + tmpl = GfmAutoComplete.Milestones.templateFunction(value.title); } return tmpl; }, @@ -309,7 +309,7 @@ class GfmAutoComplete { searchKey: 'search', data: GfmAutoComplete.defaultLoadingData, displayTpl(value) { - let tmpl = GfmAutoComplete.Labels.template; + let tmpl = GfmAutoComplete.Labels.templateFunction(value.color, value.title); if (GfmAutoComplete.isLoading(value)) { tmpl = GfmAutoComplete.Loading.template; } @@ -559,8 +559,11 @@ GfmAutoComplete.Members = { }, }; GfmAutoComplete.Labels = { - // eslint-disable-next-line no-template-curly-in-string - template: '<li><span class="dropdown-label-box" style="background: ${color}"></span> ${title}</li>', + templateFunction(color, title) { + return `<li><span class="dropdown-label-box" style="background: ${_.escape( + color, + )}"></span> ${_.escape(title)}</li>`; + }, }; // Issues, MergeRequests and Snippets GfmAutoComplete.Issues = { @@ -570,8 +573,9 @@ GfmAutoComplete.Issues = { }; // Milestones GfmAutoComplete.Milestones = { - // eslint-disable-next-line no-template-curly-in-string - template: '<li>${title}</li>', + templateFunction(title) { + return `<li>${_.escape(title)}</li>`; + }, }; GfmAutoComplete.Loading = { template: '<li style="pointer-events: none;"><i class="fa fa-spinner fa-spin"></i> Loading...</li>', diff --git a/app/controllers/concerns/sends_blob.rb b/app/controllers/concerns/sends_blob.rb index 971390d9118..8ecdaced9f5 100644 --- a/app/controllers/concerns/sends_blob.rb +++ b/app/controllers/concerns/sends_blob.rb @@ -8,7 +8,7 @@ module SendsBlob include SendFileUpload end - def send_blob(blob, params = {}) + def send_blob(repository, blob, params = {}) if blob headers['X-Content-Type-Options'] = 'nosniff' diff --git a/app/controllers/projects/avatars_controller.rb b/app/controllers/projects/avatars_controller.rb index 1c385c0e15a..1f4a25f82e9 100644 --- a/app/controllers/projects/avatars_controller.rb +++ b/app/controllers/projects/avatars_controller.rb @@ -8,7 +8,7 @@ class Projects::AvatarsController < Projects::ApplicationController def show @blob = @repository.blob_at_branch(@repository.root_ref, @project.avatar_in_git) - send_blob(@blob) + send_blob(@repository, @blob) end def destroy diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb index 1dd5d1ff2e8..42ae5b0ef3c 100644 --- a/app/controllers/projects/raw_controller.rb +++ b/app/controllers/projects/raw_controller.rb @@ -12,6 +12,6 @@ class Projects::RawController < Projects::ApplicationController def show @blob = @repository.blob_at(@commit.id, @path) - send_blob(@blob, inline: (params[:inline] != 'false')) + send_blob(@repository, @blob, inline: (params[:inline] != 'false')) end end diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb index 8c6d87a421f..88dd111132b 100644 --- a/app/controllers/projects/wikis_controller.rb +++ b/app/controllers/projects/wikis_controller.rb @@ -2,6 +2,7 @@ class Projects::WikisController < Projects::ApplicationController include PreviewMarkdown + include SendsBlob include Gitlab::Utils::StrongMemoize before_action :authorize_read_wiki! @@ -26,16 +27,8 @@ class Projects::WikisController < Projects::ApplicationController set_encoding_error unless valid_encoding? render 'show' - elsif file = @project_wiki.find_file(params[:id], params[:version_id]) - response.headers['Content-Security-Policy'] = "default-src 'none'" - response.headers['X-Content-Security-Policy'] = "default-src 'none'" - - send_data( - file.raw_data, - type: file.mime_type, - disposition: 'inline', - filename: file.name - ) + elsif file_blob + send_blob(@project_wiki.repository, file_blob) elsif can?(current_user, :create_wiki, @project) && view_param == 'create' @page = build_page(title: params[:id]) @@ -164,4 +157,14 @@ class Projects::WikisController < Projects::ApplicationController def set_encoding_error flash.now[:notice] = "The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository." end + + def file_blob + strong_memoize(:file_blob) do + commit = @project_wiki.repository.commit(@project_wiki.default_branch) + + next unless commit + + @project_wiki.repository.blob_at(commit.id, params[:id]) + end + end end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index af8cedc4e5b..3fbfc6bcaee 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -19,6 +19,7 @@ class ProjectsController < Projects::ApplicationController before_action :lfs_blob_ids, only: [:show], if: [:repo_exists?, :project_view_files?] before_action :project_export_enabled, only: [:export, :download_export, :remove_export, :generate_new_export] before_action :present_project, only: [:edit] + before_action :authorize_download_code!, only: [:refs] # Authorize before_action :authorize_admin_project!, only: [:edit, :update, :housekeeping, :download_export, :export, :remove_export, :generate_new_export] diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index 883e5ddff57..98678aefa8a 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -150,7 +150,9 @@ module BlobHelper # example of Javascript) we tell the browser of the victim not to # execute untrusted data. def safe_content_type(blob) - if blob.text? + if blob.extension == 'svg' + blob.mime_type + elsif blob.text? 'text/plain; charset=utf-8' elsif blob.image? blob.content_type @@ -159,6 +161,12 @@ module BlobHelper end end + def content_disposition(blob, inline) + return 'attachment' if blob.extension == 'svg' + + inline ? 'inline' : 'attachment' + end + def ref_project @ref_project ||= @target_project || @project end diff --git a/app/helpers/workhorse_helper.rb b/app/helpers/workhorse_helper.rb index f19445fca1a..49c08dce96c 100644 --- a/app/helpers/workhorse_helper.rb +++ b/app/helpers/workhorse_helper.rb @@ -6,7 +6,7 @@ module WorkhorseHelper # Send a Git blob through Workhorse def send_git_blob(repository, blob, inline: true) headers.store(*Gitlab::Workhorse.send_git_blob(repository, blob)) - headers['Content-Disposition'] = inline ? 'inline' : 'attachment' + headers['Content-Disposition'] = content_disposition(blob, inline) headers['Content-Type'] = safe_content_type(blob) render plain: "" end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index cdfe8175a42..1d4a1d25597 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -9,6 +9,7 @@ module Ci include Presentable include Importable include Gitlab::Utils::StrongMemoize + include HasRef belongs_to :project, inverse_of: :builds belongs_to :runner @@ -153,6 +154,10 @@ module Ci .execute(build) # rubocop: enable CodeReuse/ServiceClass end + + def find_running_by_token(token) + running.find_by_token(token) + end end state_machine :status do @@ -593,11 +598,11 @@ module Ci def secret_group_variables return [] unless project.group - project.group.secret_variables_for(ref, project) + project.group.secret_variables_for(git_ref, project) end def secret_project_variables(environment: persisted_environment) - project.secret_variables_for(ref: ref, environment: environment) + project.secret_variables_for(ref: git_ref, environment: environment) end def steps diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 17024e8a0af..c66556d501c 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -11,6 +11,7 @@ module Ci include Gitlab::Utils::StrongMemoize include AtomicInternalId include EnumWithNil + include HasRef belongs_to :project, inverse_of: :pipelines belongs_to :user @@ -355,10 +356,6 @@ module Ci @commit ||= Commit.lazy(project, sha) end - def branch? - !tag? - end - def stuck? pending_builds.any?(&:stuck?) end @@ -558,7 +555,7 @@ module Ci end def protected_ref? - strong_memoize(:protected_ref) { project.protected_for?(ref) } + strong_memoize(:protected_ref) { project.protected_for?(git_ref) } end def legacy_trigger diff --git a/app/models/concerns/has_ref.rb b/app/models/concerns/has_ref.rb new file mode 100644 index 00000000000..d7089294efc --- /dev/null +++ b/app/models/concerns/has_ref.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module HasRef + extend ActiveSupport::Concern + + def branch? + !tag? + end + + def git_ref + if branch? + Gitlab::Git::BRANCH_REF_PREFIX + ref.to_s + elsif tag? + Gitlab::Git::TAG_REF_PREFIX + ref.to_s + end + end +end diff --git a/app/models/project.rb b/app/models/project.rb index cb63520f593..33f436a7e6f 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -305,10 +305,10 @@ class Project < ActiveRecord::Base validates :namespace, presence: true validates :name, uniqueness: { scope: :namespace_id } - validates :import_url, url: { protocols: %w(http https ssh git), - allow_localhost: false, - enforce_user: true, - ports: VALID_IMPORT_PORTS }, if: [:external_import?, :import_url_changed?] + validates :import_url, public_url: { protocols: %w(http https ssh git), + allow_localhost: false, + enforce_user: true, + ports: VALID_IMPORT_PORTS }, if: [:external_import?, :import_url_changed?] validates :star_count, numericality: { greater_than_or_equal_to: 0 } validate :check_limit, on: :create validate :check_repository_path_availability, on: :update, if: ->(project) { project.renamed? } @@ -1846,10 +1846,21 @@ class Project < ActiveRecord::Base end def protected_for?(ref) - if repository.branch_exists?(ref) - ProtectedBranch.protected?(self, ref) - elsif repository.tag_exists?(ref) - ProtectedTag.protected?(self, ref) + raise Repository::AmbiguousRefError if repository.ambiguous_ref?(ref) + + resolved_ref = repository.expand_ref(ref) || ref + return false unless Gitlab::Git.tag_ref?(resolved_ref) || Gitlab::Git.branch_ref?(resolved_ref) + + ref_name = if resolved_ref == ref + Gitlab::Git.ref_name(resolved_ref) + else + ref + end + + if Gitlab::Git.branch_ref?(resolved_ref) + ProtectedBranch.protected?(self, ref_name) + elsif Gitlab::Git.tag_ref?(resolved_ref) + ProtectedTag.protected?(self, ref_name) end end diff --git a/app/models/remote_mirror.rb b/app/models/remote_mirror.rb index c1f53b5da4f..b2d04d198cd 100644 --- a/app/models/remote_mirror.rb +++ b/app/models/remote_mirror.rb @@ -18,7 +18,7 @@ class RemoteMirror < ActiveRecord::Base belongs_to :project, inverse_of: :remote_mirrors - validates :url, presence: true, url: { protocols: %w(ssh git http https), allow_blank: true, enforce_user: true } + validates :url, presence: true, public_url: { protocols: %w(ssh git http https), allow_blank: true, enforce_user: true } before_save :set_new_remote_name, if: :mirror_url_changed? diff --git a/app/models/repository.rb b/app/models/repository.rb index a3a3ce179fc..1aaea0d46da 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -26,6 +26,7 @@ class Repository delegate :bundle_to_disk, to: :raw_repository CreateTreeError = Class.new(StandardError) + AmbiguousRefError = Class.new(StandardError) # Methods that cache data from the Git repository. # @@ -177,6 +178,18 @@ class Repository tags.find { |tag| tag.name == name } end + def ambiguous_ref?(ref) + tag_exists?(ref) && branch_exists?(ref) + end + + def expand_ref(ref) + if tag_exists?(ref) + Gitlab::Git::TAG_REF_PREFIX + ref + elsif branch_exists?(ref) + Gitlab::Git::BRANCH_REF_PREFIX + ref + end + end + def add_branch(user, branch_name, ref) branch = raw_repository.add_branch(branch_name, user: user, target: ref) diff --git a/app/models/todo.rb b/app/models/todo.rb index 265fb932f7c..acd87a6d173 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -4,6 +4,11 @@ class Todo < ActiveRecord::Base include Sortable include FromUnion + # Time to wait for todos being removed when not visible for user anymore. + # Prevents TODOs being removed by mistake, for example, removing access from a user + # and giving it back again. + WAIT_FOR_DELETE = 1.hour + ASSIGNED = 1 MENTIONED = 2 BUILD_FAILED = 3 diff --git a/app/policies/issuable_policy.rb b/app/policies/issuable_policy.rb index 6d8b575102e..ecb2797d1d9 100644 --- a/app/policies/issuable_policy.rb +++ b/app/policies/issuable_policy.rb @@ -11,7 +11,7 @@ class IssuablePolicy < BasePolicy @user && @subject.assignee_or_author?(@user) end - rule { assignee_or_author }.policy do + rule { can?(:guest_access) & assignee_or_author }.policy do enable :read_issue enable :update_issue enable :reopen_issue diff --git a/app/services/groups/update_service.rb b/app/services/groups/update_service.rb index fe47aa2f140..dd7b30639b0 100644 --- a/app/services/groups/update_service.rb +++ b/app/services/groups/update_service.rb @@ -29,7 +29,7 @@ module Groups def after_update if group.previous_changes.include?(:visibility_level) && group.private? # don't enqueue immediately to prevent todos removal in case of a mistake - TodosDestroyer::GroupPrivateWorker.perform_in(1.hour, group.id) + TodosDestroyer::GroupPrivateWorker.perform_in(Todo::WAIT_FOR_DELETE, group.id) end end diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb index b54b0bf6ef6..27013dc21ad 100644 --- a/app/services/issues/update_service.rb +++ b/app/services/issues/update_service.rb @@ -38,7 +38,7 @@ module Issues if issue.previous_changes.include?('confidential') # don't enqueue immediately to prevent todos removal in case of a mistake - TodosDestroyer::ConfidentialIssueWorker.perform_in(1.hour, issue.id) if issue.confidential? + TodosDestroyer::ConfidentialIssueWorker.perform_in(Todo::WAIT_FOR_DELETE, issue.id) if issue.confidential? create_confidentiality_note(issue) end diff --git a/app/services/members/base_service.rb b/app/services/members/base_service.rb index 8248f1441d7..db719132ca9 100644 --- a/app/services/members/base_service.rb +++ b/app/services/members/base_service.rb @@ -47,5 +47,11 @@ module Members raise "Unknown action '#{action}' on #{member}!" end end + + def enqueue_delete_todos(member) + type = member.is_a?(GroupMember) ? 'Group' : 'Project' + # don't enqueue immediately to prevent todos removal in case of a mistake + TodosDestroyer::EntityLeaveWorker.perform_in(Todo::WAIT_FOR_DELETE, member.user_id, member.source_id, type) + end end end diff --git a/app/services/members/destroy_service.rb b/app/services/members/destroy_service.rb index c186a5971dc..ae0c644e6c0 100644 --- a/app/services/members/destroy_service.rb +++ b/app/services/members/destroy_service.rb @@ -15,7 +15,7 @@ module Members notification_service.decline_access_request(member) end - enqeue_delete_todos(member) + enqueue_delete_todos(member) after_execute(member: member) @@ -24,12 +24,6 @@ module Members private - def enqeue_delete_todos(member) - type = member.is_a?(GroupMember) ? 'Group' : 'Project' - # don't enqueue immediately to prevent todos removal in case of a mistake - TodosDestroyer::EntityLeaveWorker.perform_in(1.hour, member.user_id, member.source_id, type) - end - def can_destroy_member?(member) can?(current_user, destroy_member_permission(member), member) end diff --git a/app/services/members/update_service.rb b/app/services/members/update_service.rb index 1f5618dae53..ff8d5c1d8c9 100644 --- a/app/services/members/update_service.rb +++ b/app/services/members/update_service.rb @@ -10,9 +10,18 @@ module Members if member.update(params) after_execute(action: permission, old_access_level: old_access_level, member: member) + + # Deletes only confidential issues todos for guests + enqueue_delete_todos(member) if downgrading_to_guest? end member end + + private + + def downgrading_to_guest? + params[:access_level] == Gitlab::Access::GUEST + end end end diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index 0e76d2cc3ab..c37d867672c 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -13,7 +13,7 @@ module MergeRequests merge_request.source_project = find_source_project merge_request.target_project = find_target_project merge_request.target_branch = find_target_branch - merge_request.can_be_created = branches_valid? + merge_request.can_be_created = projects_and_branches_valid? # compare branches only if branches are valid, otherwise # compare_branches may raise an error @@ -44,15 +44,19 @@ module MergeRequests to: :merge_request def find_source_project - return source_project if source_project.present? && can?(current_user, :read_project, source_project) + return source_project if source_project.present? && can?(current_user, :create_merge_request_from, source_project) project end def find_target_project - return target_project if target_project.present? && can?(current_user, :read_project, target_project) + return target_project if target_project.present? && can?(current_user, :create_merge_request_in, target_project) - project.default_merge_request_target + target_project = project.default_merge_request_target + + return target_project if target_project.present? && can?(current_user, :create_merge_request_in, target_project) + + project end def find_target_branch @@ -67,10 +71,11 @@ module MergeRequests params[:target_branch].present? end - def branches_valid? + def projects_and_branches_valid? + return false if source_project.nil? || target_project.nil? return false unless source_branch_specified? || target_branch_specified? - validate_branches + validate_projects_and_branches errors.blank? end @@ -89,7 +94,12 @@ module MergeRequests end end - def validate_branches + def validate_projects_and_branches + merge_request.validate_target_project + merge_request.validate_fork + + return if errors.any? + add_error('You must select source and target branch') unless branches_present? add_error('You must select different branches') if same_source_and_target? add_error("Source branch \"#{source_branch}\" does not exist") unless source_branch_exists? diff --git a/app/services/projects/lfs_pointers/lfs_download_service.rb b/app/services/projects/lfs_pointers/lfs_download_service.rb index f9b9781ad5f..b5128443435 100644 --- a/app/services/projects/lfs_pointers/lfs_download_service.rb +++ b/app/services/projects/lfs_pointers/lfs_download_service.rb @@ -12,28 +12,43 @@ module Projects return if LfsObject.exists?(oid: oid) - sanitized_uri = Gitlab::UrlSanitizer.new(url) - Gitlab::UrlBlocker.validate!(sanitized_uri.sanitized_url, protocols: VALID_PROTOCOLS) + sanitized_uri = sanitize_url!(url) with_tmp_file(oid) do |file| - size = download_and_save_file(file, sanitized_uri) - lfs_object = LfsObject.new(oid: oid, size: size, file: file) + download_and_save_file(file, sanitized_uri) + lfs_object = LfsObject.new(oid: oid, size: file.size, file: file) project.all_lfs_objects << lfs_object end + rescue Gitlab::UrlBlocker::BlockedUrlError => e + Rails.logger.error("LFS file with oid #{oid} couldn't be downloaded: #{e.message}") rescue StandardError => e - Rails.logger.error("LFS file with oid #{oid} could't be downloaded from #{sanitized_uri.sanitized_url}: #{e.message}") + Rails.logger.error("LFS file with oid #{oid} couldn't be downloaded from #{sanitized_uri.sanitized_url}: #{e.message}") end # rubocop: enable CodeReuse/ActiveRecord private + def sanitize_url!(url) + Gitlab::UrlSanitizer.new(url).tap do |sanitized_uri| + # Just validate that HTTP/HTTPS protocols are used. The + # subsequent Gitlab::HTTP.get call will do network checks + # based on the settings. + Gitlab::UrlBlocker.validate!(sanitized_uri.sanitized_url, + protocols: VALID_PROTOCOLS) + end + end + def download_and_save_file(file, sanitized_uri) - IO.copy_stream(open(sanitized_uri.sanitized_url, headers(sanitized_uri)), file) # rubocop:disable Security/Open + response = Gitlab::HTTP.get(sanitized_uri.sanitized_url, headers(sanitized_uri)) do |fragment| + file.write(fragment) + end + + raise StandardError, "Received error code #{response.code}" unless response.success? end def headers(sanitized_uri) - {}.tap do |headers| + query_options.tap do |headers| credentials = sanitized_uri.credentials if credentials[:user].present? || credentials[:password].present? @@ -43,10 +58,14 @@ module Projects end end + def query_options + { stream_body: true } + end + def with_tmp_file(oid) create_tmp_storage_dir - File.open(File.join(tmp_storage_dir, oid), 'w') { |file| yield file } + File.open(File.join(tmp_storage_dir, oid), 'wb') { |file| yield file } end def create_tmp_storage_dir diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb index f25a4e30938..6063392da0a 100644 --- a/app/services/projects/update_service.rb +++ b/app/services/projects/update_service.rb @@ -61,9 +61,9 @@ module Projects if project.previous_changes.include?(:visibility_level) && project.private? # don't enqueue immediately to prevent todos removal in case of a mistake - TodosDestroyer::ProjectPrivateWorker.perform_in(1.hour, project.id) + TodosDestroyer::ProjectPrivateWorker.perform_in(Todo::WAIT_FOR_DELETE, project.id) elsif (project_changed_feature_keys & todos_features_changes).present? - TodosDestroyer::PrivateFeaturesWorker.perform_in(1.hour, project.id) + TodosDestroyer::PrivateFeaturesWorker.perform_in(Todo::WAIT_FOR_DELETE, project.id) end if project.previous_changes.include?('path') |