diff options
author | DJ Mountney <david@twkie.net> | 2017-03-20 18:55:40 -0700 |
---|---|---|
committer | DJ Mountney <david@twkie.net> | 2017-03-20 18:55:40 -0700 |
commit | 4ea85da9fb99bc4d875cc3dc644476f34f0b8bc3 (patch) | |
tree | 4da8d0ca1ef292db5eca8e00ae9ce59e55647b82 | |
parent | f5bc48cf5b639644fc4bf8b6d41cc82cf2eeaf84 (diff) | |
parent | 7be39a894b27c0c0e4fab52c2f8147f216376538 (diff) | |
download | gitlab-ce-4ea85da9fb99bc4d875cc3dc644476f34f0b8bc3.tar.gz |
Merge remote-tracking branch 'dev/master'
50 files changed, 258 insertions, 54 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 42e094bdfc6..da1898e3770 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 8.17.4 (2017-03-19) + +- Only show public emails in atom feeds. +- To protect against Server-side Request Forgery project import URLs are now prohibited against localhost or the server IP except for the assigned instance URL and port. Imports are also prohibited from ports below 1024 with the exception of ports 22, 80, and 443. + ## 8.17.3 (2017-03-07) - Fix the redirect to custom home page URL. !9518 @@ -210,6 +215,11 @@ entry. - Remove deprecated GitlabCiService. - Requeue pending deletion projects. +## 8.16.8 (2017-03-19) + +- Only show public emails in atom feeds. +- To protect against Server-side Request Forgery project import URLs are now prohibited against localhost or the server IP except for the assigned instance URL and port. Imports are also prohibited from ports below 1024 with the exception of ports 22, 80, and 443. + ## 8.16.7 (2017-02-27) - No changes. @@ -411,6 +421,11 @@ entry. - Add margin to markdown math blocks. - Add hover state to MR comment reply button. +## 8.15.8 (2017-03-19) + +- Only show public emails in atom feeds. +- To protect against Server-side Request Forgery project import URLs are now prohibited against localhost or the server IP except for the assigned instance URL and port. Imports are also prohibited from ports below 1024 with the exception of ports 22, 80, and 443. + ## 8.15.7 (2017-02-15) - No changes. diff --git a/app/assets/javascripts/environments/components/environment_external_url.js b/app/assets/javascripts/environments/components/environment_external_url.js index a554998f52c..b4f9eb357fd 100644 --- a/app/assets/javascripts/environments/components/environment_external_url.js +++ b/app/assets/javascripts/environments/components/environment_external_url.js @@ -14,6 +14,7 @@ export default { class="btn external_url" :href="externalUrl" target="_blank" + rel="noopener noreferrer" title="Environment external URL"> <i class="fa fa-external-link" aria-hidden="true"></i> </a> diff --git a/app/assets/javascripts/merge_request_widget.js b/app/assets/javascripts/merge_request_widget.js index 94a4f24f1d7..0e2af3df071 100644 --- a/app/assets/javascripts/merge_request_widget.js +++ b/app/assets/javascripts/merge_request_widget.js @@ -14,13 +14,13 @@ import MiniPipelineGraph from './mini_pipeline_graph_dropdown'; <%= ci_success_icon %> <span> Deployed to - <a href="<%- url %>" target="_blank" class="environment"> + <a href="<%- url %>" target="_blank" rel="noopener noreferrer" class="environment"> <%- name %> </a> <span class="js-environment-timeago" data-toggle="tooltip" data-placement="top" data-title="<%- deployed_at_formatted %>"> <%- deployed_at %> </span> - <a class="js-environment-link" href="<%- external_url %>" target="_blank"> + <a class="js-environment-link" href="<%- external_url %>" target="_blank" rel="noopener noreferrer"> <i class="fa fa-external-link"></i> View on <%- external_url_formatted %> </a> diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 491cd5dc351..cdb5b4173d3 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -148,7 +148,7 @@ class Projects::IssuesController < Projects::ApplicationController end format.json do - render json: @issue.to_json(include: { milestone: {}, assignee: { methods: :avatar_url }, labels: { methods: :text_color } }, methods: [:task_status, :task_status_short]) + render json: @issue.to_json(include: { milestone: {}, assignee: { only: [:name, :username], methods: [:avatar_url] }, labels: { methods: :text_color } }, methods: [:task_status, :task_status_short]) end end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 82f9b6e06db..677a8a1a73a 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -308,7 +308,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController end format.json do - render json: @merge_request.to_json(include: { milestone: {}, assignee: { methods: :avatar_url }, labels: { methods: :text_color } }, methods: [:task_status, :task_status_short]) + render json: @merge_request.to_json(include: { milestone: {}, assignee: { only: [:name, :username], methods: [:avatar_url] }, labels: { methods: :text_color } }, methods: [:task_status, :task_status_short]) end end rescue ActiveRecord::StaleObjectError diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index 0b0c6a07efd..8631bc54509 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -215,6 +215,6 @@ module BlobHelper end def open_raw_file_button(path) - link_to icon('file-code-o'), path, class: 'btn btn-sm has-tooltip', target: '_blank', title: 'Open raw', data: { container: 'body' } + link_to icon('file-code-o'), path, class: 'btn btn-sm has-tooltip', target: '_blank', rel: 'noopener noreferrer', title: 'Open raw', data: { container: 'body' } end end diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index 8aad39e148b..cef624430da 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -211,7 +211,7 @@ module CommitsHelper external_url = environment.external_url_for(diff_new_path, commit_sha) return unless external_url - link_to(external_url, class: 'btn btn-file-option has-tooltip', target: '_blank', title: "View on #{environment.formatted_external_url}", data: { container: 'body' }) do + link_to(external_url, class: 'btn btn-file-option has-tooltip', target: '_blank', rel: 'noopener noreferrer', title: "View on #{environment.formatted_external_url}", data: { container: 'body' }) do icon('external-link') end end diff --git a/app/helpers/import_helper.rb b/app/helpers/import_helper.rb index a0642a1894b..a57b5a8fea5 100644 --- a/app/helpers/import_helper.rb +++ b/app/helpers/import_helper.rb @@ -7,7 +7,7 @@ module ImportHelper def provider_project_link(provider, path_with_namespace) url = __send__("#{provider}_project_url", path_with_namespace) - link_to path_with_namespace, url, target: '_blank' + link_to path_with_namespace, url, target: '_blank', rel: 'noopener noreferrer' end private diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 91f4eb13ecc..e7bd20b322a 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -48,11 +48,13 @@ module Issuable delegate :name, :email, + :public_email, to: :author, prefix: true delegate :name, :email, + :public_email, to: :assignee, allow_nil: true, prefix: true diff --git a/app/models/event.rb b/app/models/event.rb index d7ca8e3c599..5c34844b5d3 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -16,7 +16,7 @@ class Event < ActiveRecord::Base RESET_PROJECT_ACTIVITY_INTERVAL = 1.hour - delegate :name, :email, to: :author, prefix: true, allow_nil: true + delegate :name, :email, :public_email, to: :author, prefix: true, allow_nil: true delegate :title, to: :issue, prefix: true, allow_nil: true delegate :title, to: :merge_request, prefix: true, allow_nil: true delegate :title, to: :note, prefix: true, allow_nil: true diff --git a/app/models/project.rb b/app/models/project.rb index 17cf8226bcc..4a3faff7d5b 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -196,6 +196,7 @@ class Project < ActiveRecord::Base validates :name, uniqueness: { scope: :namespace_id } validates :path, uniqueness: { scope: :namespace_id } validates :import_url, addressable_url: true, if: :external_import? + validates :import_url, importable_url: true, if: [:external_import?, :import_url_changed?] validates :star_count, numericality: { greater_than_or_equal_to: 0 } validate :check_limit, on: :create validate :avatar_type, diff --git a/app/services/projects/import_service.rb b/app/services/projects/import_service.rb index 1c5a549feb9..d484a96f785 100644 --- a/app/services/projects/import_service.rb +++ b/app/services/projects/import_service.rb @@ -33,6 +33,7 @@ module Projects def import_repository begin + raise Error, "Blocked import URL." if Gitlab::UrlBlocker.blocked_url?(project.import_url) gitlab_shell.import_repository(project.repository_storage_path, project.path_with_namespace, project.import_url) rescue => e # Expire cache to prevent scenarios such as: @@ -40,7 +41,7 @@ module Projects # 2. Retried import, repo is broken or not imported but +exists?+ still returns true project.repository.before_import if project.repository_exists? - raise Error, "Error importing repository #{project.import_url} into #{project.path_with_namespace} - #{e.message}" + raise Error, "Error importing repository #{project.import_url} into #{project.path_with_namespace} - #{e.message}" end end diff --git a/app/validators/importable_url_validator.rb b/app/validators/importable_url_validator.rb new file mode 100644 index 00000000000..37a314adee6 --- /dev/null +++ b/app/validators/importable_url_validator.rb @@ -0,0 +1,11 @@ +# ImportableUrlValidator +# +# This validator blocks projects from using dangerous import_urls to help +# protect against Server-side Request Forgery (SSRF). +class ImportableUrlValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + if Gitlab::UrlBlocker.blocked_url?(value) + record.errors.add(attribute, "imports are not allowed from that URL") + end + end +end diff --git a/app/views/admin/appearances/_form.html.haml b/app/views/admin/appearances/_form.html.haml index 9175b3d3f96..e403a9da616 100644 --- a/app/views/admin/appearances/_form.html.haml +++ b/app/views/admin/appearances/_form.html.haml @@ -48,7 +48,7 @@ .form-actions = f.submit 'Save', class: 'btn btn-save append-right-10' - if @appearance.persisted? - = link_to 'Preview last save', preview_admin_appearances_path, class: 'btn', target: '_blank' + = link_to 'Preview last save', preview_admin_appearances_path, class: 'btn', target: '_blank', rel: 'noopener noreferrer' - if @appearance.updated_at %span.pull-right diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index 00366b0a8c9..3eab065bb9f 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -404,7 +404,7 @@ Enable Sentry .help-block Sentry is an error reporting and logging tool which is currently not shipped with GitLab, get it here: - %a{ href: 'https://getsentry.com', target: '_blank' } https://getsentry.com + %a{ href: 'https://getsentry.com', target: '_blank', rel: 'noopener noreferrer' } https://getsentry.com .form-group = f.label :sentry_dsn, 'Sentry DSN', class: 'control-label col-sm-2' diff --git a/app/views/events/_event.atom.builder b/app/views/events/_event.atom.builder index 43a52cf3002..158061579f6 100644 --- a/app/views/events/_event.atom.builder +++ b/app/views/events/_event.atom.builder @@ -9,7 +9,7 @@ xml.entry do xml.author do xml.name event.author_name - xml.email event.author_email + xml.email event.author_public_email end xml.summary(type: "xhtml") do |summary| diff --git a/app/views/events/event/_note.html.haml b/app/views/events/event/_note.html.haml index f08c96df309..64b5a733b77 100644 --- a/app/views/events/event/_note.html.haml +++ b/app/views/events/event/_note.html.haml @@ -15,6 +15,6 @@ = link_to note.attachment.url, target: '_blank' do = image_tag note.attachment.url, class: 'note-image-attach' - else - = link_to note.attachment.url, target: "_blank", class: 'note-file-attach' do + = link_to note.attachment.url, target: '_blank', class: 'note-file-attach' do %i.fa.fa-paperclip = note.attachment_identifier diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml index 31631887317..f93b6b63426 100644 --- a/app/views/help/index.html.haml +++ b/app/views/help/index.html.haml @@ -17,7 +17,7 @@ %br Used by more than 100,000 organizations, GitLab is the most popular solution to manage git repositories on-premises. %br - Read more about GitLab at #{link_to promo_host, promo_url, target: '_blank'}. + Read more about GitLab at #{link_to promo_host, promo_url, target: '_blank', rel: 'noopener noreferrer'}. - if current_application_settings.help_page_text.present? %hr = markdown_field(current_application_settings, :help_page_text) diff --git a/app/views/import/bitbucket/status.html.haml b/app/views/import/bitbucket/status.html.haml index e18bd47798b..e6058617ac9 100644 --- a/app/views/import/bitbucket/status.html.haml +++ b/app/views/import/bitbucket/status.html.haml @@ -33,7 +33,7 @@ - @already_added_projects.each do |project| %tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" } %td - = link_to project.import_source, "https://bitbucket.org/#{project.import_source}", target: '_blank' + = link_to project.import_source, "https://bitbucket.org/#{project.import_source}", target: '_blank', rel: 'noopener noreferrer' %td = link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] %td.job-status @@ -50,7 +50,7 @@ - @repos.each do |repo| %tr{ id: "repo_#{repo.owner}___#{repo.slug}" } %td - = link_to repo.full_name, "https://bitbucket.org/#{repo.full_name}", target: "_blank" + = link_to repo.full_name, "https://bitbucket.org/#{repo.full_name}", target: '_blank', rel: 'noopener noreferrer' %td.import-target %fieldset.row .input-group @@ -70,7 +70,7 @@ - @incompatible_repos.each do |repo| %tr{ id: "repo_#{repo.owner}___#{repo.slug}" } %td - = link_to repo.full_name, "https://bitbucket.org/#{repo.full_name}", target: '_blank' + = link_to repo.full_name, "https://bitbucket.org/#{repo.full_name}", target: '_blank', rel: 'noopener noreferrer' %td.import-target %td.import-actions-job-status = label_tag 'Incompatible Project', nil, class: 'label label-danger' diff --git a/app/views/import/gitlab/status.html.haml b/app/views/import/gitlab/status.html.haml index d5b88709a34..7456799ca0e 100644 --- a/app/views/import/gitlab/status.html.haml +++ b/app/views/import/gitlab/status.html.haml @@ -43,7 +43,7 @@ - @repos.each do |repo| %tr{ id: "repo_#{repo["id"]}" } %td - = link_to repo["path_with_namespace"], "https://gitlab.com/#{repo["path_with_namespace"]}", target: "_blank" + = link_to repo["path_with_namespace"], "https://gitlab.com/#{repo["path_with_namespace"]}", target: "_blank", rel: 'noopener noreferrer' %td.import-target = import_project_target(repo['namespace']['path'], repo['name']) %td.import-actions.job-status diff --git a/app/views/import/google_code/new.html.haml b/app/views/import/google_code/new.html.haml index 336becd229e..c5800a1cca0 100644 --- a/app/views/import/google_code/new.html.haml +++ b/app/views/import/google_code/new.html.haml @@ -13,7 +13,7 @@ %li %p Go to - #{link_to "Google Takeout", "https://www.google.com/settings/takeout", target: "_blank"}. + #{link_to "Google Takeout", "https://www.google.com/settings/takeout", target: '_blank', rel: 'noopener noreferrer'}. %li %p Make sure you're logged into the account that owns the projects you'd like to import. diff --git a/app/views/import/google_code/status.html.haml b/app/views/import/google_code/status.html.haml index 5e01af008be..60de6bfe816 100644 --- a/app/views/import/google_code/status.html.haml +++ b/app/views/import/google_code/status.html.haml @@ -36,7 +36,7 @@ - @already_added_projects.each do |project| %tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" } %td - = link_to project.import_source, "https://code.google.com/p/#{project.import_source}", target: "_blank" + = link_to project.import_source, "https://code.google.com/p/#{project.import_source}", target: "_blank", rel: 'noopener noreferrer' %td = link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] %td.job-status @@ -53,7 +53,7 @@ - @repos.each do |repo| %tr{ id: "repo_#{repo.id}" } %td - = link_to repo.name, "https://code.google.com/p/#{repo.name}", target: "_blank" + = link_to repo.name, "https://code.google.com/p/#{repo.name}", target: "_blank", rel: 'noopener noreferrer' %td.import-target #{current_user.username}/#{repo.name} %td.import-actions.job-status @@ -63,7 +63,7 @@ - @incompatible_repos.each do |repo| %tr{ id: "repo_#{repo.id}" } %td - = link_to repo.name, "https://code.google.com/p/#{repo.name}", target: "_blank" + = link_to repo.name, "https://code.google.com/p/#{repo.name}", target: "_blank", rel: 'noopener noreferrer' %td.import-target %td.import-actions-job-status = label_tag "Incompatible Project", nil, class: "label label-danger" diff --git a/app/views/issues/_issue.atom.builder b/app/views/issues/_issue.atom.builder index fcd30c8c765..23a88448055 100644 --- a/app/views/issues/_issue.atom.builder +++ b/app/views/issues/_issue.atom.builder @@ -7,7 +7,7 @@ xml.entry do xml.author do xml.name issue.author_name - xml.email issue.author_email + xml.email issue.author_public_email end xml.summary issue.title @@ -26,7 +26,7 @@ xml.entry do if issue.assignee xml.assignee do xml.name issue.assignee.name - xml.email issue.assignee.email + xml.email issue.assignee_public_email end end end diff --git a/app/views/koding/index.html.haml b/app/views/koding/index.html.haml index 65887aacbaf..04e2d4b63e6 100644 --- a/app/views/koding/index.html.haml +++ b/app/views/koding/index.html.haml @@ -2,5 +2,5 @@ %p = icon('circle', class: 'cgreen') Integration is active for - = link_to koding_project_url, target: '_blank' do + = link_to koding_project_url, target: '_blank', rel: 'noopener noreferrer' do #{current_application_settings.koding_url} diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index d551754a2e5..c74b3249a13 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -18,7 +18,7 @@ or change it at #{link_to Gitlab.config.gravatar.host, "http://" + Gitlab.config.gravatar.host} .col-lg-9 .clearfix.avatar-image.append-bottom-default - = link_to avatar_icon(@user, 400), target: '_blank' do + = link_to avatar_icon(@user, 400), target: '_blank', rel: 'noopener noreferrer' do = image_tag avatar_icon(@user, 160), alt: '', class: 'avatar s160' %h5.prepend-top-0 Upload new avatar diff --git a/app/views/projects/blob/_image.html.haml b/app/views/projects/blob/_image.html.haml index f864702d862..ea3cecb86a9 100644 --- a/app/views/projects/blob/_image.html.haml +++ b/app/views/projects/blob/_image.html.haml @@ -9,7 +9,7 @@ - else .nothing-here-block The SVG could not be displayed as it is too large, you can - #{link_to('view the raw file', namespace_project_raw_path(@project.namespace, @project, @id), target: '_blank')} + #{link_to('view the raw file', namespace_project_raw_path(@project.namespace, @project, @id), target: '_blank', rel: 'noopener noreferrer')} instead. - else %img{ src: namespace_project_raw_path(@project.namespace, @project, tree_join(@commit.id, blob.path)), alt: "#{blob.name}" } diff --git a/app/views/projects/blob/_text.html.haml b/app/views/projects/blob/_text.html.haml index b1e1be49de9..7b16d266982 100644 --- a/app/views/projects/blob/_text.html.haml +++ b/app/views/projects/blob/_text.html.haml @@ -3,7 +3,7 @@ .nothing-here-block File too large, you can = succeed '.' do - = link_to 'view the raw file', namespace_project_raw_path(@project.namespace, @project, @id), target: '_blank' + = link_to 'view the raw file', namespace_project_raw_path(@project.namespace, @project, @id), target: '_blank', rel: 'noopener noreferrer' - else - blob.load_all_data!(@repository) diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml index 8853801016b..3bcddcb37f1 100644 --- a/app/views/projects/blob/edit.html.haml +++ b/app/views/projects/blob/edit.html.haml @@ -9,7 +9,7 @@ - if @conflict .alert.alert-danger Someone edited the file the same time you did. Please check out - = link_to "the file", namespace_project_blob_path(@project.namespace, @project, tree_join(@target_branch, @file_path)), target: "_blank" + = link_to "the file", namespace_project_blob_path(@project.namespace, @project, tree_join(@target_branch, @file_path)), target: "_blank", rel: 'noopener noreferrer' and make sure your changes will not unintentionally remove theirs. .file-editor diff --git a/app/views/projects/buttons/_koding.html.haml b/app/views/projects/buttons/_koding.html.haml index 5d9a776da89..a5a9e4d0621 100644 --- a/app/views/projects/buttons/_koding.html.haml +++ b/app/views/projects/buttons/_koding.html.haml @@ -1,3 +1,3 @@ - if koding_enabled? && current_user && @repository.koding_yml && can_push_branch?(@project, @project.default_branch) - = link_to koding_project_url(@project), class: 'btn project-action-button inline', target: '_blank' do + = link_to koding_project_url(@project), class: 'btn project-action-button inline', target: '_blank', rel: 'noopener noreferrer' do Run in IDE (Koding) diff --git a/app/views/projects/cycle_analytics/_overview.html.haml b/app/views/projects/cycle_analytics/_overview.html.haml index c8f0b547f80..9007f2c24ba 100644 --- a/app/views/projects/cycle_analytics/_overview.html.haml +++ b/app/views/projects/cycle_analytics/_overview.html.haml @@ -9,7 +9,7 @@ Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project. To set up CA, you must first define a production environment by setting up your CI and then deploy to production. %p - %a.btn{ href: help_page_path('user/project/cycle_analytics'), target: "_blank" } Read more + %a.btn{ href: help_page_path('user/project/cycle_analytics'), target: '_blank' } Read more .col-md-6.overview-image %span.overview-icon = custom_icon ('icon_cycle_analytics_overview') diff --git a/app/views/projects/environments/_external_url.html.haml b/app/views/projects/environments/_external_url.html.haml index 4c8fe1c271b..bf0f1819073 100644 --- a/app/views/projects/environments/_external_url.html.haml +++ b/app/views/projects/environments/_external_url.html.haml @@ -1,3 +1,3 @@ - if environment.external_url && can?(current_user, :read_environment, environment) - = link_to environment.external_url, target: '_blank', class: 'btn external-url' do + = link_to environment.external_url, target: '_blank', rel: 'noopener noreferrer', class: 'btn external-url' do = icon('external-link') diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index c8f097c69da..6682a85ffa6 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -16,7 +16,7 @@ .pull-right - if @merge_request.source_branch_exists? - if koding_enabled? && @repository.koding_yml - = link_to koding_project_url(@merge_request.source_project, @merge_request.source_branch, @merge_request.commits.first.short_id), class: "btn inline btn-grouped btn-sm", target: '_blank' do + = link_to koding_project_url(@merge_request.source_project, @merge_request.source_branch, @merge_request.commits.first.short_id), class: "btn inline btn-grouped btn-sm", target: '_blank', rel: 'noopener noreferrer' do Run in IDE (Koding) = link_to "#modal_merge_info", class: "btn inline btn-grouped btn-sm", "data-toggle" => "modal" do Check out branch diff --git a/app/views/projects/merge_requests/show/_how_to_merge.html.haml b/app/views/projects/merge_requests/show/_how_to_merge.html.haml index 93ed4b68e0e..cde0ce08e14 100644 --- a/app/views/projects/merge_requests/show/_how_to_merge.html.haml +++ b/app/views/projects/merge_requests/show/_how_to_merge.html.haml @@ -49,7 +49,7 @@ %strong Tip: = succeed '.' do You can also checkout merge requests locally by - = link_to 'following these guidelines', help_page_path('user/project/merge_requests.md', anchor: "checkout-merge-requests-locally"), target: '_blank' + = link_to 'following these guidelines', help_page_path('user/project/merge_requests.md', anchor: "checkout-merge-requests-locally"), target: '_blank', rel: 'noopener noreferrer' :javascript $(function(){ diff --git a/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml b/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml index 3a323d94cc2..2fb88297fb3 100644 --- a/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml +++ b/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml @@ -4,13 +4,13 @@ %ul.list-unstyled.indent-list %li 1. - = link_to 'https://docs.mattermost.com/developer/slash-commands.html#enabling-custom-commands', target: '_blank', rel: 'noreferrer noopener nofollow' do + = link_to 'https://docs.mattermost.com/developer/slash-commands.html#enabling-custom-commands', target: '_blank', rel: 'noopener noreferrer nofollow' do Enable custom slash commands = icon('external-link') on your Mattermost installation %li 2. - = link_to 'https://docs.mattermost.com/developer/slash-commands.html#set-up-a-custom-command', target: '_blank', rel: 'noreferrer noopener nofollow' do + = link_to 'https://docs.mattermost.com/developer/slash-commands.html#set-up-a-custom-command', target: '_blank', rel: 'noopener noreferrer nofollow' do Add a slash command = icon('external-link') in your Mattermost team with these options: diff --git a/app/views/projects/services/mattermost_slash_commands/_help.html.haml b/app/views/projects/services/mattermost_slash_commands/_help.html.haml index a04fd5035a6..2a1b9d4c465 100644 --- a/app/views/projects/services/mattermost_slash_commands/_help.html.haml +++ b/app/views/projects/services/mattermost_slash_commands/_help.html.haml @@ -4,7 +4,7 @@ %p This service allows users to perform common operations on this project by entering slash commands in Mattermost. - = link_to help_page_path('user/project/integrations/mattermost_slash_commands.md'), target: '_blank', ref: 'noreferrer nofollow noopener' do + = link_to help_page_path('user/project/integrations/mattermost_slash_commands.md'), target: '_blank' do View documentation = icon('external-link') %p.inline diff --git a/app/views/projects/services/slack_slash_commands/_help.html.haml b/app/views/projects/services/slack_slash_commands/_help.html.haml index 0d973a20d4c..078b7be6865 100644 --- a/app/views/projects/services/slack_slash_commands/_help.html.haml +++ b/app/views/projects/services/slack_slash_commands/_help.html.haml @@ -5,7 +5,7 @@ %p This service allows users to perform common operations on this project by entering slash commands in Slack. - = link_to help_page_path('user/project/integrations/slack_slash_commands.md'), target: '_blank', ref: 'noreferrer nofollow noopener' do + = link_to help_page_path('user/project/integrations/slack_slash_commands.md'), target: '_blank' do View documentation = icon('external-link') %p.inline @@ -57,7 +57,7 @@ = label_tag nil, 'Customize icon', class: 'col-sm-2 col-xs-12 control-label' .col-sm-10.col-xs-12.text-block = image_tag(asset_url('slash-command-logo.png'), width: 36, height: 36) - = link_to('Download image', asset_url('gitlab_logo.png'), class: 'btn btn-sm', target: '_blank') + = link_to('Download image', asset_url('gitlab_logo.png'), class: 'btn btn-sm', target: '_blank', rel: 'noopener noreferrer') .form-group = label_tag nil, 'Autocomplete', class: 'col-sm-2 col-xs-12 control-label' diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index 0b0f2c9cd1a..17107f55a2d 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -8,7 +8,7 @@ .alert.alert-danger Someone edited the #{issuable.class.model_name.human.downcase} the same time you did. Please check out - = link_to "the #{issuable.class.model_name.human.downcase}", polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), target: "_blank" + = link_to "the #{issuable.class.model_name.human.downcase}", polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable]), target: "_blank", rel: 'noopener noreferrer' and make sure your changes will not unintentionally remove theirs .form-group diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 76cd330e80a..dc9a3b0d0df 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -33,7 +33,7 @@ .profile-header .avatar-holder - = link_to avatar_icon(@user, 400), target: '_blank' do + = link_to avatar_icon(@user, 400), target: '_blank', rel: 'noopener noreferrer' do = image_tag avatar_icon(@user, 90), class: "avatar s90", alt: '' .user-info diff --git a/changelogs/unreleased/28058-hide-emails-in-atom-feeds.yml b/changelogs/unreleased/28058-hide-emails-in-atom-feeds.yml new file mode 100644 index 00000000000..e0e826a67f8 --- /dev/null +++ b/changelogs/unreleased/28058-hide-emails-in-atom-feeds.yml @@ -0,0 +1,4 @@ +--- +title: Only show public emails in atom feeds +merge_request: +author: diff --git a/changelogs/unreleased/ssrf-protections.yml b/changelogs/unreleased/ssrf-protections.yml new file mode 100644 index 00000000000..8d803738009 --- /dev/null +++ b/changelogs/unreleased/ssrf-protections.yml @@ -0,0 +1,4 @@ +--- +title: To protect against Server-side Request Forgery project import URLs are now prohibited against localhost or the server IP except for the assigned instance URL and port. Imports are also prohibited from ports below 1024 with the exception of ports 22, 80, and 443. +merge_request: +author: diff --git a/lib/banzai/filter/image_link_filter.rb b/lib/banzai/filter/image_link_filter.rb index 651b55523c0..123c92fd250 100644 --- a/lib/banzai/filter/image_link_filter.rb +++ b/lib/banzai/filter/image_link_filter.rb @@ -2,7 +2,6 @@ module Banzai module Filter # HTML filter that wraps links around inline images. class ImageLinkFilter < HTML::Pipeline::Filter - # Find every image that isn't already wrapped in an `a` tag, create # a new node (a link to the image source), copy the image as a child # of the anchor, and then replace the img with the link-wrapped version. @@ -12,7 +11,8 @@ module Banzai 'a', class: 'no-attachment-icon', href: img['src'], - target: '_blank' + target: '_blank', + rel: 'noopener noreferrer' ) link.children = img.clone diff --git a/lib/banzai/filter/video_link_filter.rb b/lib/banzai/filter/video_link_filter.rb index b64a1287d4d..35cb10eae5d 100644 --- a/lib/banzai/filter/video_link_filter.rb +++ b/lib/banzai/filter/video_link_filter.rb @@ -43,6 +43,7 @@ module Banzai element['title'] || element['alt'], href: element['src'], target: '_blank', + rel: 'noopener noreferrer', title: "Download '#{element['title'] || element['alt']}'") download_paragraph = doc.document.create_element('p') download_paragraph.children = link diff --git a/lib/gitlab/url_blocker.rb b/lib/gitlab/url_blocker.rb new file mode 100644 index 00000000000..7e14a566696 --- /dev/null +++ b/lib/gitlab/url_blocker.rb @@ -0,0 +1,59 @@ +require 'resolv' + +module Gitlab + class UrlBlocker + class << self + # Used to specify what hosts and port numbers should be prohibited for project + # imports. + VALID_PORTS = [22, 80, 443].freeze + + def blocked_url?(url) + return false if url.nil? + + blocked_ips = ["127.0.0.1", "::1", "0.0.0.0"] + blocked_ips.concat(Socket.ip_address_list.map(&:ip_address)) + + begin + uri = Addressable::URI.parse(url) + # Allow imports from the GitLab instance itself but only from the configured ports + return false if internal?(uri) + + return true if blocked_port?(uri.port) + + server_ips = Resolv.getaddresses(uri.hostname) + return true if (blocked_ips & server_ips).any? + rescue Addressable::URI::InvalidURIError + return true + end + + false + end + + private + + def blocked_port?(port) + return false if port.blank? + + port < 1024 && !VALID_PORTS.include?(port) + end + + def internal?(uri) + internal_web?(uri) || internal_shell?(uri) + end + + def internal_web?(uri) + uri.hostname == config.gitlab.host && + (uri.port.blank? || uri.port == config.gitlab.port) + end + + def internal_shell?(uri) + uri.hostname == config.gitlab_shell.ssh_host && + (uri.port.blank? || uri.port == config.gitlab_shell.ssh_port) + end + + def config + Gitlab.config + end + end + end +end diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index 8263301c439..57a921e3676 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -152,6 +152,24 @@ describe Projects::IssuesController do it_behaves_like 'update invalid issuable', Issue + context 'changing the assignee' do + it 'limits the attributes exposed on the assignee' do + assignee = create(:user) + project.add_developer(assignee) + + put :update, + namespace_id: project.namespace.to_param, + project_id: project, + id: issue.iid, + issue: { assignee_id: assignee.id }, + format: :json + body = JSON.parse(response.body) + + expect(body['assignee'].keys) + .to match_array(%w(name username avatar_url)) + end + end + context 'when moving issue to another private project' do let(:another_project) { create(:empty_project, :private) } diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 250d64f7055..c310d830e81 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -203,6 +203,24 @@ describe Projects::MergeRequestsController do end describe 'PUT update' do + context 'changing the assignee' do + it 'limits the attributes exposed on the assignee' do + assignee = create(:user) + project.add_developer(assignee) + + put :update, + namespace_id: project.namespace.to_param, + project_id: project, + id: merge_request.iid, + merge_request: { assignee_id: assignee.id }, + format: :json + body = JSON.parse(response.body) + + expect(body['assignee'].keys) + .to match_array(%w(name username avatar_url)) + end + end + context 'there is no source project' do let(:project) { create(:project) } let(:fork_project) { create(:forked_project_with_submodules) } diff --git a/spec/features/atom/dashboard_issues_spec.rb b/spec/features/atom/dashboard_issues_spec.rb index a7c22615b89..58b14e09740 100644 --- a/spec/features/atom/dashboard_issues_spec.rb +++ b/spec/features/atom/dashboard_issues_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' describe "Dashboard Issues Feed", feature: true do describe "GET /issues" do - let!(:user) { create(:user) } + let!(:user) { create(:user, email: 'private1@example.com', public_email: 'public1@example.com') } + let!(:assignee) { create(:user, email: 'private2@example.com', public_email: 'public2@example.com') } let!(:project1) { create(:project) } let!(:project2) { create(:project) } @@ -31,7 +32,7 @@ describe "Dashboard Issues Feed", feature: true do end context "issue with basic fields" do - let!(:issue2) { create(:issue, author: user, assignee: user, project: project2, description: 'test desc') } + let!(:issue2) { create(:issue, author: user, assignee: assignee, project: project2, description: 'test desc') } it "renders issue fields" do visit issues_dashboard_path(:atom, private_token: user.private_token) @@ -39,8 +40,8 @@ describe "Dashboard Issues Feed", feature: true do entry = find(:xpath, "//feed/entry[contains(summary/text(),'#{issue2.title}')]") expect(entry).to be_present - expect(entry).to have_selector('author email', text: issue2.author_email) - expect(entry).to have_selector('assignee email', text: issue2.author_email) + expect(entry).to have_selector('author email', text: issue2.author_public_email) + expect(entry).to have_selector('assignee email', text: issue2.assignee_public_email) expect(entry).not_to have_selector('labels') expect(entry).not_to have_selector('milestone') expect(entry).to have_selector('description', text: issue2.description) @@ -50,7 +51,7 @@ describe "Dashboard Issues Feed", feature: true do context "issue with label and milestone" do let!(:milestone1) { create(:milestone, project: project1, title: 'v1') } let!(:label1) { create(:label, project: project1, title: 'label1') } - let!(:issue1) { create(:issue, author: user, assignee: user, project: project1, milestone: milestone1) } + let!(:issue1) { create(:issue, author: user, assignee: assignee, project: project1, milestone: milestone1) } before do issue1.labels << label1 @@ -62,8 +63,8 @@ describe "Dashboard Issues Feed", feature: true do entry = find(:xpath, "//feed/entry[contains(summary/text(),'#{issue1.title}')]") expect(entry).to be_present - expect(entry).to have_selector('author email', text: issue1.author_email) - expect(entry).to have_selector('assignee email', text: issue1.author_email) + expect(entry).to have_selector('author email', text: issue1.author_public_email) + expect(entry).to have_selector('assignee email', text: issue1.assignee_public_email) expect(entry).to have_selector('labels label', text: label1.title) expect(entry).to have_selector('milestone', text: milestone1.title) expect(entry).not_to have_selector('description') diff --git a/spec/features/atom/issues_spec.rb b/spec/features/atom/issues_spec.rb index a01a050a013..b3903ec2faf 100644 --- a/spec/features/atom/issues_spec.rb +++ b/spec/features/atom/issues_spec.rb @@ -2,10 +2,11 @@ require 'spec_helper' describe 'Issues Feed', feature: true do describe 'GET /issues' do - let!(:user) { create(:user) } + let!(:user) { create(:user, email: 'private1@example.com', public_email: 'public1@example.com') } + let!(:assignee) { create(:user, email: 'private2@example.com', public_email: 'public2@example.com') } let!(:group) { create(:group) } let!(:project) { create(:project) } - let!(:issue) { create(:issue, author: user, project: project) } + let!(:issue) { create(:issue, author: user, assignee: assignee, project: project) } before do project.team << [user, :developer] @@ -20,7 +21,8 @@ describe 'Issues Feed', feature: true do expect(response_headers['Content-Type']). to have_content('application/atom+xml') expect(body).to have_selector('title', text: "#{project.name} issues") - expect(body).to have_selector('author email', text: issue.author_email) + expect(body).to have_selector('author email', text: issue.author_public_email) + expect(body).to have_selector('assignee email', text: issue.author_public_email) expect(body).to have_selector('entry summary', text: issue.title) end end @@ -33,7 +35,8 @@ describe 'Issues Feed', feature: true do expect(response_headers['Content-Type']). to have_content('application/atom+xml') expect(body).to have_selector('title', text: "#{project.name} issues") - expect(body).to have_selector('author email', text: issue.author_email) + expect(body).to have_selector('author email', text: issue.author_public_email) + expect(body).to have_selector('assignee email', text: issue.author_public_email) expect(body).to have_selector('entry summary', text: issue.title) end end diff --git a/spec/lib/gitlab/url_blocker_spec.rb b/spec/lib/gitlab/url_blocker_spec.rb new file mode 100644 index 00000000000..a504d299307 --- /dev/null +++ b/spec/lib/gitlab/url_blocker_spec.rb @@ -0,0 +1,31 @@ +require 'spec_helper' + +describe Gitlab::UrlBlocker, lib: true do + describe '#blocked_url?' do + it 'allows imports from configured web host and port' do + import_url = "http://#{Gitlab.config.gitlab.host}:#{Gitlab.config.gitlab.port}/t.git" + expect(described_class.blocked_url?(import_url)).to be false + end + + it 'allows imports from configured SSH host and port' do + import_url = "http://#{Gitlab.config.gitlab_shell.ssh_host}:#{Gitlab.config.gitlab_shell.ssh_port}/t.git" + expect(described_class.blocked_url?(import_url)).to be false + end + + it 'returns true for bad localhost hostname' do + expect(described_class.blocked_url?('https://localhost:65535/foo/foo.git')).to be true + end + + it 'returns true for bad port' do + expect(described_class.blocked_url?('https://gitlab.com:25/foo/foo.git')).to be true + end + + it 'returns true for invalid URL' do + expect(described_class.blocked_url?('http://:8080')).to be true + end + + it 'returns false for legitimate URL' do + expect(described_class.blocked_url?('https://gitlab.com/foo/foo.git')).to be false + end + end +end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 618ce2b6d53..f68631ebe06 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -218,6 +218,20 @@ describe Project, models: true do expect(project2.import_data).to be_nil end + it "does not allow blocked import_url localhost" do + project2 = build(:empty_project, import_url: 'http://localhost:9000/t.git') + + expect(project2).to be_invalid + expect(project2.errors[:import_url]).to include('imports are not allowed from that URL') + end + + it "does not allow blocked import_url port" do + project2 = build(:empty_project, import_url: 'http://github.com:25/t.git') + + expect(project2).to be_invalid + expect(project2.errors[:import_url]).to include('imports are not allowed from that URL') + end + describe 'project pending deletion' do let!(:project_pending_deletion) do create(:empty_project, diff --git a/spec/services/projects/import_service_spec.rb b/spec/services/projects/import_service_spec.rb index ab6e8f537ba..e5917bb0b7a 100644 --- a/spec/services/projects/import_service_spec.rb +++ b/spec/services/projects/import_service_spec.rb @@ -120,6 +120,26 @@ describe Projects::ImportService, services: true do end end + context 'with blocked import_URL' do + it 'fails with localhost' do + project.import_url = 'https://localhost:9000/vim/vim.git' + + result = described_class.new(project, user).execute + + expect(result[:status]).to eq :error + expect(result[:message]).to end_with 'Blocked import URL.' + end + + it 'fails with port 25' do + project.import_url = "https://github.com:25/vim/vim.git" + + result = described_class.new(project, user).execute + + expect(result[:status]).to eq :error + expect(result[:message]).to end_with 'Blocked import URL.' + end + end + def stub_github_omniauth_provider provider = OpenStruct.new( 'name' => 'github', |