From b1d04cf9d58ac461f70a2cbf4df617cc14c3de1c Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Wed, 5 Sep 2018 21:41:59 -0700 Subject: Block loopback addresses in UrlBlocker Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/51128 --- changelogs/unreleased/sh-block-other-localhost.yml | 5 +++++ lib/gitlab/url_blocker.rb | 7 +++++++ spec/lib/gitlab/url_blocker_spec.rb | 22 +++++++++++++++++++++- 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/sh-block-other-localhost.yml diff --git a/changelogs/unreleased/sh-block-other-localhost.yml b/changelogs/unreleased/sh-block-other-localhost.yml new file mode 100644 index 00000000000..a6a41f0bd81 --- /dev/null +++ b/changelogs/unreleased/sh-block-other-localhost.yml @@ -0,0 +1,5 @@ +--- +title: Block loopback addresses in UrlBlocker +merge_request: +author: +type: security diff --git a/lib/gitlab/url_blocker.rb b/lib/gitlab/url_blocker.rb index 3b483f27e70..a2cce9151c3 100644 --- a/lib/gitlab/url_blocker.rb +++ b/lib/gitlab/url_blocker.rb @@ -30,6 +30,7 @@ module Gitlab end validate_localhost!(addrs_info) unless allow_localhost + validate_loopback!(addrs_info) unless allow_localhost validate_local_network!(addrs_info) unless allow_local_network validate_link_local!(addrs_info) unless allow_local_network @@ -84,6 +85,12 @@ module Gitlab raise BlockedUrlError, "Requests to localhost are not allowed" end + def validate_loopback!(addrs_info) + return unless addrs_info.any? { |addr| addr.ipv4_loopback? || addr.ipv6_loopback? } + + raise BlockedUrlError, "Requests to loopback addresses are not allowed" + end + def validate_local_network!(addrs_info) return unless addrs_info.any? { |addr| addr.ipv4_private? || addr.ipv6_sitelocal? } diff --git a/spec/lib/gitlab/url_blocker_spec.rb b/spec/lib/gitlab/url_blocker_spec.rb index 624e2add860..8df0facdab3 100644 --- a/spec/lib/gitlab/url_blocker_spec.rb +++ b/spec/lib/gitlab/url_blocker_spec.rb @@ -29,6 +29,16 @@ describe Gitlab::UrlBlocker do expect(described_class.blocked_url?('https://gitlab.com/foo/foo.git', protocols: ['http'])).to be true end + it 'returns true for localhost IPs' do + expect(described_class.blocked_url?('https://0.0.0.0/foo/foo.git')).to be true + expect(described_class.blocked_url?('https://[::1]/foo/foo.git')).to be true + expect(described_class.blocked_url?('https://127.0.0.1/foo/foo.git')).to be true + end + + it 'returns true for loopback IP' do + expect(described_class.blocked_url?('https://127.0.0.2/foo/foo.git')).to be true + end + it 'returns true for alternative version of 127.0.0.1 (0177.1)' do expect(described_class.blocked_url?('https://0177.1:65535/foo/foo.git')).to be true end @@ -84,6 +94,16 @@ describe Gitlab::UrlBlocker do end end + it 'allows localhost endpoints' do + expect(described_class).not_to be_blocked_url('http://0.0.0.0', allow_localhost: true) + expect(described_class).not_to be_blocked_url('http://localhost', allow_localhost: true) + expect(described_class).not_to be_blocked_url('http://127.0.0.1', allow_localhost: true) + end + + it 'allows loopback endpoints' do + expect(described_class).not_to be_blocked_url('http://127.0.0.2', allow_localhost: true) + end + it 'allows IPv4 link-local endpoints' do expect(described_class).not_to be_blocked_url('http://169.254.169.254') expect(described_class).not_to be_blocked_url('http://169.254.168.100') @@ -122,7 +142,7 @@ describe Gitlab::UrlBlocker do end def stub_domain_resolv(domain, ip) - allow(Addrinfo).to receive(:getaddrinfo).with(domain, any_args).and_return([double(ip_address: ip, ipv4_private?: true, ipv6_link_local?: false)]) + allow(Addrinfo).to receive(:getaddrinfo).with(domain, any_args).and_return([double(ip_address: ip, ipv4_private?: true, ipv6_link_local?: false, ipv4_loopback?: false, ipv6_loopback?: false)]) end def unstub_domain_resolv -- cgit v1.2.1 From 1fa93c6e202132319d873f4699e051853caa421c Mon Sep 17 00:00:00 2001 From: Jason Colyer Date: Mon, 17 Sep 2018 13:38:53 -0500 Subject: Added info on getting k8s integration for existing cluster --- doc/user/project/clusters/index.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md index 41768998a59..20accdb38b5 100644 --- a/doc/user/project/clusters/index.md +++ b/doc/user/project/clusters/index.md @@ -113,6 +113,19 @@ To add an existing Kubernetes cluster to your project: After a couple of minutes, your cluster will be ready to go. You can now proceed to install some [pre-defined applications](#installing-applications). +If you need to determine some of the above values, the following should prove helpful: + +- The API URL: + - You can get this via the command: `kubectl config view|grep server` +- The CA Certificate: + - You can determine the certificate via this command: `kubectl config view --raw|awk '/certificate-authority-data/ {print $NF}'|base64 -d` +- The Token: + - You will first need to determine which secret you need the token for. + - To list the secrets, run the command: `kubectl get secrets` + - Determine which secret you want the token for + - Run this command to get the token: `kubectl describe secrets/|grep ^token` + - Replace `` with the secret you want the token for + ## Security implications CAUTION: **Important:** -- cgit v1.2.1 From 215feb642de94485d7644a532b6a9982d964d539 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 28 Sep 2018 21:51:02 -0400 Subject: Prevent SSRF attacks in HipChat integration This change monkey patches the HipChat client to use the GitLab HTTParty connection adapter, which can block access to certain hosts. Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/51142 --- changelogs/unreleased/sh-fix-hipchat-ssrf.yml | 5 +++++ config/initializers/hipchat_client_patch.rb | 14 ++++++++++++++ spec/models/project_services/hipchat_service_spec.rb | 18 ++++++++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 changelogs/unreleased/sh-fix-hipchat-ssrf.yml create mode 100644 config/initializers/hipchat_client_patch.rb diff --git a/changelogs/unreleased/sh-fix-hipchat-ssrf.yml b/changelogs/unreleased/sh-fix-hipchat-ssrf.yml new file mode 100644 index 00000000000..cdc95a34fcf --- /dev/null +++ b/changelogs/unreleased/sh-fix-hipchat-ssrf.yml @@ -0,0 +1,5 @@ +--- +title: Prevent SSRF attacks in HipChat integration +merge_request: +author: +type: security diff --git a/config/initializers/hipchat_client_patch.rb b/config/initializers/hipchat_client_patch.rb new file mode 100644 index 00000000000..aec265312bb --- /dev/null +++ b/config/initializers/hipchat_client_patch.rb @@ -0,0 +1,14 @@ +# This monkey patches the HTTParty used in https://github.com/hipchat/hipchat-rb. +module HipChat + class Client + connection_adapter ::Gitlab::ProxyHTTPConnectionAdapter + end + + class Room + connection_adapter ::Gitlab::ProxyHTTPConnectionAdapter + end + + class User + connection_adapter ::Gitlab::ProxyHTTPConnectionAdapter + end +end diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb index 0cd712e2f40..b0fd2ceead0 100644 --- a/spec/models/project_services/hipchat_service_spec.rb +++ b/spec/models/project_services/hipchat_service_spec.rb @@ -387,4 +387,22 @@ describe HipchatService do end end end + + context 'with UrlBlocker' do + let(:user) { create(:user) } + let(:project) { create(:project, :repository) } + let(:hipchat) { described_class.new(project: project) } + let(:push_sample_data) { Gitlab::DataBuilder::Push.build_sample(project, user) } + + describe '#execute' do + before do + hipchat.server = 'http://localhost:9123' + end + + it 'raises UrlBlocker for localhost' do + expect(Gitlab::UrlBlocker).to receive(:validate!).and_call_original + expect { hipchat.execute(push_sample_data) }.to raise_error(Gitlab::HTTP::BlockedUrlError) + end + end + end end -- cgit v1.2.1 From 3cd511733b5b646becfdf72e36062b863dfbcf20 Mon Sep 17 00:00:00 2001 From: Ronald van Zon Date: Thu, 30 Aug 2018 14:17:36 +0000 Subject: Fixing count on Milestones By adding groups to milestones we can now include them in the count of Open and Closed. --- app/controllers/dashboard/application_controller.rb | 4 ++++ app/controllers/dashboard/milestones_controller.rb | 3 ++- app/models/global_milestone.rb | 20 ++++++++++++++++++-- changelogs/unreleased/rz_fix_milestone_count.yml | 5 +++++ 4 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/rz_fix_milestone_count.yml diff --git a/app/controllers/dashboard/application_controller.rb b/app/controllers/dashboard/application_controller.rb index cee0753a021..1c9a5917da5 100644 --- a/app/controllers/dashboard/application_controller.rb +++ b/app/controllers/dashboard/application_controller.rb @@ -12,4 +12,8 @@ class Dashboard::ApplicationController < ApplicationController def projects @projects ||= current_user.authorized_projects.sorted_by_activity.non_archived end + + def groups + @groups ||= GroupsFinder.new(current_user, state_all: true).execute + end end diff --git a/app/controllers/dashboard/milestones_controller.rb b/app/controllers/dashboard/milestones_controller.rb index 6e17bc212e4..ddc1a66d11d 100644 --- a/app/controllers/dashboard/milestones_controller.rb +++ b/app/controllers/dashboard/milestones_controller.rb @@ -4,12 +4,13 @@ class Dashboard::MilestonesController < Dashboard::ApplicationController include MilestoneActions before_action :projects + before_action :groups before_action :milestone, only: [:show, :merge_requests, :participants, :labels] def index respond_to do |format| format.html do - @milestone_states = GlobalMilestone.states_count(@projects) + @milestone_states = GlobalMilestone.states_count(@projects, @groups) @milestones = Kaminari.paginate_array(milestones).page(params[:page]) end format.json do diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb index a6cebabe089..d07ef43c97e 100644 --- a/app/models/global_milestone.rb +++ b/app/models/global_milestone.rb @@ -34,15 +34,31 @@ class GlobalMilestone new(title, child_milestones) end - def self.states_count(projects, group = nil) + def self.states_count(projects, groups = nil) legacy_group_milestones_count = legacy_group_milestone_states_count(projects) - group_milestones_count = group_milestones_states_count(group) + group_milestones_count = groups_milestone_state_count(groups) legacy_group_milestones_count.merge(group_milestones_count) do |k, legacy_group_milestones_count, group_milestones_count| legacy_group_milestones_count + group_milestones_count end end + def self.groups_milestone_state_count(groups) + return STATE_COUNT_HASH unless groups + return self.group_milestones_states_count(groups) unless groups.respond_to?(:each) + + milestone_states = STATE_COUNT_HASH + + groups.each do |group| + group_milestones_count = self.group_milestones_states_count(group) + milestone_states = milestone_states.merge(group_milestones_count) do |k, milestone_state, group_milestones_count| + milestone_state + group_milestones_count + end + end + + milestone_states + end + def self.group_milestones_states_count(group) return STATE_COUNT_HASH unless group diff --git a/changelogs/unreleased/rz_fix_milestone_count.yml b/changelogs/unreleased/rz_fix_milestone_count.yml new file mode 100644 index 00000000000..f85cfe48b2d --- /dev/null +++ b/changelogs/unreleased/rz_fix_milestone_count.yml @@ -0,0 +1,5 @@ +--- +title: Fixing count on Milestones +merge_request: 21446 +author: eagllus +type: fixed -- cgit v1.2.1 From 43f32c6bf966a85d6d5e1d8933a1cea67ec89ba4 Mon Sep 17 00:00:00 2001 From: Eagllus Date: Wed, 3 Oct 2018 15:19:13 +0200 Subject: Add test for the milestone count --- spec/controllers/dashboard/milestones_controller_spec.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/spec/controllers/dashboard/milestones_controller_spec.rb b/spec/controllers/dashboard/milestones_controller_spec.rb index 56047c0c8d2..278b980b6d8 100644 --- a/spec/controllers/dashboard/milestones_controller_spec.rb +++ b/spec/controllers/dashboard/milestones_controller_spec.rb @@ -45,6 +45,8 @@ describe Dashboard::MilestonesController do end describe "#index" do + render_views + it 'returns group and project milestones to which the user belongs' do get :index, format: :json @@ -53,5 +55,12 @@ describe Dashboard::MilestonesController do expect(json_response.map { |i| i["first_milestone"]["id"] }).to match_array([group_milestone.id, project_milestone.id]) expect(json_response.map { |i| i["group_name"] }.compact).to match_array(group.name) end + + it 'should contain group and project milestones to which the user belongs to' do + get :index + + expect(response.body).to include("Open\n3") + expect(response.body).to include("Closed\n0") + end end end -- cgit v1.2.1 From 54330656ac3abbe566a24ddcaa5d85ea5f7e8f6b Mon Sep 17 00:00:00 2001 From: Jason Colyer Date: Thu, 11 Oct 2018 15:41:17 -0500 Subject: Updated commands and order from MR recomendations --- doc/user/project/clusters/index.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md index 20accdb38b5..5076dfc8107 100644 --- a/doc/user/project/clusters/index.md +++ b/doc/user/project/clusters/index.md @@ -116,15 +116,15 @@ to install some [pre-defined applications](#installing-applications). If you need to determine some of the above values, the following should prove helpful: - The API URL: - - You can get this via the command: `kubectl config view|grep server` -- The CA Certificate: - - You can determine the certificate via this command: `kubectl config view --raw|awk '/certificate-authority-data/ {print $NF}'|base64 -d` + - You can get this via the command: `kubectl cluster-info | grep 'Kubernetes master' | awk '/http/ {print $NF}'` - The Token: - You will first need to determine which secret you need the token for. - To list the secrets, run the command: `kubectl get secrets` - Determine which secret you want the token for - - Run this command to get the token: `kubectl describe secrets/|grep ^token` + - Run this command to get the token: `kubectl get secret -o jsonpath="{['data']['token']}" | base64 -D` - Replace `` with the secret you want the token for +- The CA Certificate: + - You can determine the certificate via this command: `kubectl get secret -o jsonpath="{['data']['ca\.crt']}" | base64 -D` ## Security implications -- cgit v1.2.1 From 2f4afe45525f6536c808d46249d7557ea14de7e8 Mon Sep 17 00:00:00 2001 From: Paul Slaughter Date: Fri, 12 Oct 2018 16:39:13 -0500 Subject: Fix XSS in MR source branch name --- app/presenters/merge_request_presenter.rb | 12 +++--------- changelogs/unreleased/51527-xss-in-mr-source-branch.yml | 5 +++++ spec/presenters/merge_request_presenter_spec.rb | 9 +++++++++ 3 files changed, 17 insertions(+), 9 deletions(-) create mode 100644 changelogs/unreleased/51527-xss-in-mr-source-branch.yml diff --git a/app/presenters/merge_request_presenter.rb b/app/presenters/merge_request_presenter.rb index 3f565b826dd..1db6c9eff36 100644 --- a/app/presenters/merge_request_presenter.rb +++ b/app/presenters/merge_request_presenter.rb @@ -108,16 +108,10 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated namespace = source_project_namespace branch = source_branch - if source_branch_exists? - namespace = link_to(namespace, project_path(source_project)) - branch = link_to(branch, project_tree_path(source_project, source_branch)) - end + namespace_link = source_branch_exists? ? link_to(namespace, project_path(source_project)) : ERB::Util.html_escape(namespace) + branch_link = source_branch_exists? ? link_to(branch, project_tree_path(source_project, source_branch)) : ERB::Util.html_escape(branch) - if for_fork? - namespace + ":" + branch - else - branch - end + for_fork? ? "#{namespace_link}:#{branch_link}" : branch_link end def closing_issues_links diff --git a/changelogs/unreleased/51527-xss-in-mr-source-branch.yml b/changelogs/unreleased/51527-xss-in-mr-source-branch.yml new file mode 100644 index 00000000000..dae277b6413 --- /dev/null +++ b/changelogs/unreleased/51527-xss-in-mr-source-branch.yml @@ -0,0 +1,5 @@ +--- +title: Fix XSS in merge request source branch name +merge_request: +author: +type: security diff --git a/spec/presenters/merge_request_presenter_spec.rb b/spec/presenters/merge_request_presenter_spec.rb index a1b52d8692d..bafcddebbb7 100644 --- a/spec/presenters/merge_request_presenter_spec.rb +++ b/spec/presenters/merge_request_presenter_spec.rb @@ -403,6 +403,15 @@ describe MergeRequestPresenter do is_expected .to eq("#{resource.source_branch}") end + + it 'escapes html, when source_branch does not exist' do + xss_attempt = "" + + allow(resource).to receive(:source_branch) { xss_attempt } + allow(resource).to receive(:source_branch_exists?) { false } + + is_expected.to eq(ERB::Util.html_escape(xss_attempt)) + end end describe '#rebase_path' do -- cgit v1.2.1 From 074fafe9e09935cd53cf286ad00b9d53240a9413 Mon Sep 17 00:00:00 2001 From: Eagllus Date: Tue, 16 Oct 2018 14:52:08 +0200 Subject: Update code according comment recommendations --- app/controllers/dashboard/application_controller.rb | 4 ---- app/controllers/dashboard/milestones_controller.rb | 8 ++++++-- app/controllers/groups/milestones_controller.rb | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/controllers/dashboard/application_controller.rb b/app/controllers/dashboard/application_controller.rb index 1c9a5917da5..cee0753a021 100644 --- a/app/controllers/dashboard/application_controller.rb +++ b/app/controllers/dashboard/application_controller.rb @@ -12,8 +12,4 @@ class Dashboard::ApplicationController < ApplicationController def projects @projects ||= current_user.authorized_projects.sorted_by_activity.non_archived end - - def groups - @groups ||= GroupsFinder.new(current_user, state_all: true).execute - end end diff --git a/app/controllers/dashboard/milestones_controller.rb b/app/controllers/dashboard/milestones_controller.rb index ddc1a66d11d..8252a0f2511 100644 --- a/app/controllers/dashboard/milestones_controller.rb +++ b/app/controllers/dashboard/milestones_controller.rb @@ -4,13 +4,13 @@ class Dashboard::MilestonesController < Dashboard::ApplicationController include MilestoneActions before_action :projects - before_action :groups + before_action :groups, only: :index before_action :milestone, only: [:show, :merge_requests, :participants, :labels] def index respond_to do |format| format.html do - @milestone_states = GlobalMilestone.states_count(@projects, @groups) + @milestone_states = Milestone.states_count(@projects, @groups) @milestones = Kaminari.paginate_array(milestones).page(params[:page]) end format.json do @@ -43,4 +43,8 @@ class Dashboard::MilestonesController < Dashboard::ApplicationController @milestone = DashboardMilestone.build(@projects, params[:title]) render_404 unless @milestone end + + def groups + @groups ||= GroupsFinder.new(current_user, state_all: true).execute + end end diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb index a7cee426cf1..b42116b0f36 100644 --- a/app/controllers/groups/milestones_controller.rb +++ b/app/controllers/groups/milestones_controller.rb @@ -10,7 +10,7 @@ class Groups::MilestonesController < Groups::ApplicationController def index respond_to do |format| format.html do - @milestone_states = GlobalMilestone.states_count(group_projects, group) + @milestone_states = Milestone.states_count(group_projects, [group]) @milestones = Kaminari.paginate_array(milestones).page(params[:page]) end format.json do -- cgit v1.2.1 From d96585f5739f4cb83fd00fa402192a15d6958881 Mon Sep 17 00:00:00 2001 From: Eagllus Date: Tue, 16 Oct 2018 15:18:25 +0200 Subject: Moving state_count to Milestone model and related tests By moving and improving state_count the functions in GlobalMilestone are no longer used. --- app/models/global_milestone.rb | 60 ------------------------------------ app/models/milestone.rb | 16 ++++++++++ spec/models/global_milestone_spec.rb | 35 --------------------- spec/models/milestone_spec.rb | 35 +++++++++++++++++++++ 4 files changed, 51 insertions(+), 95 deletions(-) diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb index d07ef43c97e..085ffd16c6a 100644 --- a/app/models/global_milestone.rb +++ b/app/models/global_milestone.rb @@ -34,66 +34,6 @@ class GlobalMilestone new(title, child_milestones) end - def self.states_count(projects, groups = nil) - legacy_group_milestones_count = legacy_group_milestone_states_count(projects) - group_milestones_count = groups_milestone_state_count(groups) - - legacy_group_milestones_count.merge(group_milestones_count) do |k, legacy_group_milestones_count, group_milestones_count| - legacy_group_milestones_count + group_milestones_count - end - end - - def self.groups_milestone_state_count(groups) - return STATE_COUNT_HASH unless groups - return self.group_milestones_states_count(groups) unless groups.respond_to?(:each) - - milestone_states = STATE_COUNT_HASH - - groups.each do |group| - group_milestones_count = self.group_milestones_states_count(group) - milestone_states = milestone_states.merge(group_milestones_count) do |k, milestone_state, group_milestones_count| - milestone_state + group_milestones_count - end - end - - milestone_states - end - - def self.group_milestones_states_count(group) - return STATE_COUNT_HASH unless group - - params = { group_ids: [group.id], state: 'all' } - - relation = MilestonesFinder.new(params).execute # rubocop: disable CodeReuse/Finder - grouped_by_state = relation.reorder(nil).group(:state).count - - { - opened: grouped_by_state['active'] || 0, - closed: grouped_by_state['closed'] || 0, - all: grouped_by_state.values.sum - } - end - - # Counts the legacy group milestones which must be grouped by title - def self.legacy_group_milestone_states_count(projects) - return STATE_COUNT_HASH unless projects - - params = { project_ids: projects.map(&:id), state: 'all' } - - relation = MilestonesFinder.new(params).execute # rubocop: disable CodeReuse/Finder - project_milestones_by_state_and_title = relation.reorder(nil).group(:state, :title).count - - opened = count_by_state(project_milestones_by_state_and_title, 'active') - closed = count_by_state(project_milestones_by_state_and_title, 'closed') - all = project_milestones_by_state_and_title.map { |(_, title), _| title }.uniq.count - - { - opened: opened, - closed: closed, - all: all - } - end - def self.count_by_state(milestones_by_state_and_title, state) milestones_by_state_and_title.count do |(milestone_state, _), _| milestone_state == state diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 892a680f221..9f2c4efaa96 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -170,6 +170,22 @@ class Milestone < ActiveRecord::Base sorted.with_order_id_desc end + def self.states_count(projects, groups = nil) + return STATE_COUNT_HASH unless projects || groups + + counts = Milestone + .for_projects_and_groups(projects&.map(&:id), groups&.map(&:id)) + .reorder(nil) + .group(:state) + .count + + { + opened: counts['active'] || 0, + closed: counts['closed'] || 0, + all: counts.values.sum + } + end + ## # Returns the String necessary to reference this Milestone in Markdown. Group # milestones only support name references, and do not support cross-project diff --git a/spec/models/global_milestone_spec.rb b/spec/models/global_milestone_spec.rb index ab58f5c5021..b6355455c1d 100644 --- a/spec/models/global_milestone_spec.rb +++ b/spec/models/global_milestone_spec.rb @@ -92,41 +92,6 @@ describe GlobalMilestone do end end - describe '.states_count' do - context 'when the projects have milestones' do - before do - create(:closed_milestone, title: 'Active Group Milestone', project: project3) - create(:active_milestone, title: 'Active Group Milestone', project: project1) - create(:active_milestone, title: 'Active Group Milestone', project: project2) - create(:closed_milestone, title: 'Closed Group Milestone', project: project1) - create(:closed_milestone, title: 'Closed Group Milestone', project: project2) - create(:closed_milestone, title: 'Closed Group Milestone', project: project3) - end - - it 'returns the quantity of global milestones in each possible state' do - expected_count = { opened: 1, closed: 2, all: 2 } - - count = described_class.states_count(Project.all) - - expect(count).to eq(expected_count) - end - end - - context 'when the projects do not have milestones' do - before do - project1 - end - - it 'returns 0 as the quantity of global milestones in each state' do - expected_count = { opened: 0, closed: 0, all: 0 } - - count = described_class.states_count(Project.all) - - expect(count).to eq(expected_count) - end - end - end - describe '#initialize' do let(:milestone1_project1) { create(:milestone, title: "Milestone v1.2", project: project1) } let(:milestone1_project2) { create(:milestone, title: "Milestone v1.2", project: project2) } diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index 27d4e622710..b6aad769e3b 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -348,4 +348,39 @@ describe Milestone do end end end + + describe '.states_count' do + context 'when the projects have milestones' do + let(:project_1) { create(:project) } + let(:project_2) { create(:project) } + let(:project_3) { create(:project) } + + before do + create(:closed_milestone, title: 'Active Group Milestone', project: project_3) + create(:active_milestone, title: 'Active Group Milestone', project: project_1) + create(:active_milestone, title: 'Active Group Milestone', project: project_2) + create(:closed_milestone, title: 'Closed Group Milestone', project: project_1) + create(:closed_milestone, title: 'Closed Group Milestone', project: project_2) + create(:closed_milestone, title: 'Closed Group Milestone', project: project_3) + end + + it 'returns the quantity of milestones in each possible state' do + expected_count = { opened: 5, closed: 4, all: 9 } + + count = described_class.states_count(Project.all) + + expect(count).to eq(expected_count) + end + end + + context 'when the projects do not have milestones' do + it 'returns 0 as the quantity of global milestones in each state' do + expected_count = { opened: 0, closed: 0, all: 0 } + + count = described_class.states_count([project]) + + expect(count).to eq(expected_count) + end + end + end end -- cgit v1.2.1 From c42f15c03f3e4f72aa0bd5d978cc4ee1cf2a75de Mon Sep 17 00:00:00 2001 From: Eagllus Date: Thu, 18 Oct 2018 09:13:49 +0200 Subject: Update related tests --- spec/features/groups/milestone_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/features/groups/milestone_spec.rb b/spec/features/groups/milestone_spec.rb index 80df0618a6a..a7ef215df5f 100644 --- a/spec/features/groups/milestone_spec.rb +++ b/spec/features/groups/milestone_spec.rb @@ -95,9 +95,9 @@ describe 'Group milestones' do end it 'counts milestones correctly' do - expect(find('.top-area .active .badge').text).to eq("2") - expect(find('.top-area .closed .badge').text).to eq("2") - expect(find('.top-area .all .badge').text).to eq("4") + expect(find('.top-area .active .badge').text).to eq("3") + expect(find('.top-area .closed .badge').text).to eq("3") + expect(find('.top-area .all .badge').text).to eq("6") end it 'lists legacy group milestones and group milestones' do -- cgit v1.2.1 From 4f23bb9caaf626dffad1ebaf875b75921dbc7307 Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Fri, 19 Oct 2018 13:47:56 +0530 Subject: Escape issue title while template rendering to prevent XSS --- app/assets/javascripts/gfm_auto_complete.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js index 95636a9ccdd..7dd0efd622d 100644 --- a/app/assets/javascripts/gfm_auto_complete.js +++ b/app/assets/javascripts/gfm_auto_complete.js @@ -201,7 +201,7 @@ class GfmAutoComplete { displayTpl(value) { let tmpl = GfmAutoComplete.Loading.template; if (value.title != null) { - tmpl = GfmAutoComplete.Issues.template; + tmpl = GfmAutoComplete.Issues.templateFunction(value.id, value.title); } return tmpl; }, @@ -267,7 +267,7 @@ class GfmAutoComplete { displayTpl(value) { let tmpl = GfmAutoComplete.Loading.template; if (value.title != null) { - tmpl = GfmAutoComplete.Issues.template; + tmpl = GfmAutoComplete.Issues.templateFunction(value.id, value.title); } return tmpl; }, @@ -370,7 +370,7 @@ class GfmAutoComplete { displayTpl(value) { let tmpl = GfmAutoComplete.Loading.template; if (value.title != null) { - tmpl = GfmAutoComplete.Issues.template; + tmpl = GfmAutoComplete.Issues.templateFunction(value.id, value.title); } return tmpl; }, @@ -557,8 +557,9 @@ GfmAutoComplete.Labels = { }; // Issues, MergeRequests and Snippets GfmAutoComplete.Issues = { - // eslint-disable-next-line no-template-curly-in-string - template: '
  • ${id} ${title}
  • ', + templateFunction(id, title) { + return `
  • ${id} ${_.escape(title)}
  • `; + }, }; // Milestones GfmAutoComplete.Milestones = { -- cgit v1.2.1 From 1e1b250d4bf501954d55bdd7572f67734fde1fb2 Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Fri, 19 Oct 2018 15:44:17 +0530 Subject: Add spec to test HTML escaping while rendering autocomplete --- spec/features/issues/gfm_autocomplete_spec.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb index 08bf9bc7243..593dc6b6690 100644 --- a/spec/features/issues/gfm_autocomplete_spec.rb +++ b/spec/features/issues/gfm_autocomplete_spec.rb @@ -35,6 +35,21 @@ describe 'GFM autocomplete', :js do expect(page).to have_selector('.atwho-container') end + it 'opens autocomplete menu when field starts with text with item escaping HTML characters' do + alert_title = 'This will execute alert Date: Fri, 19 Oct 2018 16:04:28 +0530 Subject: Add changelog entry --- changelogs/unreleased/security-2717-fix-issue-title-xss.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/security-2717-fix-issue-title-xss.yml diff --git a/changelogs/unreleased/security-2717-fix-issue-title-xss.yml b/changelogs/unreleased/security-2717-fix-issue-title-xss.yml new file mode 100644 index 00000000000..f2e638e5ab5 --- /dev/null +++ b/changelogs/unreleased/security-2717-fix-issue-title-xss.yml @@ -0,0 +1,5 @@ +--- +title: Escape entity title while autocomplete template rendering to prevent XSS +merge_request: 2556 +author: +type: security -- cgit v1.2.1 From 473138f1151905b86527c2664d822c056f57b5cd Mon Sep 17 00:00:00 2001 From: Eagllus Date: Mon, 22 Oct 2018 13:56:08 +0200 Subject: Update related tests based on comment --- spec/models/milestone_spec.rb | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index b6aad769e3b..617f0151f33 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -353,21 +353,26 @@ describe Milestone do context 'when the projects have milestones' do let(:project_1) { create(:project) } let(:project_2) { create(:project) } - let(:project_3) { create(:project) } + let(:group_1) { create(:group) } + let(:group_2) { create(:group) } before do - create(:closed_milestone, title: 'Active Group Milestone', project: project_3) create(:active_milestone, title: 'Active Group Milestone', project: project_1) - create(:active_milestone, title: 'Active Group Milestone', project: project_2) create(:closed_milestone, title: 'Closed Group Milestone', project: project_1) + create(:active_milestone, title: 'Active Group Milestone', project: project_2) create(:closed_milestone, title: 'Closed Group Milestone', project: project_2) - create(:closed_milestone, title: 'Closed Group Milestone', project: project_3) + create(:closed_milestone, title: 'Active Group Milestone', group: group_1) + create(:closed_milestone, title: 'Closed Group Milestone', group: group_1) + create(:closed_milestone, title: 'Active Group Milestone', group: group_2) + create(:closed_milestone, title: 'Closed Group Milestone', group: group_2) end it 'returns the quantity of milestones in each possible state' do - expected_count = { opened: 5, closed: 4, all: 9 } + expected_count = { opened: 5, closed: 6, all: 11 } + + count = described_class.states_count(Project.all, Group.all) - count = described_class.states_count(Project.all) + p Project.all, Group.all expect(count).to eq(expected_count) end -- cgit v1.2.1 From e211ef4e9d3a1d1b44872b600d694afc38d4d820 Mon Sep 17 00:00:00 2001 From: blackst0ne Date: Tue, 23 Oct 2018 12:00:28 +1100 Subject: Make new merge request URL more friendly when pushing code --- app/controllers/projects/merge_requests/creations_controller.rb | 2 ++ app/services/merge_requests/get_urls_service.rb | 4 ++-- .../unreleased/blackst0ne-update-push-new-merge-request-url.yml | 5 +++++ config/routes/project.rb | 3 +-- spec/requests/api/internal_spec.rb | 6 +++--- spec/services/merge_requests/get_urls_service_spec.rb | 4 ++-- 6 files changed, 15 insertions(+), 9 deletions(-) create mode 100644 changelogs/unreleased/blackst0ne-update-push-new-merge-request-url.yml diff --git a/app/controllers/projects/merge_requests/creations_controller.rb b/app/controllers/projects/merge_requests/creations_controller.rb index 5639402a1e9..bbf662a63c8 100644 --- a/app/controllers/projects/merge_requests/creations_controller.rb +++ b/app/controllers/projects/merge_requests/creations_controller.rb @@ -89,6 +89,8 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap def build_merge_request params[:merge_request] ||= ActionController::Parameters.new(source_project: @project) + params[:merge_request][:source_branch] ||= params[:merge_request_source_branch].presence + @merge_request = ::MergeRequests::BuildService.new(project, current_user, merge_request_params.merge(diff_options: diff_options)).execute end diff --git a/app/services/merge_requests/get_urls_service.rb b/app/services/merge_requests/get_urls_service.rb index 7c88c9abb41..35a22449e34 100644 --- a/app/services/merge_requests/get_urls_service.rb +++ b/app/services/merge_requests/get_urls_service.rb @@ -50,8 +50,8 @@ module MergeRequests end def url_for_new_merge_request(branch_name) - merge_request_params = { source_branch: branch_name } - url = Gitlab::Routing.url_helpers.project_new_merge_request_url(project, merge_request: merge_request_params) + url = Gitlab::Routing.url_helpers.project_new_merge_request_url(project, branch_name) + { branch_name: branch_name, url: url, new_merge_request: true } end diff --git a/changelogs/unreleased/blackst0ne-update-push-new-merge-request-url.yml b/changelogs/unreleased/blackst0ne-update-push-new-merge-request-url.yml new file mode 100644 index 00000000000..b89ba754952 --- /dev/null +++ b/changelogs/unreleased/blackst0ne-update-push-new-merge-request-url.yml @@ -0,0 +1,5 @@ +--- +title: Make new merge request URL more friendly when pushing code +merge_request: 22526 +author: "@blackst0ne" +type: changed diff --git a/config/routes/project.rb b/config/routes/project.rb index 9cbd5b644f6..e621f5eb8dd 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -150,8 +150,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do post '', action: :create, as: nil scope path: 'new', as: :new_merge_request do - get '', action: :new - scope constraints: { format: nil }, action: :new do get :diffs, defaults: { tab: 'diffs' } get :pipelines, defaults: { tab: 'pipelines' } @@ -165,6 +163,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do get :diff_for_path get :branch_from get :branch_to + get '(:merge_request_source_branch)', action: :new end end diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index e0b5b34f9c4..5fbcc8ec55d 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -665,7 +665,7 @@ describe API::Internal do expect(json_response).to match [{ "branch_name" => "new_branch", - "url" => "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new?merge_request%5Bsource_branch%5D=new_branch", + "url" => "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new/new_branch", "new_merge_request" => true }] end @@ -686,7 +686,7 @@ describe API::Internal do expect(json_response).to match [{ "branch_name" => "new_branch", - "url" => "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new?merge_request%5Bsource_branch%5D=new_branch", + "url" => "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new/new_branch", "new_merge_request" => true }] end @@ -819,7 +819,7 @@ describe API::Internal do expect(json_response['merge_request_urls']).to match [{ "branch_name" => "new_branch", - "url" => "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new?merge_request%5Bsource_branch%5D=new_branch", + "url" => "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new/new_branch", "new_merge_request" => true }] end diff --git a/spec/services/merge_requests/get_urls_service_spec.rb b/spec/services/merge_requests/get_urls_service_spec.rb index 274624aa8bb..3e33a165e55 100644 --- a/spec/services/merge_requests/get_urls_service_spec.rb +++ b/spec/services/merge_requests/get_urls_service_spec.rb @@ -6,7 +6,7 @@ describe MergeRequests::GetUrlsService do let(:project) { create(:project, :public, :repository) } let(:service) { described_class.new(project) } let(:source_branch) { "merge-test" } - let(:new_merge_request_url) { "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new?merge_request%5Bsource_branch%5D=#{source_branch}" } + let(:new_merge_request_url) { "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new/#{source_branch}" } let(:show_merge_request_url) { "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/#{merge_request.iid}" } let(:new_branch_changes) { "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{source_branch}" } let(:deleted_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 #{Gitlab::Git::BLANK_SHA} refs/heads/#{source_branch}" } @@ -117,7 +117,7 @@ describe MergeRequests::GetUrlsService do let(:new_branch_changes) { "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/new_branch" } let(:existing_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/markdown" } let(:changes) { "#{new_branch_changes}\n#{existing_branch_changes}" } - let(:new_merge_request_url) { "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new?merge_request%5Bsource_branch%5D=new_branch" } + let(:new_merge_request_url) { "http://#{Gitlab.config.gitlab.host}/#{project.namespace.name}/#{project.path}/merge_requests/new/new_branch" } it 'returns 2 urls for both creating new and showing merge request' do result = service.execute(changes) -- cgit v1.2.1 From ee40dc3a7f1c3f11fad2fde3be17e4ddd5d87585 Mon Sep 17 00:00:00 2001 From: GitLab Release Tools Bot Date: Tue, 23 Oct 2018 04:18:20 +0000 Subject: Update CHANGELOG.md for 11.4.1 [ci skip] --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fc5b24aa39..825a3bdf517 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 11.4.1 (2018-10-23) + +### Security (2 changes) + +- Fix XSS in merge request source branch name. +- Prevent SSRF attacks in HipChat integration. + + ## 11.4.0 (2018-10-22) ### Security (9 changes) -- cgit v1.2.1 From 6bf8032735bddb5fead74ec3f4782aff008256ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Wed, 3 Oct 2018 15:22:34 +0200 Subject: Remove full exception stack trace from error --- lib/gitlab/ci/parsers/test/junit.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/gitlab/ci/parsers/test/junit.rb b/lib/gitlab/ci/parsers/test/junit.rb index 5d7d9a751d8..ed5a79d9b9b 100644 --- a/lib/gitlab/ci/parsers/test/junit.rb +++ b/lib/gitlab/ci/parsers/test/junit.rb @@ -14,10 +14,10 @@ module Gitlab test_case = create_test_case(test_case) test_suite.add_test_case(test_case) end - rescue REXML::ParseException => e - raise JunitParserError, "XML parsing failed: #{e.message}" - rescue => e - raise JunitParserError, "JUnit parsing failed: #{e.message}" + rescue REXML::ParseException + raise JunitParserError, "XML parsing failed" + rescue + raise JunitParserError, "JUnit parsing failed" end private -- cgit v1.2.1 From 782badd0a2cd00d2a9cbe591e78b30aca32e252b Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 18 Oct 2018 13:28:12 +0200 Subject: Fix content caching for non auth users --- app/controllers/application_controller.rb | 9 +++++++++ spec/controllers/application_controller_spec.rb | 26 +++++++++++++++++++++++++ spec/controllers/uploads_controller_spec.rb | 21 +++++++++++++------- spec/features/projects_spec.rb | 16 +++++++++++++++ 4 files changed, 65 insertions(+), 7 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index eeabcc0c9bb..7f4aa8244ac 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -46,6 +46,8 @@ class ApplicationController < ActionController::Base :git_import_enabled?, :gitlab_project_import_enabled?, :manifest_import_enabled? + DEFAULT_GITLAB_CACHE_CONTROL = "#{ActionDispatch::Http::Cache::Response::DEFAULT_CACHE_CONTROL}, no-store".freeze + rescue_from Encoding::CompatibilityError do |exception| log_exception(exception) render "errors/encoding", layout: "errors", status: 500 @@ -244,6 +246,13 @@ class ApplicationController < ActionController::Base headers['X-XSS-Protection'] = '1; mode=block' headers['X-UA-Compatible'] = 'IE=edge' headers['X-Content-Type-Options'] = 'nosniff' + + if current_user + # Adds `no-store` to the DEFAULT_CACHE_CONTROL, to prevent security + # concerns due to caching private data. + headers['Cache-Control'] = DEFAULT_GITLAB_CACHE_CONTROL + headers["Pragma"] = "no-cache" # HTTP 1.0 compatibility + end end def validate_user_service_ticket! diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index be3fc832008..4e91068ab88 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -792,4 +792,30 @@ describe ApplicationController do end end end + + context 'control headers' do + controller(described_class) do + def index + render json: :ok + end + end + + context 'user not logged in' do + it 'sets the default headers' do + get :index + + expect(response.headers['Cache-Control']).to be_nil + end + end + + context 'user logged in' do + it 'sets the default headers' do + sign_in(user) + + get :index + + expect(response.headers['Cache-Control']).to eq 'max-age=0, private, must-revalidate, no-store' + end + end + end end diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb index bcf289f36a9..f7af482b508 100644 --- a/spec/controllers/uploads_controller_spec.rb +++ b/spec/controllers/uploads_controller_spec.rb @@ -5,6 +5,13 @@ shared_examples 'content not cached without revalidation' do end end +shared_examples 'content not cached without revalidation and no-store' do + it 'ensures content will not be cached without revalidation' do + # Fixed in newer versions of ActivePack, it will only output a single `private`. + expect(subject['Cache-Control']).to eq('max-age=0, private, must-revalidate, private, no-store') + end +end + describe UploadsController do let!(:user) { create(:user, avatar: fixture_file_upload("spec/fixtures/dk.png", "image/png")) } @@ -177,7 +184,7 @@ describe UploadsController do expect(response).to have_gitlab_http_status(200) end - it_behaves_like 'content not cached without revalidation' do + it_behaves_like 'content not cached without revalidation and no-store' do subject do get :show, model: 'user', mounted_as: 'avatar', id: user.id, filename: 'dk.png' @@ -239,7 +246,7 @@ describe UploadsController do expect(response).to have_gitlab_http_status(200) end - it_behaves_like 'content not cached without revalidation' do + it_behaves_like 'content not cached without revalidation and no-store' do subject do get :show, model: 'project', mounted_as: 'avatar', id: project.id, filename: 'dk.png' @@ -292,7 +299,7 @@ describe UploadsController do expect(response).to have_gitlab_http_status(200) end - it_behaves_like 'content not cached without revalidation' do + it_behaves_like 'content not cached without revalidation and no-store' do subject do get :show, model: 'project', mounted_as: 'avatar', id: project.id, filename: 'dk.png' @@ -344,7 +351,7 @@ describe UploadsController do expect(response).to have_gitlab_http_status(200) end - it_behaves_like 'content not cached without revalidation' do + it_behaves_like 'content not cached without revalidation and no-store' do subject do get :show, model: 'group', mounted_as: 'avatar', id: group.id, filename: 'dk.png' @@ -388,7 +395,7 @@ describe UploadsController do expect(response).to have_gitlab_http_status(200) end - it_behaves_like 'content not cached without revalidation' do + it_behaves_like 'content not cached without revalidation and no-store' do subject do get :show, model: 'group', mounted_as: 'avatar', id: group.id, filename: 'dk.png' @@ -445,7 +452,7 @@ describe UploadsController do expect(response).to have_gitlab_http_status(200) end - it_behaves_like 'content not cached without revalidation' do + it_behaves_like 'content not cached without revalidation and no-store' do subject do get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'dk.png' @@ -498,7 +505,7 @@ describe UploadsController do expect(response).to have_gitlab_http_status(200) end - it_behaves_like 'content not cached without revalidation' do + it_behaves_like 'content not cached without revalidation and no-store' do subject do get :show, model: 'note', mounted_as: 'attachment', id: note.id, filename: 'dk.png' diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index fb766addb31..0add129dde2 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -322,6 +322,22 @@ describe 'Project' do end end + context 'content is not cached after signing out', :js do + let(:user) { create(:user, project_view: 'activity') } + let(:project) { create(:project, :repository) } + + it 'does not load activity', :js do + project.add_maintainer(user) + sign_in(user) + visit project_path(project) + sign_out(user) + + page.evaluate_script('window.history.back()') + + expect(page).not_to have_selector('.event-item') + end + end + def remove_with_confirm(button_text, confirm_with) click_button button_text fill_in 'confirm_name_input', with: confirm_with -- cgit v1.2.1 From 0b93f8cddedc3d2baea84b7694ecbd1aa3fcaa99 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Tue, 23 Oct 2018 16:19:43 +0200 Subject: Fix spec for Rails 5 --- spec/controllers/uploads_controller_spec.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb index f7af482b508..6420b70a54f 100644 --- a/spec/controllers/uploads_controller_spec.rb +++ b/spec/controllers/uploads_controller_spec.rb @@ -8,7 +8,11 @@ end shared_examples 'content not cached without revalidation and no-store' do it 'ensures content will not be cached without revalidation' do # Fixed in newer versions of ActivePack, it will only output a single `private`. - expect(subject['Cache-Control']).to eq('max-age=0, private, must-revalidate, private, no-store') + if Gitlab.rails5? + expect(subject['Cache-Control']).to eq('max-age=0, private, must-revalidate, no-store') + else + expect(subject['Cache-Control']).to eq('max-age=0, private, must-revalidate, private, no-store') + end end end -- cgit v1.2.1 From c1c1496405620d99d5943b1c4b5277b4b7d6ad63 Mon Sep 17 00:00:00 2001 From: Jan Provaznik Date: Thu, 20 Sep 2018 16:14:46 +0200 Subject: Redact unsubscribe links in issuable texts It's possible that user pastes accidentally also unsubscribe link which is included in footer of notification emails. This unsubscribe link contains personal token which attacker then use to act as the original user (e.g. for sending comments under his/her identity). --- app/models/concerns/issuable.rb | 3 + app/models/concerns/redactable.rb | 33 ++++++++ app/models/note.rb | 3 + app/models/snippet.rb | 3 + changelogs/unreleased/redact-links-dev.yml | 5 ++ .../20181014121030_enqueue_redact_links.rb | 65 +++++++++++++++ db/schema.rb | 2 +- lib/gitlab/background_migration/redact_links.rb | 62 ++++++++++++++ .../background_migration/redact_links_spec.rb | 96 ++++++++++++++++++++++ spec/migrations/enqueue_redact_links_spec.rb | 42 ++++++++++ spec/models/concerns/redactable_spec.rb | 69 ++++++++++++++++ 11 files changed, 382 insertions(+), 1 deletion(-) create mode 100644 app/models/concerns/redactable.rb create mode 100644 changelogs/unreleased/redact-links-dev.yml create mode 100644 db/post_migrate/20181014121030_enqueue_redact_links.rb create mode 100644 lib/gitlab/background_migration/redact_links.rb create mode 100644 spec/lib/gitlab/background_migration/redact_links_spec.rb create mode 100644 spec/migrations/enqueue_redact_links_spec.rb create mode 100644 spec/models/concerns/redactable_spec.rb diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 2aa52bbaeea..a808f9ad4ad 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -9,6 +9,7 @@ module Issuable extend ActiveSupport::Concern include Gitlab::SQL::Pattern + include Redactable include CacheMarkdownField include Participable include Mentionable @@ -32,6 +33,8 @@ module Issuable cache_markdown_field :title, pipeline: :single_line cache_markdown_field :description, issuable_state_filter_enabled: true + redact_field :description + belongs_to :author, class_name: "User" belongs_to :updated_by, class_name: "User" belongs_to :last_edited_by, class_name: 'User' diff --git a/app/models/concerns/redactable.rb b/app/models/concerns/redactable.rb new file mode 100644 index 00000000000..5ad96d6cc46 --- /dev/null +++ b/app/models/concerns/redactable.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +# This module searches and redacts sensitive information in +# redactable fields. Currently only unsubscribe link is redacted. +# Add following lines into your model: +# +# include Redactable +# redact_field :foo +# +module Redactable + extend ActiveSupport::Concern + + UNSUBSCRIBE_PATTERN = %r{/sent_notifications/\h{32}/unsubscribe} + + class_methods do + def redact_field(field) + before_validation do + redact_field!(field) if attribute_changed?(field) + end + end + end + + private + + def redact_field!(field) + text = public_send(field) # rubocop:disable GitlabSecurity/PublicSend + return unless text.present? + + redacted = text.gsub(UNSUBSCRIBE_PATTERN, '/sent_notifications/REDACTED/unsubscribe') + + public_send("#{field}=", redacted) # rubocop:disable GitlabSecurity/PublicSend + end +end diff --git a/app/models/note.rb b/app/models/note.rb index 95e1d3afa00..90efc4d19e2 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -10,6 +10,7 @@ class Note < ActiveRecord::Base include Awardable include Importable include FasterCacheKeys + include Redactable include CacheMarkdownField include AfterCommitQueue include ResolvableNote @@ -33,6 +34,8 @@ class Note < ActiveRecord::Base cache_markdown_field :note, pipeline: :note, issuable_state_filter_enabled: true + redact_field :note + # Aliases to make application_helper#edited_time_ago_with_tooltip helper work properly with notes. # See https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10392/diffs#note_28719102 alias_attribute :last_edited_at, :updated_at diff --git a/app/models/snippet.rb b/app/models/snippet.rb index e9533ee7c77..1c5846b4023 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -2,6 +2,7 @@ class Snippet < ActiveRecord::Base include Gitlab::VisibilityLevel + include Redactable include CacheMarkdownField include Noteable include Participable @@ -18,6 +19,8 @@ class Snippet < ActiveRecord::Base cache_markdown_field :description cache_markdown_field :content + redact_field :description + # Aliases to make application_helper#edited_time_ago_with_tooltip helper work properly with snippets. # See https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10392/diffs#note_28719102 alias_attribute :last_edited_at, :updated_at diff --git a/changelogs/unreleased/redact-links-dev.yml b/changelogs/unreleased/redact-links-dev.yml new file mode 100644 index 00000000000..338e7965465 --- /dev/null +++ b/changelogs/unreleased/redact-links-dev.yml @@ -0,0 +1,5 @@ +--- +title: Redact personal tokens in unsubscribe links. +merge_request: +author: +type: security diff --git a/db/post_migrate/20181014121030_enqueue_redact_links.rb b/db/post_migrate/20181014121030_enqueue_redact_links.rb new file mode 100644 index 00000000000..1ee4703c88a --- /dev/null +++ b/db/post_migrate/20181014121030_enqueue_redact_links.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +class EnqueueRedactLinks < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + BATCH_SIZE = 1000 + DELAY_INTERVAL = 5.minutes.to_i + MIGRATION = 'RedactLinks' + + disable_ddl_transaction! + + class Note < ActiveRecord::Base + include EachBatch + + self.table_name = 'notes' + self.inheritance_column = :_type_disabled + end + + class Issue < ActiveRecord::Base + include EachBatch + + self.table_name = 'issues' + self.inheritance_column = :_type_disabled + end + + class MergeRequest < ActiveRecord::Base + include EachBatch + + self.table_name = 'merge_requests' + self.inheritance_column = :_type_disabled + end + + class Snippet < ActiveRecord::Base + include EachBatch + + self.table_name = 'snippets' + self.inheritance_column = :_type_disabled + end + + def up + disable_statement_timeout do + schedule_migration(Note, 'note') + schedule_migration(Issue, 'description') + schedule_migration(MergeRequest, 'description') + schedule_migration(Snippet, 'description') + end + end + + def down + # nothing to do + end + + private + + def schedule_migration(model, field) + link_pattern = "%/sent_notifications/" + ("_" * 32) + "/unsubscribe%" + + model.where("#{field} like ?", link_pattern).each_batch(of: BATCH_SIZE) do |batch, index| + start_id, stop_id = batch.pluck('MIN(id)', 'MAX(id)').first + + BackgroundMigrationWorker.perform_in(index * DELAY_INTERVAL, MIGRATION, [model.name.demodulize, field, start_id, stop_id]) + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 50989960aa9..e3416aaf3e2 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: 20181013005024) do +ActiveRecord::Schema.define(version: 20181014121030) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" diff --git a/lib/gitlab/background_migration/redact_links.rb b/lib/gitlab/background_migration/redact_links.rb new file mode 100644 index 00000000000..f5d3bcdd517 --- /dev/null +++ b/lib/gitlab/background_migration/redact_links.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true +# rubocop:disable Style/Documentation + +module Gitlab + module BackgroundMigration + class RedactLinks + module Redactable + extend ActiveSupport::Concern + + def redact_field!(field) + self[field].gsub!(%r{/sent_notifications/\h{32}/unsubscribe}, '/sent_notifications/REDACTED/unsubscribe') + + if self.changed? + self.update_columns(field => self[field], + "#{field}_html" => nil) + end + end + end + + class Note < ActiveRecord::Base + include EachBatch + include Redactable + + self.table_name = 'notes' + self.inheritance_column = :_type_disabled + end + + class Issue < ActiveRecord::Base + include EachBatch + include Redactable + + self.table_name = 'issues' + self.inheritance_column = :_type_disabled + end + + class MergeRequest < ActiveRecord::Base + include EachBatch + include Redactable + + self.table_name = 'merge_requests' + self.inheritance_column = :_type_disabled + end + + class Snippet < ActiveRecord::Base + include EachBatch + include Redactable + + self.table_name = 'snippets' + self.inheritance_column = :_type_disabled + end + + def perform(model_name, field, start_id, stop_id) + link_pattern = "%/sent_notifications/" + ("_" * 32) + "/unsubscribe%" + model = "Gitlab::BackgroundMigration::RedactLinks::#{model_name}".constantize + + model.where("#{field} like ?", link_pattern).where(id: start_id..stop_id).each do |resource| + resource.redact_field!(field) + end + end + end + end +end diff --git a/spec/lib/gitlab/background_migration/redact_links_spec.rb b/spec/lib/gitlab/background_migration/redact_links_spec.rb new file mode 100644 index 00000000000..a40e68069cc --- /dev/null +++ b/spec/lib/gitlab/background_migration/redact_links_spec.rb @@ -0,0 +1,96 @@ +require 'spec_helper' + +describe Gitlab::BackgroundMigration::RedactLinks, :migration, schema: 20181014121030 do + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + let(:issues) { table(:issues) } + let(:notes) { table(:notes) } + let(:snippets) { table(:snippets) } + let(:users) { table(:users) } + let(:merge_requests) { table(:merge_requests) } + let(:namespace) { namespaces.create(name: 'gitlab', path: 'gitlab-org') } + let(:project) { projects.create(namespace_id: namespace.id, name: 'foo') } + let(:user) { users.create!(email: 'test@example.com', projects_limit: 100, username: 'test') } + + def create_merge_request(id, params) + params.merge!(id: id, + target_project_id: project.id, + target_branch: 'master', + source_project_id: project.id, + source_branch: 'mr name', + title: "mr name#{id}") + + merge_requests.create(params) + end + + def create_issue(id, params) + params.merge!(id: id, title: "issue#{id}", project_id: project.id) + + issues.create(params) + end + + def create_note(id, params) + params[:id] = id + + notes.create(params) + end + + def create_snippet(id, params) + params.merge!(id: id, author_id: user.id) + + snippets.create(params) + end + + def create_resource(model, id, params) + send("create_#{model.name.underscore}", id, params) + end + + shared_examples_for 'redactable resource' do + it 'updates only matching texts' do + matching_text = 'some text /sent_notifications/00000000000000000000000000000000/unsubscribe more text' + redacted_text = 'some text /sent_notifications/REDACTED/unsubscribe more text' + create_resource(model, 1, { field => matching_text }) + create_resource(model, 2, { field => 'not matching text' }) + create_resource(model, 3, { field => matching_text }) + create_resource(model, 4, { field => redacted_text }) + create_resource(model, 5, { field => matching_text }) + + expected = { field => 'some text /sent_notifications/REDACTED/unsubscribe more text', + "#{field}_html" => nil } + expect_any_instance_of("Gitlab::BackgroundMigration::RedactLinks::#{model}".constantize).to receive(:update_columns).with(expected).and_call_original + + subject.perform(model, field, 2, 4) + + expect(model.where(field => matching_text).pluck(:id)).to eq [1, 5] + expect(model.find(3).reload[field]).to eq redacted_text + end + end + + context 'resource is Issue' do + it_behaves_like 'redactable resource' do + let(:model) { Issue } + let(:field) { :description } + end + end + + context 'resource is Merge Request' do + it_behaves_like 'redactable resource' do + let(:model) { MergeRequest } + let(:field) { :description } + end + end + + context 'resource is Note' do + it_behaves_like 'redactable resource' do + let(:model) { Note } + let(:field) { :note } + end + end + + context 'resource is Snippet' do + it_behaves_like 'redactable resource' do + let(:model) { Snippet } + let(:field) { :description } + end + end +end diff --git a/spec/migrations/enqueue_redact_links_spec.rb b/spec/migrations/enqueue_redact_links_spec.rb new file mode 100644 index 00000000000..a5da76977b7 --- /dev/null +++ b/spec/migrations/enqueue_redact_links_spec.rb @@ -0,0 +1,42 @@ +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20181014121030_enqueue_redact_links.rb') + +describe EnqueueRedactLinks, :migration, :sidekiq do + let(:merge_requests) { table(:merge_requests) } + let(:issues) { table(:issues) } + let(:notes) { table(:notes) } + let(:projects) { table(:projects) } + let(:namespaces) { table(:namespaces) } + let(:snippets) { table(:snippets) } + let(:users) { table(:users) } + let(:user) { users.create!(email: 'test@example.com', projects_limit: 100, username: 'test') } + + before do + stub_const("#{described_class.name}::BATCH_SIZE", 1) + + text = 'some text /sent_notifications/00000000000000000000000000000000/unsubscribe more text' + group = namespaces.create!(name: 'gitlab', path: 'gitlab') + project = projects.create!(namespace_id: group.id) + + merge_requests.create!(id: 1, target_project_id: project.id, source_project_id: project.id, target_branch: 'feature', source_branch: 'master', description: text) + issues.create!(id: 1, description: text) + notes.create!(id: 1, note: text) + notes.create!(id: 2, note: text) + snippets.create!(id: 1, description: text, author_id: user.id) + end + + it 'correctly schedules background migrations' do + Sidekiq::Testing.fake! do + Timecop.freeze do + migrate! + + expect(described_class::MIGRATION).to be_scheduled_delayed_migration(5.minutes, "Note", "note", 1, 1) + expect(described_class::MIGRATION).to be_scheduled_delayed_migration(10.minutes, "Note", "note", 2, 2) + expect(described_class::MIGRATION).to be_scheduled_delayed_migration(5.minutes, "Issue", "description", 1, 1) + expect(described_class::MIGRATION).to be_scheduled_delayed_migration(5.minutes, "MergeRequest", "description", 1, 1) + expect(described_class::MIGRATION).to be_scheduled_delayed_migration(5.minutes, "Snippet", "description", 1, 1) + expect(BackgroundMigrationWorker.jobs.size).to eq 5 + end + end + end +end diff --git a/spec/models/concerns/redactable_spec.rb b/spec/models/concerns/redactable_spec.rb new file mode 100644 index 00000000000..7d320edd492 --- /dev/null +++ b/spec/models/concerns/redactable_spec.rb @@ -0,0 +1,69 @@ +require 'spec_helper' + +describe Redactable do + shared_examples 'model with redactable field' do + it 'redacts unsubscribe token' do + model[field] = 'some text /sent_notifications/00000000000000000000000000000000/unsubscribe more text' + + model.save! + + expect(model[field]).to eq 'some text /sent_notifications/REDACTED/unsubscribe more text' + end + + it 'ignores not hexadecimal tokens' do + text = 'some text /sent_notifications/token/unsubscribe more text' + model[field] = text + + model.save! + + expect(model[field]).to eq text + end + + it 'ignores not matching texts' do + text = 'some text /sent_notifications/.*/unsubscribe more text' + model[field] = text + + model.save! + + expect(model[field]).to eq text + end + + it 'redacts the field when saving the model before creating markdown cache' do + model[field] = 'some text /sent_notifications/00000000000000000000000000000000/unsubscribe more text' + + model.save! + + expected = 'some text /sent_notifications/REDACTED/unsubscribe more text' + expect(model[field]).to eq expected + expect(model["#{field}_html"]).to eq "

    #{expected}

    " + end + end + + context 'when model is an issue' do + it_behaves_like 'model with redactable field' do + let(:model) { create(:issue) } + let(:field) { :description } + end + end + + context 'when model is a merge request' do + it_behaves_like 'model with redactable field' do + let(:model) { create(:merge_request) } + let(:field) { :description } + end + end + + context 'when model is a note' do + it_behaves_like 'model with redactable field' do + let(:model) { create(:note) } + let(:field) { :note } + end + end + + context 'when model is a snippet' do + it_behaves_like 'model with redactable field' do + let(:model) { create(:snippet) } + let(:field) { :description } + end + end +end -- cgit v1.2.1 From c265fc25f7d3664d71763767d18d3b02ab83cb03 Mon Sep 17 00:00:00 2001 From: danielgruesso Date: Tue, 23 Oct 2018 19:42:01 -0400 Subject: Add first draft for runbook docs --- doc/README.md | 1 + doc/topics/runbooks/index.md | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 doc/topics/runbooks/index.md diff --git a/doc/README.md b/doc/README.md index 03371226041..f6c1a4f1d88 100644 --- a/doc/README.md +++ b/doc/README.md @@ -165,6 +165,7 @@ configuration. Then customize everything from buildpacks to CI/CD. - [Deployment of Helm, Ingress, and Prometheus on Kubernetes](user/project/clusters/index.md#installing-applications) - [Protected variables](ci/variables/README.md#protected-variables) - [Easy creation of Kubernetes clusters on GKE](user/project/clusters/index.md#adding-and-creating-a-new-gke-cluster-via-gitlab) +- [Executable Runbooks](topics/runbooks/index.md) ### Monitor diff --git a/doc/topics/runbooks/index.md b/doc/topics/runbooks/index.md new file mode 100644 index 00000000000..f55d2868f20 --- /dev/null +++ b/doc/topics/runbooks/index.md @@ -0,0 +1,36 @@ +# Runbooks + +Runbooks are a collection of documented procedures that explain how to +carry out a particular process, be it starting, stopping, debugging, +or troubleshooting a particular system. + +## Overview + +Historically, runbooks took the form of a decision tree or a detailed +step-by-step guide depending on the condition or system. + +Modern implementations have introduced the concept of an "executable +runbooks", where along with a well define process, operators can execute +code blocks or database queries against a given environment. + +## Nurtch Executable Runbooks + +> [Introduced][ce-45912] in GitLab 11.4. + +The JupyterHub app offered via GitLab’s Kubernetes integration now ships +with Nurtch’s Rubix library, providing a simple way to create DevOps +runbooks. A sample runbook is provided, showcasing common operations. + +The below video provides an overview of how this is acomplished in GitLab. + + + +## Requirements + +To create an executable runbook, you will need: + +1. **Kubernetes Cluster** - +1. **Helm Tiller** - +1. **Ingress** - +1. **JupyterHub** - \ No newline at end of file -- cgit v1.2.1 From a12d25d8a5e95ef868370e7c09e777237047366b Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 23 Oct 2018 14:59:00 -0700 Subject: Validate Wiki attachments are valid temporary files A malicious attacker could craft a request to read arbitrary files on the system. This change adds a Grape validation to ensure that the tempfile parameter delivered by the Rack multipart uploader is a Tempfile type to prevent users from being able to specify arbitrary filenames. Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/53072 --- .../unreleased/sh-fix-wiki-security-issue-53072.yml | 5 +++++ lib/api/validations/types/safe_file.rb | 15 +++++++++++++++ lib/api/wikis.rb | 4 ++-- spec/requests/api/wikis_spec.rb | 10 ++++++++++ 4 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 changelogs/unreleased/sh-fix-wiki-security-issue-53072.yml create mode 100644 lib/api/validations/types/safe_file.rb diff --git a/changelogs/unreleased/sh-fix-wiki-security-issue-53072.yml b/changelogs/unreleased/sh-fix-wiki-security-issue-53072.yml new file mode 100644 index 00000000000..ac6ab7cc3f4 --- /dev/null +++ b/changelogs/unreleased/sh-fix-wiki-security-issue-53072.yml @@ -0,0 +1,5 @@ +--- +title: Validate Wiki attachments are valid temporary files +merge_request: +author: +type: security diff --git a/lib/api/validations/types/safe_file.rb b/lib/api/validations/types/safe_file.rb new file mode 100644 index 00000000000..53b5790bfa2 --- /dev/null +++ b/lib/api/validations/types/safe_file.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +# This module overrides the Grape type validator defined in +# https://github.com/ruby-grape/grape/blob/master/lib/grape/validations/types/file.rb +module API + module Validations + module Types + class SafeFile < ::Grape::Validations::Types::File + def value_coerced?(value) + super && value[:tempfile].is_a?(Tempfile) + end + end + end + end +end diff --git a/lib/api/wikis.rb b/lib/api/wikis.rb index 6e1d4eb335f..24746f4efc6 100644 --- a/lib/api/wikis.rb +++ b/lib/api/wikis.rb @@ -6,7 +6,7 @@ module API def commit_params(attrs) { file_name: attrs[:file][:filename], - file_content: File.read(attrs[:file][:tempfile]), + file_content: attrs[:file][:tempfile].read, branch_name: attrs[:branch] } end @@ -100,7 +100,7 @@ module API success Entities::WikiAttachment end params do - requires :file, type: File, desc: 'The attachment file to be uploaded' + requires :file, type: ::API::Validations::Types::SafeFile, desc: 'The attachment file to be uploaded' optional :branch, type: String, desc: 'The name of the branch' end post ":id/wikis/attachments", requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do diff --git a/spec/requests/api/wikis_spec.rb b/spec/requests/api/wikis_spec.rb index c40d01e1a14..08bada44178 100644 --- a/spec/requests/api/wikis_spec.rb +++ b/spec/requests/api/wikis_spec.rb @@ -158,6 +158,16 @@ describe API::Wikis do expect(json_response.size).to eq(1) expect(json_response['error']).to eq('file is missing') end + + it 'responds with validation error on invalid temp file' do + payload[:file] = { tempfile: '/etc/hosts' } + + post(api(url, user), payload) + + expect(response).to have_gitlab_http_status(400) + expect(json_response.size).to eq(1) + expect(json_response['error']).to eq('file is invalid') + end end describe 'GET /projects/:id/wikis' do -- cgit v1.2.1 From 02268a85be615877b9080a8c931ad93ae73ef2e2 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Wed, 24 Oct 2018 10:13:58 +0100 Subject: Extract EE-specific lines from issues controller --- app/controllers/projects/issues_controller.rb | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index b06a6f3bb0d..308f666394c 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -9,12 +9,25 @@ class Projects::IssuesController < Projects::ApplicationController include IssuesCalendar include SpammableActions - prepend_before_action :authenticate_user!, only: [:new] + def self.authenticate_user_only_actions + %i[new] + end + + def self.issue_except_actions + %i[index calendar new create bulk_update] + end + + def self.set_issuables_index_only_actions + %i[index calendar] + end + + prepend_before_action :authenticate_user!, only: authenticate_user_only_actions before_action :whitelist_query_limiting, only: [:create, :create_merge_request, :move, :bulk_update] before_action :check_issues_available! - before_action :issue, except: [:index, :calendar, :new, :create, :bulk_update] - before_action :set_issuables_index, only: [:index, :calendar] + before_action :issue, except: issue_except_actions + + before_action :set_issuables_index, only: set_issuables_index_only_actions # Allow write(create) issue before_action :authorize_create_issue!, only: [:new, :create] -- cgit v1.2.1 From f3fabd86033f12829b817e1d430ea1cdb29ca473 Mon Sep 17 00:00:00 2001 From: Marcel Amirault Date: Wed, 24 Oct 2018 11:50:00 +0000 Subject: Update links to external sites --- doc/administration/high_availability/redis.md | 8 ++++---- doc/administration/high_availability/redis_source.md | 4 ++-- doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md | 6 +++--- doc/topics/authentication/index.md | 2 +- doc/university/README.md | 2 +- doc/university/glossary/README.md | 2 +- doc/user/project/pages/getting_started_part_three.md | 2 +- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/doc/administration/high_availability/redis.md b/doc/administration/high_availability/redis.md index b5d1ff698c6..dcee57def74 100644 --- a/doc/administration/high_availability/redis.md +++ b/doc/administration/high_availability/redis.md @@ -15,7 +15,7 @@ Omnibus GitLab packages. > **Notes:** > - Redis requires authentication for High Availability. See -> [Redis Security](http://redis.io/topics/security) documentation for more +> [Redis Security](https://redis.io/topics/security) documentation for more > information. We recommend using a combination of a Redis password and tight > firewall rules to secure your Redis service. > - You are highly encouraged to read the [Redis Sentinel][sentinel] documentation @@ -82,7 +82,7 @@ When a **Master** fails to respond, it's the application's responsibility for a new **Master**). To get a better understanding on how to correctly set up Sentinel, please read -the [Redis Sentinel documentation](http://redis.io/topics/sentinel) first, as +the [Redis Sentinel documentation](https://redis.io/topics/sentinel) first, as failing to configure it correctly can lead to data loss or can bring your whole cluster down, invalidating the failover effort. @@ -885,8 +885,8 @@ Read more on High Availability: [reconfigure]: ../restart_gitlab.md#omnibus-gitlab-reconfigure [gh-531]: https://github.com/redis/redis-rb/issues/531 [gh-534]: https://github.com/redis/redis-rb/issues/534 -[redis]: http://redis.io/ -[sentinel]: http://redis.io/topics/sentinel +[redis]: https://redis.io/ +[sentinel]: https://redis.io/topics/sentinel [omnifile]: https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-cookbooks/gitlab/libraries/gitlab_rails.rb [source]: ../../install/installation.md [ce]: https://about.gitlab.com/downloads diff --git a/doc/administration/high_availability/redis_source.md b/doc/administration/high_availability/redis_source.md index 5823c575251..2101d36d2b6 100644 --- a/doc/administration/high_availability/redis_source.md +++ b/doc/administration/high_availability/redis_source.md @@ -107,7 +107,7 @@ starting with `sentinel` prefix. Assuming that the Redis Sentinel is installed on the same instance as Redis master with IP `10.0.0.1` (some settings might overlap with the master): -1. [Install Redis Sentinel](http://redis.io/topics/sentinel) +1. [Install Redis Sentinel](https://redis.io/topics/sentinel) 1. Edit `/etc/redis/sentinel.conf`: ```conf @@ -363,7 +363,7 @@ production: port: 26379 # point to sentinel, not to redis port ``` -When in doubt, please read [Redis Sentinel documentation](http://redis.io/topics/sentinel). +When in doubt, please read [Redis Sentinel documentation](https://redis.io/topics/sentinel). [gh-531]: https://github.com/redis/redis-rb/issues/531 [downloads]: https://about.gitlab.com/downloads diff --git a/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md b/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md index b2c73caae2e..c0346d78141 100644 --- a/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md +++ b/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md @@ -398,10 +398,10 @@ other reasons][ci-reasons] to keep using GitLab CI/CD. The benefits to our teams - [Using Docker images documentation][using-docker] - [Example project: Hello GitLab CI/CD on GitLab][hello-gitlab] -[phoenix-site]: http://phoenixframework.org/ "Phoenix Framework" +[phoenix-site]: https://phoenixframework.org/ "Phoenix Framework" [phoenix-learning-guide]: https://hexdocs.pm/phoenix/learning.html "Phoenix Learning Guide" -[phoenix-install]: http://www.phoenixframework.org/docs/installation "Phoenix Installation" -[phoenix-mysql]: http://www.phoenixframework.org/docs/using-mysql "Phoenix with MySQL" +[phoenix-install]: https://hexdocs.pm/phoenix/installation.html "Phoenix Installation" +[phoenix-mysql]: https://hexdocs.pm/phoenix/ecto.html#using-mysql "Phoenix with MySQL" [elixir-site]: http://elixir-lang.org/ "Elixir" [elixir-mix]: http://elixir-lang.org/getting-started/mix-otp/introduction-to-mix.html "Introduction to mix" [elixir-docs]: http://elixir-lang.org/getting-started/introduction.html "Elixir Documentation" diff --git a/doc/topics/authentication/index.md b/doc/topics/authentication/index.md index 9546f43eea8..73301394e9f 100644 --- a/doc/topics/authentication/index.md +++ b/doc/topics/authentication/index.md @@ -43,7 +43,7 @@ This page gathers all the resources for the topic **Authentication** within GitL ## Third-party resources - [Kanboard Plugin GitLab Authentication](https://github.com/kanboard/plugin-gitlab-auth) -- [Jenkins GitLab OAuth Plugin](https://wiki.jenkins-ci.org/display/JENKINS/GitLab+OAuth+Plugin) +- [Jenkins GitLab OAuth Plugin](https://wiki.jenkins.io/display/JENKINS/GitLab+OAuth+Plugin) - [Set up Gitlab CE with Active Directory authentication](https://www.caseylabs.com/setup-gitlab-ce-with-active-directory-authentication/) - [How to customize GitLab to support OpenID authentication](http://eric.van-der-vlist.com/blog/2013/11/23/how-to-customize-gitlab-to-support-openid-authentication/) - [Openshift - Configuring Authentication and User Agent](https://docs.openshift.org/latest/install_config/configuring_authentication.html#GitLab) diff --git a/doc/university/README.md b/doc/university/README.md index 5edf92b3b09..f19b1ffd3d9 100644 --- a/doc/university/README.md +++ b/doc/university/README.md @@ -205,7 +205,7 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project ### 4. External Articles -1. [2011 WSJ article by Marc Andreessen - Software is Eating the World](http://www.wsj.com/articles/SB10001424053111903480904576512250915629460) +1. [2011 WSJ article by Marc Andreessen - Software is Eating the World](https://www.wsj.com/articles/SB10001424053111903480904576512250915629460) 1. [2014 Blog post by Chris Dixon - Software eats software development](http://cdixon.org/2014/04/13/software-eats-software-development/) 1. [2015 Venture Beat article - Actually, Open Source is Eating the World](http://venturebeat.com/2015/12/06/its-actually-open-source-software-thats-eating-the-world/) diff --git a/doc/university/glossary/README.md b/doc/university/glossary/README.md index 6ff27e495fb..6e0f71017c6 100644 --- a/doc/university/glossary/README.md +++ b/doc/university/glossary/README.md @@ -303,7 +303,7 @@ A [tool](https://docs.gitlab.com/ee/integration/external-issue-tracker.html) use ### Jenkins -An Open Source CI tool written using the Java programming language. [Jenkins](https://jenkins-ci.org/) does the same job as GitLab CI, Bamboo, and Travis CI. It is extremely popular. Related [documentation](https://docs.gitlab.com/ee/integration/jenkins.html). +An Open Source CI tool written using the Java programming language. [Jenkins](https://jenkins.io/) does the same job as GitLab CI, Bamboo, and Travis CI. It is extremely popular. Related [documentation](https://docs.gitlab.com/ee/integration/jenkins.html). ### Jira diff --git a/doc/user/project/pages/getting_started_part_three.md b/doc/user/project/pages/getting_started_part_three.md index e1eede8bbed..89b9621b8b9 100644 --- a/doc/user/project/pages/getting_started_part_three.md +++ b/doc/user/project/pages/getting_started_part_three.md @@ -212,7 +212,7 @@ security measure, necessary just for big companies, like banks and shoppings sit with financial transactions. Now we have a different picture. [According to Josh Aas](https://letsencrypt.org/2015/10/29/phishing-and-malware.html), Executive Director at [ISRG](https://en.wikipedia.org/wiki/Internet_Security_Research_Group): -> _We’ve since come to realize that HTTPS is important for almost all websites. It’s important for any website that allows people to log in with a password, any website that [tracks its users](https://www.washingtonpost.com/news/the-switch/wp/2013/12/10/nsa-uses-google-cookies-to-pinpoint-targets-for-hacking/) in any way, any website that [doesn’t want its content altered](http://arstechnica.com/tech-policy/2014/09/why-comcasts-javascript-ad-injections-threaten-security-net-neutrality/), and for any site that offers content people might not want others to know they are consuming. We’ve also learned that any site not secured by HTTPS [can be used to attack other sites](http://krebsonsecurity.com/2015/04/dont-be-fodder-for-chinas-great-cannon/)._ +> _We’ve since come to realize that HTTPS is important for almost all websites. It’s important for any website that allows people to log in with a password, any website that [tracks its users](https://www.washingtonpost.com/news/the-switch/wp/2013/12/10/nsa-uses-google-cookies-to-pinpoint-targets-for-hacking/) in any way, any website that [doesn’t want its content altered](http://arstechnica.com/tech-policy/2014/09/why-comcasts-javascript-ad-injections-threaten-security-net-neutrality/), and for any site that offers content people might not want others to know they are consuming. We’ve also learned that any site not secured by HTTPS [can be used to attack other sites](https://krebsonsecurity.com/2015/04/dont-be-fodder-for-chinas-great-cannon/)._ Therefore, the reason why certificates are so important is that they encrypt the connection between the **client** (you, me, your visitors) -- cgit v1.2.1 From 742a9c834425fef126d066b66e5780b48ff035ef Mon Sep 17 00:00:00 2001 From: Jason Colyer Date: Wed, 24 Oct 2018 07:32:07 -0500 Subject: Added Evan's suggestions --- doc/user/project/clusters/index.md | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md index 5076dfc8107..8a522a3e255 100644 --- a/doc/user/project/clusters/index.md +++ b/doc/user/project/clusters/index.md @@ -113,18 +113,13 @@ To add an existing Kubernetes cluster to your project: After a couple of minutes, your cluster will be ready to go. You can now proceed to install some [pre-defined applications](#installing-applications). -If you need to determine some of the above values, the following should prove helpful: - -- The API URL: - - You can get this via the command: `kubectl cluster-info | grep 'Kubernetes master' | awk '/http/ {print $NF}'` -- The Token: - - You will first need to determine which secret you need the token for. - - To list the secrets, run the command: `kubectl get secrets` - - Determine which secret you want the token for - - Run this command to get the token: `kubectl get secret -o jsonpath="{['data']['token']}" | base64 -D` - - Replace `` with the secret you want the token for -- The CA Certificate: - - You can determine the certificate via this command: `kubectl get secret -o jsonpath="{['data']['ca\.crt']}" | base64 -D` +To determine the: + +- API URL, run `kubectl cluster-info | grep 'Kubernetes master' | awk '/http/ {print $NF}'` +- Token: + 1. List the secrets by running `kubectl get secrets`. Note the name of the secret you need the token for. + 2. Get the token for the noted secret by running `kubectl get secret -o jsonpath="{['data']['token']}" | base64 -D` +- CA Certificate, run `kubectl get secret -o jsonpath="{['data']['ca\.crt']}" | base64 -D` ## Security implications -- cgit v1.2.1 From 9e2f585ced11a94a13487d22e2b7aef87fc912cb Mon Sep 17 00:00:00 2001 From: Olivier Gonzalez Date: Wed, 24 Oct 2018 10:19:56 -0400 Subject: Add deprecation notice for renamed licensed feature --- doc/ci/examples/container_scanning.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/ci/examples/container_scanning.md b/doc/ci/examples/container_scanning.md index 0f79f7d1b17..bc948dc6ea9 100644 --- a/doc/ci/examples/container_scanning.md +++ b/doc/ci/examples/container_scanning.md @@ -63,4 +63,10 @@ are still maintained, they have been deprecated with GitLab 11.0 and may be remo in next major release, GitLab 12.0. You are advised to update your current `.gitlab-ci.yml` configuration to reflect that change. +CAUTION: **Caution:** +Starting with GitLab 11.5, Container Scanning feature is licensed under the name `container_scanning`. +While the old name `sast_container` is still maintained, it has been deprecated with GitLab 11.5 and +may be removed in next major release, GitLab 12.0. You are advised to update your current `.gitlab-ci.yml` +configuration to reflect that change if you are using the `$GITLAB_FEATURES` environment variable. + [ee]: https://about.gitlab.com/pricing/ -- cgit v1.2.1 From 9f0dbf8a44cf8322edd5097f37b30853d78c4d79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Tue, 23 Oct 2018 15:03:10 +0200 Subject: Change CI status error message --- .../vue_merge_request_widget/components/mr_widget_pipeline.vue | 2 +- spec/features/merge_request/user_sees_merge_widget_spec.rb | 2 +- spec/features/merge_request/user_sees_pipelines_spec.rb | 2 +- spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue index fee41b239e8..718fd3507b2 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue @@ -78,7 +78,7 @@ export default { />
    - Could not connect to the CI server. Please check your settings and try again + Could not retrieve the pipeline status. For potential solutions please read the documentation.
    +import Icon from '~/vue_shared/components/icon.vue'; + +export default { + components: { + Icon, + }, + props: { + link: { + type: String, + required: true, + }, + cssClass: { + type: String, + required: true, + }, + }, +}; + + diff --git a/changelogs/unreleased/53227-empty-list.yml b/changelogs/unreleased/53227-empty-list.yml new file mode 100644 index 00000000000..8b222145299 --- /dev/null +++ b/changelogs/unreleased/53227-empty-list.yml @@ -0,0 +1,6 @@ +--- +title: Only renders dropdown for review app changes when we have a list of files to + show. Otherwise will render the regular review app button +merge_request: +author: +type: other diff --git a/spec/javascripts/vue_mr_widget/components/deployment_spec.js b/spec/javascripts/vue_mr_widget/components/deployment_spec.js index d26ffa4b4a6..059abc8b7fb 100644 --- a/spec/javascripts/vue_mr_widget/components/deployment_spec.js +++ b/spec/javascripts/vue_mr_widget/components/deployment_spec.js @@ -211,6 +211,26 @@ describe('Deployment component', () => { }); }); + describe('without changes', () => { + beforeEach(() => { + window.gon = window.gon || {}; + window.gon.features = window.gon.features || {}; + window.gon.features.ciEnvironmentsStatusChanges = true; + delete deploymentMockData.changes; + + vm = mountComponent(Component, { deployment: { ...deploymentMockData } }); + }); + + afterEach(() => { + delete window.gon.features.ciEnvironmentsStatusChanges; + }); + + it('renders the link to the review app without dropdown', () => { + expect(vm.$el.querySelector('.js-mr-wigdet-deployment-dropdown')).toBeNull(); + expect(vm.$el.querySelector('.js-deploy-url-feature-flag')).not.toBeNull(); + }); + }) + describe('deployment status', () => { describe('running', () => { beforeEach(() => { diff --git a/spec/javascripts/vue_mr_widget/components/review_app_link_spec.js b/spec/javascripts/vue_mr_widget/components/review_app_link_spec.js new file mode 100644 index 00000000000..68a65bd21c6 --- /dev/null +++ b/spec/javascripts/vue_mr_widget/components/review_app_link_spec.js @@ -0,0 +1,38 @@ +import Vue from 'vue'; +import component from '~/vue_merge_request_widget/components/review_app_link.vue'; +import mountComponent from '../../helpers/vue_mount_component_helper'; + +describe('review app link', () => { + const Component = Vue.extend(component); + const props = { + link: '/review', + cssClass: 'js-link', + }; + let vm; + let el; + + beforeEach(() => { + vm = mountComponent(Component, props); + el = vm.$el; + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('renders provided link as href attribute', () => { + expect(el.getAttribute('href')).toEqual(props.link); + }); + + it('renders provided cssClass as class attribute', () => { + expect(el.getAttribute('class')).toEqual(props.cssClass); + }); + + it('renders View app text', () => { + expect(el.textContent.trim()).toEqual('View app'); + }); + + it('renders svg icon', () => { + expect(el.querySelector('svg')).not.toBeNull(); + }); +}); -- cgit v1.2.1 From 15db499757a07a488be3caf2699134e7a7a20234 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 30 Oct 2018 11:03:11 +0000 Subject: Runs prettier on changed files --- spec/javascripts/vue_mr_widget/components/deployment_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/javascripts/vue_mr_widget/components/deployment_spec.js b/spec/javascripts/vue_mr_widget/components/deployment_spec.js index 059abc8b7fb..3d44af11153 100644 --- a/spec/javascripts/vue_mr_widget/components/deployment_spec.js +++ b/spec/javascripts/vue_mr_widget/components/deployment_spec.js @@ -229,7 +229,7 @@ describe('Deployment component', () => { expect(vm.$el.querySelector('.js-mr-wigdet-deployment-dropdown')).toBeNull(); expect(vm.$el.querySelector('.js-deploy-url-feature-flag')).not.toBeNull(); }); - }) + }); describe('deployment status', () => { describe('running', () => { -- cgit v1.2.1 From 2c198b5785a76cb26b90c19ff885b0ca42c5a4ff Mon Sep 17 00:00:00 2001 From: George Tsiolis Date: Tue, 30 Oct 2018 13:46:20 +0200 Subject: Fix typos in IDE props --- app/assets/javascripts/ide/components/ide.vue | 4 ++-- app/assets/javascripts/ide/components/ide_side_bar.vue | 4 ++-- app/assets/javascripts/ide/components/repo_commit_section.vue | 4 ++-- app/assets/javascripts/ide/stores/getters.js | 2 +- changelogs/unreleased/gt-fix-ide-typos-in-props.yml | 5 +++++ 5 files changed, 12 insertions(+), 7 deletions(-) create mode 100644 changelogs/unreleased/gt-fix-ide-typos-in-props.yml diff --git a/app/assets/javascripts/ide/components/ide.vue b/app/assets/javascripts/ide/components/ide.vue index ad6151e3bf6..0a368f6558c 100644 --- a/app/assets/javascripts/ide/components/ide.vue +++ b/app/assets/javascripts/ide/components/ide.vue @@ -43,7 +43,7 @@ export default { 'currentProjectId', 'errorMessage', ]), - ...mapGetters(['activeFile', 'hasChanges', 'someUncommitedChanges', 'isCommitModeActive']), + ...mapGetters(['activeFile', 'hasChanges', 'someUncommittedChanges', 'isCommitModeActive']), }, mounted() { window.onbeforeunload = e => this.onBeforeUnload(e); @@ -63,7 +63,7 @@ export default { onBeforeUnload(e = {}) { const returnValue = __('Are you sure you want to lose unsaved changes?'); - if (!this.someUncommitedChanges) return undefined; + if (!this.someUncommittedChanges) return undefined; Object.assign(e, { returnValue, diff --git a/app/assets/javascripts/ide/components/ide_side_bar.vue b/app/assets/javascripts/ide/components/ide_side_bar.vue index d4c430cd2f3..364ab9426e0 100644 --- a/app/assets/javascripts/ide/components/ide_side_bar.vue +++ b/app/assets/javascripts/ide/components/ide_side_bar.vue @@ -25,11 +25,11 @@ export default { }, computed: { ...mapState(['loading', 'currentActivityView', 'changedFiles', 'stagedFiles', 'lastCommitMsg']), - ...mapGetters(['currentProject', 'someUncommitedChanges']), + ...mapGetters(['currentProject', 'someUncommittedChanges']), showSuccessMessage() { return ( this.currentActivityView === activityBarViews.edit && - (this.lastCommitMsg && !this.someUncommitedChanges) + (this.lastCommitMsg && !this.someUncommittedChanges) ); }, }, diff --git a/app/assets/javascripts/ide/components/repo_commit_section.vue b/app/assets/javascripts/ide/components/repo_commit_section.vue index d3b24c5b793..5e86876c1c1 100644 --- a/app/assets/javascripts/ide/components/repo_commit_section.vue +++ b/app/assets/javascripts/ide/components/repo_commit_section.vue @@ -27,10 +27,10 @@ export default { 'unusedSeal', ]), ...mapState('commit', ['commitMessage', 'submitCommitLoading']), - ...mapGetters(['lastOpenedFile', 'hasChanges', 'someUncommitedChanges', 'activeFile']), + ...mapGetters(['lastOpenedFile', 'hasChanges', 'someUncommittedChanges', 'activeFile']), ...mapGetters('commit', ['discardDraftButtonDisabled']), showStageUnstageArea() { - return !!(this.someUncommitedChanges || this.lastCommitMsg || !this.unusedSeal); + return !!(this.someUncommittedChanges || this.lastCommitMsg || !this.unusedSeal); }, activeFileKey() { return this.activeFile ? this.activeFile.key : null; diff --git a/app/assets/javascripts/ide/stores/getters.js b/app/assets/javascripts/ide/stores/getters.js index 709748fb530..8ad85074d6b 100644 --- a/app/assets/javascripts/ide/stores/getters.js +++ b/app/assets/javascripts/ide/stores/getters.js @@ -63,7 +63,7 @@ export const isEditModeActive = state => state.currentActivityView === activityB export const isCommitModeActive = state => state.currentActivityView === activityBarViews.commit; export const isReviewModeActive = state => state.currentActivityView === activityBarViews.review; -export const someUncommitedChanges = state => +export const someUncommittedChanges = state => !!(state.changedFiles.length || state.stagedFiles.length); export const getChangesInFolder = state => path => { diff --git a/changelogs/unreleased/gt-fix-ide-typos-in-props.yml b/changelogs/unreleased/gt-fix-ide-typos-in-props.yml new file mode 100644 index 00000000000..a81b227c82f --- /dev/null +++ b/changelogs/unreleased/gt-fix-ide-typos-in-props.yml @@ -0,0 +1,5 @@ +--- +title: Fix IDE typos in props +merge_request: 22685 +author: George Tsiolis +type: other -- cgit v1.2.1 From e290ebf18ed594c9b5c3a4f6bc4a7fe87bf2376e Mon Sep 17 00:00:00 2001 From: Simon Knox Date: Tue, 30 Oct 2018 12:47:15 +0000 Subject: Backport ee 7203 sticky logs topbar --- app/assets/stylesheets/framework/mixins.scss | 94 ++++++++++++++++++++++++++++ app/assets/stylesheets/pages/builds.scss | 78 ++--------------------- app/views/layouts/header/_default.html.haml | 2 +- 3 files changed, 100 insertions(+), 74 deletions(-) diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index 1c84baf68ed..c030d75f5a4 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -250,6 +250,100 @@ max-width: 100%; } +/* +* Mixin that handles the container for the job logs (CI/CD and kubernetes pod logs) +*/ +@mixin build-trace { + background: $black; + color: $gray-darkest; + white-space: pre; + overflow-x: auto; + font-size: 12px; + border-radius: 0; + border: 0; + padding: $grid-size; + + .bash { + display: block; + } + + &.build-trace-rounded { + border-radius: $border-radius-base; + } +} + +@mixin build-trace-top-bar($height) { + height: $height; + min-height: $height; + background: $gray-light; + border: 1px solid $border-color; + color: $gl-text-color; + position: sticky; + position: -webkit-sticky; + top: $header-height; + padding: $grid-size; + + .with-performance-bar & { + top: $header-height + $performance-bar-height; + } +} + +/* +* Mixin that handles the position of the controls placed on the top bar +*/ +@mixin build-controllers($control-font-size, $flex-direction, $with-grow, $flex-grow-size) { + display: flex; + font-size: $control-font-size; + justify-content: $flex-direction; + align-items: center; + align-self: baseline; + @if $with-grow { + flex-grow: $flex-grow-size; + } + + svg { + width: 15px; + height: 15px; + display: block; + fill: $gl-text-color; + } + + .controllers-buttons { + color: $gl-text-color; + margin: 0 $grid-size; + + &:last-child { + margin-right: 0; + } + } + + .btn-scroll.animate { + .first-triangle { + animation: blinking-scroll-button 1s ease infinite; + animation-delay: 0.3s; + } + + .second-triangle { + animation: blinking-scroll-button 1s ease infinite; + animation-delay: 0.2s; + } + + .third-triangle { + animation: blinking-scroll-button 1s ease infinite; + } + + &:disabled { + opacity: 1; + } + } + + .btn-scroll:disabled, + .btn-refresh:disabled { + opacity: 0.35; + cursor: not-allowed; + } +} + @mixin build-loader-animation { position: relative; white-space: initial; diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 227f49ec595..31b258e56dd 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -50,35 +50,13 @@ position: relative; } - .build-trace { - background: $black; - color: $gray-darkest; - white-space: pre; - overflow-x: auto; - font-size: 12px; - border-radius: 0; - border: 0; - padding: $grid-size; - - .bash { - display: block; - } - &.build-trace-rounded { - border-radius: $border-radius-base; - } + .build-trace { + @include build-trace(); } .top-bar { - height: 35px; - min-height: 35px; - background: $gray-light; - border: 1px solid $border-color; - color: $gl-text-color; - position: sticky; - position: -webkit-sticky; - top: $header-height; - padding: $grid-size; + @include build-trace-top-bar(35px); &.affix { top: $header-height; @@ -116,49 +94,7 @@ } .controllers { - display: flex; - justify-content: center; - align-items: center; - - svg { - height: 15px; - display: block; - fill: $gl-text-color; - } - - .controllers-buttons { - color: $gl-text-color; - margin: 0 $grid-size; - - &:last-child { - margin-right: 0; - } - } - - .btn-scroll.animate { - .first-triangle { - animation: blinking-scroll-button 1s ease infinite; - animation-delay: 0.3s; - } - - .second-triangle { - animation: blinking-scroll-button 1s ease infinite; - animation-delay: 0.2s; - } - - .third-triangle { - animation: blinking-scroll-button 1s ease infinite; - } - - &:disabled { - opacity: 1; - } - } - - .btn-scroll:disabled { - opacity: 0.35; - cursor: not-allowed; - } + @include build-controllers(15px, center, false, 0); } } @@ -183,12 +119,8 @@ } .with-performance-bar .build-page { - .top-bar { + .top-bar.affix { top: $header-height + $performance-bar-height; - - &.affix { - top: $header-height + $performance-bar-height; - } } } diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 596fc3985b3..b7d69539eb7 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -5,7 +5,7 @@ - else - search_path_url = search_path -%header.navbar.navbar-gitlab.qa-navbar.navbar-expand-sm +%header.navbar.navbar-gitlab.qa-navbar.navbar-expand-sm.js-navbar %a.sr-only.gl-accessibility{ href: "#content-body", tabindex: "1" } Skip to content .container-fluid .header-content -- cgit v1.2.1 From c2d379b35ddffd82e671d4b7ab8b16ba6d8632e9 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 30 Oct 2018 12:59:33 +0000 Subject: Adds missing i18n to pipelines table --- .../pipelines/components/pipelines_actions.vue | 4 ++-- .../pipelines/components/pipelines_artifacts.vue | 4 ++-- .../javascripts/pipelines/components/pipelines_table.vue | 8 ++++---- .../pipelines/components/pipelines_table_row.vue | 12 +++++++----- app/assets/javascripts/pipelines/components/time_ago.vue | 5 +++-- changelogs/unreleased/fl-missing-i18n.yml | 5 +++++ locale/gitlab.pot | 15 +++++++++++++++ 7 files changed, 38 insertions(+), 15 deletions(-) create mode 100644 changelogs/unreleased/fl-missing-i18n.yml diff --git a/app/assets/javascripts/pipelines/components/pipelines_actions.vue b/app/assets/javascripts/pipelines/components/pipelines_actions.vue index d032ad73f71..a7507fb3b6f 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_actions.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_actions.vue @@ -1,7 +1,7 @@ diff --git a/changelogs/unreleased/gl-ui-modal.yml b/changelogs/unreleased/gl-ui-modal.yml new file mode 100644 index 00000000000..fbdb8260d24 --- /dev/null +++ b/changelogs/unreleased/gl-ui-modal.yml @@ -0,0 +1,5 @@ +--- +title: Remove gitlab-ui's modal from global +merge_request: +author: +type: performance -- cgit v1.2.1 From 8f36f1cad215ac1bc37bd203f69bc0c56847e85b Mon Sep 17 00:00:00 2001 From: Steve Azzopardi Date: Wed, 31 Oct 2018 09:44:22 +0100 Subject: Send continue parameter on for cancel_path In https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21595 `ContinueParams` was introducted in the `Projects::JobController#cancel` since `continue` parameter is not present for in `cancel_path` for `Projects::JobController#show.sjon` the user is being redirect to the pipeline page, where it should be redirected to the current job page instead. Add the `continue` parameter as a query string for `cancel_path`. --- app/serializers/job_entity.rb | 16 +++++++++--- spec/controllers/projects/jobs_controller_spec.rb | 32 ++++++++++++++++------- spec/features/projects/jobs_spec.rb | 18 +++++++++++++ 3 files changed, 52 insertions(+), 14 deletions(-) diff --git a/app/serializers/job_entity.rb b/app/serializers/job_entity.rb index 0b19cb16955..a0a66511b7b 100644 --- a/app/serializers/job_entity.rb +++ b/app/serializers/job_entity.rb @@ -9,7 +9,7 @@ class JobEntity < Grape::Entity expose :started?, as: :started expose :build_path do |build| - build.target_url || path_to(:namespace_project_job, build) + build_path(build) end expose :retry_path, if: -> (*) { retryable? } do |build| @@ -17,7 +17,11 @@ class JobEntity < Grape::Entity end expose :cancel_path, if: -> (*) { cancelable? } do |build| - path_to(:cancel_namespace_project_job, build) + path_to( + :cancel_namespace_project_job, + build, + { continue: { to: build_path(build) } } + ) end expose :play_path, if: -> (*) { playable? } do |build| @@ -60,8 +64,12 @@ class JobEntity < Grape::Entity build.detailed_status(request.current_user) end - def path_to(route, build) - send("#{route}_path", build.project.namespace, build.project, build) # rubocop:disable GitlabSecurity/PublicSend + def path_to(route, build, params = {}) + send("#{route}_path", build.project.namespace, build.project, build, params) # rubocop:disable GitlabSecurity/PublicSend + end + + def build_path(build) + build.target_url || path_to(:namespace_project_job, build) end def failed? diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb index 2023d4b0bd0..2efae59caae 100644 --- a/spec/controllers/projects/jobs_controller_spec.rb +++ b/spec/controllers/projects/jobs_controller_spec.rb @@ -157,6 +157,28 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do end end + context 'when job is running' do + context 'job is cancelable' do + let(:job) { create(:ci_build, :running, pipeline: pipeline) } + + it 'cancel_path is present with correct redirect' do + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('job/job_details') + expect(json_response['cancel_path']).to include(CGI.escape(json_response['build_path'])) + end + end + + context 'with web terminal' do + let(:job) { create(:ci_build, :running, :with_runner_session, pipeline: pipeline) } + + it 'exposes the terminal path' do + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('job/job_details') + expect(json_response['terminal_path']).to match(%r{/terminal}) + end + end + end + context 'when job has artifacts' do context 'with not expiry date' do let(:job) { create(:ci_build, :success, :artifacts, pipeline: pipeline) } @@ -185,16 +207,6 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do end end - context 'when job has terminal' do - let(:job) { create(:ci_build, :running, :with_runner_session, pipeline: pipeline) } - - it 'exposes the terminal path' do - expect(response).to have_gitlab_http_status(:ok) - expect(response).to match_response_schema('job/job_details') - expect(json_response['terminal_path']).to match(%r{/terminal}) - end - end - context 'when job passed with no trace' do let(:job) { create(:ci_build, :success, :artifacts, pipeline: pipeline) } diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index b3bea92e635..5cb3f7c732f 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -198,6 +198,24 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do end end + context 'when job is running', :js do + let(:job) { create(:ci_build, :running, pipeline: pipeline) } + let(:job_url) { project_job_path(project, job) } + + before do + visit job_url + wait_for_requests + end + + context 'job is cancelable' do + it 'shows cancel button' do + click_link 'Cancel' + + expect(page.current_path).to eq(job_url) + end + end + end + context "Job from other project" do before do visit project_job_path(project, job2) -- cgit v1.2.1 From 6d6767c20148204162308477d44cae27488cb11c Mon Sep 17 00:00:00 2001 From: Steve Azzopardi Date: Wed, 31 Oct 2018 11:53:39 +0100 Subject: Use array syntax instead of dig for consistency We are using hash[symbol][symbol] everywhere else in the test file. --- spec/controllers/projects/jobs_controller_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb index 2efae59caae..8eb01145ed5 100644 --- a/spec/controllers/projects/jobs_controller_spec.rb +++ b/spec/controllers/projects/jobs_controller_spec.rb @@ -152,7 +152,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do expect(response).to have_gitlab_http_status(:ok) expect(response).to match_response_schema('job/job_details') expect(json_response['raw_path']).to match(%r{jobs/\d+/raw\z}) - expect(json_response.dig('merge_request', 'path')).to match(%r{merge_requests/\d+\z}) + expect(json_response['merge_request']['path']).to match(%r{merge_requests/\d+\z}) expect(json_response['new_issue_path']).to include('/issues/new') end end -- cgit v1.2.1 From 6f11593774d89dfb3266db6a98e727210802cc58 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 31 Oct 2018 16:47:53 +0200 Subject: Fix bug when links in tabs of the labels index pages ends with .html Signed-off-by: Dmitriy Zaporozhets --- app/helpers/labels_helper.rb | 2 +- ...bs-of-the-labels-index-pages-ends-with-html.yml | 5 +++++ spec/helpers/labels_helper_spec.rb | 25 ++++++++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/52548-links-in-tabs-of-the-labels-index-pages-ends-with-html.yml diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 76ed8efe2c6..39f661b5f0c 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -143,7 +143,7 @@ module LabelsHelper def labels_filter_path(options = {}) project = @target_project || @project - format = options.delete(:format) || :html + format = options.delete(:format) if project project_labels_path(project, format, options) diff --git a/changelogs/unreleased/52548-links-in-tabs-of-the-labels-index-pages-ends-with-html.yml b/changelogs/unreleased/52548-links-in-tabs-of-the-labels-index-pages-ends-with-html.yml new file mode 100644 index 00000000000..052ef70c41a --- /dev/null +++ b/changelogs/unreleased/52548-links-in-tabs-of-the-labels-index-pages-ends-with-html.yml @@ -0,0 +1,5 @@ +--- +title: Fix bug when links in tabs of the labels index pages ends with .html +merge_request: 22716 +author: +type: fixed diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb index a2cda58e5d2..306bcdc5430 100644 --- a/spec/helpers/labels_helper_spec.rb +++ b/spec/helpers/labels_helper_spec.rb @@ -211,4 +211,29 @@ describe LabelsHelper do end end end + + describe 'labels_filter_path' do + let(:group) { create(:group) } + let(:project) { create(:project) } + + it 'links to the dashboard labels page' do + expect(labels_filter_path).to eq(dashboard_labels_path) + end + + it 'links to the group labels page' do + assign(:group, group) + + expect(helper.labels_filter_path).to eq(group_labels_path(group)) + end + + it 'links to the group labels page' do + assign(:project, project) + + expect(helper.labels_filter_path).to eq(project_labels_path(project)) + end + + it 'supports json format' do + expect(labels_filter_path(format: :json)).to eq(dashboard_labels_path(format: :json)) + end + end end -- cgit v1.2.1 From 70ee4e1b3ea9b5fa59fbe3e60733c5601804fc9e Mon Sep 17 00:00:00 2001 From: Ian Baum Date: Tue, 9 Oct 2018 13:26:43 -0500 Subject: Build a docker container storing only the frontent assets * Run as part of gitlab:assets:compile job * Will be used by omnibus-gitlab and the CNG images to avoid compiling multiple times https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22235 --- .gitlab-ci.yml | 8 ++++++++ Dockerfile.assets | 4 ++++ scripts/build_assets_image | 21 +++++++++++++++++++++ 3 files changed, 33 insertions(+) create mode 100644 Dockerfile.assets create mode 100755 scripts/build_assets_image diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index bcb0c8fbca8..b03b5c42376 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -694,7 +694,10 @@ gitlab:setup-mysql: # Frontend-related jobs gitlab:assets:compile: <<: *dedicated-no-docs-and-no-qa-pull-cache-job + image: dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.4.4-git-2.18-chrome-69.0-node-8.x-yarn-1.2-graphicsmagick-1.3.29-docker-18.06.1 dependencies: [] + services: + - docker:stable-dind variables: NODE_ENV: "production" RAILS_ENV: "production" @@ -703,18 +706,23 @@ gitlab:assets:compile: WEBPACK_REPORT: "true" # we override the max_old_space_size to prevent OOM errors NODE_OPTIONS: --max_old_space_size=3584 + DOCKER_DRIVER: overlay2 + DOCKER_HOST: tcp://docker:2375 script: - date - yarn install --frozen-lockfile --production --cache-folder .yarn-cache - date - free -m - bundle exec rake gitlab:assets:compile + - scripts/build_assets_image artifacts: name: webpack-report expire_in: 31d paths: - webpack-report/ - public/assets/ + tags: + - docker karma: <<: *dedicated-no-docs-pull-cache-job diff --git a/Dockerfile.assets b/Dockerfile.assets new file mode 100644 index 00000000000..403d16cc4ab --- /dev/null +++ b/Dockerfile.assets @@ -0,0 +1,4 @@ +# Simple container to store assets for later use +FROM scratch +ADD public/assets /assets/ +CMD /bin/true diff --git a/scripts/build_assets_image b/scripts/build_assets_image new file mode 100755 index 00000000000..218606b9a40 --- /dev/null +++ b/scripts/build_assets_image @@ -0,0 +1,21 @@ +#!/bin/bash + +# Generate the image name based on the project this is being run in +ASSETS_IMAGE_NAME=$(echo ${CI_PROJECT_NAME} | + awk '{ + split($1, p, "-"); + interim = sprintf("%s-assets-%s", p[1], p[2]); + sub(/-$/, "", interim); + print interim + }' +) + +ASSETS_IMAGE_PATH=${CI_REGISTRY}/${CI_PROJECT_PATH}/${ASSETS_IMAGE_NAME} + +mkdir -p assets_container.build/public +cp -r public/assets assets_container.build/public/ +cp Dockerfile.assets assets_container.build/ +docker build -t ${ASSETS_IMAGE_PATH}:${CI_COMMIT_REF_NAME} -f assets_container.build/Dockerfile.assets assets_container.build/ +docker login -u gitlab-ci-token -p ${CI_JOB_TOKEN} ${CI_REGISTRY} +docker push ${ASSETS_IMAGE_PATH} + -- cgit v1.2.1 From e75d5b7020f2450c4f6927bc58c29092c613a2cb Mon Sep 17 00:00:00 2001 From: Marcia Ramos Date: Wed, 31 Oct 2018 17:09:18 +0000 Subject: Docs: update GFM guide --- doc/user/markdown.md | 111 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 65 insertions(+), 46 deletions(-) diff --git a/doc/user/markdown.md b/doc/user/markdown.md index f9bdaea185b..96a509c4b21 100644 --- a/doc/user/markdown.md +++ b/doc/user/markdown.md @@ -1,17 +1,8 @@ # Markdown -## GitLab Flavored Markdown (GFM) - -> **Note:** -> Not all of the GitLab-specific extensions to Markdown that are described in -> this document currently work on our documentation website. -> -> For the best result, we encourage you to check this document out as rendered -> by GitLab: [markdown.md] - -_GitLab uses (as of 11.1) the [CommonMark Ruby Library][commonmarker] for Markdown processing of all new issues, merge requests, comments, and other Markdown content in the GitLab system. As of 11.3, wiki pages and Markdown files (`.md`) in the repositories are also processed with CommonMark. Older content in issues/comments are still processed using the [Redcarpet Ruby library][redcarpet]._ +This markdown guide is valid for GitLab's system markdown entries and files. -_Where there are significant differences, we will try to call them out in this document._ +## GitLab Flavored Markdown (GFM) GitLab uses "GitLab Flavored Markdown" (GFM). It extends the [CommonMark specification][commonmark-spec] (which is based on standard Markdown) in a few significant ways to add some useful functionality. It was inspired by [GitHub Flavored Markdown](https://help.github.com/articles/basic-writing-and-formatting-syntax/). @@ -26,11 +17,28 @@ You can use GFM in the following areas: - markdown documents inside the repository You can also use other rich text files in GitLab. You might have to install a -dependency to do so. Please see the [github-markup gem readme](https://github.com/gitlabhq/markup#markups) for more information. +dependency to do so. Please see the [`github-markup` gem readme](https://github.com/gitlabhq/markup#markups) for more information. + +> **Notes:** +> +> For the best result, we encourage you to check this document out as rendered +> by GitLab itself: [markdown.md] +> +> As of 11.1, GitLab uses the [CommonMark Ruby Library][commonmarker] for Markdown +processing of all new issues, merge requests, comments, and other Markdown content +in the GitLab system. As of 11.3, wiki pages and Markdown files (`.md`) in the +repositories are also processed with CommonMark. Older content in issues/comments +are still processed using the [Redcarpet Ruby library][redcarpet]. +> +> _Where there are significant differences, we will try to call them out in this document._ ### Transitioning to CommonMark -You may have Markdown documents in your repository that were written using some of the nuances of RedCarpet's version of Markdown. Since CommonMark uses a slightly stricter syntax, these documents may now display a little strangely since we've transitioned to CommonMark. Numbered lists with nested lists in particular can be displayed incorrectly. +You may have Markdown documents in your repository that were written using some +of the nuances of RedCarpet's version of Markdown. Since CommonMark uses a +slightly stricter syntax, these documents may now display a little strangely +since we've transitioned to CommonMark. Numbered lists with nested lists in +particular can be displayed incorrectly. It is usually quite easy to fix. In the case of a nested list such as this: @@ -50,11 +58,18 @@ simply add a space to each nested item: In the documentation below, we try to highlight some of the differences. -If you have a need to view a document using RedCarpet, you can add the token `legacy_render=1` to the end of the url, like this: +If you have a need to view a document using RedCarpet, you can add the token +`legacy_render=1` to the end of the url, like this: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md?legacy_render=1 -If you have a large volume of Markdown files, it can be tedious to determine if they will be displayed correctly or not. You can use the [diff_redcarpet_cmark](https://gitlab.com/digitalmoksha/diff_redcarpet_cmark) tool (not an officially supported product) to generate a list of files and differences between how RedCarpet and CommonMark render the files. It can give you a great idea if anything needs to be changed - many times nothing will need to changed. +If you have a large volume of Markdown files, it can be tedious to determine +if they will be displayed correctly or not. You can use the +[diff_redcarpet_cmark](https://gitlab.com/digitalmoksha/diff_redcarpet_cmark) +tool (not an officially supported product) to generate a list of files and +differences between how RedCarpet and CommonMark render the files. It can give +you a great idea if anything needs to be changed - many times nothing will need +to changed. ### Newlines @@ -63,7 +78,8 @@ https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#newline GFM honors the markdown specification in how [paragraphs and line breaks are handled][commonmark-spec]. -A paragraph is simply one or more consecutive lines of text, separated by one or more blank lines. +A paragraph is simply one or more consecutive lines of text, separated by one or +more blank lines. Line-breaks, or soft returns, are rendered if you end a line with two or more spaces: @@ -85,7 +101,9 @@ Sugar is sweet > If this is not rendered correctly, see https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#multiple-underscores-in-words -It is not reasonable to italicize just _part_ of a word, especially when you're dealing with code and names that often appear with multiple underscores. Therefore, GFM ignores multiple underscores in words: +It is not reasonable to italicize just _part_ of a word, especially when you're +dealing with code and names that often appear with multiple underscores. +Therefore, GFM ignores multiple underscores in words: perform_complicated_task @@ -124,7 +142,7 @@ https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#multili On top of standard Markdown [blockquotes](#blockquotes), which require prepending `>` to quoted lines, GFM supports multiline blockquotes fenced by >>>: -```no-highlight +``` >>> If you paste a message from somewhere else @@ -158,7 +176,7 @@ Blocks of code are either fenced by lines with three back-ticks ``` or are indented with four spaces. Only the fenced code blocks support syntax highlighting: -```no-highlight +``` Inline `code` has `back-ticks around` it. ``` @@ -248,21 +266,23 @@ However the wrapping tags cannot be mixed as such: > If this is not rendered correctly, see https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#emoji - Sometimes you want to :monkey: around a bit and add some :star2: to your :speech_balloon:. Well we have a gift for you: +``` +Sometimes you want to :monkey: around a bit and add some :star2: to your :speech_balloon:. Well we have a gift for you: - :zap: You can use emoji anywhere GFM is supported. :v: +:zap: You can use emoji anywhere GFM is supported. :v: - You can use it to point out a :bug: or warn about :speak_no_evil: patches. And if someone improves your really :snail: code, send them some :birthday:. People will :heart: you for that. +You can use it to point out a :bug: or warn about :speak_no_evil: patches. And if someone improves your really :snail: code, send them some :birthday:. People will :heart: you for that. - If you are new to this, don't be :fearful:. You can easily join the emoji :family:. All you need to do is to look up one of the supported codes. +If you are new to this, don't be :fearful:. You can easily join the emoji :family:. All you need to do is to look up one of the supported codes. - Consult the [Emoji Cheat Sheet](https://www.emojicopy.com) for a list of all supported emoji codes. :thumbsup: +Consult the [Emoji Cheat Sheet](https://www.emojicopy.com) for a list of all supported emoji codes. :thumbsup: - Most emoji are natively supported on macOS, Windows, iOS, Android and will fallback to image-based emoji where there is lack of support. +Most emoji are natively supported on macOS, Windows, iOS, Android and will fallback to image-based emoji where there is lack of support. - On Linux, you can download [Noto Color Emoji](https://www.google.com/get/noto/help/emoji/) to get full native emoji support. +On Linux, you can download [Noto Color Emoji](https://www.google.com/get/noto/help/emoji/) to get full native emoji support. - Ubuntu 18.04 (like many modern Linux distros) has this font installed by default. +Ubuntu 18.04 (like many modern Linux distros) has this font installed by default. +``` Sometimes you want to around a bit and add some to your . Well we have a gift for you: @@ -281,7 +301,6 @@ On Linux, you can download [Noto Color Emoji](https://www.google.com/get/noto/he Ubuntu 18.04 (like many modern Linux distros) has this font installed by default. - ### Special GitLab References GFM recognizes special references. @@ -343,7 +362,7 @@ https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#task-li You can add task lists to issues, merge requests and comments. To create a task list, add a specially-formatted Markdown list, like so: -```no-highlight +``` - [x] Completed task - [ ] Incomplete task - [ ] Sub-task 1 @@ -355,7 +374,7 @@ You can add task lists to issues, merge requests and comments. To create a task Tasks formatted as ordered lists are supported as well: -```no-highlight +``` 1. [x] Completed task 1. [ ] Incomplete task 1. [ ] Sub-task 1 @@ -412,7 +431,7 @@ This math is inline ![alt text](img/math_inline_sup_render_gfm.png). This is on a separate line -
    + _Be advised that KaTeX only supports a [subset][katex-subset] of LaTeX._ @@ -440,7 +459,7 @@ Examples: `HSL(540,70%,50%)` `HSLA(540,70%,50%,0.7)` -Become: +Becomes: ![alt color-inline-colorchip-render-gfm](img/color_inline_colorchip_render_gfm.png) @@ -482,7 +501,7 @@ For details see the [Mermaid official page][mermaid]. ### Headers -```no-highlight +``` # H1 ## H2 ### H3 @@ -540,7 +559,7 @@ Note that the Emoji processing happens before the header IDs are generated, so t Examples: -```no-highlight +``` Emphasis, aka italics, with *asterisks* or _underscores_. Strong emphasis, aka bold, with **asterisks** or __underscores__. @@ -550,7 +569,7 @@ Combined emphasis with **asterisks and _underscores_**. Strikethrough uses two tildes. ~~Scratch this.~~ ``` -Become: +Becomes: Emphasis, aka italics, with *asterisks* or _underscores_. @@ -564,7 +583,7 @@ Strikethrough uses two tildes. ~~Scratch this.~~ Examples: -```no-highlight +``` 1. First ordered list item 2. Another item * Unordered sub-list. @@ -577,7 +596,7 @@ Examples: + Or pluses ``` -Become: +Becomes: 1. First ordered list item 2. Another item @@ -595,7 +614,7 @@ each subsequent paragraph should be indented to the same level as the start of t Example: -```no-highlight +``` 1. First ordered list item Second paragraph of first item. @@ -616,7 +635,7 @@ the paragraph will appear outside the list, instead of properly indented under t Example: -```no-highlight +``` 1. First ordered list item Paragraph of first item. @@ -676,7 +695,7 @@ Examples: [logo]: img/markdown_logo.png -Become: +Becomes: Here's our logo: @@ -694,7 +713,7 @@ Reference-style: Examples: -```no-highlight +``` > Blockquotes are very handy in email to emulate reply text. > This line is part of the same quote. @@ -703,7 +722,7 @@ Quote break. > This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can *put* **Markdown** into a blockquote. ``` -Become: +Becomes: > Blockquotes are very handy in email to emulate reply text. > This line is part of the same quote. @@ -720,7 +739,7 @@ See the documentation for HTML::Pipeline's [SanitizationFilter](http://www.rubyd Examples: -```no-highlight +```
    Definition list
    Is something people use sometimes.
    @@ -730,7 +749,7 @@ Examples:
    ``` -Become: +Becomes:
    Definition list
    @@ -788,7 +807,7 @@ ___ Underscores ``` -Become: +Becomes: Three or more... @@ -826,7 +845,7 @@ This line is *on its own line*, because the previous line ends with two spaces. spaces. ``` -Become: +Becomes: Here's a line for us to start with. -- cgit v1.2.1 From 87444fb6eda755a0340ab2e38ad83bb8edc67f6f Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 30 Oct 2018 15:56:30 +0000 Subject: Adds pagination to pipelines table in merge request page --- .../commit/pipelines/pipelines_table.vue | 19 ++++++++++- .../javascripts/pipelines/components/pipelines.vue | 38 ---------------------- .../javascripts/pipelines/mixins/pipelines.js | 38 ++++++++++++++++++++++ .../vue_shared/mixins/ci_pagination_api_mixin.js | 9 ++++- app/controllers/projects/commit_controller.rb | 3 +- .../projects/merge_requests_controller.rb | 3 +- changelogs/unreleased/28249-add-pagination.yml | 5 +++ .../controllers/projects/commit_controller_spec.rb | 1 + .../projects/merge_requests_controller_spec.rb | 1 + .../javascripts/commit/pipelines/pipelines_spec.js | 23 +++++++++++++ 10 files changed, 98 insertions(+), 42 deletions(-) create mode 100644 changelogs/unreleased/28249-add-pagination.yml diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.vue b/app/assets/javascripts/commit/pipelines/pipelines_table.vue index a2aa3d197e3..82532539c9c 100644 --- a/app/assets/javascripts/commit/pipelines/pipelines_table.vue +++ b/app/assets/javascripts/commit/pipelines/pipelines_table.vue @@ -2,9 +2,15 @@ import PipelinesService from '../../pipelines/services/pipelines_service'; import PipelineStore from '../../pipelines/stores/pipelines_store'; import pipelinesMixin from '../../pipelines/mixins/pipelines'; +import TablePagination from '../../vue_shared/components/table_pagination.vue'; +import { getParameterByName } from '../../lib/utils/common_utils'; +import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin'; export default { - mixins: [pipelinesMixin], + components: { + TablePagination, + }, + mixins: [pipelinesMixin, CIPaginationMixin], props: { endpoint: { type: String, @@ -35,6 +41,8 @@ export default { return { store, state: store.state, + page: getParameterByName('page') || '1', + requestData: {}, }; }, @@ -48,11 +56,14 @@ export default { }, created() { this.service = new PipelinesService(this.endpoint); + this.requestData = { page: this.page }; }, methods: { successCallback(resp) { // depending of the endpoint the response can either bring a `pipelines` key or not. const pipelines = resp.data.pipelines || resp.data; + + this.store.storePagination(resp.headers); this.setCommonData(pipelines); const updatePipelinesEvent = new CustomEvent('update-pipelines-count', { @@ -97,5 +108,11 @@ export default { :view-type="viewType" /> + + diff --git a/app/assets/javascripts/pipelines/components/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines.vue index ea526cf1309..fcd8a54c9c1 100644 --- a/app/assets/javascripts/pipelines/components/pipelines.vue +++ b/app/assets/javascripts/pipelines/components/pipelines.vue @@ -155,14 +155,6 @@ export default { ); }, - shouldRenderPagination() { - return ( - !this.isLoading && - this.state.pipelines.length && - this.state.pageInfo.total > this.state.pageInfo.perPage - ); - }, - emptyTabMessage() { const { scopes } = this.$options; const possibleScopes = [scopes.pending, scopes.running, scopes.finished]; @@ -232,36 +224,6 @@ export default { this.setCommonData(resp.data.pipelines); } }, - /** - * Handles URL and query parameter changes. - * When the user uses the pagination or the tabs, - * - update URL - * - Make API request to the server with new parameters - * - Update the polling function - * - Update the internal state - */ - updateContent(parameters) { - this.updateInternalState(parameters); - - // fetch new data - return this.service - .getPipelines(this.requestData) - .then(response => { - this.isLoading = false; - this.successCallback(response); - - // restart polling - this.poll.restart({ data: this.requestData }); - }) - .catch(() => { - this.isLoading = false; - this.errorCallback(); - - // restart polling - this.poll.restart({ data: this.requestData }); - }); - }, - handleResetRunnersCache(endpoint) { this.isResetCacheButtonLoading = true; diff --git a/app/assets/javascripts/pipelines/mixins/pipelines.js b/app/assets/javascripts/pipelines/mixins/pipelines.js index 8929b397f6c..85781f548c6 100644 --- a/app/assets/javascripts/pipelines/mixins/pipelines.js +++ b/app/assets/javascripts/pipelines/mixins/pipelines.js @@ -23,6 +23,15 @@ export default { hasMadeRequest: false, }; }, + computed: { + shouldRenderPagination() { + return ( + !this.isLoading && + this.state.pipelines.length && + this.state.pageInfo.total > this.state.pageInfo.perPage + ); + }, + }, beforeMount() { this.poll = new Poll({ resource: this.service, @@ -65,6 +74,35 @@ export default { this.poll.stop(); }, methods: { + /** + * Handles URL and query parameter changes. + * When the user uses the pagination or the tabs, + * - update URL + * - Make API request to the server with new parameters + * - Update the polling function + * - Update the internal state + */ + updateContent(parameters) { + this.updateInternalState(parameters); + + // fetch new data + return this.service + .getPipelines(this.requestData) + .then(response => { + this.isLoading = false; + this.successCallback(response); + + // restart polling + this.poll.restart({ data: this.requestData }); + }) + .catch(() => { + this.isLoading = false; + this.errorCallback(); + + // restart polling + this.poll.restart({ data: this.requestData }); + }); + }, updateTable() { // Cancel ongoing request if (this.isMakingRequest) { diff --git a/app/assets/javascripts/vue_shared/mixins/ci_pagination_api_mixin.js b/app/assets/javascripts/vue_shared/mixins/ci_pagination_api_mixin.js index 67a1632269e..f9e3f3df0cc 100644 --- a/app/assets/javascripts/vue_shared/mixins/ci_pagination_api_mixin.js +++ b/app/assets/javascripts/vue_shared/mixins/ci_pagination_api_mixin.js @@ -14,7 +14,14 @@ export default { onChangePage(page) { /* URLS parameters are strings, we need to parse to match types */ - this.updateContent({ scope: this.scope, page: Number(page).toString() }); + const params = { + page: Number(page).toString(), + }; + + if (this.scope) { + params.scope = this.scope; + } + this.updateContent(params); }, updateInternalState(parameters) { diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index 00b63f55710..32fc5140366 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -43,7 +43,7 @@ class Projects::CommitController < Projects::ApplicationController # rubocop: disable CodeReuse/ActiveRecord def pipelines @pipelines = @commit.pipelines.order(id: :desc) - @pipelines = @pipelines.where(ref: params[:ref]) if params[:ref] + @pipelines = @pipelines.where(ref: params[:ref]).page(params[:page]).per(30) if params[:ref] respond_to do |format| format.html @@ -53,6 +53,7 @@ class Projects::CommitController < Projects::ApplicationController render json: { pipelines: PipelineSerializer .new(project: @project, current_user: @current_user) + .with_pagination(request, response) .represent(@pipelines), count: { all: @pipelines.count diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 27b83da4f54..4bdb857b2d9 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -84,13 +84,14 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo end def pipelines - @pipelines = @merge_request.all_pipelines + @pipelines = @merge_request.all_pipelines.page(params[:page]).per(30) Gitlab::PollingInterval.set_header(response, interval: 10_000) render json: { pipelines: PipelineSerializer .new(project: @project, current_user: @current_user) + .with_pagination(request, response) .represent(@pipelines), count: { all: @pipelines.count diff --git a/changelogs/unreleased/28249-add-pagination.yml b/changelogs/unreleased/28249-add-pagination.yml new file mode 100644 index 00000000000..df15094405a --- /dev/null +++ b/changelogs/unreleased/28249-add-pagination.yml @@ -0,0 +1,5 @@ +--- +title: Adds pagination to pipelines table in merge request page +merge_request: +author: +type: performance diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb index 9e149bc4c3c..e34fdee62d6 100644 --- a/spec/controllers/projects/commit_controller_spec.rb +++ b/spec/controllers/projects/commit_controller_spec.rb @@ -356,6 +356,7 @@ describe Projects::CommitController do expect(response).to be_ok expect(JSON.parse(response.body)['pipelines']).not_to be_empty expect(JSON.parse(response.body)['count']['all']).to eq 1 + expect(response).to include_pagination_headers end end end diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 7b0459e0325..d08cbfb134c 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -563,6 +563,7 @@ describe Projects::MergeRequestsController do it 'responds with serialized pipelines' do expect(json_response['pipelines']).not_to be_empty expect(json_response['count']['all']).to eq 1 + expect(response).to include_pagination_headers end end diff --git a/spec/javascripts/commit/pipelines/pipelines_spec.js b/spec/javascripts/commit/pipelines/pipelines_spec.js index b797cc44ae7..04c8ab44405 100644 --- a/spec/javascripts/commit/pipelines/pipelines_spec.js +++ b/spec/javascripts/commit/pipelines/pipelines_spec.js @@ -72,6 +72,29 @@ describe('Pipelines table in Commits and Merge requests', function() { done(); }, 0); }); + + describe('with pagination', () => { + it('should make an API request when using pagination', done => { + setTimeout(() => { + spyOn(vm, 'updateContent'); + + vm.store.state.pageInfo = { + page: 1, + total: 10, + perPage: 2, + nextPage: 2, + totalPages: 5, + }; + + vm.$nextTick(() => { + vm.$el.querySelector('.js-next-button a').click(); + + expect(vm.updateContent).toHaveBeenCalledWith({ page: '2' }); + done(); + }); + }); + }); + }); }); describe('pipeline badge counts', () => { -- cgit v1.2.1 From 69b9a879a12e3eeaae1394534247c97e5ad14495 Mon Sep 17 00:00:00 2001 From: James Edwards-Jones Date: Wed, 31 Oct 2018 17:28:42 +0000 Subject: TokenAuthenticatable allows non-unique tokens Avoids needing an index to repeatedly check that the token doesn't already exist when saving. --- app/models/concerns/token_authenticatable.rb | 7 +++++-- app/models/concerns/token_authenticatable_strategies/base.rb | 6 +++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/app/models/concerns/token_authenticatable.rb b/app/models/concerns/token_authenticatable.rb index 66db4bd92de..23a43aec677 100644 --- a/app/models/concerns/token_authenticatable.rb +++ b/app/models/concerns/token_authenticatable.rb @@ -10,6 +10,7 @@ module TokenAuthenticatable def add_authentication_token_field(token_field, options = {}) @token_fields = [] unless @token_fields + unique = options.fetch(:unique, true) if @token_fields.include?(token_field) raise ArgumentError.new("#{token_field} already configured via add_authentication_token_field") @@ -25,8 +26,10 @@ module TokenAuthenticatable TokenAuthenticatableStrategies::Insecure.new(self, token_field, options) end - define_singleton_method("find_by_#{token_field}") do |token| - strategy.find_token_authenticatable(token) + if unique + define_singleton_method("find_by_#{token_field}") do |token| + strategy.find_token_authenticatable(token) + end end define_method(token_field) do diff --git a/app/models/concerns/token_authenticatable_strategies/base.rb b/app/models/concerns/token_authenticatable_strategies/base.rb index f0f7107d627..413721d3e6c 100644 --- a/app/models/concerns/token_authenticatable_strategies/base.rb +++ b/app/models/concerns/token_authenticatable_strategies/base.rb @@ -43,10 +43,14 @@ module TokenAuthenticatableStrategies set_token(instance, new_token) end + def unique + @options.fetch(:unique, true) + end + def generate_available_token loop do token = generate_token - break token unless find_token_authenticatable(token, true) + break token unless unique && find_token_authenticatable(token, true) end end -- cgit v1.2.1 From 4ceabef9d2bda6d72b01fb650c61c4ba1df94bbf Mon Sep 17 00:00:00 2001 From: Lukas Eipert Date: Wed, 31 Oct 2018 12:35:33 +0100 Subject: Rename @gitlab-org/gitlab-svgs to @gitlab/svgs --- app/assets/javascripts/clusters/components/applications.vue | 2 +- app/assets/javascripts/vue_shared/components/icon.vue | 2 +- app/helpers/icons_helper.rb | 2 +- config/application.rb | 2 +- config/dependency_decisions.yml | 2 +- doc/development/fe_guide/icons.md | 4 ++-- package.json | 2 +- vendor/licenses.csv | 2 +- yarn.lock | 10 +++++----- 9 files changed, 14 insertions(+), 14 deletions(-) diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue index 6e7b5eb5526..6d7f45a35d8 100644 --- a/app/assets/javascripts/clusters/components/applications.vue +++ b/app/assets/javascripts/clusters/components/applications.vue @@ -1,6 +1,6 @@