# frozen_string_literal: true
class EncryptPlaintextAttributesOnApplicationSettings < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
PLAINTEXT_ATTRIBUTES = %w[
akismet_api_key
elasticsearch_aws_secret_access_key
recaptcha_private_key
recaptcha_site_key
slack_app_secret
slack_app_verification_token
].freeze
class ApplicationSetting < ActiveRecord::Base
self.table_name = 'application_settings'
def self.encryption_options_base_truncated_aes_256_gcm
{
mode: :per_attribute_iv,
key: Gitlab::Application.secrets.db_key_base[0..31],
algorithm: 'aes-256-gcm',
encode: true
}
end
attr_encrypted :akismet_api_key, encryption_options_base_truncated_aes_256_gcm
attr_encrypted :elasticsearch_aws_secret_access_key, encryption_options_base_truncated_aes_256_gcm
attr_encrypted :recaptcha_private_key, encryption_options_base_truncated_aes_256_gcm
attr_encrypted :recaptcha_site_key, encryption_options_base_truncated_aes_256_gcm
attr_encrypted :slack_app_secret, encryption_options_base_truncated_aes_256_gcm
attr_encrypted :slack_app_verification_token, encryption_options_base_truncated_aes_256_gcm
def akismet_api_key
decrypt(:akismet_api_key, self[:encrypted_akismet_api_key]) || self[:akismet_api_key]
end
def elasticsearch_aws_secret_access_key
decrypt(:elasticsearch_aws_secret_access_key, self[:encrypted_elasticsearch_aws_secret_access_key]) || self[:elasticsearch_aws_secret_access_key]
end
def recaptcha_private_key
decrypt(:recaptcha_private_key, self[:encrypted_recaptcha_private_key]) || self[:recaptcha_private_key]
end
def recaptcha_site_key
decrypt(:recaptcha_site_key, self[:encrypted_recaptcha_site_key]) || self[:recaptcha_site_key]
end
def slack_app_secret
decrypt(:slack_app_secret, self[:encrypted_slack_app_secret]) || self[:slack_app_secret]
end
def slack_app_verification_token
decrypt(:slack_app_verification_token, self[:encrypted_slack_app_verification_token]) || self[:slack_app_verification_token]
end
end
def up
ApplicationSetting.find_each do |application_setting|
# We are using the setter from attr_encrypted gem to encrypt the data.
# The gem updates the two columns needed to decrypt the value:
# - "encrypted_#{plaintext_attribute}"
# - "encrypted_#{plaintext_attribute}_iv"
application_setting.assign_attributes(
PLAINTEXT_ATTRIBUTES.each_with_object({}) do |plaintext_attribute, attributes|
attributes[plaintext_attribute] = application_setting.send(plaintext_attribute)
end
)
application_setting.save(validate: false)
end
end
def down
ApplicationSetting.find_each do |application_setting|
application_setting.update_columns(
PLAINTEXT_ATTRIBUTES.each_with_object({}) do |plaintext_attribute, attributes|
attributes[plaintext_attribute] = application_setting.send(plaintext_attribute)
attributes["encrypted_#{plaintext_attribute}"] = nil
attributes["encrypted_#{plaintext_attribute}_iv"] = nil
end
)
end
end
end
# frozen_string_literal: true
class RemovePlaintextColumnsFromApplicationSettings < ActiveRecord::Migration[5.2]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
PLAINTEXT_ATTRIBUTES = %w[
akismet_api_key
elasticsearch_aws_secret_access_key
recaptcha_private_key
recaptcha_site_key
slack_app_secret
slack_app_verification_token
].freeze
def up
PLAINTEXT_ATTRIBUTES.each do |plaintext_attribute|
remove_column :application_settings, plaintext_attribute
end
end
def down
PLAINTEXT_ATTRIBUTES.each do |plaintext_attribute|
add_column :application_settings, plaintext_attribute, :text
end
end
end
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2019_11_15_091425) do ActiveRecord::Schema.define(version: 2019_11_22_135327) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm" enable_extension "pg_trgm"
...@@ -180,11 +180,8 @@ ActiveRecord::Schema.define(version: 2019_11_15_091425) do ...@@ -180,11 +180,8 @@ ActiveRecord::Schema.define(version: 2019_11_15_091425) do
t.integer "metrics_timeout", default: 10 t.integer "metrics_timeout", default: 10
t.integer "metrics_method_call_threshold", default: 10 t.integer "metrics_method_call_threshold", default: 10
t.boolean "recaptcha_enabled", default: false t.boolean "recaptcha_enabled", default: false
t.string "recaptcha_site_key"
t.string "recaptcha_private_key"
t.integer "metrics_port", default: 8089 t.integer "metrics_port", default: 8089
t.boolean "akismet_enabled", default: false t.boolean "akismet_enabled", default: false
t.string "akismet_api_key"
t.integer "metrics_sample_interval", default: 15 t.integer "metrics_sample_interval", default: 15
t.boolean "email_author_in_body", default: false t.boolean "email_author_in_body", default: false
t.integer "default_group_visibility" t.integer "default_group_visibility"
...@@ -231,7 +228,6 @@ ActiveRecord::Schema.define(version: 2019_11_15_091425) do ...@@ -231,7 +228,6 @@ ActiveRecord::Schema.define(version: 2019_11_15_091425) do
t.boolean "elasticsearch_aws", default: false, null: false t.boolean "elasticsearch_aws", default: false, null: false
t.string "elasticsearch_aws_region", default: "us-east-1" t.string "elasticsearch_aws_region", default: "us-east-1"
t.string "elasticsearch_aws_access_key" t.string "elasticsearch_aws_access_key"
t.string "elasticsearch_aws_secret_access_key"
t.integer "geo_status_timeout", default: 10 t.integer "geo_status_timeout", default: 10
t.string "uuid" t.string "uuid"
t.decimal "polling_interval_multiplier", default: "1.0", null: false t.decimal "polling_interval_multiplier", default: "1.0", null: false
...@@ -247,8 +243,6 @@ ActiveRecord::Schema.define(version: 2019_11_15_091425) do ...@@ -247,8 +243,6 @@ ActiveRecord::Schema.define(version: 2019_11_15_091425) do
t.string "help_page_support_url" t.string "help_page_support_url"
t.boolean "slack_app_enabled", default: false t.boolean "slack_app_enabled", default: false
t.string "slack_app_id" t.string "slack_app_id"
t.string "slack_app_secret"
t.string "slack_app_verification_token"
t.integer "performance_bar_allowed_group_id" t.integer "performance_bar_allowed_group_id"
t.boolean "allow_group_owners_to_manage_ldap", default: true, null: false t.boolean "allow_group_owners_to_manage_ldap", default: true, null: false
t.boolean "hashed_storage_enabled", default: true, null: false t.boolean "hashed_storage_enabled", default: true, null: false
...@@ -355,6 +349,18 @@ ActiveRecord::Schema.define(version: 2019_11_15_091425) do ...@@ -355,6 +349,18 @@ ActiveRecord::Schema.define(version: 2019_11_15_091425) do
t.boolean "sourcegraph_enabled", default: false, null: false t.boolean "sourcegraph_enabled", default: false, null: false
t.string "sourcegraph_url", limit: 255 t.string "sourcegraph_url", limit: 255
t.boolean "sourcegraph_public_only", default: true, null: false t.boolean "sourcegraph_public_only", default: true, null: false
t.text "encrypted_akismet_api_key"
t.string "encrypted_akismet_api_key_iv", limit: 255
t.text "encrypted_elasticsearch_aws_secret_access_key"
t.string "encrypted_elasticsearch_aws_secret_access_key_iv", limit: 255
t.text "encrypted_recaptcha_private_key"
t.string "encrypted_recaptcha_private_key_iv", limit: 255
t.text "encrypted_recaptcha_site_key"
t.string "encrypted_recaptcha_site_key_iv", limit: 255
t.text "encrypted_slack_app_secret"
t.string "encrypted_slack_app_secret_iv", limit: 255
t.text "encrypted_slack_app_verification_token"
t.string "encrypted_slack_app_verification_token_iv", limit: 255
t.index ["custom_project_templates_group_id"], name: "index_application_settings_on_custom_project_templates_group_id" t.index ["custom_project_templates_group_id"], name: "index_application_settings_on_custom_project_templates_group_id"
t.index ["file_template_project_id"], name: "index_application_settings_on_file_template_project_id" t.index ["file_template_project_id"], name: "index_application_settings_on_file_template_project_id"
t.index ["instance_administration_project_id"], name: "index_applicationsettings_on_instance_administration_project_id" t.index ["instance_administration_project_id"], name: "index_applicationsettings_on_instance_administration_project_id"
... ...
......
...@@ -283,7 +283,9 @@ module API ...@@ -283,7 +283,9 @@ module API
expose :shared_runners_enabled expose :shared_runners_enabled
expose :lfs_enabled?, as: :lfs_enabled expose :lfs_enabled?, as: :lfs_enabled
expose :creator_id expose :creator_id
expose :forked_from_project, using: Entities::BasicProjectDetails, if: lambda { |project, options| project.forked? } expose :forked_from_project, using: Entities::BasicProjectDetails, if: ->(project, options) do
project.forked? && Ability.allowed?(options[:current_user], :read_project, project.forked_from_project)
end
expose :import_status expose :import_status
expose :import_error, if: lambda { |_project, options| options[:user_can_admin_project] } do |project| expose :import_error, if: lambda { |_project, options| options[:user_can_admin_project] } do |project|
... ...
......
...@@ -89,7 +89,7 @@ module Banzai ...@@ -89,7 +89,7 @@ module Banzai
parent_from_ref = from_ref_cached(project_path) parent_from_ref = from_ref_cached(project_path)
reference = parent_from_ref.to_human_reference(parent) reference = parent_from_ref.to_human_reference(parent)
label_suffix = " <i>in #{reference}</i>" if reference.present? label_suffix = " <i>in #{ERB::Util.html_escape(reference)}</i>" if reference.present?
end end
presenter = object.present(issuable_subject: parent) presenter = object.present(issuable_subject: parent)
... ...
......
...@@ -172,7 +172,7 @@ module Banzai ...@@ -172,7 +172,7 @@ module Banzai
end end
def cleaned_file_path(uri) def cleaned_file_path(uri)
Addressable::URI.unescape(uri.path).delete("\0").chomp("/") Addressable::URI.unescape(uri.path).scrub.delete("\0").chomp("/")
end end
def relative_file_path(uri) def relative_file_path(uri)
... ...
......
...@@ -11,13 +11,29 @@ module Gitlab ...@@ -11,13 +11,29 @@ module Gitlab
end end
def data def data
[serialize(Summary::Issue.new(project: @project, from: @from, to: @to, current_user: @current_user)), summary = [issue_stats]
serialize(Summary::Commit.new(project: @project, from: @from, to: @to)), summary << commit_stats if user_has_sufficient_access?
serialize(Summary::Deploy.new(project: @project, from: @from, to: @to))] summary << deploy_stats
end end
private private
def issue_stats
serialize(Summary::Issue.new(project: @project, from: @from, to: @to, current_user: @current_user))
end
def commit_stats
serialize(Summary::Commit.new(project: @project, from: @from, to: @to))
end
def deploy_stats
serialize(Summary::Deploy.new(project: @project, from: @from, to: @to))
end
def user_has_sufficient_access?
@project.team.member?(@current_user, Gitlab::Access::REPORTER)
end
def serialize(summary_object) def serialize(summary_object)
AnalyticsSummarySerializer.new.represent(summary_object) AnalyticsSummarySerializer.new.represent(summary_object)
end end
... ...
......
...@@ -4,7 +4,7 @@ module Gitlab ...@@ -4,7 +4,7 @@ module Gitlab
module ImportExport module ImportExport
class AttributeCleaner class AttributeCleaner
ALLOWED_REFERENCES = RelationFactory::PROJECT_REFERENCES + RelationFactory::USER_REFERENCES + %w[group_id commit_id] ALLOWED_REFERENCES = RelationFactory::PROJECT_REFERENCES + RelationFactory::USER_REFERENCES + %w[group_id commit_id]
PROHIBITED_REFERENCES = Regexp.union(/\Acached_markdown_version\Z/, /_id\Z/, /_html\Z/).freeze PROHIBITED_REFERENCES = Regexp.union(/\Acached_markdown_version\Z/, /_id\Z/, /_ids\Z/, /_html\Z/).freeze
def self.clean(*args) def self.clean(*args)
new(*args).clean new(*args).clean
... ...
......
...@@ -14,7 +14,6 @@ module MicrosoftTeams ...@@ -14,7 +14,6 @@ module MicrosoftTeams
response = Gitlab::HTTP.post( response = Gitlab::HTTP.post(
@webhook.to_str, @webhook.to_str,
headers: @header, headers: @header,
allow_local_requests: true,
body: body(options) body: body(options)
) )
... ...
......
...@@ -7635,7 +7635,7 @@ msgstr "" ...@@ -7635,7 +7635,7 @@ msgstr ""
msgid "ForkedFromProjectPath|Forked from" msgid "ForkedFromProjectPath|Forked from"
msgstr "" msgstr ""
msgid "ForkedFromProjectPath|Forked from %{project_name} (deleted)" msgid "ForkedFromProjectPath|Forked from an inaccessible project"
msgstr "" msgstr ""
msgid "Forking in progress" msgid "Forking in progress"
...@@ -17779,7 +17779,10 @@ msgstr "" ...@@ -17779,7 +17779,10 @@ msgstr ""
msgid "This will redirect you to an external sign in page." msgid "This will redirect you to an external sign in page."
msgstr "" msgstr ""
msgid "This will remove the fork relationship to source project" msgid "This will remove the fork relationship between this project and %{fork_source}."
msgstr ""
msgid "This will remove the fork relationship between this project and other projects in the fork network."
msgstr "" msgstr ""
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here." msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
...@@ -19701,7 +19704,7 @@ msgstr "" ...@@ -19701,7 +19704,7 @@ msgstr ""
msgid "You are going to remove %{project_full_name}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?" msgid "You are going to remove %{project_full_name}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?"
msgstr "" msgstr ""
msgid "You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?" msgid "You are going to remove the fork relationship from %{project_full_name}. Are you ABSOLUTELY sure?"
msgstr "" msgstr ""
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?" msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
... ...
......
...@@ -1435,6 +1435,43 @@ describe Projects::IssuesController do ...@@ -1435,6 +1435,43 @@ describe Projects::IssuesController do
expect { get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issue.iid } }.not_to exceed_query_limit(control_count) expect { get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issue.iid } }.not_to exceed_query_limit(control_count)
end end
end end
context 'private project' do
let!(:branch_note) { create(:discussion_note_on_issue, :system, noteable: issue, project: project) }
let!(:commit_note) { create(:discussion_note_on_issue, :system, noteable: issue, project: project) }
let!(:branch_note_meta) { create(:system_note_metadata, note: branch_note, action: "branch") }
let!(:commit_note_meta) { create(:system_note_metadata, note: commit_note, action: "commit") }
context 'user is allowed access' do
before do
project.add_user(user, :maintainer)
end
it 'displays all available notes' do
get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issue.iid }
expect(json_response.length).to eq(3)
end
end
context 'user is a guest' do
let(:json_response_note_ids) do
json_response.collect { |discussion| discussion["notes"] }.flatten
.collect { |note| note["id"].to_i }
end
before do
project.add_guest(user)
end
it 'does not display notes w/type listed in TYPES_RESTRICTED_BY_ACCESS_LEVEL' do
get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issue.iid }
expect(json_response.length).to eq(2)
expect(json_response_note_ids).not_to include(branch_note.id)
end
end
end
end end
end end
... ...
......
...@@ -112,6 +112,10 @@ describe 'Cycle Analytics', :js do ...@@ -112,6 +112,10 @@ describe 'Cycle Analytics', :js do
wait_for_requests wait_for_requests
end end
it 'does not show the commit stats' do
expect(page).to have_no_selector(:xpath, commits_counter_selector)
end
it 'needs permissions to see restricted stages' do it 'needs permissions to see restricted stages' do
expect(find('.stage-events')).to have_content(issue.title) expect(find('.stage-events')).to have_content(issue.title)
...@@ -127,8 +131,12 @@ describe 'Cycle Analytics', :js do ...@@ -127,8 +131,12 @@ describe 'Cycle Analytics', :js do
find(:xpath, "//p[contains(text(),'New Issue')]/preceding-sibling::h3") find(:xpath, "//p[contains(text(),'New Issue')]/preceding-sibling::h3")
end end
def commits_counter_selector
"//p[contains(text(),'Commits')]/preceding-sibling::h3"
end
def commits_counter def commits_counter
find(:xpath, "//p[contains(text(),'Commits')]/preceding-sibling::h3") find(:xpath, commits_counter_selector)
end end
def deploys_counter def deploys_counter
... ...
......
...@@ -202,13 +202,13 @@ describe 'Project' do ...@@ -202,13 +202,13 @@ describe 'Project' do
expect(page).not_to have_content('Forked from') expect(page).not_to have_content('Forked from')
end end
it 'shows the name of the deleted project when the source was deleted', :sidekiq_might_not_need_inline do it 'does not show the name of the deleted project when the source was deleted', :sidekiq_might_not_need_inline do
forked_project forked_project
Projects::DestroyService.new(base_project, base_project.owner).execute Projects::DestroyService.new(base_project, base_project.owner).execute
visit project_path(forked_project) visit project_path(forked_project)
expect(page).to have_content("Forked from #{base_project.full_name} (deleted)") expect(page).to have_content('Forked from an inaccessible project')
end end
context 'a fork of a fork' do context 'a fork of a fork' do
... ...
......
# frozen_string_literal: true
require 'spec_helper'
describe 'HangoutsChat::Sender Gitlab::HTTP override' do
describe 'HangoutsChat::Sender::HTTP#post' do
it 'calls Gitlab::HTTP.post with default protection settings' do
webhook_url = 'https://example.gitlab.com'
payload = { key: 'value' }
http = HangoutsChat::Sender::HTTP.new(webhook_url)
mock_response = double(response: 'the response')
expect(Gitlab::HTTP).to receive(:post)
.with(
URI.parse(webhook_url),
body: payload.to_json,
headers: { 'Content-Type' => 'application/json' },
parse: nil
)
.and_return(mock_response)
expect(http.post(payload)).to eq(mock_response.response)
end
it_behaves_like 'a request using Gitlab::UrlBlocker' do
let(:http_method) { :post }
let(:url_blocked_error_class) { Gitlab::HTTP::BlockedUrlError }
def make_request(uri)
HangoutsChat::Sender::HTTP.new(uri).post({})
end
end
end
end
...@@ -3,147 +3,12 @@ ...@@ -3,147 +3,12 @@
require 'spec_helper' require 'spec_helper'
describe 'rest-client dns rebinding protection' do describe 'rest-client dns rebinding protection' do
include StubRequests it_behaves_like 'a request using Gitlab::UrlBlocker' do
let(:http_method) { :get }
let(:url_blocked_error_class) { ArgumentError }
context 'when local requests are not allowed' do def make_request(uri)
it 'allows an external request with http' do RestClient.get(uri)
request_stub = stub_full_request('http://example.com', ip_address: '93.184.216.34')
RestClient.get('http://example.com/')
expect(request_stub).to have_been_requested
end
it 'allows an external request with https' do
request_stub = stub_full_request('https://example.com', ip_address: '93.184.216.34')
RestClient.get('https://example.com/')
expect(request_stub).to have_been_requested
end
it 'raises error when it is a request that resolves to a local address' do
stub_full_request('https://example.com', ip_address: '172.16.0.0')
expect { RestClient.get('https://example.com') }
.to raise_error(ArgumentError,
"URL 'https://example.com' is blocked: Requests to the local network are not allowed")
end
it 'raises error when it is a request that resolves to a localhost address' do
stub_full_request('https://example.com', ip_address: '127.0.0.1')
expect { RestClient.get('https://example.com') }
.to raise_error(ArgumentError,
"URL 'https://example.com' is blocked: Requests to localhost are not allowed")
end
it 'raises error when it is a request to local address' do
expect { RestClient.get('http://172.16.0.0') }
.to raise_error(ArgumentError,
"URL 'http://172.16.0.0' is blocked: Requests to the local network are not allowed")
end
it 'raises error when it is a request to localhost address' do
expect { RestClient.get('http://127.0.0.1') }
.to raise_error(ArgumentError,
"URL 'http://127.0.0.1' is blocked: Requests to localhost are not allowed")
end
end
context 'when port different from URL scheme is used' do
it 'allows the request' do
request_stub = stub_full_request('https://example.com:8080', ip_address: '93.184.216.34')
RestClient.get('https://example.com:8080/')
expect(request_stub).to have_been_requested
end
it 'raises error when it is a request to local address' do
expect { RestClient.get('https://172.16.0.0:8080') }
.to raise_error(ArgumentError,
"URL 'https://172.16.0.0:8080' is blocked: Requests to the local network are not allowed")
end
it 'raises error when it is a request to localhost address' do
expect { RestClient.get('https://127.0.0.1:8080') }
.to raise_error(ArgumentError,
"URL 'https://127.0.0.1:8080' is blocked: Requests to localhost are not allowed")
end
end
context 'when DNS rebinding protection is disabled' do
before do
stub_application_setting(dns_rebinding_protection_enabled: false)
end
it 'allows the request' do
request_stub = stub_request(:get, 'https://example.com')
RestClient.get('https://example.com/')
expect(request_stub).to have_been_requested
end
end
context 'when http(s) proxy environment variable is set' do
before do
stub_env('https_proxy' => 'https://my.proxy')
end
it 'allows the request' do
request_stub = stub_request(:get, 'https://example.com')
RestClient.get('https://example.com/')
expect(request_stub).to have_been_requested
end
end
context 'when local requests are allowed' do
before do
stub_application_setting(allow_local_requests_from_web_hooks_and_services: true)
end
it 'allows an external request' do
request_stub = stub_full_request('https://example.com', ip_address: '93.184.216.34')
RestClient.get('https://example.com/')
expect(request_stub).to have_been_requested
end
it 'allows an external request that resolves to a local address' do
request_stub = stub_full_request('https://example.com', ip_address: '172.16.0.0')
RestClient.get('https://example.com/')
expect(request_stub).to have_been_requested
end
it 'allows an external request that resolves to a localhost address' do
request_stub = stub_full_request('https://example.com', ip_address: '127.0.0.1')
RestClient.get('https://example.com/')
expect(request_stub).to have_been_requested
end
it 'allows a local address request' do
request_stub = stub_request(:get, 'http://172.16.0.0')
RestClient.get('http://172.16.0.0')
expect(request_stub).to have_been_requested
end
it 'allows a localhost address request' do
request_stub = stub_request(:get, 'http://127.0.0.1')
RestClient.get('http://127.0.0.1')
expect(request_stub).to have_been_requested
end end
end end
end end
...@@ -521,6 +521,15 @@ describe Banzai::Filter::LabelReferenceFilter do ...@@ -521,6 +521,15 @@ describe Banzai::Filter::LabelReferenceFilter do
expect(reference_filter(act).to_html).to eq exp expect(reference_filter(act).to_html).to eq exp
end end
context 'when group name has HTML entities' do
let(:another_group) { create(:group, name: '<img src=x onerror=alert(1)>', path: 'another_group') }
it 'escapes the HTML entities' do
expect(result.text)
.to eq "See #{group_label.name} in #{another_project.full_name}"
end
end
end end
describe 'cross-project / same-group_label complete reference' do describe 'cross-project / same-group_label complete reference' do
... ...
......
...@@ -119,6 +119,11 @@ describe Banzai::Filter::RelativeLinkFilter do ...@@ -119,6 +119,11 @@ describe Banzai::Filter::RelativeLinkFilter do
expect { filter(act) }.not_to raise_error expect { filter(act) }.not_to raise_error
end end
it 'does not raise an exception on URIs containing invalid utf-8 byte sequences' do
act = link("%FF")
expect { filter(act) }.not_to raise_error
end
it 'does not raise an exception with a garbled path' do it 'does not raise an exception with a garbled path' do
act = link("open(/var/tmp/):%20/location%0Afrom:%20/test") act = link("open(/var/tmp/):%20/location%0Afrom:%20/test")
expect { filter(act) }.not_to raise_error expect { filter(act) }.not_to raise_error
... ...
......
...@@ -6,6 +6,11 @@ describe Gitlab::CycleAnalytics::StageSummary do ...@@ -6,6 +6,11 @@ describe Gitlab::CycleAnalytics::StageSummary do
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
let(:options) { { from: 1.day.ago, current_user: user } } let(:options) { { from: 1.day.ago, current_user: user } }
let(:user) { create(:user, :admin) } let(:user) { create(:user, :admin) }
before do
project.add_maintainer(user)
end
let(:stage_summary) { described_class.new(project, options).data } let(:stage_summary) { described_class.new(project, options).data }
describe "#new_issues" do describe "#new_issues" do
...@@ -86,6 +91,24 @@ describe Gitlab::CycleAnalytics::StageSummary do ...@@ -86,6 +91,24 @@ describe Gitlab::CycleAnalytics::StageSummary do
expect(subject).to eq(2) expect(subject).to eq(2)
end end
end end
context 'when a guest user is signed in' do
let(:guest_user) { create(:user) }
before do
project.add_guest(guest_user)
options.merge!({ current_user: guest_user })
end
it 'does not include commit stats' do
data = described_class.new(project, options).data
expect(includes_commits?(data)).to be_falsy
end
def includes_commits?(data)
data.any? { |h| h["title"] == 'Commits' }
end
end
end end
describe "#deploys" do describe "#deploys" do
... ...
......
...@@ -24,7 +24,10 @@ describe Gitlab::ImportExport::AttributeCleaner do ...@@ -24,7 +24,10 @@ describe Gitlab::ImportExport::AttributeCleaner do
'_html' => '<p>perfectly ordinary html</p>', '_html' => '<p>perfectly ordinary html</p>',
'cached_markdown_version' => 12345, 'cached_markdown_version' => 12345,
'group_id' => 99, 'group_id' => 99,
'commit_id' => 99 'commit_id' => 99,
'issue_ids' => [1, 2, 3],
'merge_request_ids' => [1, 2, 3],
'note_ids' => [1, 2, 3]
} }
end end
... ...
......
# frozen_string_literal: true
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20191120115530_encrypt_plaintext_attributes_on_application_settings.rb')
describe EncryptPlaintextAttributesOnApplicationSettings, :migration do
let(:migration) { described_class.new }
let(:application_settings) { table(:application_settings) }
let(:plaintext) { 'secret-token' }
PLAINTEXT_ATTRIBUTES = %w[
akismet_api_key
elasticsearch_aws_secret_access_key
recaptcha_private_key
recaptcha_site_key
slack_app_secret
slack_app_verification_token
].freeze
describe '#up' do
it 'encrypts token and saves it' do
application_setting = application_settings.create
application_setting.update_columns(
PLAINTEXT_ATTRIBUTES.each_with_object({}) do |plaintext_attribute, attributes|
attributes[plaintext_attribute] = plaintext
end
)
migration.up
application_setting.reload
PLAINTEXT_ATTRIBUTES.each do |plaintext_attribute|
expect(application_setting[plaintext_attribute]).not_to be_nil
expect(application_setting["encrypted_#{plaintext_attribute}"]).not_to be_nil
expect(application_setting["encrypted_#{plaintext_attribute}_iv"]).not_to be_nil
end
end
end
describe '#down' do
it 'decrypts encrypted token and saves it' do
application_setting = application_settings.create(
PLAINTEXT_ATTRIBUTES.each_with_object({}) do |plaintext_attribute, attributes|
attributes[plaintext_attribute] = plaintext
end
)
migration.down
application_setting.reload
PLAINTEXT_ATTRIBUTES.each do |plaintext_attribute|
expect(application_setting[plaintext_attribute]).to eq(plaintext)
expect(application_setting["encrypted_#{plaintext_attribute}"]).to be_nil
expect(application_setting["encrypted_#{plaintext_attribute}_iv"]).to be_nil
end
end
end
end