Skip to content
GitLab
Menu
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
heptapod
heptapod
Commits
b01230e782e2
Commit
b01230e7
authored
Apr 12, 2017
by
Bob Van Landuyt
Browse files
Rename `NamespaceValidator` to `DynamicPathValidator`
This reflects better that it validates paths instead of a namespace model
parent
1782d664ba6e
Changes
10
Hide whitespace changes
Inline
Side-by-side
app/models/namespace.rb
View file @
b01230e7
...
...
@@ -33,7 +33,7 @@
validates
:path
,
presence:
true
,
length:
{
maximum:
255
},
nam
espace
:
true
dy
nam
ic_path
:
true
validate
:nesting_level_allowed
...
...
app/models/project.rb
View file @
b01230e7
...
...
@@ -196,7 +196,7 @@
message:
Gitlab
::
Regex
.
project_name_regex_message
}
validates
:path
,
presence:
true
,
nam
espace
:
true
,
dy
nam
ic_path
:
true
,
length:
{
maximum:
255
},
format:
{
with:
Gitlab
::
Regex
.
project_path_regex
,
message:
Gitlab
::
Regex
.
project_path_regex_message
},
...
...
app/models/user.rb
View file @
b01230e7
...
...
@@ -118,7 +118,7 @@
presence:
true
,
numericality:
{
greater_than_or_equal_to:
0
,
less_than_or_equal_to:
Gitlab
::
Database
::
MAX_INT_VALUE
}
validates
:username
,
nam
espace
:
true
,
dy
nam
ic_path
:
true
,
presence:
true
,
uniqueness:
{
case_sensitive:
false
}
...
...
app/validators/dynamic_path_validator.rb
0 → 100644
View file @
b01230e7
# DynamicPathValidator
#
# Custom validator for GitLab path values.
# These paths are assigned to `Namespace` (& `Group` as a subclass) & `Project`
#
# Values are checked for formatting and exclusion from a list of reserved path
# names.
class
DynamicPathValidator
<
ActiveModel
::
EachValidator
# All routes that appear on the top level must be listed here.
# This will make sure that groups cannot be created with these names
# as these routes would be masked by the paths already in place.
#
# Example:
# /api/api-project
#
# the path `api` shouldn't be allowed because it would be masked by `api/*`
#
TOP_LEVEL_ROUTES
=
Set
.
new
(
%w[
.well-known
admin
all
assets
ci
dashboard
files
groups
help
hooks
issues
merge_requests
new
notes
profile
projects
public
repository
robots.txt
s
search
services
snippets
teams
u
unsubscribes
users
api
autocomplete
search
member
explore
uploads
import
notification_settings
abuse_reports
invites
help
koding
health_check
jwt
oauth
sent_notifications
]
).
freeze
# All project routes with wildcard argument must be listed here.
# Otherwise it can lead to routing issues when route considered as project name.
#
# Example:
# /group/project/tree/deploy_keys
#
# without tree as reserved name routing can match 'group/project' as group name,
# 'tree' as project name and 'deploy_keys' as route.
#
WILDCARD_ROUTES
=
Set
.
new
(
%w[tree commits wikis new edit create update logs_tree
preview blob blame raw files create_dir find_file
artifacts graphs refs badges objects folders file]
)
STRICT_RESERVED
=
(
TOP_LEVEL_ROUTES
|
WILDCARD_ROUTES
).
freeze
def
self
.
valid_full_path?
(
full_path
)
path_segments
=
full_path
.
split
(
'/'
)
root_segment
=
path_segments
.
shift
valid?
(
root_segment
,
type: :top_level
)
&&
valid_wildcard_segments?
(
path_segments
)
end
def
self
.
valid_wildcard_segments?
(
segments
)
segments
.
all?
{
|
segment
|
valid?
(
segment
,
type: :wildcard
)
}
end
def
self
.
valid?
(
value
,
type: :strict
)
!
reserved?
(
value
,
type:
type
)
&&
follow_format?
(
value
)
end
def
self
.
reserved?
(
value
,
type: :strict
)
case
type
when
:wildcard
WILDCARD_ROUTES
.
include?
(
value
)
when
:top_level
TOP_LEVEL_ROUTES
.
include?
(
value
)
else
STRICT_RESERVED
.
include?
(
value
)
end
end
def
self
.
follow_format?
(
value
)
value
=~
Gitlab
::
Regex
.
namespace_regex
end
delegate
:reserved?
,
:follow_format?
,
to: :class
def
validate_each
(
record
,
attribute
,
value
)
unless
follow_format?
(
value
)
record
.
errors
.
add
(
attribute
,
Gitlab
::
Regex
.
namespace_regex_message
)
end
if
reserved?
(
value
,
type:
validation_type
(
record
))
record
.
errors
.
add
(
attribute
,
"
#{
value
}
is a reserved name"
)
end
end
def
validation_type
(
record
)
case
record
when
Namespace
record
.
has_parent?
?
:wildcard
:
:top_level
when
Project
:wildcard
when
User
:top_level
else
:strict
end
end
end
app/validators/namespace_validator.rb
deleted
100644 → 0
View file @
1782d664
# NamespaceValidator
#
# Custom validator for GitLab namespace values.
#
# Values are checked for formatting and exclusion from a list of reserved path
# names.
class
NamespaceValidator
<
ActiveModel
::
EachValidator
# All routes that appear on the top level must be listed here.
# This will make sure that groups cannot be created with these names
# as these routes would be masked by the paths already in place.
#
# Example:
# /api/api-project
#
# the path `api` shouldn't be allowed because it would be masked by `api/*`
#
TOP_LEVEL_ROUTES
=
Set
.
new
(
%w[
.well-known
admin
all
assets
ci
dashboard
files
groups
help
hooks
issues
merge_requests
new
notes
profile
projects
public
repository
robots.txt
s
search
services
snippets
teams
u
unsubscribes
users
api
autocomplete
search
member
explore
uploads
import
notification_settings
abuse_reports
invites
help
koding
health_check
jwt
oauth
sent_notifications
]
).
freeze
# All project routes with wildcard argument must be listed here.
# Otherwise it can lead to routing issues when route considered as project name.
#
# Example:
# /group/project/tree/deploy_keys
#
# without tree as reserved name routing can match 'group/project' as group name,
# 'tree' as project name and 'deploy_keys' as route.
#
WILDCARD_ROUTES
=
Set
.
new
(
%w[tree commits wikis new edit create update logs_tree
preview blob blame raw files create_dir find_file
artifacts graphs refs badges objects folders file]
)
STRICT_RESERVED
=
(
TOP_LEVEL_ROUTES
|
WILDCARD_ROUTES
).
freeze
def
self
.
valid_full_path?
(
full_path
)
path_segments
=
full_path
.
split
(
'/'
)
root_segment
=
path_segments
.
shift
valid?
(
root_segment
,
type: :top_level
)
&&
valid_wildcard_segments?
(
path_segments
)
end
def
self
.
valid_wildcard_segments?
(
segments
)
segments
.
all?
{
|
segment
|
valid?
(
segment
,
type: :wildcard
)
}
end
def
self
.
valid?
(
value
,
type: :strict
)
!
reserved?
(
value
,
type:
type
)
&&
follow_format?
(
value
)
end
def
self
.
reserved?
(
value
,
type: :strict
)
case
type
when
:wildcard
WILDCARD_ROUTES
.
include?
(
value
)
when
:top_level
TOP_LEVEL_ROUTES
.
include?
(
value
)
else
STRICT_RESERVED
.
include?
(
value
)
end
end
def
self
.
follow_format?
(
value
)
value
=~
Gitlab
::
Regex
.
namespace_regex
end
delegate
:reserved?
,
:follow_format?
,
to: :class
def
validate_each
(
record
,
attribute
,
value
)
unless
follow_format?
(
value
)
record
.
errors
.
add
(
attribute
,
Gitlab
::
Regex
.
namespace_regex_message
)
end
if
reserved?
(
value
,
type:
validation_type
(
record
))
record
.
errors
.
add
(
attribute
,
"
#{
value
}
is a reserved name"
)
end
end
def
validation_type
(
record
)
case
record
when
Namespace
record
.
has_parent?
?
:wildcard
:
:top_level
when
Project
:wildcard
else
:strict
end
end
end
lib/constraints/group_url_constrainer.rb
View file @
b01230e7
...
...
@@ -2,7 +2,7 @@
def
matches?
(
request
)
id
=
request
.
params
[
:id
]
return
false
unless
Namespace
Validator
.
valid_full_path?
(
id
)
return
false
unless
DynamicPath
Validator
.
valid_full_path?
(
id
)
Group
.
find_by_full_path
(
id
).
present?
end
...
...
lib/constraints/project_url_constrainer.rb
View file @
b01230e7
...
...
@@ -4,7 +4,7 @@
project_path
=
request
.
params
[
:project_id
]
||
request
.
params
[
:id
]
full_path
=
namespace_path
+
'/'
+
project_path
unless
Namespace
Validator
.
valid_full_path?
(
full_path
)
unless
DynamicPath
Validator
.
valid_full_path?
(
full_path
)
return
false
end
...
...
lib/gitlab/etag_caching/router.rb
View file @
b01230e7
...
...
@@ -10,7 +10,7 @@
# - Ending in `issues/id`/rendered_title` for the `issue_title` route
USED_IN_ROUTES
=
%w[noteable issue notes issues renderred_title
commit pipelines merge_requests new]
.
freeze
RESERVED_WORDS
=
Namespace
Validator
::
WILDCARD_ROUTES
-
USED_IN_ROUTES
RESERVED_WORDS
=
DynamicPath
Validator
::
WILDCARD_ROUTES
-
USED_IN_ROUTES
RESERVED_WORDS_REGEX
=
Regexp
.
union
(
*
RESERVED_WORDS
)
ROUTES
=
[
Gitlab
::
EtagCaching
::
Router
::
Route
.
new
(
...
...
spec/validators/dynamic_path_validator_spec.rb
0 → 100644
View file @
b01230e7
require
'spec_helper'
describe
DynamicPathValidator
do
let
(
:validator
)
{
described_class
.
new
(
attributes:
[
:path
])
}
# Pass in a full path to remove the format segment:
# `/ci/lint(.:format)` -> `/ci/lint`
def
without_format
(
path
)
path
.
split
(
'('
,
2
)[
0
]
end
# Pass in a full path and get the last segment before a wildcard
# That's not a parameter
# `/*namespace_id/:project_id/builds/artifacts/*ref_name_and_path`
# -> 'artifacts'
def
segment_before_last_wildcard
(
path
)
path_segments
=
path
.
split
(
'/'
).
reject
{
|
segment
|
segment
.
empty?
}
last_wildcard_index
=
path_segments
.
rindex
{
|
part
|
part
.
starts_with?
(
'*'
)
}
index_of_non_param_segment
=
last_wildcard_index
-
1
part_before_wildcard
=
path_segments
[
index_of_non_param_segment
]
while
parameter?
(
part_before_wildcard
)
index_of_non_param_segment
-=
1
part_before_wildcard
=
path_segments
[
index_of_non_param_segment
]
end
part_before_wildcard
end
def
parameter?
(
path_segment
)
path_segment
.
starts_with?
(
':'
)
||
path_segment
.
starts_with?
(
'*'
)
end
let
(
:all_routes
)
do
Rails
.
application
.
routes
.
routes
.
routes
.
map
{
|
r
|
r
.
path
.
spec
.
to_s
}
end
let
(
:routes_without_format
)
{
all_routes
.
map
{
|
path
|
without_format
(
path
)
}
}
# Routes not starting with `/:` or `/*`
# all routes not starting with a param
let
(
:routes_not_starting_in_wildcard
)
{
routes_without_format
.
select
{
|
p
|
p
!~
%r{^/[:*]}
}
}
# All routes that start with a namespaced path, that have 1 or more
# path-segments before having another wildcard parameter.
# - Starting with paths:
# - `/*namespace_id/:project_id/`
# - `/*namespace_id/:id/`
# - Followed by one or more path-parts not starting with `:` or `/`
# - Followed by a path-part that includes a wildcard parameter `*`
# At the time of writing these routes match: http://rubular.com/r/QDxulzZlxZ
STARTING_WITH_NAMESPACE
=
/^\/\*namespace_id\/:(project_)?id/
NON_PARAM_PARTS
=
/[^:*][a-z\-_\/]*/
ANY_OTHER_PATH_PART
=
/[a-z\-_\/:]*/
WILDCARD_SEGMENT
=
/\*/
let
(
:namespaced_wildcard_routes
)
do
routes_without_format
.
select
do
|
p
|
p
=~
%r{
#{
STARTING_WITH_NAMESPACE
}
\/
#{
NON_PARAM_PARTS
}
\/
#{
ANY_OTHER_PATH_PART
}#{
WILDCARD_SEGMENT
}
}
end
end
describe
'TOP_LEVEL_ROUTES'
do
it
'includes all the top level namespaces'
do
top_level_words
=
routes_not_starting_in_wildcard
.
map
{
|
p
|
p
.
split
(
'/'
)[
1
]
}
.
# Get the first part of the path
compact
.
uniq
expect
(
described_class
::
TOP_LEVEL_ROUTES
).
to
include
(
*
top_level_words
)
end
end
describe
'WILDCARD_ROUTES'
do
it
'includes all paths that can be used after a namespace/project path'
do
all_wildcard_paths
=
namespaced_wildcard_routes
.
map
do
|
path
|
segment_before_last_wildcard
(
path
)
end
.
uniq
expect
(
described_class
::
WILDCARD_ROUTES
).
to
include
(
*
all_wildcard_paths
)
end
end
describe
'#valid_full_path'
do
it
"isn't valid when the top level is reserved"
do
test_path
=
'u/should-be-a/reserved-word'
expect
(
described_class
.
valid_full_path?
(
test_path
)).
to
be
(
false
)
end
it
"isn't valid if any of the path segments is reserved"
do
test_path
=
'the-wildcard/wikis/is-a-reserved-path'
expect
(
described_class
.
valid_full_path?
(
test_path
)).
to
be
(
false
)
end
it
"is valid if the path doen't contain reserved words"
do
test_path
=
'there-are/no-wildcards/in-this-path'
expect
(
described_class
.
valid_full_path?
(
test_path
)).
to
be
(
true
)
end
end
describe
'#validation_type'
do
it
'uses top level validation for groups without parent'
do
group
=
build
(
:group
)
type
=
validator
.
validation_type
(
group
)
expect
(
type
).
to
eq
(
:top_level
)
end
it
'uses wildcard validation for groups with a parent'
do
group
=
build
(
:group
,
parent:
create
(
:group
))
type
=
validator
.
validation_type
(
group
)
expect
(
type
).
to
eq
(
:wildcard
)
end
it
'uses wildcard validation for a project'
do
project
=
build
(
:project
)
type
=
validator
.
validation_type
(
project
)
expect
(
type
).
to
eq
(
:wildcard
)
end
it
'uses strict validation for everything else'
do
type
=
validator
.
validation_type
(
double
)
expect
(
type
).
to
eq
(
:strict
)
end
end
end
spec/validators/namespace_validator_spec.rb
deleted
100644 → 0
View file @
1782d664
require
'spec_helper'
describe
NamespaceValidator
do
let
(
:validator
)
{
described_class
.
new
(
attributes:
[
:path
])
}
# Pass in a full path to remove the format segment:
# `/ci/lint(.:format)` -> `/ci/lint`
def
without_format
(
path
)
path
.
split
(
'('
,
2
)[
0
]
end
# Pass in a full path and get the last segment before a wildcard
# That's not a parameter
# `/*namespace_id/:project_id/builds/artifacts/*ref_name_and_path`
# -> 'artifacts'
def
segment_before_last_wildcard
(
path
)
path_segments
=
path
.
split
(
'/'
).
reject
{
|
segment
|
segment
.
empty?
}
last_wildcard_index
=
path_segments
.
rindex
{
|
part
|
part
.
starts_with?
(
'*'
)
}
index_of_non_param_segment
=
last_wildcard_index
-
1
part_before_wildcard
=
path_segments
[
index_of_non_param_segment
]
while
parameter?
(
part_before_wildcard
)
index_of_non_param_segment
-=
1
part_before_wildcard
=
path_segments
[
index_of_non_param_segment
]
end
part_before_wildcard
end
def
parameter?
(
path_segment
)
path_segment
.
starts_with?
(
':'
)
||
path_segment
.
starts_with?
(
'*'
)
end
let
(
:all_routes
)
do
Rails
.
application
.
routes
.
routes
.
routes
.
map
{
|
r
|
r
.
path
.
spec
.
to_s
}
end
let
(
:routes_without_format
)
{
all_routes
.
map
{
|
path
|
without_format
(
path
)
}
}
# Routes not starting with `/:` or `/*`
# all routes not starting with a param
let
(
:routes_not_starting_in_wildcard
)
{
routes_without_format
.
select
{
|
p
|
p
!~
%r{^/[:*]}
}
}
# All routes that start with a namespaced path, that have 1 or more
# path-segments before having another wildcard parameter.
# - Starting with paths:
# - `/*namespace_id/:project_id/`
# - `/*namespace_id/:id/`
# - Followed by one or more path-parts not starting with `:` or `/`
# - Followed by a path-part that includes a wildcard parameter `*`
# At the time of writing these routes match: http://rubular.com/r/QDxulzZlxZ
STARTING_WITH_NAMESPACE
=
/^\/\*namespace_id\/:(project_)?id/
NON_PARAM_PARTS
=
/[^:*][a-z\-_\/]*/
ANY_OTHER_PATH_PART
=
/[a-z\-_\/:]*/
WILDCARD_SEGMENT
=
/\*/
let
(
:namespaced_wildcard_routes
)
do
routes_without_format
.
select
do
|
p
|
p
=~
%r{
#{
STARTING_WITH_NAMESPACE
}
\/
#{
NON_PARAM_PARTS
}
\/
#{
ANY_OTHER_PATH_PART
}#{
WILDCARD_SEGMENT
}
}
end
end
describe
'TOP_LEVEL_ROUTES'
do
it
'includes all the top level namespaces'
do
top_level_words
=
routes_not_starting_in_wildcard
.
map
{
|
p
|
p
.
split
(
'/'
)[
1
]
}
.
# Get the first part of the path
compact
.
uniq
expect
(
described_class
::
TOP_LEVEL_ROUTES
).
to
include
(
*
top_level_words
)
end
end
describe
'WILDCARD_ROUTES'
do
it
'includes all paths that can be used after a namespace/project path'
do
all_wildcard_paths
=
namespaced_wildcard_routes
.
map
do
|
path
|
segment_before_last_wildcard
(
path
)
end
.
uniq
expect
(
described_class
::
WILDCARD_ROUTES
).
to
include
(
*
all_wildcard_paths
)
end
end
describe
'#valid_full_path'
do
it
"isn't valid when the top level is reserved"
do
test_path
=
'u/should-be-a/reserved-word'
expect
(
described_class
.
valid_full_path?
(
test_path
)).
to
be
(
false
)
end
it
"isn't valid if any of the path segments is reserved"
do
test_path
=
'the-wildcard/wikis/is-a-reserved-path'
expect
(
described_class
.
valid_full_path?
(
test_path
)).
to
be
(
false
)
end
it
"is valid if the path doen't contain reserved words"
do
test_path
=
'there-are/no-wildcards/in-this-path'
expect
(
described_class
.
valid_full_path?
(
test_path
)).
to
be
(
true
)
end
end
describe
'#validation_type'
do
it
'uses top level validation for groups without parent'
do
group
=
build
(
:group
)
type
=
validator
.
validation_type
(
group
)
expect
(
type
).
to
eq
(
:top_level
)
end
it
'uses wildcard validation for groups with a parent'
do
group
=
build
(
:group
,
parent:
create
(
:group
))
type
=
validator
.
validation_type
(
group
)
expect
(
type
).
to
eq
(
:wildcard
)
end
it
'uses wildcard validation for a project'
do
project
=
build
(
:project
)
type
=
validator
.
validation_type
(
project
)
expect
(
type
).
to
eq
(
:wildcard
)
end
it
'uses strict validation for everything else'
do
type
=
validator
.
validation_type
(
double
)
expect
(
type
).
to
eq
(
:strict
)
end
end
end
Write
Preview
Supports
Markdown
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