diff options
95 files changed, 654 insertions, 229 deletions
diff --git a/.babelrc b/.babelrc new file mode 100644 index 00000000000..ee4c391da30 --- /dev/null +++ b/.babelrc @@ -0,0 +1,21 @@ +{ + "presets": [ + ["latest", { "es2015": { "modules": false } }], + "stage-2" + ], + "env": { + "coverage": { + "plugins": [ + ["istanbul", { + "exclude": [ + "app/assets/javascripts/droplab/**/*", + "spec/javascripts/**/*" + ] + }], + ["transform-define", { + "process.env.BABEL_ENV": "coverage" + }] + ] + } + } +} diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 080d8cd6c7f..34c10b3b77f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -277,6 +277,8 @@ rake karma: stage: test <<: *use-db <<: *dedicated-runner + variables: + BABEL_ENV: "coverage" script: - bundle exec rake karma artifacts: @@ -389,9 +391,11 @@ trigger_docs: cache: {} artifacts: {} script: - - "curl -X POST -F token=${DOCS_TRIGGER_TOKEN} -F ref=master -F variables[PROJECT]=ce https://gitlab.com/api/v3/projects/1794617/trigger/builds" + - "HTTP_STATUS=$(curl -X POST -F token=${DOCS_TRIGGER_TOKEN} -F ref=master -F variables[PROJECT]=${CI_PROJECT_NAME} --silent --output curl.log --write-out '%{http_code}' https://gitlab.com/api/v3/projects/1794617/trigger/builds)" + - if [ "${HTTP_STATUS}" -ne "201" ]; then echo "Error ${HTTP_STATUS}"; cat curl.log; echo; exit 1; fi only: - master@gitlab-org/gitlab-ce + - master@gitlab-org/gitlab-ee # Notify slack in the end notify:slack: 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/boards/components/modal/index.js b/app/assets/javascripts/boards/components/modal/index.js index 1b66c8b922d..4240c97617d 100644 --- a/app/assets/javascripts/boards/components/modal/index.js +++ b/app/assets/javascripts/boards/components/modal/index.js @@ -64,6 +64,7 @@ require('./empty_state'); }, filter: { handler() { + this.page = 1; this.loadIssues(true); }, deep: true, @@ -115,6 +116,9 @@ require('./empty_state'); return this.activeTab === 'selected' && this.selectedIssues.length === 0; }, }, + created() { + this.page = 1; + }, components: { 'modal-header': gl.issueBoards.ModalHeader, 'modal-list': gl.issueBoards.ModalList, diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index db1a2848d8d..3557f6f617e 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -1,4 +1,3 @@ -import PrometheusGraph from './monitoring/prometheus_graph'; // TODO: Maybe Make this a bundle /* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, wrap-iife, no-shadow, consistent-return, one-var, one-var-declaration-per-line, camelcase, default-case, no-new, quotes, no-duplicate-case, no-case-declarations, no-fallthrough, max-len */ /* global UsernameValidator */ /* global ActiveTabMemoizer */ @@ -329,8 +328,6 @@ const UserCallout = require('./user_callout'); case 'ci:lints:show': new gl.CILintEditor(); break; - case 'projects:environments:metrics': - new PrometheusGraph(); case 'users:show': new UserCallout(); break; 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/filtered_search/filtered_search_dropdown.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js index 134bdc6ad80..e7bf530d343 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js @@ -38,6 +38,7 @@ gl.FilteredSearchDropdownManager.addWordToInput(this.filter, value, true); } + this.resetFilters(); this.dismissDropdown(); this.dispatchInputEvent(); } @@ -107,7 +108,7 @@ const hook = this.getCurrentHook(); if (hook) { - const data = hook.list.data; + const data = hook.list.data || []; const results = data.map((o) => { const updated = o; updated.droplab_hidden = false; diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js index ef4029a8623..47e675f537e 100644 --- a/app/assets/javascripts/issue.js +++ b/app/assets/javascripts/issue.js @@ -2,6 +2,7 @@ /* global Flash */ require('./flash'); +require('~/lib/utils/text_utility'); require('vendor/jquery.waitforimages'); require('./task_list'); @@ -50,20 +51,21 @@ class Issue { success: function(data, textStatus, jqXHR) { if ('id' in data) { $(document).trigger('issuable:change'); - const currentTotal = Number($('.issue_counter').text()); + let total = Number($('.issue_counter').text().replace(/[^\d]/, '')); if (isClose) { $('a.btn-close').addClass('hidden'); $('a.btn-reopen').removeClass('hidden'); $('div.status-box-closed').removeClass('hidden'); $('div.status-box-open').addClass('hidden'); - $('.issue_counter').text(currentTotal - 1); + total -= 1; } else { $('a.btn-reopen').addClass('hidden'); $('a.btn-close').removeClass('hidden'); $('div.status-box-closed').addClass('hidden'); $('div.status-box-open').removeClass('hidden'); - $('.issue_counter').text(currentTotal + 1); + total += 1; } + $('.issue_counter').text(gl.text.addDelimiter(total)); } else { new Flash(issueFailMessage, 'alert'); } 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/assets/javascripts/monitoring/monitoring_bundle.js b/app/assets/javascripts/monitoring/monitoring_bundle.js new file mode 100644 index 00000000000..b3ce9310417 --- /dev/null +++ b/app/assets/javascripts/monitoring/monitoring_bundle.js @@ -0,0 +1,6 @@ +import PrometheusGraph from './prometheus_graph'; + +document.addEventListener('DOMContentLoaded', function onLoad() { + document.removeEventListener('DOMContentLoaded', onLoad, false); + return new PrometheusGraph(); +}, false); diff --git a/app/assets/javascripts/monitoring/prometheus_graph.js b/app/assets/javascripts/monitoring/prometheus_graph.js index 71eb746edac..fcffc11a2df 100644 --- a/app/assets/javascripts/monitoring/prometheus_graph.js +++ b/app/assets/javascripts/monitoring/prometheus_graph.js @@ -2,10 +2,9 @@ /* global Flash */ import d3 from 'd3'; -import _ from 'underscore'; import statusCodes from '~/lib/utils/http_status'; -import '~/lib/utils/common_utils'; -import '~/flash'; +import '../lib/utils/common_utils'; +import '../flash'; const prometheusGraphsContainer = '.prometheus-graph'; const metricsEndpoint = 'metrics.json'; @@ -31,22 +30,21 @@ class PrometheusGraph { } createGraph() { - const self = this; - _.each(this.data, (value, key) => { - if (value.length > 0 && (key === 'cpu_values' || key === 'memory_values')) { - self.plotValues(value, key); + Object.keys(this.data).forEach((key) => { + const value = this.data[key]; + if (value.length > 0) { + this.plotValues(value, key); } }); } init() { - const self = this; this.getData().then((metricsResponse) => { - if (metricsResponse === {}) { + if (Object.keys(metricsResponse).length === 0) { new Flash('Empty metrics', 'alert'); } else { - self.transformData(metricsResponse); - self.createGraph(); + this.transformData(metricsResponse); + this.createGraph(); } }); } @@ -321,12 +319,14 @@ class PrometheusGraph { transformData(metricsResponse) { const metricTypes = {}; - _.each(metricsResponse.metrics, (value, key) => { - const metricValues = value[0].values; - metricTypes[key] = _.map(metricValues, metric => ({ - time: new Date(metric[0] * 1000), - value: metric[1], - })); + Object.keys(metricsResponse.metrics).forEach((key) => { + if (key === 'cpu_values' || key === 'memory_values') { + const metricValues = (metricsResponse.metrics[key])[0]; + metricTypes[key] = metricValues.values.map(metric => ({ + time: new Date(metric[0] * 1000), + value: metric[1], + })); + } }); this.data = metricTypes; } diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index a4b38723bbd..2c33b235980 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -429,3 +429,9 @@ table { @include str-truncated(100%); } } + +.tooltip { + .tooltip-inner { + word-wrap: break-word; + } +} diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index 2ebeaf9a40d..7cb72fe516f 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -76,12 +76,14 @@ } .input-token { - flex: 1; - -webkit-flex: 1; + max-width: 200px; } - .filtered-search-token + .input-token:not(:last-child) { - max-width: 200px; + .input-token:only-child, + .input-token:last-child { + flex: 1; + -webkit-flex: 1; + max-width: initial; } } @@ -158,8 +160,8 @@ background-color: $white-light; @media (max-width: $screen-xs-min) { - -webkit-flex: 1 1 100%; - flex: 1 1 100%; + -webkit-flex: 1 1 auto; + flex: 1 1 auto; margin-bottom: 10px; .dropdown-menu { @@ -174,8 +176,7 @@ .form-control { position: relative; min-width: 200px; - padding-left: 0; - padding-right: 25px; + padding: 5px 25px 6px 0; border-color: transparent; &:focus ~ .fa-filter { @@ -221,6 +222,10 @@ .filter-dropdown-container { display: -webkit-flex; display: flex; + + .dropdown-toggle { + line-height: 22px; + } } .dropdown-menu .filter-dropdown-item { @@ -246,7 +251,9 @@ background-color: $white-light; border-top: 0; } +} +@media (max-width: $screen-xs) { .filter-dropdown-container { .dropdown-toggle, .dropdown { diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index c2156a5ac69..927bf9805ce 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -148,6 +148,18 @@ .error-alert > .alert { margin-top: 5px; margin-bottom: 5px; + + &.alert-dismissable { + .close { + color: $white-light; + opacity: 0.85; + font-weight: normal; + + &:hover { + opacity: 1; + } + } + } } .discussion-body, diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index f2fee62ebd6..cdb5b4173d3 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -6,6 +6,8 @@ class Projects::IssuesController < Projects::ApplicationController include IssuableCollections include SpammableActions + prepend_before_action :authenticate_user!, only: [:new] + before_action :redirect_to_external_issue_tracker, only: [:index, :new] before_action :module_enabled before_action :issue, only: [:edit, :update, :show, :referenced_merge_requests, @@ -146,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/issue.rb b/app/models/issue.rb index 1427fdc31a4..602eed86d9e 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -55,6 +55,14 @@ class Issue < ActiveRecord::Base state :opened state :reopened state :closed + + before_transition any => :closed do |issue| + issue.closed_at = Time.zone.now + end + + before_transition closed: any do |issue| + issue.closed_at = nil + end end def hook_attrs 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/models/route.rb b/app/models/route.rb index 73574a6206b..41e6eb7cb73 100644 --- a/app/models/route.rb +++ b/app/models/route.rb @@ -21,7 +21,7 @@ class Route < ActiveRecord::Base attributes[:path] = route.path.sub(path_was, path) end - if name_changed? && route.name.present? + if name_changed? && name_was.present? && route.name.present? attributes[:name] = route.name.sub(name_was, name) end 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/services/system_hooks_service.rb b/app/services/system_hooks_service.rb index 868fa7b3f21..af0ddbe5934 100644 --- a/app/services/system_hooks_service.rb +++ b/app/services/system_hooks_service.rb @@ -24,10 +24,9 @@ class SystemHooksService key: model.key, id: model.id ) + if model.user - data.merge!( - username: model.user.username - ) + data[:username] = model.user.username end when Project data.merge!(project_data(model)) @@ -35,8 +34,6 @@ class SystemHooksService if event == :rename || event == :transfer data[:old_path_with_namespace] = model.old_path_with_namespace end - - data when User data.merge!({ name: model.name, @@ -59,6 +56,8 @@ class SystemHooksService when GroupMember data.merge!(group_member_data(model)) end + + data end def build_event_name(model, event) 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/environments/metrics.html.haml b/app/views/projects/environments/metrics.html.haml index f8e94ca98ae..b8c1782f050 100644 --- a/app/views/projects/environments/metrics.html.haml +++ b/app/views/projects/environments/metrics.html.haml @@ -1,5 +1,8 @@ - @no_container = true - page_title "Metrics for environment", @environment.name +- content_for :page_specific_javascripts do + = page_specific_javascript_bundle_tag('common_d3') + = page_specific_javascript_bundle_tag('monitoring') = render "projects/pipelines/head" %div{ class: container_class } diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml index 7b7d7b1e00e..f3a429d12d9 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -19,15 +19,14 @@ .nav-controls = link_to params.merge(rss_url_options), class: 'btn append-right-10 has-tooltip', title: 'Subscribe' do = icon('rss') - - if can? current_user, :create_issue, @project - = link_to new_namespace_project_issue_path(@project.namespace, - @project, - issue: { assignee_id: issues_finder.assignee.try(:id), - milestone_id: issues_finder.milestones.first.try(:id) }), - class: "btn btn-new", - title: "New Issue", - id: "new_issue_link" do - New Issue + = link_to new_namespace_project_issue_path(@project.namespace, + @project, + issue: { assignee_id: issues_finder.assignee.try(:id), + milestone_id: issues_finder.milestones.first.try(:id) }), + class: "btn btn-new", + title: "New Issue", + id: "new_issue_link" do + New Issue = render 'shared/issuable/search_bar', type: :issues .issues-holder diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index d39f36e94c7..6ac05bf3afe 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -20,37 +20,34 @@ = confidential_icon(@issue) = issuable_meta(@issue, @project, "Issue") - - if can?(current_user, :create_issue, @project) || can?(current_user, :update_issue, @issue) - .issuable-actions - .clearfix.issue-btn-group.dropdown - %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ type: "button", data: { toggle: "dropdown" } } - Options - = icon('caret-down') - .dropdown-menu.dropdown-menu-align-right.hidden-lg - %ul - - if can?(current_user, :create_issue, @project) - %li - = link_to 'New issue', new_namespace_project_issue_path(@project.namespace, @project), title: 'New issue', id: 'new_issue_link' - - if can?(current_user, :update_issue, @issue) - %li - = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, format: 'json'), data: {no_turbolink: true}, class: "btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue' - %li - = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, format: 'json'), data: {no_turbolink: true}, class: "btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue' - %li - = link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue) - - if @issue.submittable_as_spam_by?(current_user) - %li - = link_to 'Submit as spam', mark_as_spam_namespace_project_issue_path(@project.namespace, @project, @issue), method: :post, class: 'btn-spam', title: 'Submit as spam' - - - if can?(current_user, :create_issue, @project) - = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'hidden-xs hidden-sm btn btn-grouped new-issue-link btn-new btn-inverted', title: 'New issue', id: 'new_issue_link' do - New issue - - if can?(current_user, :update_issue, @issue) - = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue' - = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue' + .issuable-actions + .clearfix.issue-btn-group.dropdown + %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ type: "button", data: { toggle: "dropdown" } } + Options + = icon('caret-down') + .dropdown-menu.dropdown-menu-align-right.hidden-lg + %ul + %li + = link_to 'New issue', new_namespace_project_issue_path(@project.namespace, @project), title: 'New issue', id: 'new_issue_link' + - if can?(current_user, :update_issue, @issue) + %li + = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, format: 'json'), data: {no_turbolink: true}, class: "btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue' + %li + = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, format: 'json'), data: {no_turbolink: true}, class: "btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue' + %li + = link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue) - if @issue.submittable_as_spam_by?(current_user) - = link_to 'Submit as spam', mark_as_spam_namespace_project_issue_path(@project.namespace, @project, @issue), method: :post, class: 'hidden-xs hidden-sm btn btn-grouped btn-spam', title: 'Submit as spam' - = link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'hidden-xs hidden-sm btn btn-grouped issuable-edit' + %li + = link_to 'Submit as spam', mark_as_spam_namespace_project_issue_path(@project.namespace, @project, @issue), method: :post, class: 'btn-spam', title: 'Submit as spam' + + = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'hidden-xs hidden-sm btn btn-grouped new-issue-link btn-new btn-inverted', title: 'New issue', id: 'new_issue_link' do + New issue + - if can?(current_user, :update_issue, @issue) + = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue' + = link_to 'Close issue', issue_path(@issue, issue: { state_event: :close }, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-grouped btn-close #{issue_button_visibility(@issue, true)}", title: 'Close issue' + - if @issue.submittable_as_spam_by?(current_user) + = link_to 'Submit as spam', mark_as_spam_namespace_project_issue_path(@project.namespace, @project, @issue), method: :post, class: 'hidden-xs hidden-sm btn btn-grouped btn-spam', title: 'Submit as spam' + = link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: 'hidden-xs hidden-sm btn btn-grouped issuable-edit' .issue-details.issuable-details 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/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml index 367aa550a78..a212c714826 100644 --- a/app/views/shared/_sort_dropdown.html.haml +++ b/app/views/shared/_sort_dropdown.html.haml @@ -1,6 +1,5 @@ .dropdown.inline.prepend-left-10 %button.dropdown-toggle{ type: 'button', data: {toggle: 'dropdown' } } - %span.light - if @sort.present? = sort_options_hash[@sort] - else diff --git a/app/views/shared/empty_states/_issues.html.haml b/app/views/shared/empty_states/_issues.html.haml index e2033654018..7a7e3d46796 100644 --- a/app/views/shared/empty_states/_issues.html.haml +++ b/app/views/shared/empty_states/_issues.html.haml @@ -16,7 +16,6 @@ Also, issues are searchable and filterable. - if project_select_button = render 'shared/new_project_item_select', path: 'issues/new', label: 'New issue' - - else - = link_to 'New issue', button_path, class: 'btn btn-new', title: 'New issue', id: 'new_issue_link' - else - %h4.text-center There are no issues to show. + %h4 There are no issues to show. + = link_to 'New issue', button_path, class: 'btn btn-new', title: 'New issue', id: 'new_issue_link' 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/28499-fix-large-text-tooltip-in-diff-file-name.yml b/changelogs/unreleased/28499-fix-large-text-tooltip-in-diff-file-name.yml new file mode 100644 index 00000000000..660a881e094 --- /dev/null +++ b/changelogs/unreleased/28499-fix-large-text-tooltip-in-diff-file-name.yml @@ -0,0 +1,4 @@ +--- +title: Fixes large file name tooltip cutoff in diff header +merge_request: 9529 +author: diff --git a/changelogs/unreleased/28660-fix-dismissable-error-close-not-visible-enough.yml b/changelogs/unreleased/28660-fix-dismissable-error-close-not-visible-enough.yml new file mode 100644 index 00000000000..8b592766bf3 --- /dev/null +++ b/changelogs/unreleased/28660-fix-dismissable-error-close-not-visible-enough.yml @@ -0,0 +1,4 @@ +--- +title: Fixes dismissable error close is not visible enough +merge_request: 9516 +author: diff --git a/changelogs/unreleased/bugfix-systemhook.yml b/changelogs/unreleased/bugfix-systemhook.yml new file mode 100644 index 00000000000..4c4d0dcc7a2 --- /dev/null +++ b/changelogs/unreleased/bugfix-systemhook.yml @@ -0,0 +1,4 @@ +--- +title: Fix bug when system hook for deploy key +merge_request: 9796 +author: billy.lb diff --git a/changelogs/unreleased/fix-prometheus-including-d3-main-bundle.yml b/changelogs/unreleased/fix-prometheus-including-d3-main-bundle.yml new file mode 100644 index 00000000000..a42b0db3cfc --- /dev/null +++ b/changelogs/unreleased/fix-prometheus-including-d3-main-bundle.yml @@ -0,0 +1,4 @@ +--- +title: Removed d3 from the main application.js bundle +merge_request: 10062 +author: diff --git a/changelogs/unreleased/issue_27212.yml b/changelogs/unreleased/issue_27212.yml new file mode 100644 index 00000000000..7a7e04f7ca7 --- /dev/null +++ b/changelogs/unreleased/issue_27212.yml @@ -0,0 +1,4 @@ +--- +title: Add closed_at field to issues +merge_request: +author: diff --git a/changelogs/unreleased/make-karma-fast-again.yml b/changelogs/unreleased/make-karma-fast-again.yml new file mode 100644 index 00000000000..9b95e06954a --- /dev/null +++ b/changelogs/unreleased/make-karma-fast-again.yml @@ -0,0 +1,4 @@ +--- +title: Only add code coverage instrumentation when generating coverage report +merge_request: 9987 +author: diff --git a/changelogs/unreleased/simplify-docs-trigger.yml b/changelogs/unreleased/simplify-docs-trigger.yml new file mode 100644 index 00000000000..062626359ef --- /dev/null +++ b/changelogs/unreleased/simplify-docs-trigger.yml @@ -0,0 +1,4 @@ +--- +title: Simplify trigger_docs build job for CE and EE +merge_request: 9820 +author: winniehell 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/config/karma.config.js b/config/karma.config.js index c1d3751d88f..eb082dd28bf 100644 --- a/config/karma.config.js +++ b/config/karma.config.js @@ -3,17 +3,6 @@ var webpack = require('webpack'); var webpackConfig = require('./webpack.config.js'); var ROOT_PATH = path.resolve(__dirname, '..'); -// add coverage instrumentation to babel config -if (webpackConfig.module && webpackConfig.module.rules) { - var babelConfig = webpackConfig.module.rules.find(function (rule) { - return rule.loader === 'babel-loader'; - }); - - babelConfig.options = babelConfig.options || {}; - babelConfig.options.plugins = babelConfig.options.plugins || []; - babelConfig.options.plugins.push('istanbul'); -} - // remove problematic plugins if (webpackConfig.plugins) { webpackConfig.plugins = webpackConfig.plugins.filter(function (plugin) { @@ -27,7 +16,8 @@ if (webpackConfig.plugins) { // Karma configuration module.exports = function(config) { var progressReporter = process.env.CI ? 'mocha' : 'progress'; - config.set({ + + var karmaConfig = { basePath: ROOT_PATH, browsers: ['PhantomJS'], frameworks: ['jasmine'], @@ -38,14 +28,20 @@ module.exports = function(config) { preprocessors: { 'spec/javascripts/**/*.js': ['webpack', 'sourcemap'], }, - reporters: [progressReporter, 'coverage-istanbul'], - coverageIstanbulReporter: { + reporters: [progressReporter], + webpack: webpackConfig, + webpackMiddleware: { stats: 'errors-only' }, + }; + + if (process.env.BABEL_ENV === 'coverage' || process.env.NODE_ENV === 'coverage') { + karmaConfig.reporters.push('coverage-istanbul'); + karmaConfig.coverageIstanbulReporter = { reports: ['html', 'text-summary'], dir: 'coverage-javascript/', subdir: '.', fixWebpackSourcePaths: true - }, - webpack: webpackConfig, - webpackMiddleware: { stats: 'errors-only' }, - }); + }; + } + + config.set(karmaConfig); }; diff --git a/config/webpack.config.js b/config/webpack.config.js index cbcc9ac5aea..c6794d6b944 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -35,6 +35,7 @@ var config = { issuable: './issuable/issuable_bundle.js', merge_conflicts: './merge_conflicts/merge_conflicts_bundle.js', merge_request_widget: './merge_request_widget/ci_bundle.js', + monitoring: './monitoring/monitoring_bundle.js', network: './network/network_bundle.js', profile: './profile/profile_bundle.js', protected_branches: './protected_branches/protected_branches_bundle.js', @@ -58,13 +59,7 @@ var config = { { test: /\.js$/, exclude: /(node_modules|vendor\/assets)/, - loader: 'babel-loader', - options: { - presets: [ - ["es2015", {"modules": false}], - 'stage-2' - ] - } + loader: 'babel-loader' }, { test: /\.svg$/, @@ -120,7 +115,7 @@ var config = { // create cacheable common library bundle for all d3 chunks new webpack.optimize.CommonsChunkPlugin({ name: 'common_d3', - chunks: ['graphs', 'users'], + chunks: ['graphs', 'users', 'monitoring'], }), // create cacheable common library bundles diff --git a/db/fixtures/development/17_cycle_analytics.rb b/db/fixtures/development/17_cycle_analytics.rb index aea0a72b633..4bc735916c1 100644 --- a/db/fixtures/development/17_cycle_analytics.rb +++ b/db/fixtures/development/17_cycle_analytics.rb @@ -155,7 +155,7 @@ class Gitlab::Seeder::CycleAnalytics issue.project.repository.add_branch(@user, branch_name, 'master') - commit_sha = issue.project.repository.create_file(@user, filename, "content", options, message: "Commit for ##{issue.iid}", branch_name: branch_name) + commit_sha = issue.project.repository.create_file(@user, filename, "content", message: "Commit for ##{issue.iid}", branch_name: branch_name) issue.project.repository.commit(commit_sha) GitPushService.new(issue.project, diff --git a/db/migrate/20170315194013_add_closed_at_to_issues.rb b/db/migrate/20170315194013_add_closed_at_to_issues.rb new file mode 100644 index 00000000000..1326118cc8d --- /dev/null +++ b/db/migrate/20170315194013_add_closed_at_to_issues.rb @@ -0,0 +1,7 @@ +class AddClosedAtToIssues < ActiveRecord::Migration + DOWNTIME = false + + def change + add_column :issues, :closed_at, :datetime + end +end diff --git a/db/schema.rb b/db/schema.rb index 634d02bb5bc..ee5000ea64c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20170315174634) do +ActiveRecord::Schema.define(version: 20170315194013) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -445,6 +445,7 @@ ActiveRecord::Schema.define(version: 20170315174634) do t.text "description_html" t.integer "time_estimate" t.integer "relative_position" + t.datetime "closed_at" end add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree diff --git a/doc/administration/monitoring/prometheus/index.md b/doc/administration/monitoring/prometheus/index.md index 69b16b7c483..b2445d1c0e5 100644 --- a/doc/administration/monitoring/prometheus/index.md +++ b/doc/administration/monitoring/prometheus/index.md @@ -96,21 +96,15 @@ Sample Prometheus queries: > Introduced in GitLab 9.0. -If your GitLab server is running within Kubernetes, an option is now available -to monitor the health of each node in the cluster. This is particularly helpful -if your CI/CD environments run in the same cluster, and you would like enable -[Prometheus integration][] to monitor them. +If your GitLab server is running within Kubernetes, Prometheus will collect metrics from the Nodes in the cluster including performance data on each container. This is particularly helpful if your CI/CD environments run in the same cluster, as you can use the [Prometheus project integration][] to monitor them. -When enabled, the bundled Prometheus server monitors Kubernetes and automatically -[collects metrics][prometheus-cadvisor-metrics] from each Node in the cluster. - -To enable the Kubernetes monitoring: +To disable the monitoring of Kubernetes: 1. Edit `/etc/gitlab/gitlab.rb` -1. Add or find and uncomment the following line: +1. Add or find and uncomment the following line and set it to `false`: ```ruby - prometheus['monitor_kubernetes'] = true + prometheus['monitor_kubernetes'] = false ``` 1. Save the file and [reconfigure GitLab][reconfigure] for the changes to @@ -165,4 +159,4 @@ The GitLab monitor exporter allows you to measure various GitLab metrics. [reconfigure]: ../../restart_gitlab.md#omnibus-gitlab-reconfigure [1261]: https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests/1261 [prometheus integration]: ../../../user/project/integrations/prometheus.md -[rometheus-cadvisor-metrics]: https://github.com/google/cadvisor/blob/master/docs/storage/prometheus.md +[prometheus-cadvisor-metrics]: https://github.com/google/cadvisor/blob/master/docs/storage/prometheus.md diff --git a/doc/ssh/README.md b/doc/ssh/README.md index 678f5199b02..cf28f1a2eca 100644 --- a/doc/ssh/README.md +++ b/doc/ssh/README.md @@ -170,12 +170,12 @@ Integration (CI) server. By using deploy keys, you don't have to setup a dummy user account. If you are a project master or owner, you can add a deploy key in the -project settings under the section 'Deploy Keys'. Press the 'New Deploy -Key' button and upload a public SSH key. After this, the machine that uses +project settings under the section 'Repository'. Specify a title for the new +deploy key and paste a public SSH key. After this, the machine that uses the corresponding private SSH key has read-only or read-write (if enabled) access to the project. -You can't add the same deploy key twice with the 'New Deploy Key' option. +You can't add the same deploy key twice using the form. If you want to add the same key to another project, please enable it in the list that says 'Deploy keys from projects available to you'. All the deploy keys of all the projects you have access to are available. This project diff --git a/doc/update/8.17-to-9.0.md b/doc/update/8.17-to-9.0.md index 626507c0482..b7ba970031c 100644 --- a/doc/update/8.17-to-9.0.md +++ b/doc/update/8.17-to-9.0.md @@ -115,11 +115,11 @@ sudo -u git -H bundle clean # Run database migrations sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production -# Install/update frontend asset dependencies -sudo -u git -H npm install --production +# Update node dependencies and recompile assets +sudo -u git -H bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile RAILS_ENV=production NODE_ENV=production -# Clean up assets and cache -sudo -u git -H bundle exec rake gitlab:assets:clean gitlab:assets:compile cache:clear RAILS_ENV=production +# Clean up cache +sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production ``` **MySQL installations**: Run through the `MySQL strings limits` and `Tables and data conversion to utf8mb4` [tasks](../install/database_mysql.md). 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/package.json b/package.json index 1048e29d0ac..b3d038bd3d1 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "eslint-fix": "eslint --max-warnings 0 --ext .js --fix .", "eslint-report": "eslint --max-warnings 0 --ext .js --format html --output-file ./eslint-report.html .", "karma": "karma start config/karma.config.js --single-run", + "karma-coverage": "BABEL_ENV=coverage karma start config/karma.config.js --single-run", "karma-start": "karma start config/karma.config.js", "webpack": "webpack --config config/webpack.config.js", "webpack-prod": "NODE_ENV=production webpack --config config/webpack.config.js" @@ -13,7 +14,8 @@ "dependencies": { "babel-core": "^6.22.1", "babel-loader": "^6.2.10", - "babel-preset-es2015": "^6.22.0", + "babel-plugin-transform-define": "^1.2.0", + "babel-preset-latest": "^6.24.0", "babel-preset-stage-2": "^6.22.0", "bootstrap-sass": "^3.3.6", "compression-webpack-plugin": "^0.3.2", @@ -57,12 +59,5 @@ "karma-sourcemap-loader": "^0.3.7", "karma-webpack": "^2.0.2", "webpack-dev-server": "^2.3.0" - }, - "nyc": { - "exclude": [ - "spec/javascripts/test_bundle.js", - "spec/javascripts/**/*_spec.js", - "app/assets/javascripts/droplab/**/*" - ] } } diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index 6ceaf96f78f..57a921e3676 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -87,6 +87,12 @@ describe Projects::IssuesController do end describe 'GET #new' do + it 'redirects to signin if not logged in' do + get :new, namespace_id: project.namespace, project_id: project + + expect(response).to redirect_to(new_user_session_path) + end + context 'internal issue tracker' do before do sign_in(user) @@ -121,6 +127,11 @@ describe Projects::IssuesController do end context 'external issue tracker' do + before do + sign_in(user) + project.team << [user, :developer] + end + it 'redirects to the external issue tracker' do external = double(new_issue_path: 'https://example.com/issues/new') allow(project).to receive(:external_issue_tracker).and_return(external) @@ -141,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/features/groups/group_name_toggle.rb b/spec/features/groups/group_name_toggle_spec.rb index ada4ac66e04..8528718a2f7 100644 --- a/spec/features/groups/group_name_toggle.rb +++ b/spec/features/groups/group_name_toggle_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Group name toggle', js: true do +feature 'Group name toggle', feature: true, js: true do let(:group) { create(:group) } let(:nested_group_1) { create(:group, parent: group) } let(:nested_group_2) { create(:group, parent: nested_group_1) } diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index 1c8267b1593..a58aedc924e 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -6,7 +6,7 @@ describe 'Issues', feature: true do include SortingHelper include WaitForAjax - let(:project) { create(:project) } + let(:project) { create(:project, :public) } before do login_as :user @@ -565,6 +565,24 @@ describe 'Issues', feature: true do end describe 'new issue' do + context 'by unauthenticated user' do + before do + logout + end + + it 'redirects to signin then back to new issue after signin' do + visit namespace_project_issues_path(project.namespace, project) + + click_link 'New issue' + + expect(current_path).to eq new_user_session_path + + login_as :user + + expect(current_path).to eq new_namespace_project_issue_path(project.namespace, project) + end + end + context 'dropzone upload file', js: true do before do visit new_namespace_project_issue_path(project.namespace, project) diff --git a/spec/features/merge_requests/reset_filters_spec.rb b/spec/features/merge_requests/reset_filters_spec.rb index 6fed1568fcf..14511707af4 100644 --- a/spec/features/merge_requests/reset_filters_spec.rb +++ b/spec/features/merge_requests/reset_filters_spec.rb @@ -49,6 +49,26 @@ feature 'Merge requests filter clear button', feature: true, js: true do end end + context 'when multiple label filters have been applied' do + let!(:label) { create(:label, project: project, name: 'Frontend') } + let(:filter_dropdown) { find("#js-dropdown-label .filter-dropdown") } + + before do + visit_merge_requests(project) + init_label_search + end + + it 'filters bug label' do + filtered_search.set('~bug') + + filter_dropdown.find('.filter-dropdown-item', text: bug.title).click + init_label_search + + expect(filter_dropdown.find('.filter-dropdown-item', text: bug.title)).to be_visible + expect(filter_dropdown.find('.filter-dropdown-item', text: label.title)).to be_visible + end + end + context 'when a text search has been conducted' do it 'resets the text search filter' do visit_merge_requests(project, search: 'Bug') diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js index 8d25500b9fd..aabc8bea12f 100644 --- a/spec/javascripts/issue_spec.js +++ b/spec/javascripts/issue_spec.js @@ -136,6 +136,21 @@ describe('Issue', function() { expectErrorMessage(); expect($('.issue_counter')).toHaveText(1); }); + + it('updates counter', () => { + spyOn(jQuery, 'ajax').and.callFake(function(req) { + expectPendingRequest(req, $btnClose); + req.success({ + id: 34 + }); + }); + + expect($('.issue_counter')).toHaveText(1); + $('.issue_counter').text('1,001'); + expect($('.issue_counter').text()).toEqual('1,001'); + $btnClose.trigger('click'); + expect($('.issue_counter').text()).toEqual('1,000'); + }); }); describe('reopen issue', function() { diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js index c12b44cea89..5cdb6473eda 100644 --- a/spec/javascripts/test_bundle.js +++ b/spec/javascripts/test_bundle.js @@ -32,10 +32,11 @@ testsContext.keys().forEach(function (path) { } }); -// workaround: include all source files to find files with 0% coverage -// see also https://github.com/deepsweet/istanbul-instrumenter-loader/issues/15 -describe('Uncovered files', function () { - // the following files throw errors because of undefined variables +// if we're generating coverage reports, make sure to include all files so +// that we can catch files with 0% coverage +// see: https://github.com/deepsweet/istanbul-instrumenter-loader/issues/15 +if (process.env.BABEL_ENV === 'coverage') { + // exempt these files from the coverage report const troubleMakers = [ './blob_edit/blob_edit_bundle.js', './cycle_analytics/components/stage_plan_component.js', @@ -48,21 +49,23 @@ describe('Uncovered files', function () { './network/branch_graph.js', ]; - const sourceFiles = require.context('~', true, /^\.\/(?!application\.js).*\.js$/); - sourceFiles.keys().forEach(function (path) { - // ignore if there is a matching spec file - if (testsContext.keys().indexOf(`${path.replace(/\.js$/, '')}_spec`) > -1) { - return; - } + describe('Uncovered files', function () { + const sourceFiles = require.context('~', true, /\.js$/); + sourceFiles.keys().forEach(function (path) { + // ignore if there is a matching spec file + if (testsContext.keys().indexOf(`${path.replace(/\.js$/, '')}_spec`) > -1) { + return; + } - it(`includes '${path}'`, function () { - try { - sourceFiles(path); - } catch (err) { - if (troubleMakers.indexOf(path) === -1) { - expect(err).toBeNull(); + it(`includes '${path}'`, function () { + try { + sourceFiles(path); + } catch (err) { + if (troubleMakers.indexOf(path) === -1) { + expect(err).toBeNull(); + } } - } + }); }); }); -}); +} diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 042b7b0a20d..1ad16a9b57d 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -15,6 +15,7 @@ Issue: - updated_by_id - confidential - deleted_at +- closed_at - due_date - moved_to_id - lock_version 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/issue_spec.rb b/spec/models/issue_spec.rb index 9ffcb88bafd..73977d031f9 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -37,6 +37,30 @@ describe Issue, models: true do end end + describe '#closed_at' do + after do + Timecop.return + end + + let!(:now) { Timecop.freeze(Time.now) } + + it 'sets closed_at to Time.now when issue is closed' do + issue = create(:issue, state: 'opened') + + issue.close + + expect(issue.closed_at).to eq(now) + end + + it 'sets closed_at to nil when issue is reopened' do + issue = create(:issue, state: 'closed') + + issue.reopen + + expect(issue.closed_at).to be_nil + end + end + describe '#to_reference' do let(:namespace) { build(:namespace, path: 'sample-namespace') } let(:project) { build(:empty_project, name: 'sample-project', namespace: namespace) } 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/models/route_spec.rb b/spec/models/route_spec.rb index 0b222022e62..bc8ae4ae5a8 100644 --- a/spec/models/route_spec.rb +++ b/spec/models/route_spec.rb @@ -43,14 +43,22 @@ describe Route, models: true do end context 'name update' do - before { route.update_attributes(name: 'bar') } - it "updates children routes with new path" do + route.update_attributes(name: 'bar') + expect(described_class.exists?(name: 'bar')).to be_truthy expect(described_class.exists?(name: 'bar / test')).to be_truthy expect(described_class.exists?(name: 'bar / test / foo')).to be_truthy expect(described_class.exists?(name: 'gitlab-org')).to be_truthy end + + it 'handles a rename from nil' do + # Note: using `update_columns` to skip all validation and callbacks + route.update_columns(name: nil) + + expect { route.update_attributes(name: 'bar') } + .to change { route.name }.from(nil).to('bar') + end end end end 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', diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb index db9f1231682..11037a4917b 100644 --- a/spec/services/system_hooks_service_spec.rb +++ b/spec/services/system_hooks_service_spec.rb @@ -5,6 +5,7 @@ describe SystemHooksService, services: true do let(:project) { create :project } let(:project_member) { create :project_member } let(:key) { create(:key, user: user) } + let(:deploy_key) { create(:key) } let(:group) { create(:group) } let(:group_member) { create(:group_member) } @@ -18,6 +19,8 @@ describe SystemHooksService, services: true do it { expect(event_data(project_member, :destroy)).to include(:event_name, :created_at, :updated_at, :project_name, :project_path, :project_path_with_namespace, :project_id, :user_name, :user_username, :user_email, :user_id, :access_level, :project_visibility) } it { expect(event_data(key, :create)).to include(:username, :key, :id) } it { expect(event_data(key, :destroy)).to include(:username, :key, :id) } + it { expect(event_data(deploy_key, :create)).to include(:key, :id) } + it { expect(event_data(deploy_key, :destroy)).to include(:key, :id) } it do project.old_path_with_namespace = 'renamed_from_path' diff --git a/yarn.lock b/yarn.lock index 391b1c7eccf..2500ddc6f6b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -473,6 +473,13 @@ babel-plugin-transform-decorators@^6.22.0: babel-template "^6.22.0" babel-types "^6.22.0" +babel-plugin-transform-define@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-define/-/babel-plugin-transform-define-1.2.0.tgz#f036bda05162f29a542e434f585da1ccf1e7ec6a" + dependencies: + lodash.get "4.4.2" + traverse "0.6.6" + babel-plugin-transform-es2015-arrow-functions@^6.22.0: version "6.22.0" resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz#452692cb711d5f79dc7f85e440ce41b9f244d221" @@ -549,17 +556,17 @@ babel-plugin-transform-es2015-literals@^6.22.0: dependencies: babel-runtime "^6.22.0" -babel-plugin-transform-es2015-modules-amd@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.22.0.tgz#bf69cd34889a41c33d90dfb740e0091ccff52f21" +babel-plugin-transform-es2015-modules-amd@^6.24.0: + version "6.24.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.0.tgz#a1911fb9b7ec7e05a43a63c5995007557bcf6a2e" dependencies: - babel-plugin-transform-es2015-modules-commonjs "^6.22.0" + babel-plugin-transform-es2015-modules-commonjs "^6.24.0" babel-runtime "^6.22.0" babel-template "^6.22.0" -babel-plugin-transform-es2015-modules-commonjs@^6.22.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.23.0.tgz#cba7aa6379fb7ec99250e6d46de2973aaffa7b92" +babel-plugin-transform-es2015-modules-commonjs@^6.24.0: + version "6.24.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.24.0.tgz#e921aefb72c2cc26cb03d107626156413222134f" dependencies: babel-plugin-transform-strict-mode "^6.22.0" babel-runtime "^6.22.0" @@ -574,11 +581,11 @@ babel-plugin-transform-es2015-modules-systemjs@^6.22.0: babel-runtime "^6.22.0" babel-template "^6.23.0" -babel-plugin-transform-es2015-modules-umd@^6.22.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.23.0.tgz#8d284ae2e19ed8fe21d2b1b26d6e7e0fcd94f0f1" +babel-plugin-transform-es2015-modules-umd@^6.24.0: + version "6.24.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.0.tgz#fd5fa63521cae8d273927c3958afd7c067733450" dependencies: - babel-plugin-transform-es2015-modules-amd "^6.22.0" + babel-plugin-transform-es2015-modules-amd "^6.24.0" babel-runtime "^6.22.0" babel-template "^6.23.0" @@ -669,9 +676,9 @@ babel-plugin-transform-strict-mode@^6.22.0: babel-runtime "^6.22.0" babel-types "^6.22.0" -babel-preset-es2015@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-preset-es2015/-/babel-preset-es2015-6.22.0.tgz#af5a98ecb35eb8af764ad8a5a05eb36dc4386835" +babel-preset-es2015@^6.24.0: + version "6.24.0" + resolved "https://registry.yarnpkg.com/babel-preset-es2015/-/babel-preset-es2015-6.24.0.tgz#c162d68b1932696e036cd3110dc1ccd303d2673a" dependencies: babel-plugin-check-es2015-constants "^6.22.0" babel-plugin-transform-es2015-arrow-functions "^6.22.0" @@ -684,10 +691,10 @@ babel-preset-es2015@^6.22.0: babel-plugin-transform-es2015-for-of "^6.22.0" babel-plugin-transform-es2015-function-name "^6.22.0" babel-plugin-transform-es2015-literals "^6.22.0" - babel-plugin-transform-es2015-modules-amd "^6.22.0" - babel-plugin-transform-es2015-modules-commonjs "^6.22.0" + babel-plugin-transform-es2015-modules-amd "^6.24.0" + babel-plugin-transform-es2015-modules-commonjs "^6.24.0" babel-plugin-transform-es2015-modules-systemjs "^6.22.0" - babel-plugin-transform-es2015-modules-umd "^6.22.0" + babel-plugin-transform-es2015-modules-umd "^6.24.0" babel-plugin-transform-es2015-object-super "^6.22.0" babel-plugin-transform-es2015-parameters "^6.22.0" babel-plugin-transform-es2015-shorthand-properties "^6.22.0" @@ -698,6 +705,27 @@ babel-preset-es2015@^6.22.0: babel-plugin-transform-es2015-unicode-regex "^6.22.0" babel-plugin-transform-regenerator "^6.22.0" +babel-preset-es2016@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-preset-es2016/-/babel-preset-es2016-6.22.0.tgz#b061aaa3983d40c9fbacfa3743b5df37f336156c" + dependencies: + babel-plugin-transform-exponentiation-operator "^6.22.0" + +babel-preset-es2017@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-preset-es2017/-/babel-preset-es2017-6.22.0.tgz#de2f9da5a30c50d293fb54a0ba15d6ddc573f0f2" + dependencies: + babel-plugin-syntax-trailing-function-commas "^6.22.0" + babel-plugin-transform-async-to-generator "^6.22.0" + +babel-preset-latest@^6.24.0: + version "6.24.0" + resolved "https://registry.yarnpkg.com/babel-preset-latest/-/babel-preset-latest-6.24.0.tgz#a68d20f509edcc5d7433a48dfaebf7e4f2cd4cb7" + dependencies: + babel-preset-es2015 "^6.24.0" + babel-preset-es2016 "^6.22.0" + babel-preset-es2017 "^6.22.0" + babel-preset-stage-2@^6.22.0: version "6.22.0" resolved "https://registry.yarnpkg.com/babel-preset-stage-2/-/babel-preset-stage-2-6.22.0.tgz#ccd565f19c245cade394b21216df704a73b27c07" @@ -2900,6 +2928,10 @@ lodash.deburr@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/lodash.deburr/-/lodash.deburr-4.1.0.tgz#ddb1bbb3ef07458c0177ba07de14422cb033ff9b" +lodash.get@4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" + lodash.get@^3.7.0: version "3.7.0" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-3.7.0.tgz#3ce68ae2c91683b281cc5394128303cbf75e691f" @@ -4271,6 +4303,10 @@ tough-cookie@~2.3.0: dependencies: punycode "^1.4.1" +traverse@0.6.6: + version "0.6.6" + resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.6.6.tgz#cbdf560fd7b9af632502fed40f918c157ea97137" + trim-right@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" |