1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
|
# 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
|