Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Boxiang Sun
gitlab-ce
Commits
840f80d4
Commit
840f80d4
authored
Jun 01, 2018
by
Francisco Javier López
Committed by
Douwe Maan
Jun 01, 2018
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add validation to webhook and service URLs to ensure they are not blocked because of SSRF
parent
e206e328
Changes
40
Show whitespace changes
Inline
Side-by-side
Showing
40 changed files
with
286 additions
and
165 deletions
+286
-165
app/assets/javascripts/integrations/integration_settings_form.js
...ets/javascripts/integrations/integration_settings_form.js
+13
-7
app/controllers/projects/services_controller.rb
app/controllers/projects/services_controller.rb
+3
-3
app/models/badge.rb
app/models/badge.rb
+1
-1
app/models/environment.rb
app/models/environment.rb
+1
-1
app/models/generic_commit_status.rb
app/models/generic_commit_status.rb
+1
-1
app/models/hooks/system_hook.rb
app/models/hooks/system_hook.rb
+5
-0
app/models/hooks/web_hook.rb
app/models/hooks/web_hook.rb
+8
-1
app/models/project.rb
app/models/project.rb
+3
-2
app/models/project_services/bamboo_service.rb
app/models/project_services/bamboo_service.rb
+1
-1
app/models/project_services/bugzilla_service.rb
app/models/project_services/bugzilla_service.rb
+1
-1
app/models/project_services/buildkite_service.rb
app/models/project_services/buildkite_service.rb
+1
-1
app/models/project_services/chat_notification_service.rb
app/models/project_services/chat_notification_service.rb
+1
-1
app/models/project_services/custom_issue_tracker_service.rb
app/models/project_services/custom_issue_tracker_service.rb
+1
-1
app/models/project_services/drone_ci_service.rb
app/models/project_services/drone_ci_service.rb
+1
-1
app/models/project_services/external_wiki_service.rb
app/models/project_services/external_wiki_service.rb
+1
-1
app/models/project_services/gitlab_issue_tracker_service.rb
app/models/project_services/gitlab_issue_tracker_service.rb
+1
-1
app/models/project_services/jira_service.rb
app/models/project_services/jira_service.rb
+2
-2
app/models/project_services/kubernetes_service.rb
app/models/project_services/kubernetes_service.rb
+1
-1
app/models/project_services/mock_ci_service.rb
app/models/project_services/mock_ci_service.rb
+1
-1
app/models/project_services/prometheus_service.rb
app/models/project_services/prometheus_service.rb
+1
-1
app/models/project_services/redmine_service.rb
app/models/project_services/redmine_service.rb
+1
-1
app/models/project_services/teamcity_service.rb
app/models/project_services/teamcity_service.rb
+1
-1
app/models/remote_mirror.rb
app/models/remote_mirror.rb
+0
-1
app/services/projects/import_service.rb
app/services/projects/import_service.rb
+1
-1
app/validators/addressable_url_validator.rb
app/validators/addressable_url_validator.rb
+0
-45
app/validators/importable_url_validator.rb
app/validators/importable_url_validator.rb
+0
-11
app/validators/public_url_validator.rb
app/validators/public_url_validator.rb
+26
-0
app/validators/url_placeholder_validator.rb
app/validators/url_placeholder_validator.rb
+0
-32
app/validators/url_validator.rb
app/validators/url_validator.rb
+45
-7
changelogs/unreleased/fj-45059-add-validation-to-webhook.yml
changelogs/unreleased/fj-45059-add-validation-to-webhook.yml
+5
-0
lib/gitlab/url_blocker.rb
lib/gitlab/url_blocker.rb
+12
-5
spec/controllers/projects/mirrors_controller_spec.rb
spec/controllers/projects/mirrors_controller_spec.rb
+1
-1
spec/controllers/projects/services_controller_spec.rb
spec/controllers/projects/services_controller_spec.rb
+1
-1
spec/javascripts/integrations/integration_settings_form_spec.js
...avascripts/integrations/integration_settings_form_spec.js
+23
-0
spec/lib/gitlab/url_blocker_spec.rb
spec/lib/gitlab/url_blocker_spec.rb
+8
-2
spec/models/remote_mirror_spec.rb
spec/models/remote_mirror_spec.rb
+1
-1
spec/requests/api/commit_statuses_spec.rb
spec/requests/api/commit_statuses_spec.rb
+1
-1
spec/support/shared_examples/url_validator_examples.rb
spec/support/shared_examples/url_validator_examples.rb
+42
-0
spec/validators/public_url_validator_spec.rb
spec/validators/public_url_validator_spec.rb
+28
-0
spec/validators/url_validator_spec.rb
spec/validators/url_validator_spec.rb
+42
-26
No files found.
app/assets/javascripts/integrations/integration_settings_form.js
View file @
840f80d4
...
@@ -101,13 +101,19 @@ export default class IntegrationSettingsForm {
...
@@ -101,13 +101,19 @@ export default class IntegrationSettingsForm {
return
axios
.
put
(
this
.
testEndPoint
,
formData
)
return
axios
.
put
(
this
.
testEndPoint
,
formData
)
.
then
(({
data
})
=>
{
.
then
(({
data
})
=>
{
if
(
data
.
error
)
{
if
(
data
.
error
)
{
flash
(
`
${
data
.
message
}
${
data
.
service_response
}
`
,
'
alert
'
,
document
,
{
let
flashActions
;
if
(
data
.
test_failed
)
{
flashActions
=
{
title
:
'
Save anyway
'
,
title
:
'
Save anyway
'
,
clickHandler
:
(
e
)
=>
{
clickHandler
:
(
e
)
=>
{
e
.
preventDefault
();
e
.
preventDefault
();
this
.
$form
.
submit
();
this
.
$form
.
submit
();
},
},
});
};
}
flash
(
`
${
data
.
message
}
${
data
.
service_response
}
`
,
'
alert
'
,
document
,
flashActions
);
}
else
{
}
else
{
this
.
$form
.
submit
();
this
.
$form
.
submit
();
}
}
...
...
app/controllers/projects/services_controller.rb
View file @
840f80d4
...
@@ -41,13 +41,13 @@ class Projects::ServicesController < Projects::ApplicationController
...
@@ -41,13 +41,13 @@ class Projects::ServicesController < Projects::ApplicationController
if
outcome
[
:success
]
if
outcome
[
:success
]
{}
{}
else
else
{
error:
true
,
message:
'Test failed.'
,
service_response:
outcome
[
:result
].
to_s
}
{
error:
true
,
message:
'Test failed.'
,
service_response:
outcome
[
:result
].
to_s
,
test_failed:
true
}
end
end
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
(
','
)
,
test_failed:
false
}
end
end
rescue
Gitlab
::
HTTP
::
BlockedUrlError
=>
e
rescue
Gitlab
::
HTTP
::
BlockedUrlError
=>
e
{
error:
true
,
message:
'Test failed.'
,
service_response:
e
.
message
}
{
error:
true
,
message:
'Test failed.'
,
service_response:
e
.
message
,
test_failed:
true
}
end
end
def
success_message
def
success_message
...
...
app/models/badge.rb
View file @
840f80d4
...
@@ -18,7 +18,7 @@ class Badge < ActiveRecord::Base
...
@@ -18,7 +18,7 @@ class Badge < ActiveRecord::Base
scope
:order_created_at_asc
,
->
{
reorder
(
created_at: :asc
)
}
scope
:order_created_at_asc
,
->
{
reorder
(
created_at: :asc
)
}
validates
:link_url
,
:image_url
,
url
_placeholder:
{
protocols:
%w(http https)
,
placeholder_regex:
PLACEHOLDERS_REGEX
}
validates
:link_url
,
:image_url
,
url
:
{
protocols:
%w(http https)
}
validates
:type
,
presence:
true
validates
:type
,
presence:
true
def
rendered_link_url
(
project
=
nil
)
def
rendered_link_url
(
project
=
nil
)
...
...
app/models/environment.rb
View file @
840f80d4
...
@@ -32,7 +32,7 @@ class Environment < ActiveRecord::Base
...
@@ -32,7 +32,7 @@ class Environment < ActiveRecord::Base
validates
:external_url
,
validates
:external_url
,
length:
{
maximum:
255
},
length:
{
maximum:
255
},
allow_nil:
true
,
allow_nil:
true
,
addressable_
url:
true
url:
true
delegate
:stop_action
,
:manual_actions
,
to: :last_deployment
,
allow_nil:
true
delegate
:stop_action
,
:manual_actions
,
to: :last_deployment
,
allow_nil:
true
...
...
app/models/generic_commit_status.rb
View file @
840f80d4
class
GenericCommitStatus
<
CommitStatus
class
GenericCommitStatus
<
CommitStatus
before_validation
:set_default_values
before_validation
:set_default_values
validates
:target_url
,
addressable_
url:
true
,
validates
:target_url
,
url:
true
,
length:
{
maximum:
255
},
length:
{
maximum:
255
},
allow_nil:
true
allow_nil:
true
...
...
app/models/hooks/system_hook.rb
View file @
840f80d4
...
@@ -11,4 +11,9 @@ class SystemHook < WebHook
...
@@ -11,4 +11,9 @@ class SystemHook < WebHook
default_value_for
:push_events
,
false
default_value_for
:push_events
,
false
default_value_for
:repository_update_events
,
true
default_value_for
:repository_update_events
,
true
default_value_for
:merge_requests_events
,
false
default_value_for
:merge_requests_events
,
false
# Allow urls pointing localhost and the local network
def
allow_local_requests?
true
end
end
end
app/models/hooks/web_hook.rb
View file @
840f80d4
...
@@ -3,7 +3,9 @@ class WebHook < ActiveRecord::Base
...
@@ -3,7 +3,9 @@ class WebHook < ActiveRecord::Base
has_many
:web_hook_logs
,
dependent: :destroy
# rubocop:disable Cop/ActiveRecordDependent
has_many
:web_hook_logs
,
dependent: :destroy
# rubocop:disable Cop/ActiveRecordDependent
validates
:url
,
presence:
true
,
url:
true
validates
:url
,
presence:
true
,
public_url:
{
allow_localhost:
lambda
(
&
:allow_local_requests?
),
allow_local_network:
lambda
(
&
:allow_local_requests?
)
}
validates
:token
,
format:
{
without:
/\n/
}
validates
:token
,
format:
{
without:
/\n/
}
def
execute
(
data
,
hook_name
)
def
execute
(
data
,
hook_name
)
...
@@ -13,4 +15,9 @@ class WebHook < ActiveRecord::Base
...
@@ -13,4 +15,9 @@ class WebHook < ActiveRecord::Base
def
async_execute
(
data
,
hook_name
)
def
async_execute
(
data
,
hook_name
)
WebHookService
.
new
(
self
,
data
,
hook_name
).
async_execute
WebHookService
.
new
(
self
,
data
,
hook_name
).
async_execute
end
end
# Allow urls pointing localhost and the local network
def
allow_local_requests?
false
end
end
end
app/models/project.rb
View file @
840f80d4
...
@@ -289,8 +289,9 @@ class Project < ActiveRecord::Base
...
@@ -289,8 +289,9 @@ class Project < ActiveRecord::Base
validates
:namespace
,
presence:
true
validates
:namespace
,
presence:
true
validates
:name
,
uniqueness:
{
scope: :namespace_id
}
validates
:name
,
uniqueness:
{
scope: :namespace_id
}
validates
:import_url
,
addressable_url:
true
,
if: :external_import?
validates
:import_url
,
url:
{
protocols:
%w(http https ssh git)
,
validates
:import_url
,
importable_url:
true
,
if:
[
:external_import?
,
:import_url_changed?
]
allow_localhost:
false
,
ports:
VALID_IMPORT_PORTS
},
if:
[
:external_import?
,
:import_url_changed?
]
validates
:star_count
,
numericality:
{
greater_than_or_equal_to:
0
}
validates
:star_count
,
numericality:
{
greater_than_or_equal_to:
0
}
validate
:check_limit
,
on: :create
validate
:check_limit
,
on: :create
validate
:check_repository_path_availability
,
on: :update
,
if:
->
(
project
)
{
project
.
renamed?
}
validate
:check_repository_path_availability
,
on: :update
,
if:
->
(
project
)
{
project
.
renamed?
}
...
...
app/models/project_services/bamboo_service.rb
View file @
840f80d4
...
@@ -3,7 +3,7 @@ class BambooService < CiService
...
@@ -3,7 +3,7 @@ class BambooService < CiService
prop_accessor
:bamboo_url
,
:build_key
,
:username
,
:password
prop_accessor
:bamboo_url
,
:build_key
,
:username
,
:password
validates
:bamboo_url
,
presence:
true
,
url:
true
,
if: :activated?
validates
:bamboo_url
,
presence:
true
,
public_
url:
true
,
if: :activated?
validates
:build_key
,
presence:
true
,
if: :activated?
validates
:build_key
,
presence:
true
,
if: :activated?
validates
:username
,
validates
:username
,
presence:
true
,
presence:
true
,
...
...
app/models/project_services/bugzilla_service.rb
View file @
840f80d4
class
BugzillaService
<
IssueTrackerService
class
BugzillaService
<
IssueTrackerService
validates
:project_url
,
:issues_url
,
:new_issue_url
,
presence:
true
,
url:
true
,
if: :activated?
validates
:project_url
,
:issues_url
,
:new_issue_url
,
presence:
true
,
public_
url:
true
,
if: :activated?
prop_accessor
:title
,
:description
,
:project_url
,
:issues_url
,
:new_issue_url
prop_accessor
:title
,
:description
,
:project_url
,
:issues_url
,
:new_issue_url
...
...
app/models/project_services/buildkite_service.rb
View file @
840f80d4
...
@@ -8,7 +8,7 @@ class BuildkiteService < CiService
...
@@ -8,7 +8,7 @@ class BuildkiteService < CiService
prop_accessor
:project_url
,
:token
prop_accessor
:project_url
,
:token
boolean_accessor
:enable_ssl_verification
boolean_accessor
:enable_ssl_verification
validates
:project_url
,
presence:
true
,
url:
true
,
if: :activated?
validates
:project_url
,
presence:
true
,
public_
url:
true
,
if: :activated?
validates
:token
,
presence:
true
,
if: :activated?
validates
:token
,
presence:
true
,
if: :activated?
after_save
:compose_service_hook
,
if: :activated?
after_save
:compose_service_hook
,
if: :activated?
...
...
app/models/project_services/chat_notification_service.rb
View file @
840f80d4
...
@@ -8,7 +8,7 @@ class ChatNotificationService < Service
...
@@ -8,7 +8,7 @@ class ChatNotificationService < Service
prop_accessor
:webhook
,
:username
,
:channel
prop_accessor
:webhook
,
:username
,
:channel
boolean_accessor
:notify_only_broken_pipelines
,
:notify_only_default_branch
boolean_accessor
:notify_only_broken_pipelines
,
:notify_only_default_branch
validates
:webhook
,
presence:
true
,
url:
true
,
if: :activated?
validates
:webhook
,
presence:
true
,
public_
url:
true
,
if: :activated?
def
initialize_properties
def
initialize_properties
# Custom serialized properties initialization
# Custom serialized properties initialization
...
...
app/models/project_services/custom_issue_tracker_service.rb
View file @
840f80d4
class
CustomIssueTrackerService
<
IssueTrackerService
class
CustomIssueTrackerService
<
IssueTrackerService
validates
:project_url
,
:issues_url
,
:new_issue_url
,
presence:
true
,
url:
true
,
if: :activated?
validates
:project_url
,
:issues_url
,
:new_issue_url
,
presence:
true
,
public_
url:
true
,
if: :activated?
prop_accessor
:title
,
:description
,
:project_url
,
:issues_url
,
:new_issue_url
prop_accessor
:title
,
:description
,
:project_url
,
:issues_url
,
:new_issue_url
...
...
app/models/project_services/drone_ci_service.rb
View file @
840f80d4
...
@@ -4,7 +4,7 @@ class DroneCiService < CiService
...
@@ -4,7 +4,7 @@ class DroneCiService < CiService
prop_accessor
:drone_url
,
:token
prop_accessor
:drone_url
,
:token
boolean_accessor
:enable_ssl_verification
boolean_accessor
:enable_ssl_verification
validates
:drone_url
,
presence:
true
,
url:
true
,
if: :activated?
validates
:drone_url
,
presence:
true
,
public_
url:
true
,
if: :activated?
validates
:token
,
presence:
true
,
if: :activated?
validates
:token
,
presence:
true
,
if: :activated?
after_save
:compose_service_hook
,
if: :activated?
after_save
:compose_service_hook
,
if: :activated?
...
...
app/models/project_services/external_wiki_service.rb
View file @
840f80d4
class
ExternalWikiService
<
Service
class
ExternalWikiService
<
Service
prop_accessor
:external_wiki_url
prop_accessor
:external_wiki_url
validates
:external_wiki_url
,
presence:
true
,
url:
true
,
if: :activated?
validates
:external_wiki_url
,
presence:
true
,
public_
url:
true
,
if: :activated?
def
title
def
title
'External Wiki'
'External Wiki'
...
...
app/models/project_services/gitlab_issue_tracker_service.rb
View file @
840f80d4
class
GitlabIssueTrackerService
<
IssueTrackerService
class
GitlabIssueTrackerService
<
IssueTrackerService
include
Gitlab
::
Routing
include
Gitlab
::
Routing
validates
:project_url
,
:issues_url
,
:new_issue_url
,
presence:
true
,
url:
true
,
if: :activated?
validates
:project_url
,
:issues_url
,
:new_issue_url
,
presence:
true
,
public_
url:
true
,
if: :activated?
prop_accessor
:title
,
:description
,
:project_url
,
:issues_url
,
:new_issue_url
prop_accessor
:title
,
:description
,
:project_url
,
:issues_url
,
:new_issue_url
...
...
app/models/project_services/jira_service.rb
View file @
840f80d4
...
@@ -3,8 +3,8 @@ class JiraService < IssueTrackerService
...
@@ -3,8 +3,8 @@ class JiraService < IssueTrackerService
include
ApplicationHelper
include
ApplicationHelper
include
ActionView
::
Helpers
::
AssetUrlHelper
include
ActionView
::
Helpers
::
AssetUrlHelper
validates
:url
,
url:
true
,
presence:
true
,
if: :activated?
validates
:url
,
public_
url:
true
,
presence:
true
,
if: :activated?
validates
:api_url
,
url:
true
,
allow_blank:
true
validates
:api_url
,
public_
url:
true
,
allow_blank:
true
validates
:username
,
presence:
true
,
if: :activated?
validates
:username
,
presence:
true
,
if: :activated?
validates
:password
,
presence:
true
,
if: :activated?
validates
:password
,
presence:
true
,
if: :activated?
...
...
app/models/project_services/kubernetes_service.rb
View file @
840f80d4
...
@@ -24,7 +24,7 @@ class KubernetesService < DeploymentService
...
@@ -24,7 +24,7 @@ class KubernetesService < DeploymentService
prop_accessor
:ca_pem
prop_accessor
:ca_pem
with_options
presence:
true
,
if: :activated?
do
with_options
presence:
true
,
if: :activated?
do
validates
:api_url
,
url:
true
validates
:api_url
,
public_
url:
true
validates
:token
validates
:token
end
end
...
...
app/models/project_services/mock_ci_service.rb
View file @
840f80d4
...
@@ -3,7 +3,7 @@ class MockCiService < CiService
...
@@ -3,7 +3,7 @@ class MockCiService < CiService
ALLOWED_STATES
=
%w[failed canceled running pending success success_with_warnings skipped not_found]
.
freeze
ALLOWED_STATES
=
%w[failed canceled running pending success success_with_warnings skipped not_found]
.
freeze
prop_accessor
:mock_service_url
prop_accessor
:mock_service_url
validates
:mock_service_url
,
presence:
true
,
url:
true
,
if: :activated?
validates
:mock_service_url
,
presence:
true
,
public_
url:
true
,
if: :activated?
def
title
def
title
'MockCI'
'MockCI'
...
...
app/models/project_services/prometheus_service.rb
View file @
840f80d4
...
@@ -6,7 +6,7 @@ class PrometheusService < MonitoringService
...
@@ -6,7 +6,7 @@ class PrometheusService < MonitoringService
boolean_accessor
:manual_configuration
boolean_accessor
:manual_configuration
with_options
presence:
true
,
if: :manual_configuration?
do
with_options
presence:
true
,
if: :manual_configuration?
do
validates
:api_url
,
url:
true
validates
:api_url
,
public_
url:
true
end
end
before_save
:synchronize_service_state
before_save
:synchronize_service_state
...
...
app/models/project_services/redmine_service.rb
View file @
840f80d4
class
RedmineService
<
IssueTrackerService
class
RedmineService
<
IssueTrackerService
validates
:project_url
,
:issues_url
,
:new_issue_url
,
presence:
true
,
url:
true
,
if: :activated?
validates
:project_url
,
:issues_url
,
:new_issue_url
,
presence:
true
,
public_
url:
true
,
if: :activated?
prop_accessor
:title
,
:description
,
:project_url
,
:issues_url
,
:new_issue_url
prop_accessor
:title
,
:description
,
:project_url
,
:issues_url
,
:new_issue_url
...
...
app/models/project_services/teamcity_service.rb
View file @
840f80d4
...
@@ -3,7 +3,7 @@ class TeamcityService < CiService
...
@@ -3,7 +3,7 @@ class TeamcityService < CiService
prop_accessor
:teamcity_url
,
:build_type
,
:username
,
:password
prop_accessor
:teamcity_url
,
:build_type
,
:username
,
:password
validates
:teamcity_url
,
presence:
true
,
url:
true
,
if: :activated?
validates
:teamcity_url
,
presence:
true
,
public_
url:
true
,
if: :activated?
validates
:build_type
,
presence:
true
,
if: :activated?
validates
:build_type
,
presence:
true
,
if: :activated?
validates
:username
,
validates
:username
,
presence:
true
,
presence:
true
,
...
...
app/models/remote_mirror.rb
View file @
840f80d4
...
@@ -17,7 +17,6 @@ class RemoteMirror < ActiveRecord::Base
...
@@ -17,7 +17,6 @@ class RemoteMirror < ActiveRecord::Base
belongs_to
:project
,
inverse_of: :remote_mirrors
belongs_to
:project
,
inverse_of: :remote_mirrors
validates
:url
,
presence:
true
,
url:
{
protocols:
%w(ssh git http https)
,
allow_blank:
true
}
validates
:url
,
presence:
true
,
url:
{
protocols:
%w(ssh git http https)
,
allow_blank:
true
}
validates
:url
,
addressable_url:
true
,
if: :url_changed?
before_save
:set_new_remote_name
,
if: :mirror_url_changed?
before_save
:set_new_remote_name
,
if: :mirror_url_changed?
...
...
app/services/projects/import_service.rb
View file @
840f80d4
...
@@ -29,7 +29,7 @@ module Projects
...
@@ -29,7 +29,7 @@ module Projects
def
add_repository_to_project
def
add_repository_to_project
if
project
.
external_import?
&&
!
unknown_url?
if
project
.
external_import?
&&
!
unknown_url?
begin
begin
Gitlab
::
UrlBlocker
.
validate!
(
project
.
import_url
,
valid_
ports:
Project
::
VALID_IMPORT_PORTS
)
Gitlab
::
UrlBlocker
.
validate!
(
project
.
import_url
,
ports:
Project
::
VALID_IMPORT_PORTS
)
rescue
Gitlab
::
UrlBlocker
::
BlockedUrlError
=>
e
rescue
Gitlab
::
UrlBlocker
::
BlockedUrlError
=>
e
raise
Error
,
"Blocked import URL:
#{
e
.
message
}
"
raise
Error
,
"Blocked import URL:
#{
e
.
message
}
"
end
end
...
...
app/validators/addressable_url_validator.rb
deleted
100644 → 0
View file @
e206e328
# AddressableUrlValidator
#
# Custom validator for URLs. This is a stricter version of UrlValidator - it also checks
# for using the right protocol, but it actually parses the URL checking for any syntax errors.
# The regex is also different from `URI` as we use `Addressable::URI` here.
#
# By default, only URLs for http, https, ssh, and git protocols will be considered valid.
# Provide a `:protocols` option to configure accepted protocols.
#
# Example:
#
# class User < ActiveRecord::Base
# validates :personal_url, addressable_url: true
#
# validates :ftp_url, addressable_url: { protocols: %w(ftp) }
#
# validates :git_url, addressable_url: { protocols: %w(http https ssh git) }
# end
#
class
AddressableUrlValidator
<
ActiveModel
::
EachValidator
DEFAULT_OPTIONS
=
{
protocols:
%w(http https ssh git)
}.
freeze
def
validate_each
(
record
,
attribute
,
value
)
unless
valid_url?
(
value
)
record
.
errors
.
add
(
attribute
,
"must be a valid URL"
)
end
end
private
def
valid_url?
(
value
)
return
false
unless
value
valid_protocol?
(
value
)
&&
valid_uri?
(
value
)
end
def
valid_uri?
(
value
)
Gitlab
::
UrlSanitizer
.
valid?
(
value
)
end
def
valid_protocol?
(
value
)
options
=
DEFAULT_OPTIONS
.
merge
(
self
.
options
)
value
=~
/\A
#{
URI
.
regexp
(
options
[
:protocols
])
}
\z/
end
end
app/validators/importable_url_validator.rb
deleted
100644 → 0
View file @
e206e328
# ImportableUrlValidator
#
# This validator blocks projects from using dangerous import_urls to help
# protect against Server-side Request Forgery (SSRF).
class
ImportableUrlValidator
<
ActiveModel
::
EachValidator
def
validate_each
(
record
,
attribute
,
value
)
Gitlab
::
UrlBlocker
.
validate!
(
value
,
valid_ports:
Project
::
VALID_IMPORT_PORTS
)
rescue
Gitlab
::
UrlBlocker
::
BlockedUrlError
=>
e
record
.
errors
.
add
(
attribute
,
"is blocked:
#{
e
.
message
}
"
)
end
end
app/validators/public_url_validator.rb
0 → 100644
View file @
840f80d4
# PublicUrlValidator
#
# Custom validator for URLs. This validator works like UrlValidator but
# it blocks by default urls pointing to localhost or the local network.
#
# This validator accepts the same params UrlValidator does.
#
# Example:
#
# class User < ActiveRecord::Base
# validates :personal_url, public_url: true
#
# validates :ftp_url, public_url: { protocols: %w(ftp) }
#
# validates :git_url, public_url: { allow_localhost: true, allow_local_network: true}
# end
#
class
PublicUrlValidator
<
UrlValidator
private
def
default_options
# By default block all urls pointing to localhost or the local network
super
.
merge
(
allow_localhost:
false
,
allow_local_network:
false
)
end
end
app/validators/url_placeholder_validator.rb
deleted
100644 → 0
View file @
e206e328
# UrlValidator
#
# Custom validator for URLs.
#
# By default, only URLs for the HTTP(S) protocols will be considered valid.
# Provide a `:protocols` option to configure accepted protocols.
#
# Also, this validator can help you validate urls with placeholders inside.
# Usually, if you have a url like 'http://www.example.com/%{project_path}' the
# URI parser will reject that URL format. Provide a `:placeholder_regex` option
# to configure accepted placeholders.
#
# Example:
#
# class User < ActiveRecord::Base
# validates :personal_url, url: true
#
# validates :ftp_url, url: { protocols: %w(ftp) }
#
# validates :git_url, url: { protocols: %w(http https ssh git) }
#
# validates :placeholder_url, url: { placeholder_regex: /(project_path|project_id|default_branch)/ }
# end
#
class
UrlPlaceholderValidator
<
UrlValidator
def
validate_each
(
record
,
attribute
,
value
)
placeholder_regex
=
self
.
options
[
:placeholder_regex
]
value
=
value
.
gsub
(
/%{
#{
placeholder_regex
}
}/
,
'foo'
)
if
placeholder_regex
&&
value
super
(
record
,
attribute
,
value
)
end
end
app/validators/url_validator.rb
View file @
840f80d4
...
@@ -15,25 +15,63 @@
...
@@ -15,25 +15,63 @@
# validates :git_url, url: { protocols: %w(http https ssh git) }
# validates :git_url, url: { protocols: %w(http https ssh git) }
# end
# end
#
#
# This validator can also block urls pointing to localhost or the local network to
# protect against Server-side Request Forgery (SSRF), or check for the right port.
#
# Example:
# class User < ActiveRecord::Base
# validates :personal_url, url: { allow_localhost: false, allow_local_network: false}
#
# validates :web_url, url: { ports: [80, 443] }
# end
class
UrlValidator
<
ActiveModel
::
EachValidator
class
UrlValidator
<
ActiveModel
::
EachValidator
DEFAULT_PROTOCOLS
=
%w(http https)
.
freeze
attr_reader
:record
def
validate_each
(
record
,
attribute
,
value
)
def
validate_each
(
record
,
attribute
,
value
)
unless
valid_url?
(
value
)
@record
=
record
if
value
.
present?
value
.
strip!
else
record
.
errors
.
add
(
attribute
,
"must be a valid URL"
)
record
.
errors
.
add
(
attribute
,
"must be a valid URL"
)
end
end
Gitlab
::
UrlBlocker
.
validate!
(
value
,
blocker_args
)
rescue
Gitlab
::
UrlBlocker
::
BlockedUrlError
=>
e
record
.
errors
.
add
(
attribute
,
"is blocked:
#{
e
.
message
}
"
)
end
end
private
private
def
default_options
def
default_options
@default_options
||=
{
protocols:
%w(http https)
}
# By default the validator doesn't block any url based on the ip address
{
protocols:
DEFAULT_PROTOCOLS
,
ports:
[],
allow_localhost:
true
,
allow_local_network:
true
}
end
end
def
valid_url?
(
value
)
def
current_options
return
false
if
value
.
nil?
options
=
self
.
options
.
map
do
|
option
,
value
|
[
option
,
value
.
is_a?
(
Proc
)
?
value
.
call
(
record
)
:
value
]
end
.
to_h
options
=
default_options
.
merge
(
self
.
options
)
default_options
.
merge
(
options
)
end
value
.
strip!
def
blocker_args
value
=~
/\A
#{
URI
.
regexp
(
options
[
:protocols
])
}
\z/
current_options
.
slice
(
:allow_localhost
,
:allow_local_network
,
:protocols
,
:ports
).
tap
do
|
args
|
if
allow_setting_local_requests?
args
[
:allow_localhost
]
=
args
[
:allow_local_network
]
=
true
end
end
end
def
allow_setting_local_requests?
ApplicationSetting
.
current
&
.
allow_local_requests_from_hooks_and_services?
end
end
end
end
changelogs/unreleased/fj-45059-add-validation-to-webhook.yml
0 → 100644
View file @
840f80d4
---
title
:
Refactoring UrlValidators to include url blocking
merge_request
:
18686
author
:
type
:
changed
lib/gitlab/url_blocker.rb
View file @
840f80d4
...
@@ -5,7 +5,7 @@ module Gitlab
...
@@ -5,7 +5,7 @@ module Gitlab
BlockedUrlError
=
Class
.
new
(
StandardError
)
BlockedUrlError
=
Class
.
new
(
StandardError
)
class
<<
self
class
<<
self
def
validate!
(
url
,
allow_localhost:
false
,
allow_local_network:
true
,
valid_port
s:
[])
def
validate!
(
url
,
allow_localhost:
false
,
allow_local_network:
true
,
ports:
[],
protocol
s:
[])
return
true
if
url
.
nil?
return
true
if
url
.
nil?
begin
begin
...
@@ -18,7 +18,8 @@ module Gitlab
...
@@ -18,7 +18,8 @@ module Gitlab
return
true
if
internal?
(
uri
)
return
true
if
internal?
(
uri
)
port
=
uri
.
port
||
uri
.
default_port
port
=
uri
.
port
||
uri
.
default_port
validate_port!
(
port
,
valid_ports
)
if
valid_ports
.
any?
validate_protocol!
(
uri
.
scheme
,
protocols
)
validate_port!
(
port
,
ports
)
if
ports
.
any?
validate_user!
(
uri
.
user
)
validate_user!
(
uri
.
user
)
validate_hostname!
(
uri
.
hostname
)
validate_hostname!
(
uri
.
hostname
)
...
@@ -44,13 +45,19 @@ module Gitlab
...
@@ -44,13 +45,19 @@ module Gitlab
private
private
def
validate_port!
(
port
,
valid_
ports
)
def
validate_port!
(
port
,
ports
)
return
if
port
.
blank?
return
if
port
.
blank?
# Only ports under 1024 are restricted
# Only ports under 1024 are restricted
return
if
port
>=
1024
return
if
port
>=
1024
return
if
valid_
ports
.
include?
(
port
)
return
if
ports
.
include?
(
port
)
raise
BlockedUrlError
,
"Only allowed ports are
#{
valid_ports
.
join
(
', '
)
}
, and any over 1024"
raise
BlockedUrlError
,
"Only allowed ports are
#{
ports
.
join
(
', '
)
}
, and any over 1024"
end
def
validate_protocol!
(
protocol
,
protocols
)
if
protocol
.
blank?
||
(
protocols
.
any?
&&
!
protocols
.
include?
(
protocol
))
raise
BlockedUrlError
,
"Only allowed protocols are
#{
protocols
.
join
(
', '
)
}
"
end
end
end
def
validate_user!
(
value
)
def
validate_user!
(
value
)
...
...
spec/controllers/projects/mirrors_controller_spec.rb
View file @
840f80d4
...
@@ -54,7 +54,7 @@ describe Projects::MirrorsController do
...
@@ -54,7 +54,7 @@ describe Projects::MirrorsController do
do_put
(
project
,
remote_mirrors_attributes:
remote_mirror_attributes
)
do_put
(
project
,
remote_mirrors_attributes:
remote_mirror_attributes
)
expect
(
response
).
to
redirect_to
(
project_settings_repository_path
(
project
))
expect
(
response
).
to
redirect_to
(
project_settings_repository_path
(
project
))
expect
(
flash
[
:alert
]).
to
match
(
/
must be a valid URL
/
)
expect
(
flash
[
:alert
]).
to
match
(
/
Only allowed protocols are
/
)
end
end
it
'should not create a RemoteMirror object'
do
it
'should not create a RemoteMirror object'
do
...
...
spec/controllers/projects/services_controller_spec.rb
View file @
840f80d4
...
@@ -102,7 +102,7 @@ describe Projects::ServicesController do
...
@@ -102,7 +102,7 @@ describe Projects::ServicesController do
expect
(
response
.
status
).
to
eq
(
200
)
expect
(
response
.
status
).
to
eq
(
200
)
expect
(
JSON
.
parse
(
response
.
body
))
expect
(
JSON
.
parse
(
response
.
body
))
.
to
eq
(
'error'
=>
true
,
'message'
=>
'Test failed.'
,
'service_response'
=>
'Bad test'
)
.
to
eq
(
'error'
=>
true
,
'message'
=>
'Test failed.'
,
'service_response'
=>
'Bad test'
,
'test_failed'
=>
true
)
end
end
end
end
end
end
...
...
spec/javascripts/integrations/integration_settings_form_spec.js
View file @
840f80d4
...
@@ -143,6 +143,7 @@ describe('IntegrationSettingsForm', () => {
...
@@ -143,6 +143,7 @@ describe('IntegrationSettingsForm', () => {
error
:
true
,
error
:
true
,
message
:
errorMessage
,
message
:
errorMessage
,
service_response
:
'
some error
'
,
service_response
:
'
some error
'
,
test_failed
:
true
,
});
});
integrationSettingsForm
.
testSettings
(
formData
)
integrationSettingsForm
.
testSettings
(
formData
)
...
@@ -157,6 +158,27 @@ describe('IntegrationSettingsForm', () => {
...
@@ -157,6 +158,27 @@ describe('IntegrationSettingsForm', () => {
.
catch
(
done
.
fail
);
.
catch
(
done
.
fail
);
});
});
it
(
'
should not show error Flash with `Save anyway` action if ajax request responds with error in validation
'
,
(
done
)
=>
{
const
errorMessage
=
'
Validations failed.
'
;
mock
.
onPut
(
integrationSettingsForm
.
testEndPoint
).
reply
(
200
,
{
error
:
true
,
message
:
errorMessage
,
service_response
:
'
some error
'
,
test_failed
:
false
,
});
integrationSettingsForm
.
testSettings
(
formData
)
.
then
(()
=>
{
const
$flashContainer
=
$
(
'
.flash-container
'
);
expect
(
$flashContainer
.
find
(
'
.flash-text
'
).
text
().
trim
()).
toEqual
(
'
Validations failed. some error
'
);
expect
(
$flashContainer
.
find
(
'
.flash-action
'
)).
toBeDefined
();
expect
(
$flashContainer
.
find
(
'
.flash-action
'
).
text
().
trim
()).
toEqual
(
''
);
done
();
})
.
catch
(
done
.
fail
);
});
it
(
'
should submit form if ajax request responds without any error in test
'
,
(
done
)
=>
{
it
(
'
should submit form if ajax request responds without any error in test
'
,
(
done
)
=>
{
spyOn
(
integrationSettingsForm
.
$form
,
'
submit
'
);
spyOn
(
integrationSettingsForm
.
$form
,
'
submit
'
);
...
@@ -180,6 +202,7 @@ describe('IntegrationSettingsForm', () => {
...
@@ -180,6 +202,7 @@ describe('IntegrationSettingsForm', () => {
mock
.
onPut
(
integrationSettingsForm
.
testEndPoint
).
reply
(
200
,
{
mock
.
onPut
(
integrationSettingsForm
.
testEndPoint
).
reply
(
200
,
{
error
:
true
,
error
:
true
,
message
:
errorMessage
,
message
:
errorMessage
,
test_failed
:
true
,
});
});
integrationSettingsForm
.
testSettings
(
formData
)
integrationSettingsForm
.
testSettings
(
formData
)
...
...
spec/lib/gitlab/url_blocker_spec.rb
View file @
840f80d4
...
@@ -2,7 +2,7 @@ require 'spec_helper'
...
@@ -2,7 +2,7 @@ require 'spec_helper'
describe
Gitlab
::
UrlBlocker
do
describe
Gitlab
::
UrlBlocker
do
describe
'#blocked_url?'
do
describe
'#blocked_url?'
do
let
(
:
valid_
ports
)
{
Project
::
VALID_IMPORT_PORTS
}
let
(
:ports
)
{
Project
::
VALID_IMPORT_PORTS
}
it
'allows imports from configured web host and port'
do
it
'allows imports from configured web host and port'
do
import_url
=
"http://
#{
Gitlab
.
config
.
gitlab
.
host
}
:
#{
Gitlab
.
config
.
gitlab
.
port
}
/t.git"
import_url
=
"http://
#{
Gitlab
.
config
.
gitlab
.
host
}
:
#{
Gitlab
.
config
.
gitlab
.
port
}
/t.git"
...
@@ -19,7 +19,13 @@ describe Gitlab::UrlBlocker do
...
@@ -19,7 +19,13 @@ describe Gitlab::UrlBlocker do
end
end
it
'returns true for bad port'
do
it
'returns true for bad port'
do
expect
(
described_class
.
blocked_url?
(
'https://gitlab.com:25/foo/foo.git'
,
valid_ports:
valid_ports
)).
to
be
true
expect
(
described_class
.
blocked_url?
(
'https://gitlab.com:25/foo/foo.git'
,
ports:
ports
)).
to
be
true
end
it
'returns true for bad protocol'
do
expect
(
described_class
.
blocked_url?
(
'https://gitlab.com/foo/foo.git'
,
protocols:
[
'https'
])).
to
be
false
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'
,
protocols:
[
'http'
])).
to
be
true
end
end
it
'returns true for alternative version of 127.0.0.1 (0177.1)'
do
it
'returns true for alternative version of 127.0.0.1 (0177.1)'
do
...
...
spec/models/remote_mirror_spec.rb
View file @
840f80d4
...
@@ -12,8 +12,8 @@ describe RemoteMirror do
...
@@ -12,8 +12,8 @@ describe RemoteMirror do
context
'with an invalid URL'
do
context
'with an invalid URL'
do
it
'should not be valid'
do
it
'should not be valid'
do
remote_mirror
=
build
(
:remote_mirror
,
url:
'ftp://invalid.invalid'
)
remote_mirror
=
build
(
:remote_mirror
,
url:
'ftp://invalid.invalid'
)
expect
(
remote_mirror
).
not_to
be_valid
expect
(
remote_mirror
).
not_to
be_valid
expect
(
remote_mirror
.
errors
[
:url
].
size
).
to
eq
(
2
)
end
end
end
end
end
end
...
...
spec/requests/api/commit_statuses_spec.rb
View file @
840f80d4
...
@@ -304,7 +304,7 @@ describe API::CommitStatuses do
...
@@ -304,7 +304,7 @@ describe API::CommitStatuses do
it
'responds with bad request status and validation errors'
do
it
'responds with bad request status and validation errors'
do
expect
(
response
).
to
have_gitlab_http_status
(
400
)
expect
(
response
).
to
have_gitlab_http_status
(
400
)
expect
(
json_response
[
'message'
][
'target_url'
])
expect
(
json_response
[
'message'
][
'target_url'
])
.
to
include
'
must be a valid URL
'
.
to
include
'
is blocked: Only allowed protocols are http, https
'
end
end
end
end
end
end
...
...
spec/
validators/url_placeholder_validator_spec
.rb
→
spec/
support/shared_examples/url_validator_examples
.rb
View file @
840f80d4
require
'spec_helper'
RSpec
.
shared_examples
'url validator examples'
do
|
protocols
|
describe
UrlPlaceholderValidator
do
let
(
:validator
)
{
described_class
.
new
(
attributes:
[
:link_url
],
**
options
)
}
let
(
:validator
)
{
described_class
.
new
(
attributes:
[
:link_url
],
**
options
)
}
let!
(
:badge
)
{
build
(
:badge
)
}
let!
(
:badge
)
{
build
(
:badge
,
link_url:
'http://www.example.com'
)
}
let
(
:placeholder_url
)
{
'http://www.example.com/%{project_path}/%{project_id}/%{default_branch}/%{commit_sha}'
}
subject
{
validator
.
validate_each
(
badge
,
:link_url
,
badge
.
link_url
)
}
subject
{
validator
.
validate_each
(
badge
,
:link_url
,
badge
.
link_url
)
}
...
@@ -11,12 +8,12 @@ describe UrlPlaceholderValidator do
...
@@ -11,12 +8,12 @@ describe UrlPlaceholderValidator do
context
'with no options'
do
context
'with no options'
do
let
(
:options
)
{
{}
}
let
(
:options
)
{
{}
}
it
'allows http and https protocols by default'
do
it
"allows
#{
protocols
.
join
(
','
)
}
protocols by default"
do
expect
(
validator
.
send
(
:default_options
)[
:protocols
]).
to
eq
%w(http https)
expect
(
validator
.
send
(
:default_options
)[
:protocols
]).
to
eq
protocols
end
end
it
'checks that the url structure is valid'
do
it
'checks that the url structure is valid'
do
badge
.
link_url
=
placeholder_url
badge
.
link_url
=
"
#{
badge
.
link_url
}
:invalid_port"
subject
subject
...
@@ -24,16 +21,22 @@ describe UrlPlaceholderValidator do
...
@@ -24,16 +21,22 @@ describe UrlPlaceholderValidator do
end
end
end
end
context
'with placeholder regex'
do
context
'with protocols'
do
let
(
:options
)
{
{
placeholder_regex:
/(project_path|project_id|commit_sha|default_branch)/
}
}
let
(
:options
)
{
{
protocols:
%w[http]
}
}
it
'checks that the url is valid and obviate placeholders that match regex'
do
badge
.
link_url
=
placeholder_url
it
'allows urls with the defined protocols'
do
subject
subject
expect
(
badge
.
errors
.
empty?
).
to
be
true
expect
(
badge
.
errors
.
empty?
).
to
be
true
end
end
it
'add error if the url protocol does not match the selected ones'
do
badge
.
link_url
=
'https://www.example.com'
subject
expect
(
badge
.
errors
.
empty?
).
to
be
false
end
end
end
end
end
end
end
spec/validators/public_url_validator_spec.rb
0 → 100644
View file @
840f80d4
require
'spec_helper'
describe
PublicUrlValidator
do
include_examples
'url validator examples'
,
described_class
::
DEFAULT_PROTOCOLS
context
'by default'
do
let
(
:validator
)
{
described_class
.
new
(
attributes:
[
:link_url
])
}
let!
(
:badge
)
{
build
(
:badge
,
link_url:
'http://www.example.com'
)
}
subject
{
validator
.
validate_each
(
badge
,
:link_url
,
badge
.
link_url
)
}
it
'blocks urls pointing to localhost'
do
badge
.
link_url
=
'https://127.0.0.1'
subject
expect
(
badge
.
errors
.
empty?
).
to
be
false
end
it
'blocks urls pointing to the local network'
do
badge
.
link_url
=
'https://192.168.1.1'
subject
expect
(
badge
.
errors
.
empty?
).
to
be
false
end
end
end
spec/validators/url_validator_spec.rb
View file @
840f80d4
require
'spec_helper'
require
'spec_helper'
describe
UrlValidator
do
describe
UrlValidator
do
let
(
:validator
)
{
described_class
.
new
(
attributes:
[
:link_url
],
**
options
)
}
let!
(
:badge
)
{
build
(
:badge
,
link_url:
'http://www.example.com'
)
}
let!
(
:badge
)
{
build
(
:badge
)
}
subject
{
validator
.
validate_each
(
badge
,
:link_url
,
badge
.
link_url
)
}
subject
{
validator
.
validate_each
(
badge
,
:link_url
,
badge
.
link_url
)
}
describe
'#validates_each'
do
include_examples
'url validator examples'
,
described_class
::
DEFAULT_PROTOCOLS
context
'with no options'
do
let
(
:options
)
{
{}
}
context
'by default'
do
let
(
:validator
)
{
described_class
.
new
(
attributes:
[
:link_url
])
}
it
'does not block urls pointing to localhost'
do
badge
.
link_url
=
'https://127.0.0.1'
subject
it
'allows http and https protocols by default'
do
expect
(
badge
.
errors
.
empty?
).
to
be
true
expect
(
validator
.
send
(
:default_options
)[
:protocols
]).
to
eq
%w(http https)
end
end
it
'checks that the url structure is valid
'
do
it
'does not block urls pointing to the local network
'
do
badge
.
link_url
=
'http://www.google.es/%{whatever}
'
badge
.
link_url
=
'https://192.168.1.1
'
subject
subject
expect
(
badge
.
errors
.
empty?
).
to
be
fals
e
expect
(
badge
.
errors
.
empty?
).
to
be
tru
e
end
end
end
end
context
'with protocols
'
do
context
'when allow_localhost is set to false
'
do
let
(
:options
)
{
{
protocols:
%w(http)
}
}
let
(
:validator
)
{
described_class
.
new
(
attributes:
[
:link_url
],
allow_localhost:
false
)
}
it
'allows urls with the defined protocols
'
do
it
'blocks urls pointing to localhost
'
do
badge
.
link_url
=
'http://www.example.com
'
badge
.
link_url
=
'https://127.0.0.1
'
subject
subject
expect
(
badge
.
errors
.
empty?
).
to
be
true
expect
(
badge
.
errors
.
empty?
).
to
be
false
end
end
end
it
'add error if the url protocol does not match the selected ones'
do
context
'when allow_local_network is set to false'
do
badge
.
link_url
=
'https://www.example.com'
let
(
:validator
)
{
described_class
.
new
(
attributes:
[
:link_url
],
allow_local_network:
false
)
}
it
'blocks urls pointing to the local network'
do
badge
.
link_url
=
'https://192.168.1.1'
subject
subject
expect
(
badge
.
errors
.
empty?
).
to
be
false
expect
(
badge
.
errors
.
empty?
).
to
be
false
end
end
end
end
context
'when ports is set'
do
let
(
:validator
)
{
described_class
.
new
(
attributes:
[
:link_url
],
ports:
[
443
])
}
it
'blocks urls with a different port'
do
subject
expect
(
badge
.
errors
.
empty?
).
to
be
false
end
end
end
end
end
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment