gitlab-org--gitlab-foss/app/validators/dynamic_path_validator.rb

195 lines
4.8 KiB
Ruby
Raw Normal View History

# DynamicPathValidator
2015-12-07 16:17:12 -05:00
#
# Custom validator for GitLab path values.
# These paths are assigned to `Namespace` (& `Group` as a subclass) & `Project`
2015-12-07 16:17:12 -05:00
#
# 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 = %w[
2017-04-24 06:38:09 -04:00
-
.well-known
2017-04-24 06:38:09 -04:00
abuse_reports
admin
all
2017-04-24 06:38:09 -04:00
api
assets
2017-04-24 06:38:09 -04:00
autocomplete
ci
dashboard
2017-04-24 06:38:09 -04:00
explore
files
groups
2017-04-24 06:38:09 -04:00
health_check
help
hooks
2017-04-24 06:38:09 -04:00
import
invites
issues
2017-04-24 06:38:09 -04:00
jwt
koding
member
merge_requests
2016-01-08 04:19:22 -05:00
new
notes
2017-04-24 06:38:09 -04:00
notification_settings
oauth
profile
projects
public
repository
robots.txt
s
search
2017-04-24 06:38:09 -04:00
sent_notifications
services
snippets
teams
u
unicorn_test
unsubscribes
uploads
2017-04-24 06:38:09 -04:00
users
].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 = %w[
2017-04-24 06:38:09 -04:00
badges
blame
blob
builds
2017-04-24 06:38:09 -04:00
commits
create
create_dir
edit
environments/folders
files
find_file
gitlab-lfs/objects
info/lfs/objects
new
preview
raw
refs
2017-04-24 06:38:09 -04:00
tree
update
wikis
].freeze
# These are all the paths that follow `/groups/*id/ or `/groups/*group_id`
# We need to reject these because we have a `/groups/*id` page that is the same
# as the `/*id`.
#
# If we would allow a subgroup to be created with the name `activity` then
# this group would not be accessible through `/groups/parent/activity` since
# this would map to the activity-page of it's parent.
GROUP_ROUTES = %w[
activity
avatar
edit
group_members
issues
labels
merge_requests
milestones
projects
subgroups
].freeze
CHILD_ROUTES = (WILDCARD_ROUTES | GROUP_ROUTES).freeze
def self.without_reserved_wildcard_paths_regex
@without_reserved_wildcard_paths_regex ||= regex_excluding_child_paths(WILDCARD_ROUTES)
end
def self.without_reserved_child_paths_regex
@without_reserved_child_paths_regex ||= regex_excluding_child_paths(CHILD_ROUTES)
end
# This is used to validate a full path.
# It doesn't match paths
# - Starting with one of the top level words
# - Containing one of the child level words in the middle of a path
def self.regex_excluding_child_paths(child_routes)
reserved_top_level_words = Regexp.union(TOP_LEVEL_ROUTES)
not_starting_in_reserved_word = %r{\A/?(?!(#{reserved_top_level_words})(/|\z))}
reserved_child_level_words = Regexp.union(child_routes)
not_containing_reserved_child = %r{(?!\S+/(#{reserved_child_level_words})(/|\z))}
%r{#{not_starting_in_reserved_word}
#{not_containing_reserved_child}
#{Gitlab::Regex.full_namespace_regex}}x
end
def self.valid?(path)
2017-05-02 04:32:31 -04:00
path =~ Gitlab::Regex.full_namespace_regex && !full_path_reserved?(path)
end
2017-05-02 04:32:31 -04:00
def self.full_path_reserved?(path)
path = path.to_s.downcase
2017-05-02 04:32:31 -04:00
_project_part, namespace_parts = path.reverse.split('/', 2).map(&:reverse)
2017-05-02 04:32:31 -04:00
wildcard_reserved?(path) || child_reserved?(namespace_parts)
end
2017-05-02 04:32:31 -04:00
def self.child_reserved?(path)
return false unless path
path !~ without_reserved_child_paths_regex
end
def self.wildcard_reserved?(path)
return false unless path
path !~ without_reserved_wildcard_paths_regex
end
2017-05-02 04:32:31 -04:00
delegate :full_path_reserved?,
:child_reserved?,
to: :class
2017-05-02 04:32:31 -04:00
def path_reserved_for_record?(record, value)
full_path = record.respond_to?(:full_path) ? record.full_path : value
2017-05-02 04:32:31 -04:00
# For group paths the entire path cannot contain a reserved child word
# The path doesn't contain the last `_project_part` so we need to validate
# if the entire path.
# Example:
# A *group* with full path `parent/activity` is reserved.
# A *project* with full path `parent/activity` is allowed.
if record.is_a? Group
child_reserved?(full_path)
else
2017-05-02 04:32:31 -04:00
full_path_reserved?(full_path)
end
end
2015-12-07 16:17:12 -05:00
def validate_each(record, attribute, value)
unless value =~ Gitlab::Regex.namespace_regex
2015-12-07 16:17:12 -05:00
record.errors.add(attribute, Gitlab::Regex.namespace_regex_message)
end
2017-05-02 04:32:31 -04:00
if path_reserved_for_record?(record, value)
record.errors.add(attribute, "#{value} is a reserved name")
end
end
2015-12-07 16:17:12 -05:00
end