Commit 4a070084 authored by GitLab Bot's avatar GitLab Bot

Merge remote-tracking branch 'upstream/master' into ce-to-ee-2018-04-02

# Conflicts:
#	app/views/projects/deploy_keys/_index.html.haml
#	doc/ci/pipelines.md
#	doc/ci/quick_start/README.md
#	doc/ci/yaml/README.md
#	spec/javascripts/boards/mock_data.js
#	spec/javascripts/vue_mr_widget/mock_data.js

[ci skip]
parents 2a8e7703 f2e6a738
...@@ -321,7 +321,7 @@ end ...@@ -321,7 +321,7 @@ end
group :development do group :development do
gem 'foreman', '~> 0.84.0' gem 'foreman', '~> 0.84.0'
gem 'brakeman', '~> 3.6.0', require: false gem 'brakeman', '~> 4.2', require: false
gem 'letter_opener_web', '~> 1.3.0' gem 'letter_opener_web', '~> 1.3.0'
gem 'rblineprof', '~> 0.3.6', platform: :mri, require: false gem 'rblineprof', '~> 0.3.6', platform: :mri, require: false
......
...@@ -103,7 +103,7 @@ GEM ...@@ -103,7 +103,7 @@ GEM
autoprefixer-rails (>= 5.2.1) autoprefixer-rails (>= 5.2.1)
sass (>= 3.3.4) sass (>= 3.3.4)
bootstrap_form (2.7.0) bootstrap_form (2.7.0)
brakeman (3.6.1) brakeman (4.2.1)
browser (2.2.0) browser (2.2.0)
builder (3.2.3) builder (3.2.3)
bullet (5.5.1) bullet (5.5.1)
...@@ -1042,7 +1042,7 @@ DEPENDENCIES ...@@ -1042,7 +1042,7 @@ DEPENDENCIES
binding_of_caller (~> 0.7.2) binding_of_caller (~> 0.7.2)
bootstrap-sass (~> 3.3.0) bootstrap-sass (~> 3.3.0)
bootstrap_form (~> 2.7.0) bootstrap_form (~> 2.7.0)
brakeman (~> 3.6.0) brakeman (~> 4.2)
browser (~> 2.2) browser (~> 2.2)
bullet (~> 5.5.0) bullet (~> 5.5.0)
bundler-audit (~> 0.5.0) bundler-audit (~> 0.5.0)
......
<script> <script>
import Flash from '../../../flash'; import Flash from '../../../flash';
import editForm from './edit_form.vue'; import editForm from './edit_form.vue';
import Icon from '../../../vue_shared/components/icon.vue'; import Icon from '../../../vue_shared/components/icon.vue';
import { __ } from '../../../locale'; import { __ } from '../../../locale';
import eventHub from '../../event_hub';
export default { export default {
components: { components: {
editForm, editForm,
Icon, Icon,
...@@ -33,27 +34,40 @@ ...@@ -33,27 +34,40 @@
return this.isConfidential ? 'eye-slash' : 'eye'; return this.isConfidential ? 'eye-slash' : 'eye';
}, },
}, },
created() {
eventHub.$on('closeConfidentialityForm', this.toggleForm);
},
beforeDestroy() {
eventHub.$off('closeConfidentialityForm', this.toggleForm);
},
methods: { methods: {
toggleForm() { toggleForm() {
this.edit = !this.edit; this.edit = !this.edit;
}, },
updateConfidentialAttribute(confidential) { updateConfidentialAttribute(confidential) {
this.service.update('issue', { confidential }) this.service
.update('issue', { confidential })
.then(() => location.reload()) .then(() => location.reload())
.catch(() => { .catch(() => {
Flash(__('Something went wrong trying to change the confidentiality of this issue')); Flash(
__(
'Something went wrong trying to change the confidentiality of this issue',
),
);
}); });
}, },
}, },
}; };
</script> </script>
<template> <template>
<div class="block issuable-sidebar-item confidentiality"> <div class="block issuable-sidebar-item confidentiality">
<div class="sidebar-collapsed-icon"> <div
class="sidebar-collapsed-icon"
@click="toggleForm"
>
<icon <icon
:name="confidentialityIcon" :name="confidentialityIcon"
:size="16"
aria-hidden="true" aria-hidden="true"
/> />
</div> </div>
...@@ -71,7 +85,6 @@ ...@@ -71,7 +85,6 @@
<div class="value sidebar-item-value hide-collapsed"> <div class="value sidebar-item-value hide-collapsed">
<editForm <editForm
v-if="edit" v-if="edit"
:toggle-form="toggleForm"
:is-confidential="isConfidential" :is-confidential="isConfidential"
:update-confidential-attribute="updateConfidentialAttribute" :update-confidential-attribute="updateConfidentialAttribute"
/> />
......
<script> <script>
import editFormButtons from './edit_form_buttons.vue'; import editFormButtons from './edit_form_buttons.vue';
import { s__ } from '../../../locale'; import { s__ } from '../../../locale';
export default { export default {
components: { components: {
editFormButtons, editFormButtons,
}, },
...@@ -11,10 +11,6 @@ ...@@ -11,10 +11,6 @@
required: true, required: true,
type: Boolean, type: Boolean,
}, },
toggleForm: {
required: true,
type: Function,
},
updateConfidentialAttribute: { updateConfidentialAttribute: {
required: true, required: true,
type: Function, type: Function,
...@@ -22,13 +18,17 @@ ...@@ -22,13 +18,17 @@
}, },
computed: { computed: {
confidentialityOnWarning() { confidentialityOnWarning() {
return s__('confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue.'); return s__(
'confidentiality|You are going to turn on the confidentiality. This means that only team members with <strong>at least Reporter access</strong> are able to see and leave comments on the issue.',
);
}, },
confidentialityOffWarning() { confidentialityOffWarning() {
return s__('confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue.'); return s__(
'confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue.',
);
}, },
}, },
}; };
</script> </script>
<template> <template>
...@@ -45,7 +45,6 @@ ...@@ -45,7 +45,6 @@
</p> </p>
<edit-form-buttons <edit-form-buttons
:is-confidential="isConfidential" :is-confidential="isConfidential"
:toggle-form="toggleForm"
:update-confidential-attribute="updateConfidentialAttribute" :update-confidential-attribute="updateConfidentialAttribute"
/> />
</div> </div>
......
<script> <script>
import $ from 'jquery';
import eventHub from '../../event_hub';
export default { export default {
props: { props: {
isConfidential: { isConfidential: {
required: true, required: true,
type: Boolean, type: Boolean,
}, },
toggleForm: {
required: true,
type: Function,
},
updateConfidentialAttribute: { updateConfidentialAttribute: {
required: true, required: true,
type: Function, type: Function,
...@@ -22,6 +21,16 @@ export default { ...@@ -22,6 +21,16 @@ export default {
return !this.isConfidential; return !this.isConfidential;
}, },
}, },
methods: {
closeForm() {
eventHub.$emit('closeConfidentialityForm');
$(this.$el).trigger('hidden.gl.dropdown');
},
submitForm() {
this.closeForm();
this.updateConfidentialAttribute(this.updateConfidentialBool);
},
},
}; };
</script> </script>
...@@ -30,14 +39,14 @@ export default { ...@@ -30,14 +39,14 @@ export default {
<button <button
type="button" type="button"
class="btn btn-default append-right-10" class="btn btn-default append-right-10"
@click="toggleForm" @click="closeForm"
> >
{{ __('Cancel') }} {{ __('Cancel') }}
</button> </button>
<button <button
type="button" type="button"
class="btn btn-close" class="btn btn-close"
@click.prevent="updateConfidentialAttribute(updateConfidentialBool)" @click.prevent="submitForm"
> >
{{ toggleButtonText }} {{ toggleButtonText }}
</button> </button>
......
<script> <script>
import editFormButtons from './edit_form_buttons.vue'; import editFormButtons from './edit_form_buttons.vue';
import issuableMixin from '../../../vue_shared/mixins/issuable'; import issuableMixin from '../../../vue_shared/mixins/issuable';
import { __, sprintf } from '../../../locale'; import { __, sprintf } from '../../../locale';
export default { export default {
components: { components: {
editFormButtons, editFormButtons,
}, },
mixins: [ mixins: [issuableMixin],
issuableMixin,
],
props: { props: {
isLocked: { isLocked: {
required: true, required: true,
type: Boolean, type: Boolean,
}, },
toggleForm: {
required: true,
type: Function,
},
updateLockedAttribute: { updateLockedAttribute: {
required: true, required: true,
type: Function, type: Function,
...@@ -28,13 +21,23 @@ ...@@ -28,13 +21,23 @@
}, },
computed: { computed: {
lockWarning() { lockWarning() {
return sprintf(__('Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment.'), { issuableDisplayName: this.issuableDisplayName }); return sprintf(
__(
'Lock this %{issuableDisplayName}? Only <strong>project members</strong> will be able to comment.',
),
{ issuableDisplayName: this.issuableDisplayName },
);
}, },
unlockWarning() { unlockWarning() {
return sprintf(__('Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment.'), { issuableDisplayName: this.issuableDisplayName }); return sprintf(
__(
'Unlock this %{issuableDisplayName}? <strong>Everyone</strong> will be able to comment.',
),
{ issuableDisplayName: this.issuableDisplayName },
);
}, },
}, },
}; };
</script> </script>
<template> <template>
...@@ -54,7 +57,6 @@ ...@@ -54,7 +57,6 @@
<edit-form-buttons <edit-form-buttons
:is-locked="isLocked" :is-locked="isLocked"
:toggle-form="toggleForm"
:update-locked-attribute="updateLockedAttribute" :update-locked-attribute="updateLockedAttribute"
/> />
</div> </div>
......
<script> <script>
import $ from 'jquery';
import eventHub from '../../event_hub';
export default { export default {
props: { props: {
isLocked: { isLocked: {
...@@ -6,11 +9,6 @@ export default { ...@@ -6,11 +9,6 @@ export default {
type: Boolean, type: Boolean,
}, },
toggleForm: {
required: true,
type: Function,
},
updateLockedAttribute: { updateLockedAttribute: {
required: true, required: true,
type: Function, type: Function,
...@@ -26,6 +24,17 @@ export default { ...@@ -26,6 +24,17 @@ export default {
return !this.isLocked; return !this.isLocked;
}, },
}, },
methods: {
closeForm() {
eventHub.$emit('closeLockForm');
$(this.$el).trigger('hidden.gl.dropdown');
},
submitForm() {
this.closeForm();
this.updateLockedAttribute(this.toggleLock);
},
},
}; };
</script> </script>
...@@ -34,7 +43,7 @@ export default { ...@@ -34,7 +43,7 @@ export default {
<button <button
type="button" type="button"
class="btn btn-default append-right-10" class="btn btn-default append-right-10"
@click="toggleForm" @click="closeForm"
> >
{{ __('Cancel') }} {{ __('Cancel') }}
</button> </button>
...@@ -42,7 +51,7 @@ export default { ...@@ -42,7 +51,7 @@ export default {
<button <button
type="button" type="button"
class="btn btn-close" class="btn btn-close"
@click.prevent="updateLockedAttribute(toggleLock)" @click.prevent="submitForm"
> >
{{ buttonText }} {{ buttonText }}
</button> </button>
......
<script> <script>
import Flash from '~/flash'; import Flash from '~/flash';
import editForm from './edit_form.vue'; import editForm from './edit_form.vue';
import issuableMixin from '../../../vue_shared/mixins/issuable'; import issuableMixin from '../../../vue_shared/mixins/issuable';
import Icon from '../../../vue_shared/components/icon.vue'; import Icon from '../../../vue_shared/components/icon.vue';
import eventHub from '../../event_hub';
export default { export default {
components: { components: {
editForm, editForm,
Icon, Icon,
}, },
mixins: [ mixins: [issuableMixin],
issuableMixin,
],
props: { props: {
isLocked: { isLocked: {
...@@ -28,7 +27,11 @@ ...@@ -28,7 +27,11 @@
required: true, required: true,
type: Object, type: Object,
validator(mediatorObject) { validator(mediatorObject) {
return mediatorObject.service && mediatorObject.service.update && mediatorObject.store; return (
mediatorObject.service &&
mediatorObject.service.update &&
mediatorObject.store
);
}, },
}, },
}, },
...@@ -43,28 +46,48 @@ ...@@ -43,28 +46,48 @@
}, },
}, },
created() {
eventHub.$on('closeLockForm', this.toggleForm);
},
beforeDestroy() {
eventHub.$off('closeLockForm', this.toggleForm);
},
methods: { methods: {
toggleForm() { toggleForm() {
this.mediator.store.isLockDialogOpen = !this.mediator.store.isLockDialogOpen; this.mediator.store.isLockDialogOpen = !this.mediator.store
.isLockDialogOpen;
}, },
updateLockedAttribute(locked) { updateLockedAttribute(locked) {
this.mediator.service.update(this.issuableType, { this.mediator.service
.update(this.issuableType, {
discussion_locked: locked, discussion_locked: locked,
}) })
.then(() => location.reload()) .then(() => location.reload())
.catch(() => Flash(this.__(`Something went wrong trying to change the locked state of this ${this.issuableDisplayName}`))); .catch(() =>
Flash(
this.__(
`Something went wrong trying to change the locked state of this ${
this.issuableDisplayName
}`,
),
),
);
}, },
}, },
}; };
</script> </script>
<template> <template>
<div class="block issuable-sidebar-item lock"> <div class="block issuable-sidebar-item lock">
<div class="sidebar-collapsed-icon"> <div
class="sidebar-collapsed-icon"
@click="toggleForm"
>
<icon <icon
:name="lockIcon" :name="lockIcon"
:size="16"
aria-hidden="true" aria-hidden="true"
class="sidebar-item-icon is-active" class="sidebar-item-icon is-active"
/> />
...@@ -85,7 +108,6 @@ ...@@ -85,7 +108,6 @@
<div class="value sidebar-item-value hide-collapsed"> <div class="value sidebar-item-value hide-collapsed">
<edit-form <edit-form
v-if="isLockDialogOpen" v-if="isLockDialogOpen"
:toggle-form="toggleForm"
:is-locked="isLocked" :is-locked="isLocked"
:update-locked-attribute="updateLockedAttribute" :update-locked-attribute="updateLockedAttribute"
:issuable-type="issuableType" :issuable-type="issuableType"
......
...@@ -46,6 +46,8 @@ class Projects::ServicesController < Projects::ApplicationController ...@@ -46,6 +46,8 @@ class Projects::ServicesController < Projects::ApplicationController
else else
{ error: true, message: 'Validations failed.', service_response: @service.errors.full_messages.join(',') } { error: true, message: 'Validations failed.', service_response: @service.errors.full_messages.join(',') }
end end
rescue Gitlab::HTTP::BlockedUrlError => e
{ error: true, message: 'Test failed.', service_response: e.message }
end end
def success_message def success_message
......
...@@ -28,7 +28,11 @@ module Projects ...@@ -28,7 +28,11 @@ module Projects
def add_repository_to_project def add_repository_to_project
if project.external_import? && !unknown_url? if project.external_import? && !unknown_url?
raise Error, 'Blocked import URL.' if Gitlab::UrlBlocker.blocked_url?(project.import_url, valid_ports: Project::VALID_IMPORT_PORTS) begin
Gitlab::UrlBlocker.validate!(project.import_url, valid_ports: Project::VALID_IMPORT_PORTS)
rescue Gitlab::UrlBlocker::BlockedUrlError => e
raise Error, "Blocked import URL: #{e.message}"
end
end end
# We should skip the repository for a GitHub import or GitLab project import, # We should skip the repository for a GitHub import or GitLab project import,
......
...@@ -4,8 +4,8 @@ ...@@ -4,8 +4,8 @@
# protect against Server-side Request Forgery (SSRF). # protect against Server-side Request Forgery (SSRF).
class ImportableUrlValidator < ActiveModel::EachValidator class ImportableUrlValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value) def validate_each(record, attribute, value)
if Gitlab::UrlBlocker.blocked_url?(value, valid_ports: Project::VALID_IMPORT_PORTS) Gitlab::UrlBlocker.validate!(value, valid_ports: Project::VALID_IMPORT_PORTS)
record.errors.add(attribute, "imports are not allowed from that URL") rescue Gitlab::UrlBlocker::BlockedUrlError => e
end record.errors.add(attribute, "is blocked: #{e.message}")
end end
end end
...@@ -3,7 +3,11 @@ ...@@ -3,7 +3,11 @@
.settings-header .settings-header
%h4 %h4
Deploy Keys Deploy Keys
<<<<<<< HEAD
%button.btn.js-settings-toggle{ type: 'button' } %button.btn.js-settings-toggle{ type: 'button' }
=======
%button.btn.js-settings-toggle.qa-expand-deploy-keys{ type: 'button' }
>>>>>>> upstream/master
= expanded ? 'Collapse' : 'Expand' = expanded ? 'Collapse' : 'Expand'
%p %p
Deploy keys allow read-only or read-write (if enabled) access to your repository. Deploy keys can be used for CI, staging or production servers. You can create a deploy key or add an existing one. Deploy keys allow read-only or read-write (if enabled) access to your repository. Deploy keys can be used for CI, staging or production servers. You can create a deploy key or add an existing one.
......
---
title: Update brakeman 3.6.1 to 4.2.1
merge_request: 18122
author: Takuya Noguchi
type: other
...@@ -3,7 +3,11 @@ ...@@ -3,7 +3,11 @@
> Introduced in GitLab 8.8. > Introduced in GitLab 8.8.
NOTE: **Note:** NOTE: **Note:**
<<<<<<< HEAD
If you have a [mirrored repository where GitLab pulls from](../workflow/repository_mirroring.md#pulling-from-a-remote-repository), If you have a [mirrored repository where GitLab pulls from](../workflow/repository_mirroring.md#pulling-from-a-remote-repository),
=======
If you have a [mirrored repository where GitLab pulls from](https://docs.gitlab.com/ee/workflow/repository_mirroring.html#pulling-from-a-remote-repository),
>>>>>>> upstream/master
you may need to enable pipeline triggering in your project's you may need to enable pipeline triggering in your project's
**Settings > Repository > Pull from a remote repository > Trigger pipelines for mirror updates**. **Settings > Repository > Pull from a remote repository > Trigger pipelines for mirror updates**.
......
...@@ -127,7 +127,11 @@ Now if you go to the **Pipelines** page you will see that the pipeline is ...@@ -127,7 +127,11 @@ Now if you go to the **Pipelines** page you will see that the pipeline is
pending. pending.
NOTE: **Note:** NOTE: **Note:**
<<<<<<< HEAD
If you have a [mirrored repository where GitLab pulls from](../../workflow/repository_mirroring.md#pulling-from-a-remote-repository), If you have a [mirrored repository where GitLab pulls from](../../workflow/repository_mirroring.md#pulling-from-a-remote-repository),
=======
If you have a [mirrored repository where GitLab pulls from](https://docs.gitlab.com/ee/workflow/repository_mirroring.html#pulling-from-a-remote-repository),
>>>>>>> upstream/master
you may need to enable pipeline triggering in your project's you may need to enable pipeline triggering in your project's
**Settings > Repository > Pull from a remote repository > Trigger pipelines for mirror updates**. **Settings > Repository > Pull from a remote repository > Trigger pipelines for mirror updates**.
......
...@@ -11,7 +11,11 @@ If you want a quick introduction to GitLab CI, follow our ...@@ -11,7 +11,11 @@ If you want a quick introduction to GitLab CI, follow our
[quick start guide](../quick_start/README.md). [quick start guide](../quick_start/README.md).
NOTE: **Note:** NOTE: **Note:**
<<<<<<< HEAD
If you have a [mirrored repository where GitLab pulls from](../../workflow/repository_mirroring.md#pulling-from-a-remote-repository), If you have a [mirrored repository where GitLab pulls from](../../workflow/repository_mirroring.md#pulling-from-a-remote-repository),
=======
If you have a [mirrored repository where GitLab pulls from](https://docs.gitlab.com/ee/workflow/repository_mirroring.html#pulling-from-a-remote-repository),
>>>>>>> upstream/master
you may need to enable pipeline triggering in your project's you may need to enable pipeline triggering in your project's
**Settings > Repository > Pull from a remote repository > Trigger pipelines for mirror updates**. **Settings > Repository > Pull from a remote repository > Trigger pipelines for mirror updates**.
......
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
# calling internal IP or services. # calling internal IP or services.
module Gitlab module Gitlab
class HTTP class HTTP
BlockedUrlError = Class.new(StandardError)
include HTTParty # rubocop:disable Gitlab/HTTParty include HTTParty # rubocop:disable Gitlab/HTTParty
connection_adapter ProxyHTTPConnectionAdapter connection_adapter ProxyHTTPConnectionAdapter
......
...@@ -10,8 +10,12 @@ ...@@ -10,8 +10,12 @@
module Gitlab module Gitlab
class ProxyHTTPConnectionAdapter < HTTParty::ConnectionAdapter class ProxyHTTPConnectionAdapter < HTTParty::ConnectionAdapter
def connection def connection
if !allow_local_requests? && blocked_url? unless allow_local_requests?
raise URI::InvalidURIError begin
Gitlab::UrlBlocker.validate!(uri, allow_local_network: false)
rescue Gitlab::UrlBlocker::BlockedUrlError => e
raise Gitlab::HTTP::BlockedUrlError, "URL '#{uri}' is blocked: #{e.message}"
end
end end
super super
...@@ -19,10 +23,6 @@ module Gitlab ...@@ -19,10 +23,6 @@ module Gitlab
private private
def blocked_url?
Gitlab::UrlBlocker.blocked_url?(uri, allow_private_networks: false)
end
def allow_local_requests? def allow_local_requests?
options.fetch(:allow_local_requests, allow_settings_local_requests?) options.fetch(:allow_local_requests, allow_settings_local_requests?)
end end
......
...@@ -2,48 +2,84 @@ require 'resolv' ...@@ -2,48 +2,84 @@ require 'resolv'
module Gitlab module Gitlab
class UrlBlocker class UrlBlocker
class << self BlockedUrlError = Class.new(StandardError)
def blocked_url?(url, allow_private_networks: true, valid_ports: [])
return false if url.nil?
blocked_ips = ["127.0.0.1", "::1", "0.0.0.0"] class << self
blocked_ips.concat(Socket.ip_address_list.map(&:ip_address)) def validate!(url, allow_localhost: false, allow_local_network: true, valid_ports: [])
return true if url.nil?
begin begin
uri = Addressable::URI.parse(url) uri = Addressable::URI.parse(url)
# Allow imports from the GitLab instance itself but only from the configured ports rescue Addressable::URI::InvalidURIError
return false if internal?(uri) raise BlockedUrlError, "URI is invalid"
end
return true if blocked_port?(uri.port, valid_ports) # Allow imports from the GitLab instance itself but only from the configured ports
return true if blocked_user_or_hostname?(uri.user) return true if internal?(uri)
return true if blocked_user_or_hostname?(uri.hostname)
addrs_info = Addrinfo.getaddrinfo(uri.hostname, 80, nil, :STREAM) port = uri.port || uri.default_port
server_ips = addrs_info.map(&:ip_address) validate_port!(port, valid_ports) if valid_ports.any?
validate_user!(uri.user)
validate_hostname!(uri.hostname)
return true if (blocked_ips & server_ips).any? begin
return true if !allow_private_networks && private_network?(addrs_info) addrs_info = Addrinfo.getaddrinfo(uri.hostname, port, nil, :STREAM)
rescue Addressable::URI::InvalidURIError
return true
rescue SocketError rescue SocketError
return false return true
end
validate_localhost!(addrs_info) unless allow_localhost
validate_local_network!(addrs_info) unless allow_local_network
true
end end
def blocked_url?(*args)
validate!(*args)
false false
rescue BlockedUrlError
true
end end
private private
def blocked_port?(port, valid_ports) def validate_port!(port, valid_ports)
return false if port.blank? || valid_ports.blank? return if port.blank?
# Only ports under 1024 are restricted
return if port >= 1024
return if valid_ports.include?(port)
raise BlockedUrlError, "Only allowed ports are #{valid_ports.join(', ')}, and any over 1024"
end
def validate_user!(value)
return if value.blank?
return if value =~ /\A\p{Alnum}/
port < 1024 && !valid_ports.include?(port) raise BlockedUrlError, "Username needs to start with an alphanumeric character"
end end
def blocked_user_or_hostname?(value) def validate_hostname!(value)
return false if value.blank? return if value.blank?
return if value =~ /\A\p{Alnum}/
value !~ /\A\p{Alnum}/ raise BlockedUrlError, "Hostname needs to start with an alphanumeric character"
end
def validate_localhost!(addrs_info)
local_ips = ["127.0.0.1", "::1", "0.0.0.0"]
local_ips.concat(Socket.ip_address_list.map(&:ip_address))
return if (local_ips & addrs_info.map(&:ip_address)).empty?
raise BlockedUrlError, "Requests to localhost are not allowed"
end
def validate_local_network!(addrs_info)
return unless addrs_info.any? { |addr| addr.ipv4_private? || addr.ipv6_sitelocal? }
raise BlockedUrlError, "Requests to the local network are not allowed"
end end
def internal?(uri) def internal?(uri)
...@@ -60,10 +96,6 @@ module Gitlab ...@@ -60,10 +96,6 @@ module Gitlab
(uri.port.blank? || uri.port == config.gitlab_shell.ssh_port) (uri.port.blank? || uri.port == config.gitlab_shell.ssh_port)
end end
def private_network?(addrs_info)
addrs_info.any? { |addr| addr.ipv4_private? || addr.ipv6_sitelocal? }
end
def config def config
Gitlab.config Gitlab.config
end end
......
...@@ -161,6 +161,50 @@ feature 'Issue Sidebar' do ...@@ -161,6 +161,50 @@ feature 'Issue Sidebar' do
end end
end end
end end
context 'interacting with collapsed sidebar', :js do
collapsed_sidebar_selector = 'aside.right-sidebar.right-sidebar-collapsed'
expanded_sidebar_selector = 'aside.right-sidebar.right-sidebar-expanded'
confidentiality_sidebar_block = '.block.confidentiality'
lock_sidebar_block = '.block.lock'
collapsed_sidebar_block_icon = '.sidebar-collapsed-icon'
before do
resize_screen_sm
end
it 'confidentiality block expands then collapses sidebar' do
expect(page).to have_css(collapsed_sidebar_selector)
page.within(confidentiality_sidebar_block) do
find(collapsed_sidebar_block_icon).click
end
expect(page).to have_css(expanded_sidebar_selector)
page.within(confidentiality_sidebar_block) do
page.find('button', text: 'Cancel').click
end
expect(page).to have_css(collapsed_sidebar_selector)
end
it 'lock block expands then collapses sidebar' do
expect(page).to have_css(collapsed_sidebar_selector)
page.within(lock_sidebar_block) do
find(collapsed_sidebar_block_icon).click
end
expect(page).to have_css(expanded_sidebar_selector)
page.within(lock_sidebar_block) do
page.find('button', text: 'Cancel').click
end
expect(page).to have_css(collapsed_sidebar_selector)
end
end
end end
context 'as a guest' do context 'as a guest' do
......
/* global BoardService */ /* global BoardService */
<<<<<<< HEAD
export const boardObj = { export const boardObj = {
id: 1, id: 1,
...@@ -6,6 +7,8 @@ export const boardObj = { ...@@ -6,6 +7,8 @@ export const boardObj = {
milestone_id: null, milestone_id: null,
}; };
=======
>>>>>>> upstream/master
export const listObj = { export const listObj = {
id: 300, id: 300,
position: 0, position: 0,
...@@ -46,12 +49,15 @@ export const BoardsMockData = { ...@@ -46,12 +49,15 @@ export const BoardsMockData = {
}, },
], ],
}, },
<<<<<<< HEAD
'/test/issue-boards/milestones.json': [ '/test/issue-boards/milestones.json': [
{ {
id: 1, id: 1,
title: 'test', title: 'test',
}, },
], ],
=======
>>>>>>> upstream/master
}, },
POST: { POST: {
'/test/-/boards/1/lists': listObj, '/test/-/boards/1/lists': listObj,
......
...@@ -62,4 +62,22 @@ describe('Confidential Issue Sidebar Block', () => { ...@@ -62,4 +62,22 @@ describe('Confidential Issue Sidebar Block', () => {
done(); done();
}); });
}); });
it('displays the edit form when opened from collapsed state', (done) => {
expect(vm1.edit).toBe(false);
vm1.$el.querySelector('.sidebar-collapsed-icon').click();
expect(vm1.edit).toBe(true);
setTimeout(() => {
expect(
vm1.$el
.innerHTML
.includes('You are going to turn off the confidentiality.'),
).toBe(true);
done();
});
});
}); });
...@@ -68,4 +68,22 @@ describe('LockIssueSidebar', () => { ...@@ -68,4 +68,22 @@ describe('LockIssueSidebar', () => {
done(); done();
}); });
}); });
it('displays the edit form when opened from collapsed state', (done) => {
expect(vm1.isLockDialogOpen).toBe(false);
vm1.$el.querySelector('.sidebar-collapsed-icon').click();
expect(vm1.isLockDialogOpen).toBe(true);
setTimeout(() => {
expect(
vm1.$el
.innerHTML
.includes('Unlock this issue?'),
).toBe(true);
done();
});
});
}); });
...@@ -159,6 +159,7 @@ export default { ...@@ -159,6 +159,7 @@ export default {
avatar_url: avatar_url:
'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
web_url: 'http://localhost:3000/root', web_url: 'http://localhost:3000/root',
<<<<<<< HEAD
}, },
author_gravatar_url: author_gravatar_url:
'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
...@@ -393,3 +394,62 @@ export const codequalityParsedIssues = [ ...@@ -393,3 +394,62 @@ export const codequalityParsedIssues = [
urlPath: 'foo/Gemfile.lock', urlPath: 'foo/Gemfile.lock',
}, },
]; ];
=======
},
author_gravatar_url:
'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
commit_url:
'http://localhost:3000/root/acets-app/commit/104096c51715e12e7ae41f9333e9fa35b73f385d',
commit_path: '/root/acets-app/commit/104096c51715e12e7ae41f9333e9fa35b73f385d',
},
retry_path: '/root/acets-app/pipelines/172/retry',
created_at: '2017-04-07T12:27:19.520Z',
updated_at: '2017-04-07T15:28:44.800Z',
},
work_in_progress: false,
source_branch_exists: false,
mergeable_discussions_state: true,
conflicts_can_be_resolved_in_ui: false,
branch_missing: true,
commits_count: 1,
has_conflicts: false,
can_be_merged: true,
has_ci: true,
ci_status: 'success',
pipeline_status_path: '/root/acets-app/merge_requests/22/pipeline_status',
issues_links: {
closing: '',
mentioned_but_not_closing: '',
},
current_user: {
can_resolve_conflicts: true,
can_remove_source_branch: false,
can_revert_on_current_merge_request: true,
can_cherry_pick_on_current_merge_request: true,
},
target_branch_path: '/root/acets-app/branches/master',
source_branch_path: '/root/acets-app/branches/daaaa',
conflict_resolution_ui_path: '/root/acets-app/merge_requests/22/conflicts',
remove_wip_path: '/root/acets-app/merge_requests/22/remove_wip',
cancel_merge_when_pipeline_succeeds_path:
'/root/acets-app/merge_requests/22/cancel_merge_when_pipeline_succeeds',
create_issue_to_resolve_discussions_path:
'/root/acets-app/issues/new?merge_request_to_resolve_discussions_of=22',
merge_path: '/root/acets-app/merge_requests/22/merge',
cherry_pick_in_fork_path:
'/root/acets-app/forks?continue%5Bnotice%5D=You%27re+not+allowed+to+make+changes+to+this+project+directly.+A+fork+of+this+project+has+been+created+that+you+can+make+changes+in%2C+so+you+can+submit+a+merge+request.+Try+to+revert+this+commit+again.&continue%5Bnotice_now%5D=You%27re+not+allowed+to+make+changes+to+this+project+directly.+A+fork+of+this+project+is+being+created+that+you+can+make+changes+in%2C+so+you+can+submit+a+merge+request.&continue%5Bto%5D=%2Froot%2Facets-app%2Fmerge_requests%2F22&namespace_key=1',
revert_in_fork_path:
'/root/acets-app/forks?continue%5Bnotice%5D=You%27re+not+allowed+to+make+changes+to+this+project+directly.+A+fork+of+this+project+has+been+created+that+you+can+make+changes+in%2C+so+you+can+submit+a+merge+request.+Try+to+cherry-pick+this+commit+again.&continue%5Bnotice_now%5D=You%27re+not+allowed+to+make+changes+to+this+project+directly.+A+fork+of+this+project+is+being+created+that+you+can+make+changes+in%2C+so+you+can+submit+a+merge+request.&continue%5Bto%5D=%2Froot%2Facets-app%2Fmerge_requests%2F22&namespace_key=1',
email_patches_path: '/root/acets-app/merge_requests/22.patch',
plain_diff_path: '/root/acets-app/merge_requests/22.diff',
status_path: '/root/acets-app/merge_requests/22.json',
merge_check_path: '/root/acets-app/merge_requests/22/merge_check',
ci_environments_status_url: '/root/acets-app/merge_requests/22/ci_environments_status',
project_archived: false,
merge_commit_message_with_description:
"Merge branch 'daaaa' into 'master'\n\nUpdate README.md\n\nSee merge request !22",
diverged_commits_count: 0,
only_allow_merge_if_pipeline_succeeds: false,
commit_change_content_path: '/root/acets-app/merge_requests/22/commit_change_content',
};
>>>>>>> upstream/master
...@@ -12,11 +12,11 @@ describe Gitlab::HTTP do ...@@ -12,11 +12,11 @@ describe Gitlab::HTTP do
end end
it 'deny requests to localhost' do it 'deny requests to localhost' do
expect { described_class.get('http://localhost:3003') }.to raise_error(URI::InvalidURIError) expect { described_class.get('http://localhost:3003') }.to raise_error(Gitlab::HTTP::BlockedUrlError)
end end
it 'deny requests to private network' do it 'deny requests to private network' do
expect { described_class.get('http://192.168.1.2:3003') }.to raise_error(URI::InvalidURIError) expect { described_class.get('http://192.168.1.2:3003') }.to raise_error(Gitlab::HTTP::BlockedUrlError)
end end
context 'if allow_local_requests set to true' do context 'if allow_local_requests set to true' do
...@@ -41,7 +41,7 @@ describe Gitlab::HTTP do ...@@ -41,7 +41,7 @@ describe Gitlab::HTTP do
context 'if allow_local_requests set to false' do context 'if allow_local_requests set to false' do
it 'override the global value and ban requests to localhost or private network' do it 'override the global value and ban requests to localhost or private network' do
expect { described_class.get('http://localhost:3003', allow_local_requests: false) }.to raise_error(URI::InvalidURIError) expect { described_class.get('http://localhost:3003', allow_local_requests: false) }.to raise_error(Gitlab::HTTP::BlockedUrlError)
end end
end end
end end
......
...@@ -74,13 +74,13 @@ describe Gitlab::UrlBlocker do ...@@ -74,13 +74,13 @@ describe Gitlab::UrlBlocker do
expect(described_class.blocked_url?('https://gitlab.com/foo/foo.git')).to be false expect(described_class.blocked_url?('https://gitlab.com/foo/foo.git')).to be false
end end
context 'when allow_private_networks is' do context 'when allow_local_network is' do
let(:private_networks) { ['192.168.1.2', '10.0.0.2', '172.16.0.2'] } let(:local_ips) { ['192.168.1.2', '10.0.0.2', '172.16.0.2'] }
let(:fake_domain) { 'www.fakedomain.fake' } let(:fake_domain) { 'www.fakedomain.fake' }
context 'true (default)' do context 'true (default)' do
it 'does not block urls from private networks' do it 'does not block urls from private networks' do
private_networks.each do |ip| local_ips.each do |ip|
stub_domain_resolv(fake_domain, ip) stub_domain_resolv(fake_domain, ip)
expect(described_class).not_to be_blocked_url("http://#{fake_domain}") expect(described_class).not_to be_blocked_url("http://#{fake_domain}")
...@@ -94,14 +94,14 @@ describe Gitlab::UrlBlocker do ...@@ -94,14 +94,14 @@ describe Gitlab::UrlBlocker do
context 'false' do context 'false' do
it 'blocks urls from private networks' do it 'blocks urls from private networks' do
private_networks.each do |ip| local_ips.each do |ip|
stub_domain_resolv(fake_domain, ip) stub_domain_resolv(fake_domain, ip)
expect(described_class).to be_blocked_url("http://#{fake_domain}", allow_private_networks: false) expect(described_class).to be_blocked_url("http://#{fake_domain}", allow_local_network: false)
unstub_domain_resolv unstub_domain_resolv
expect(described_class).to be_blocked_url("http://#{ip}", allow_private_networks: false) expect(described_class).to be_blocked_url("http://#{ip}", allow_local_network: false)
end end
end end
end end
......
...@@ -251,14 +251,14 @@ describe Project do ...@@ -251,14 +251,14 @@ describe Project do
project2 = build(:project, import_url: 'http://localhost:9000/t.git') project2 = build(:project, import_url: 'http://localhost:9000/t.git')
expect(project2).to be_invalid expect(project2).to be_invalid
expect(project2.errors[:import_url]).to include('imports are not allowed from that URL') expect(project2.errors[:import_url].first).to include('Requests to localhost are not allowed')
end end
it "does not allow blocked import_url port" do it "does not allow blocked import_url port" do
project2 = build(:project, import_url: 'http://github.com:25/t.git') project2 = build(:project, import_url: 'http://github.com:25/t.git')
expect(project2).to be_invalid expect(project2).to be_invalid
expect(project2.errors[:import_url]).to include('imports are not allowed from that URL') expect(project2.errors[:import_url].first).to include('Only allowed ports are 22, 80, 443')
end end
it 'creates mirror data when enabled' do it 'creates mirror data when enabled' do
......
...@@ -156,7 +156,7 @@ describe Projects::ImportService do ...@@ -156,7 +156,7 @@ describe Projects::ImportService do
result = described_class.new(project, user).execute result = described_class.new(project, user).execute
expect(result[:status]).to eq :error expect(result[:status]).to eq :error
expect(result[:message]).to end_with 'Blocked import URL.' expect(result[:message]).to include('Requests to localhost are not allowed')
end end
it 'fails with port 25' do it 'fails with port 25' do
...@@ -165,7 +165,7 @@ describe Projects::ImportService do ...@@ -165,7 +165,7 @@ describe Projects::ImportService do
result = described_class.new(project, user).execute result = described_class.new(project, user).execute
expect(result[:status]).to eq :error expect(result[:status]).to eq :error
expect(result[:message]).to end_with 'Blocked import URL.' expect(result[:message]).to include('Only allowed ports are 22, 80, 443')
end end
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment