diff options
author | Douwe Maan <douwe@selenight.nl> | 2017-05-20 03:46:40 +0300 |
---|---|---|
committer | Douwe Maan <douwe@selenight.nl> | 2017-05-24 04:38:24 +0300 |
commit | 4345bb8c507a11af694617187dea14284f48fb96 (patch) | |
tree | 20bc96bf6f90f8654492fb4e8b5cb1108e3d131d /app | |
parent | 3cfcbcf35badfdb21244f7f16c8640cd83b49205 (diff) |
Fix ambiguous routing issues by teaching router about reserved words
Diffstat (limited to 'app')
-rw-r--r-- | app/controllers/projects/refs_controller.rb | 2 | ||||
-rw-r--r-- | app/models/project.rb | 10 | ||||
-rw-r--r-- | app/models/user.rb | 2 | ||||
-rw-r--r-- | app/validators/dynamic_path_validator.rb | 215 | ||||
-rw-r--r-- | app/views/devise/shared/_signup_box.html.haml | 2 | ||||
-rw-r--r-- | app/views/shared/_group_form.html.haml | 2 |
6 files changed, 32 insertions, 201 deletions
diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb index 667f4870c7a..2a0b58fae7c 100644 --- a/app/controllers/projects/refs_controller.rb +++ b/app/controllers/projects/refs_controller.rb @@ -74,6 +74,6 @@ class Projects::RefsController < Projects::ApplicationController private def validate_ref_id - return not_found! if params[:id].present? && params[:id] !~ Gitlab::Regex.git_reference_regex + return not_found! if params[:id].present? && params[:id] !~ Gitlab::PathRegex.git_reference_regex end end diff --git a/app/models/project.rb b/app/models/project.rb index 65745fd6d37..29af57d7664 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -205,8 +205,8 @@ class Project < ActiveRecord::Base presence: true, dynamic_path: true, length: { maximum: 255 }, - format: { with: Gitlab::Regex.project_path_regex, - message: Gitlab::Regex.project_path_regex_message }, + format: { with: Gitlab::PathRegex.project_path_format_regex, + message: Gitlab::PathRegex.project_path_format_message }, uniqueness: { scope: :namespace_id } validates :namespace, presence: true @@ -380,11 +380,9 @@ class Project < ActiveRecord::Base end def reference_pattern - name_pattern = Gitlab::Regex::FULL_NAMESPACE_REGEX_STR - %r{ - ((?<namespace>#{name_pattern})\/)? - (?<project>#{name_pattern}) + ((?<namespace>#{Gitlab::PathRegex::FULL_NAMESPACE_FORMAT_REGEX})\/)? + (?<project>#{Gitlab::PathRegex::PROJECT_PATH_FORMAT_REGEX}) }x end diff --git a/app/models/user.rb b/app/models/user.rb index 837ab78228b..55614233230 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -367,7 +367,7 @@ class User < ActiveRecord::Base def reference_pattern %r{ #{Regexp.escape(reference_prefix)} - (?<user>#{Gitlab::Regex::FULL_NAMESPACE_REGEX_STR}) + (?<user>#{Gitlab::PathRegex::FULL_NAMESPACE_FORMAT_REGEX}) }x end diff --git a/app/validators/dynamic_path_validator.rb b/app/validators/dynamic_path_validator.rb index d992b0c3725..6819886ebf4 100644 --- a/app/validators/dynamic_path_validator.rb +++ b/app/validators/dynamic_path_validator.rb @@ -3,212 +3,45 @@ # 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 +# Values are checked for formatting and exclusion from a list of illegal 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[ - - - .well-known - abuse_reports - admin - all - api - assets - autocomplete - ci - dashboard - explore - files - groups - health_check - help - hooks - import - invites - issues - jwt - koding - member - merge_requests - new - notes - notification_settings - oauth - profile - projects - public - repository - robots.txt - s - search - sent_notifications - services - snippets - teams - u - unicorn_test - unsubscribes - uploads - users - ].freeze - - # This list should contain all words following `/*namespace_id/:project_id` in - # routes that contain a second wildcard. - # - # Example: - # /*namespace_id/:project_id/badges/*ref/build - # - # If `badges` was allowed as a project/group name, we would not be able to access the - # `badges` route for those projects: - # - # Consider a namespace with path `foo/bar` and a project called `badges`. - # The route to the build badge would then be `/foo/bar/badges/badges/master/build.svg` - # - # When accessing this path the route would be matched to the `badges` path - # with the following params: - # - namespace_id: `foo` - # - project_id: `bar` - # - ref: `badges/master` - # - # Failing to find the project, this would result in a 404. - # - # By rejecting `badges` the router can _count_ on the fact that `badges` will - # be preceded by the `namespace/project`. - WILDCARD_ROUTES = %w[ - badges - blame - blob - builds - commits - create - create_dir - edit - environments/folders - files - find_file - gitlab-lfs/objects - info/lfs/objects - new - preview - raw - refs - 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 - analytics - audit_events - avatar - edit - group_members - hooks - issues - labels - ldap - ldap_group_links - merge_requests - milestones - notification_setting - pipeline_quota - 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) - path =~ Gitlab::Regex.full_namespace_regex && !full_path_reserved?(path) - end - - def self.full_path_reserved?(path) - path = path.to_s.downcase - _project_part, namespace_parts = path.reverse.split('/', 2).map(&:reverse) - - wildcard_reserved?(path) || child_reserved?(namespace_parts) - end - - def self.child_reserved?(path) - return false unless path - - path !~ without_reserved_child_paths_regex - end + class << self + def valid_user_path?(path) + "#{path}/" =~ Gitlab::PathRegex.root_namespace_path_regex + end - def self.wildcard_reserved?(path) - return false unless path + def valid_group_path?(path) + "#{path}/" =~ Gitlab::PathRegex.full_namespace_path_regex + end - path !~ without_reserved_wildcard_paths_regex + def valid_project_path?(path) + "#{path}/" =~ Gitlab::PathRegex.full_project_path_regex + end end - delegate :full_path_reserved?, - :child_reserved?, - to: :class - - def path_reserved_for_record?(record, value) + def path_valid_for_record?(record, value) full_path = record.respond_to?(:full_path) ? record.full_path : value - # 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 - full_path_reserved?(full_path) + return true unless full_path + + case record + when Project + self.class.valid_project_path?(full_path) + when Group + self.class.valid_group_path?(full_path) + else # User or non-Group Namespace + self.class.valid_user_path?(full_path) end end def validate_each(record, attribute, value) - unless value =~ Gitlab::Regex.namespace_regex - record.errors.add(attribute, Gitlab::Regex.namespace_regex_message) + unless value =~ Gitlab::PathRegex.namespace_format_regex + record.errors.add(attribute, Gitlab::PathRegex.namespace_format_message) return end - if path_reserved_for_record?(record, value) + unless path_valid_for_record?(record, value) record.errors.add(attribute, "#{value} is a reserved name") end end diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml index a2f6a7ab1cb..d696577278d 100644 --- a/app/views/devise/shared/_signup_box.html.haml +++ b/app/views/devise/shared/_signup_box.html.haml @@ -8,7 +8,7 @@ = f.text_field :name, class: "form-control top", required: true, title: "This field is required." .username.form-group = f.label :username - = f.text_field :username, class: "form-control middle", pattern: Gitlab::Regex::NAMESPACE_REGEX_STR_JS, required: true, title: 'Please create a username with only alphanumeric characters.' + = f.text_field :username, class: "form-control middle", pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, required: true, title: 'Please create a username with only alphanumeric characters.' %p.validation-error.hide Username is already taken. %p.validation-success.hide Username is available. %p.validation-pending.hide Checking username availability... diff --git a/app/views/shared/_group_form.html.haml b/app/views/shared/_group_form.html.haml index 90ae3f06a98..8d5b5129454 100644 --- a/app/views/shared/_group_form.html.haml +++ b/app/views/shared/_group_form.html.haml @@ -15,7 +15,7 @@ %strong= parent.full_path + '/' = f.text_field :path, placeholder: 'open-source', class: 'form-control', autofocus: local_assigns[:autofocus] || false, required: true, - pattern: Gitlab::Regex::NAMESPACE_REGEX_STR_JS, + pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, title: 'Please choose a group path with no special characters.', "data-bind-in" => "#{'create_chat_team' if Gitlab.config.mattermost.enabled}" - if parent |