Merge branch 'revert-b0498c17' into 'master'
Refactor `DynamicPathValidator` and `GitLab::Regex` some more See merge request !11693
This commit is contained in:
commit
516e3532c6
|
@ -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
|
||||
|
|
|
@ -205,8 +205,8 @@ class Project < ActiveRecord::Base
|
|||
presence: true,
|
||||
dynamic_path: true,
|
||||
length: { maximum: 255 },
|
||||
format: { with: Gitlab::Regex.project_path_format_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
|
||||
|
||||
|
|
|
@ -368,7 +368,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
|
||||
|
||||
|
|
|
@ -3,16 +3,20 @@
|
|||
# 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
|
||||
class << self
|
||||
def valid_namespace_path?(path)
|
||||
"#{path}/" =~ Gitlab::Regex.full_namespace_path_regex
|
||||
def valid_user_path?(path)
|
||||
"#{path}/" =~ Gitlab::PathRegex.root_namespace_path_regex
|
||||
end
|
||||
|
||||
def valid_group_path?(path)
|
||||
"#{path}/" =~ Gitlab::PathRegex.full_namespace_path_regex
|
||||
end
|
||||
|
||||
def valid_project_path?(path)
|
||||
"#{path}/" =~ Gitlab::Regex.full_project_path_regex
|
||||
"#{path}/" =~ Gitlab::PathRegex.full_project_path_regex
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -24,14 +28,16 @@ class DynamicPathValidator < ActiveModel::EachValidator
|
|||
case record
|
||||
when Project
|
||||
self.class.valid_project_path?(full_path)
|
||||
else
|
||||
self.class.valid_namespace_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
|
||||
|
||||
|
|
|
@ -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...
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
require 'sidekiq/web'
|
||||
require 'sidekiq/cron/web'
|
||||
require 'constraints/group_url_constrainer'
|
||||
|
||||
Rails.application.routes.draw do
|
||||
concern :access_requestable do
|
||||
|
@ -85,20 +84,6 @@ Rails.application.routes.draw do
|
|||
|
||||
root to: "root#index"
|
||||
|
||||
# Since group show page is wildcard routing
|
||||
# we want all other routing to be checked before matching this one
|
||||
constraints(GroupUrlConstrainer.new) do
|
||||
scope(path: '*id',
|
||||
as: :group,
|
||||
constraints: { id: Gitlab::Regex.namespace_route_regex, format: /(html|json|atom)/ },
|
||||
controller: :groups) do
|
||||
get '/', action: :show
|
||||
patch '/', action: :update
|
||||
put '/', action: :update
|
||||
delete '/', action: :destroy
|
||||
end
|
||||
end
|
||||
|
||||
draw :test if Rails.env.test?
|
||||
|
||||
get '*unmatched_route', to: 'application#route_not_found'
|
||||
|
|
|
@ -36,7 +36,7 @@ namespace :admin do
|
|||
|
||||
scope(path: 'groups/*id',
|
||||
controller: :groups,
|
||||
constraints: { id: Gitlab::Regex.namespace_route_regex, format: /(html|json|atom)/ }) do
|
||||
constraints: { id: Gitlab::PathRegex.full_namespace_route_regex, format: /(html|json|atom)/ }) do
|
||||
|
||||
scope(as: :group) do
|
||||
put :members_update
|
||||
|
@ -76,10 +76,10 @@ namespace :admin do
|
|||
|
||||
scope(path: 'projects/*namespace_id',
|
||||
as: :namespace,
|
||||
constraints: { namespace_id: Gitlab::Regex.namespace_route_regex }) do
|
||||
constraints: { namespace_id: Gitlab::PathRegex.full_namespace_route_regex }) do
|
||||
resources(:projects,
|
||||
path: '/',
|
||||
constraints: { id: Gitlab::Regex.project_route_regex },
|
||||
constraints: { id: Gitlab::PathRegex.project_route_regex },
|
||||
only: [:show]) do
|
||||
|
||||
member do
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
scope(path: '*namespace_id/:project_id',
|
||||
format: nil,
|
||||
constraints: { namespace_id: Gitlab::Regex.namespace_route_regex }) do
|
||||
scope(constraints: { project_id: Gitlab::Regex.project_git_route_regex }, module: :projects) do
|
||||
constraints: { namespace_id: Gitlab::PathRegex.full_namespace_route_regex }) do
|
||||
scope(constraints: { project_id: Gitlab::PathRegex.project_git_route_regex }, module: :projects) do
|
||||
# Git HTTP clients ('git clone' etc.)
|
||||
scope(controller: :git_http) do
|
||||
get '/info/refs', action: :info_refs
|
||||
|
@ -28,7 +28,7 @@ scope(path: '*namespace_id/:project_id',
|
|||
end
|
||||
|
||||
# Redirect /group/project/info/refs to /group/project.git/info/refs
|
||||
scope(constraints: { project_id: Gitlab::Regex.project_route_regex }) do
|
||||
scope(constraints: { project_id: Gitlab::PathRegex.project_route_regex }) do
|
||||
# Allow /info/refs, /info/refs?service=git-upload-pack, and
|
||||
# /info/refs?service=git-receive-pack, but nothing else.
|
||||
#
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
require 'constraints/group_url_constrainer'
|
||||
|
||||
resources :groups, only: [:index, :new, :create]
|
||||
|
||||
scope(path: 'groups/*group_id',
|
||||
module: :groups,
|
||||
as: :group,
|
||||
constraints: { group_id: Gitlab::Regex.namespace_route_regex }) do
|
||||
constraints: { group_id: Gitlab::PathRegex.full_namespace_route_regex }) do
|
||||
resources :group_members, only: [:index, :create, :update, :destroy], concerns: :access_requestable do
|
||||
post :resend_invite, on: :member
|
||||
delete :leave, on: :collection
|
||||
|
@ -25,7 +27,7 @@ end
|
|||
|
||||
scope(path: 'groups/*id',
|
||||
controller: :groups,
|
||||
constraints: { id: Gitlab::Regex.namespace_route_regex, format: /(html|json|atom)/ }) do
|
||||
constraints: { id: Gitlab::PathRegex.full_namespace_route_regex, format: /(html|json|atom)/ }) do
|
||||
get :edit, as: :edit_group
|
||||
get :issues, as: :issues_group
|
||||
get :merge_requests, as: :merge_requests_group
|
||||
|
@ -34,3 +36,15 @@ scope(path: 'groups/*id',
|
|||
get :subgroups, as: :subgroups_group
|
||||
get '/', action: :show, as: :group_canonical
|
||||
end
|
||||
|
||||
constraints(GroupUrlConstrainer.new) do
|
||||
scope(path: '*id',
|
||||
as: :group,
|
||||
constraints: { id: Gitlab::PathRegex.full_namespace_route_regex, format: /(html|json|atom)/ },
|
||||
controller: :groups) do
|
||||
get '/', action: :show
|
||||
patch '/', action: :update
|
||||
put '/', action: :update
|
||||
delete '/', action: :destroy
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,16 +13,16 @@ constraints(ProjectUrlConstrainer.new) do
|
|||
# Otherwise, Rails will overwrite the constraint with `/.+?/`,
|
||||
# which breaks some of our wildcard routes like `/blob/*id`
|
||||
# and `/tree/*id` that depend on the negative lookahead inside
|
||||
# `Gitlab::Regex.namespace_route_regex`, which helps the router
|
||||
# `Gitlab::PathRegex.full_namespace_route_regex`, which helps the router
|
||||
# determine whether a certain path segment is part of `*namespace_id`,
|
||||
# `:project_id`, or `*id`.
|
||||
#
|
||||
# See https://github.com/rails/rails/blob/v4.2.8/actionpack/lib/action_dispatch/routing/mapper.rb#L155
|
||||
scope(path: '*namespace_id',
|
||||
as: :namespace,
|
||||
namespace_id: Gitlab::Regex.namespace_route_regex) do
|
||||
namespace_id: Gitlab::PathRegex.full_namespace_route_regex) do
|
||||
scope(path: ':project_id',
|
||||
constraints: { project_id: Gitlab::Regex.project_route_regex },
|
||||
constraints: { project_id: Gitlab::PathRegex.project_route_regex },
|
||||
module: :projects,
|
||||
as: :project) do
|
||||
|
||||
|
@ -335,7 +335,7 @@ constraints(ProjectUrlConstrainer.new) do
|
|||
resources :runner_projects, only: [:create, :destroy]
|
||||
resources :badges, only: [:index] do
|
||||
collection do
|
||||
scope '*ref', constraints: { ref: Gitlab::Regex.git_reference_regex } do
|
||||
scope '*ref', constraints: { ref: Gitlab::PathRegex.git_reference_regex } do
|
||||
constraints format: /svg/ do
|
||||
get :build
|
||||
get :coverage
|
||||
|
@ -358,7 +358,7 @@ constraints(ProjectUrlConstrainer.new) do
|
|||
|
||||
resources(:projects,
|
||||
path: '/',
|
||||
constraints: { id: Gitlab::Regex.project_route_regex },
|
||||
constraints: { id: Gitlab::PathRegex.project_route_regex },
|
||||
only: [:edit, :show, :update, :destroy]) do
|
||||
member do
|
||||
put :transfer
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
resource :repository, only: [:create] do
|
||||
member do
|
||||
get 'archive', constraints: { format: Gitlab::Regex.archive_formats_regex }
|
||||
get 'archive', constraints: { format: Gitlab::PathRegex.archive_formats_regex }
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -24,7 +24,7 @@ scope format: false do
|
|||
|
||||
member do
|
||||
# tree viewer logs
|
||||
get 'logs_tree', constraints: { id: Gitlab::Regex.git_reference_regex }
|
||||
get 'logs_tree', constraints: { id: Gitlab::PathRegex.git_reference_regex }
|
||||
# Directories with leading dots erroneously get rejected if git
|
||||
# ref regex used in constraints. Regex verification now done in controller.
|
||||
get 'logs_tree/*path', action: :logs_tree, as: :logs_file, format: false, constraints: {
|
||||
|
@ -34,7 +34,7 @@ scope format: false do
|
|||
end
|
||||
end
|
||||
|
||||
scope constraints: { id: Gitlab::Regex.git_reference_regex } do
|
||||
scope constraints: { id: Gitlab::PathRegex.git_reference_regex } do
|
||||
resources :network, only: [:show]
|
||||
|
||||
resources :graphs, only: [:show] do
|
||||
|
|
|
@ -11,19 +11,7 @@ devise_scope :user do
|
|||
get '/users/almost_there' => 'confirmations#almost_there'
|
||||
end
|
||||
|
||||
constraints(UserUrlConstrainer.new) do
|
||||
# Get all keys of user
|
||||
get ':username.keys' => 'profiles/keys#get_keys', constraints: { username: Gitlab::Regex.root_namespace_route_regex }
|
||||
|
||||
scope(path: ':username',
|
||||
as: :user,
|
||||
constraints: { username: Gitlab::Regex.root_namespace_route_regex },
|
||||
controller: :users) do
|
||||
get '/', action: :show
|
||||
end
|
||||
end
|
||||
|
||||
scope(constraints: { username: Gitlab::Regex.root_namespace_route_regex }) do
|
||||
scope(constraints: { username: Gitlab::PathRegex.root_namespace_route_regex }) do
|
||||
scope(path: 'users/:username',
|
||||
as: :user,
|
||||
controller: :users) do
|
||||
|
@ -34,7 +22,7 @@ scope(constraints: { username: Gitlab::Regex.root_namespace_route_regex }) do
|
|||
get :contributed, as: :contributed_projects
|
||||
get :snippets
|
||||
get :exists
|
||||
get '/', to: redirect('/%{username}')
|
||||
get '/', to: redirect('/%{username}'), as: nil
|
||||
end
|
||||
|
||||
# Compatibility with old routing
|
||||
|
@ -46,3 +34,15 @@ scope(constraints: { username: Gitlab::Regex.root_namespace_route_regex }) do
|
|||
get '/u/:username/snippets', to: redirect('/users/%{username}/snippets')
|
||||
get '/u/:username/contributed', to: redirect('/users/%{username}/contributed')
|
||||
end
|
||||
|
||||
constraints(UserUrlConstrainer.new) do
|
||||
# Get all keys of user
|
||||
get ':username.keys' => 'profiles/keys#get_keys', constraints: { username: Gitlab::PathRegex.root_namespace_route_regex }
|
||||
|
||||
scope(path: ':username',
|
||||
as: :user,
|
||||
constraints: { username: Gitlab::PathRegex.root_namespace_route_regex },
|
||||
controller: :users) do
|
||||
get '/', action: :show
|
||||
end
|
||||
end
|
||||
|
|
|
@ -71,7 +71,7 @@ structure.
|
|||
- You need to be an Owner of a group in order to be able to create
|
||||
a subgroup. For more information check the [permissions table][permissions].
|
||||
- For a list of words that are not allowed to be used as group names see the
|
||||
[`regex.rb` file][reserved] under the `TOP_LEVEL_ROUTES`, `PROJECT_WILDCARD_ROUTES` and `GROUP_ROUTES` lists:
|
||||
[`path_regex.rb` file][reserved] under the `TOP_LEVEL_ROUTES`, `PROJECT_WILDCARD_ROUTES` and `GROUP_ROUTES` lists:
|
||||
- `TOP_LEVEL_ROUTES`: are names that are reserved as usernames or top level groups
|
||||
- `PROJECT_WILDCARD_ROUTES`: are names that are reserved for child groups or projects.
|
||||
- `GROUP_ROUTES`: are names that are reserved for all groups or projects.
|
||||
|
@ -163,4 +163,4 @@ Here's a list of what you can't do with subgroups:
|
|||
|
||||
[ce-2772]: https://gitlab.com/gitlab-org/gitlab-ce/issues/2772
|
||||
[permissions]: ../../permissions.md#group
|
||||
[reserved]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/regex.rb
|
||||
[reserved]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/path_regex.rb
|
||||
|
|
|
@ -85,7 +85,7 @@ module API
|
|||
optional :sha, type: String, desc: 'The commit sha of the archive to be downloaded'
|
||||
optional :format, type: String, desc: 'The archive format'
|
||||
end
|
||||
get ':id/repository/archive', requirements: { format: Gitlab::Regex.archive_formats_regex } do
|
||||
get ':id/repository/archive', requirements: { format: Gitlab::PathRegex.archive_formats_regex } do
|
||||
begin
|
||||
send_git_archive user_project.repository, ref: params[:sha], format: params[:format]
|
||||
rescue
|
||||
|
|
|
@ -72,7 +72,7 @@ module API
|
|||
optional :sha, type: String, desc: 'The commit sha of the archive to be downloaded'
|
||||
optional :format, type: String, desc: 'The archive format'
|
||||
end
|
||||
get ':id/repository/archive', requirements: { format: Gitlab::Regex.archive_formats_regex } do
|
||||
get ':id/repository/archive', requirements: { format: Gitlab::PathRegex.archive_formats_regex } do
|
||||
begin
|
||||
send_git_archive user_project.repository, ref: params[:sha], format: params[:format]
|
||||
rescue
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
class GroupUrlConstrainer
|
||||
def matches?(request)
|
||||
id = request.params[:group_id] || request.params[:id]
|
||||
full_path = request.params[:group_id] || request.params[:id]
|
||||
|
||||
return false unless DynamicPathValidator.valid_namespace_path?(id)
|
||||
return false unless DynamicPathValidator.valid_group_path?(full_path)
|
||||
|
||||
Group.find_by_full_path(id, follow_redirects: request.get?).present?
|
||||
Group.find_by_full_path(full_path, follow_redirects: request.get?).present?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,7 +2,7 @@ class ProjectUrlConstrainer
|
|||
def matches?(request)
|
||||
namespace_path = request.params[:namespace_id]
|
||||
project_path = request.params[:project_id] || request.params[:id]
|
||||
full_path = namespace_path + '/' + project_path
|
||||
full_path = [namespace_path, project_path].join('/')
|
||||
|
||||
return false unless DynamicPathValidator.valid_project_path?(full_path)
|
||||
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
class UserUrlConstrainer
|
||||
def matches?(request)
|
||||
User.find_by_full_path(request.params[:username], follow_redirects: request.get?).present?
|
||||
full_path = request.params[:username]
|
||||
|
||||
return false unless DynamicPathValidator.valid_user_path?(full_path)
|
||||
|
||||
User.find_by_full_path(full_path, follow_redirects: request.get?).present?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,7 +10,7 @@ module Gitlab
|
|||
# - Ending in `issues/id`/realtime_changes` for the `issue_title` route
|
||||
USED_IN_ROUTES = %w[noteable issue notes issues realtime_changes
|
||||
commit pipelines merge_requests new].freeze
|
||||
RESERVED_WORDS = Gitlab::Regex::ILLEGAL_PROJECT_PATH_WORDS - USED_IN_ROUTES
|
||||
RESERVED_WORDS = Gitlab::PathRegex::ILLEGAL_PROJECT_PATH_WORDS - USED_IN_ROUTES
|
||||
RESERVED_WORDS_REGEX = Regexp.union(*RESERVED_WORDS)
|
||||
ROUTES = [
|
||||
Gitlab::EtagCaching::Router::Route.new(
|
||||
|
|
|
@ -0,0 +1,264 @@
|
|||
module Gitlab
|
||||
module PathRegex
|
||||
extend self
|
||||
|
||||
# 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`.
|
||||
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 its 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
|
||||
|
||||
ILLEGAL_PROJECT_PATH_WORDS = PROJECT_WILDCARD_ROUTES
|
||||
ILLEGAL_GROUP_PATH_WORDS = (PROJECT_WILDCARD_ROUTES | GROUP_ROUTES).freeze
|
||||
|
||||
# The namespace regex is used in JavaScript to validate usernames in the "Register" form. However, Javascript
|
||||
# does not support the negative lookbehind assertion (?<!) that disallows usernames ending in `.git` and `.atom`.
|
||||
# Since this is a non-trivial problem to solve in Javascript (heavily complicate the regex, modify view code to
|
||||
# allow non-regex validations, etc), `NAMESPACE_FORMAT_REGEX_JS` serves as a Javascript-compatible version of
|
||||
# `NAMESPACE_FORMAT_REGEX`, with the negative lookbehind assertion removed. This means that the client-side validation
|
||||
# will pass for usernames ending in `.atom` and `.git`, but will be caught by the server-side validation.
|
||||
PATH_REGEX_STR = '[a-zA-Z0-9_\.][a-zA-Z0-9_\-\.]*'.freeze
|
||||
NAMESPACE_FORMAT_REGEX_JS = PATH_REGEX_STR + '[a-zA-Z0-9_\-]|[a-zA-Z0-9_]'.freeze
|
||||
|
||||
NO_SUFFIX_REGEX = /(?<!\.git|\.atom)/.freeze
|
||||
NAMESPACE_FORMAT_REGEX = /(?:#{NAMESPACE_FORMAT_REGEX_JS})#{NO_SUFFIX_REGEX}/.freeze
|
||||
PROJECT_PATH_FORMAT_REGEX = /(?:#{PATH_REGEX_STR})#{NO_SUFFIX_REGEX}/.freeze
|
||||
FULL_NAMESPACE_FORMAT_REGEX = %r{(#{NAMESPACE_FORMAT_REGEX}/)*#{NAMESPACE_FORMAT_REGEX}}.freeze
|
||||
|
||||
def root_namespace_route_regex
|
||||
@root_namespace_route_regex ||= begin
|
||||
illegal_words = Regexp.new(Regexp.union(TOP_LEVEL_ROUTES).source, Regexp::IGNORECASE)
|
||||
|
||||
single_line_regexp %r{
|
||||
(?!(#{illegal_words})/)
|
||||
#{NAMESPACE_FORMAT_REGEX}
|
||||
}x
|
||||
end
|
||||
end
|
||||
|
||||
def full_namespace_route_regex
|
||||
@full_namespace_route_regex ||= begin
|
||||
illegal_words = Regexp.new(Regexp.union(ILLEGAL_GROUP_PATH_WORDS).source, Regexp::IGNORECASE)
|
||||
|
||||
single_line_regexp %r{
|
||||
#{root_namespace_route_regex}
|
||||
(?:
|
||||
/
|
||||
(?!#{illegal_words}/)
|
||||
#{NAMESPACE_FORMAT_REGEX}
|
||||
)*
|
||||
}x
|
||||
end
|
||||
end
|
||||
|
||||
def project_route_regex
|
||||
@project_route_regex ||= begin
|
||||
illegal_words = Regexp.new(Regexp.union(ILLEGAL_PROJECT_PATH_WORDS).source, Regexp::IGNORECASE)
|
||||
|
||||
single_line_regexp %r{
|
||||
(?!(#{illegal_words})/)
|
||||
#{PROJECT_PATH_FORMAT_REGEX}
|
||||
}x
|
||||
end
|
||||
end
|
||||
|
||||
def project_git_route_regex
|
||||
@project_git_route_regex ||= /#{project_route_regex}\.git/.freeze
|
||||
end
|
||||
|
||||
def root_namespace_path_regex
|
||||
@root_namespace_path_regex ||= %r{\A#{root_namespace_route_regex}/\z}
|
||||
end
|
||||
|
||||
def full_namespace_path_regex
|
||||
@full_namespace_path_regex ||= %r{\A#{full_namespace_route_regex}/\z}
|
||||
end
|
||||
|
||||
def project_path_regex
|
||||
@project_path_regex ||= %r{\A#{project_route_regex}/\z}
|
||||
end
|
||||
|
||||
def full_project_path_regex
|
||||
@full_project_path_regex ||= %r{\A#{full_namespace_route_regex}/#{project_route_regex}/\z}
|
||||
end
|
||||
|
||||
def full_namespace_format_regex
|
||||
@namespace_format_regex ||= /A#{FULL_NAMESPACE_FORMAT_REGEX}\z/.freeze
|
||||
end
|
||||
|
||||
def namespace_format_regex
|
||||
@namespace_format_regex ||= /\A#{NAMESPACE_FORMAT_REGEX}\z/.freeze
|
||||
end
|
||||
|
||||
def namespace_format_message
|
||||
"can contain only letters, digits, '_', '-' and '.'. " \
|
||||
"Cannot start with '-' or end in '.', '.git' or '.atom'." \
|
||||
end
|
||||
|
||||
def project_path_format_regex
|
||||
@project_path_format_regex ||= /\A#{PROJECT_PATH_FORMAT_REGEX}\z/.freeze
|
||||
end
|
||||
|
||||
def project_path_format_message
|
||||
"can contain only letters, digits, '_', '-' and '.'. " \
|
||||
"Cannot start with '-', end in '.git' or end in '.atom'" \
|
||||
end
|
||||
|
||||
def archive_formats_regex
|
||||
# |zip|tar| tar.gz | tar.bz2 |
|
||||
@archive_formats_regex ||= /(zip|tar|tar\.gz|tgz|gz|tar\.bz2|tbz|tbz2|tb2|bz2)/.freeze
|
||||
end
|
||||
|
||||
def git_reference_regex
|
||||
# Valid git ref regex, see:
|
||||
# https://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html
|
||||
|
||||
@git_reference_regex ||= single_line_regexp %r{
|
||||
(?!
|
||||
(?# doesn't begins with)
|
||||
\/| (?# rule #6)
|
||||
(?# doesn't contain)
|
||||
.*(?:
|
||||
[\/.]\.| (?# rule #1,3)
|
||||
\/\/| (?# rule #6)
|
||||
@\{| (?# rule #8)
|
||||
\\ (?# rule #9)
|
||||
)
|
||||
)
|
||||
[^\000-\040\177~^:?*\[]+ (?# rule #4-5)
|
||||
(?# doesn't end with)
|
||||
(?<!\.lock) (?# rule #1)
|
||||
(?<![\/.]) (?# rule #6-7)
|
||||
}x
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def single_line_regexp(regex)
|
||||
# Turns a multiline extended regexp into a single line one,
|
||||
# beacuse `rake routes` breaks on multiline regexes.
|
||||
Regexp.new(regex.source.gsub(/\(\?#.+?\)/, '').gsub(/\s*/, ''), regex.options ^ Regexp::EXTENDED).freeze
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,203 +2,6 @@ module Gitlab
|
|||
module Regex
|
||||
extend self
|
||||
|
||||
# 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`.
|
||||
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 its 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
|
||||
|
||||
ILLEGAL_PROJECT_PATH_WORDS = PROJECT_WILDCARD_ROUTES
|
||||
ILLEGAL_GROUP_PATH_WORDS = (PROJECT_WILDCARD_ROUTES | GROUP_ROUTES).freeze
|
||||
|
||||
# The namespace regex is used in Javascript to validate usernames in the "Register" form. However, Javascript
|
||||
# does not support the negative lookbehind assertion (?<!) that disallows usernames ending in `.git` and `.atom`.
|
||||
# Since this is a non-trivial problem to solve in Javascript (heavily complicate the regex, modify view code to
|
||||
# allow non-regex validatiions, etc), `NAMESPACE_REGEX_STR_JS` serves as a Javascript-compatible version of
|
||||
# `NAMESPACE_REGEX_STR`, with the negative lookbehind assertion removed. This means that the client-side validation
|
||||
# will pass for usernames ending in `.atom` and `.git`, but will be caught by the server-side validation.
|
||||
PATH_REGEX_STR = '[a-zA-Z0-9_\.][a-zA-Z0-9_\-\.]*'.freeze
|
||||
NAMESPACE_REGEX_STR_JS = PATH_REGEX_STR + '[a-zA-Z0-9_\-]|[a-zA-Z0-9_]'.freeze
|
||||
NO_SUFFIX_REGEX_STR = '(?<!\.git|\.atom)'.freeze
|
||||
NAMESPACE_REGEX_STR = "(?:#{NAMESPACE_REGEX_STR_JS})#{NO_SUFFIX_REGEX_STR}".freeze
|
||||
PROJECT_REGEX_STR = "(?:#{PATH_REGEX_STR})#{NO_SUFFIX_REGEX_STR}".freeze
|
||||
|
||||
# Same as NAMESPACE_REGEX_STR but allows `/` in the path.
|
||||
# So `group/subgroup` will match this regex but not NAMESPACE_REGEX_STR
|
||||
FULL_NAMESPACE_REGEX_STR = "(?:#{NAMESPACE_REGEX_STR}/)*#{NAMESPACE_REGEX_STR}".freeze
|
||||
|
||||
def root_namespace_route_regex
|
||||
@root_namespace_route_regex ||= begin
|
||||
illegal_words = Regexp.new(Regexp.union(TOP_LEVEL_ROUTES).source, Regexp::IGNORECASE)
|
||||
|
||||
single_line_regexp %r{
|
||||
(?!(#{illegal_words})/)
|
||||
#{NAMESPACE_REGEX_STR}
|
||||
}x
|
||||
end
|
||||
end
|
||||
|
||||
def root_namespace_path_regex
|
||||
@root_namespace_path_regex ||= %r{\A#{root_namespace_route_regex}/\z}
|
||||
end
|
||||
|
||||
def full_namespace_path_regex
|
||||
@full_namespace_path_regex ||= %r{\A#{namespace_route_regex}/\z}
|
||||
end
|
||||
|
||||
def full_project_path_regex
|
||||
@full_project_path_regex ||= %r{\A#{namespace_route_regex}/#{project_route_regex}/\z}
|
||||
end
|
||||
|
||||
def namespace_regex
|
||||
@namespace_regex ||= /\A#{NAMESPACE_REGEX_STR}\z/.freeze
|
||||
end
|
||||
|
||||
def full_namespace_regex
|
||||
@full_namespace_regex ||= %r{\A#{FULL_NAMESPACE_REGEX_STR}\z}
|
||||
end
|
||||
|
||||
def namespace_route_regex
|
||||
@namespace_route_regex ||= begin
|
||||
illegal_words = Regexp.new(Regexp.union(ILLEGAL_GROUP_PATH_WORDS).source, Regexp::IGNORECASE)
|
||||
|
||||
single_line_regexp %r{
|
||||
#{root_namespace_route_regex}
|
||||
(?:
|
||||
/
|
||||
(?!#{illegal_words}/)
|
||||
#{NAMESPACE_REGEX_STR}
|
||||
)*
|
||||
}x
|
||||
end
|
||||
end
|
||||
|
||||
def namespace_regex_message
|
||||
"can contain only letters, digits, '_', '-' and '.'. " \
|
||||
"Cannot start with '-' or end in '.', '.git' or '.atom'." \
|
||||
end
|
||||
|
||||
def namespace_name_regex
|
||||
@namespace_name_regex ||= /\A[\p{Alnum}\p{Pd}_\. ]*\z/.freeze
|
||||
end
|
||||
|
@ -216,34 +19,6 @@ module Gitlab
|
|||
"It must start with letter, digit, emoji or '_'."
|
||||
end
|
||||
|
||||
def project_path_regex
|
||||
@project_path_regex ||= %r{\A#{project_route_regex}/\z}
|
||||
end
|
||||
|
||||
def project_route_regex
|
||||
@project_route_regex ||= begin
|
||||
illegal_words = Regexp.new(Regexp.union(ILLEGAL_PROJECT_PATH_WORDS).source, Regexp::IGNORECASE)
|
||||
|
||||
single_line_regexp %r{
|
||||
(?!(#{illegal_words})/)
|
||||
#{PROJECT_REGEX_STR}
|
||||
}x
|
||||
end
|
||||
end
|
||||
|
||||
def project_git_route_regex
|
||||
@project_git_route_regex ||= /#{project_route_regex}\.git/.freeze
|
||||
end
|
||||
|
||||
def project_path_format_regex
|
||||
@project_path_format_regex ||= /\A#{PROJECT_REGEX_STR}\z/.freeze
|
||||
end
|
||||
|
||||
def project_path_regex_message
|
||||
"can contain only letters, digits, '_', '-' and '.'. " \
|
||||
"Cannot start with '-', end in '.git' or end in '.atom'" \
|
||||
end
|
||||
|
||||
def file_name_regex
|
||||
@file_name_regex ||= /\A[[[:alnum:]]_\-\.\@\+]*\z/.freeze
|
||||
end
|
||||
|
@ -252,36 +27,8 @@ module Gitlab
|
|||
"can contain only letters, digits, '_', '-', '@', '+' and '.'."
|
||||
end
|
||||
|
||||
def archive_formats_regex
|
||||
# |zip|tar| tar.gz | tar.bz2 |
|
||||
@archive_formats_regex ||= /(zip|tar|tar\.gz|tgz|gz|tar\.bz2|tbz|tbz2|tb2|bz2)/.freeze
|
||||
end
|
||||
|
||||
def git_reference_regex
|
||||
# Valid git ref regex, see:
|
||||
# https://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html
|
||||
|
||||
@git_reference_regex ||= single_line_regexp %r{
|
||||
(?!
|
||||
(?# doesn't begins with)
|
||||
\/| (?# rule #6)
|
||||
(?# doesn't contain)
|
||||
.*(?:
|
||||
[\/.]\.| (?# rule #1,3)
|
||||
\/\/| (?# rule #6)
|
||||
@\{| (?# rule #8)
|
||||
\\ (?# rule #9)
|
||||
)
|
||||
)
|
||||
[^\000-\040\177~^:?*\[]+ (?# rule #4-5)
|
||||
(?# doesn't end with)
|
||||
(?<!\.lock) (?# rule #1)
|
||||
(?<![\/.]) (?# rule #6-7)
|
||||
}x
|
||||
end
|
||||
|
||||
def container_registry_reference_regex
|
||||
git_reference_regex
|
||||
Gitlab::PathRegex.git_reference_regex
|
||||
end
|
||||
|
||||
##
|
||||
|
@ -315,13 +62,5 @@ module Gitlab
|
|||
"can contain only lowercase letters, digits, and '-'. " \
|
||||
"Must start with a letter, and cannot end with '-'"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def single_line_regexp(regex)
|
||||
# Turns a multiline extended regexp into a single line one,
|
||||
# beacuse `rake routes` breaks on multiline regexes.
|
||||
Regexp.new(regex.source.gsub(/\(\?#.+?\)/, '').gsub(/\s*/, ''), regex.options ^ Regexp::EXTENDED).freeze
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -133,9 +133,13 @@ describe Import::BitbucketController do
|
|||
end
|
||||
|
||||
context "when a namespace with the Bitbucket user's username already exists" do
|
||||
let!(:existing_namespace) { create(:namespace, name: other_username, owner: user) }
|
||||
let!(:existing_namespace) { create(:group, name: other_username) }
|
||||
|
||||
context "when the namespace is owned by the GitLab user" do
|
||||
before do
|
||||
existing_namespace.add_owner(user)
|
||||
end
|
||||
|
||||
it "takes the existing namespace" do
|
||||
expect(Gitlab::BitbucketImport::ProjectCreator).
|
||||
to receive(:new).with(bitbucket_repo, bitbucket_repo.name, existing_namespace, user, access_params).
|
||||
|
@ -146,11 +150,6 @@ describe Import::BitbucketController do
|
|||
end
|
||||
|
||||
context "when the namespace is not owned by the GitLab user" do
|
||||
before do
|
||||
existing_namespace.owner = create(:user)
|
||||
existing_namespace.save
|
||||
end
|
||||
|
||||
it "doesn't create a project" do
|
||||
expect(Gitlab::BitbucketImport::ProjectCreator).
|
||||
not_to receive(:new)
|
||||
|
@ -202,10 +201,14 @@ describe Import::BitbucketController do
|
|||
end
|
||||
|
||||
context 'user has chosen an existing nested namespace and name for the project' do
|
||||
let(:parent_namespace) { create(:namespace, name: 'foo', owner: user) }
|
||||
let(:nested_namespace) { create(:namespace, name: 'bar', parent: parent_namespace, owner: user) }
|
||||
let(:parent_namespace) { create(:group, name: 'foo', owner: user) }
|
||||
let(:nested_namespace) { create(:group, name: 'bar', parent: parent_namespace) }
|
||||
let(:test_name) { 'test_name' }
|
||||
|
||||
before do
|
||||
nested_namespace.add_owner(user)
|
||||
end
|
||||
|
||||
it 'takes the selected namespace and name' do
|
||||
expect(Gitlab::BitbucketImport::ProjectCreator).
|
||||
to receive(:new).with(bitbucket_repo, test_name, nested_namespace, user, access_params).
|
||||
|
@ -248,7 +251,7 @@ describe Import::BitbucketController do
|
|||
|
||||
context 'user has chosen existent and non-existent nested namespaces and name for the project' do
|
||||
let(:test_name) { 'test_name' }
|
||||
let!(:parent_namespace) { create(:namespace, name: 'foo', owner: user) }
|
||||
let!(:parent_namespace) { create(:group, name: 'foo', owner: user) }
|
||||
|
||||
it 'takes the selected namespace and name' do
|
||||
expect(Gitlab::BitbucketImport::ProjectCreator).
|
||||
|
|
|
@ -108,9 +108,13 @@ describe Import::GitlabController do
|
|||
end
|
||||
|
||||
context "when a namespace with the GitLab.com user's username already exists" do
|
||||
let!(:existing_namespace) { create(:namespace, name: other_username, owner: user) }
|
||||
let!(:existing_namespace) { create(:group, name: other_username) }
|
||||
|
||||
context "when the namespace is owned by the GitLab server user" do
|
||||
before do
|
||||
existing_namespace.add_owner(user)
|
||||
end
|
||||
|
||||
it "takes the existing namespace" do
|
||||
expect(Gitlab::GitlabImport::ProjectCreator).
|
||||
to receive(:new).with(gitlab_repo, existing_namespace, user, access_params).
|
||||
|
@ -121,11 +125,6 @@ describe Import::GitlabController do
|
|||
end
|
||||
|
||||
context "when the namespace is not owned by the GitLab server user" do
|
||||
before do
|
||||
existing_namespace.owner = create(:user)
|
||||
existing_namespace.save
|
||||
end
|
||||
|
||||
it "doesn't create a project" do
|
||||
expect(Gitlab::GitlabImport::ProjectCreator).
|
||||
not_to receive(:new)
|
||||
|
@ -176,8 +175,12 @@ describe Import::GitlabController do
|
|||
end
|
||||
|
||||
context 'user has chosen an existing nested namespace for the project' do
|
||||
let(:parent_namespace) { create(:namespace, name: 'foo', owner: user) }
|
||||
let(:nested_namespace) { create(:namespace, name: 'bar', parent: parent_namespace, owner: user) }
|
||||
let(:parent_namespace) { create(:group, name: 'foo', owner: user) }
|
||||
let(:nested_namespace) { create(:group, name: 'bar', parent: parent_namespace) }
|
||||
|
||||
before do
|
||||
nested_namespace.add_owner(user)
|
||||
end
|
||||
|
||||
it 'takes the selected namespace and name' do
|
||||
expect(Gitlab::GitlabImport::ProjectCreator).
|
||||
|
@ -221,7 +224,7 @@ describe Import::GitlabController do
|
|||
|
||||
context 'user has chosen existent and non-existent nested namespaces and name for the project' do
|
||||
let(:test_name) { 'test_name' }
|
||||
let!(:parent_namespace) { create(:namespace, name: 'foo', owner: user) }
|
||||
let!(:parent_namespace) { create(:group, name: 'foo', owner: user) }
|
||||
|
||||
it 'takes the selected namespace and name' do
|
||||
expect(Gitlab::GitlabImport::ProjectCreator).
|
||||
|
|
|
@ -18,8 +18,8 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do
|
|||
let(:subject) { described_class.new(['parent/the-Path'], migration) }
|
||||
|
||||
it 'includes the namespace' do
|
||||
parent = create(:namespace, path: 'parent')
|
||||
child = create(:namespace, path: 'the-path', parent: parent)
|
||||
parent = create(:group, path: 'parent')
|
||||
child = create(:group, path: 'the-path', parent: parent)
|
||||
|
||||
found_ids = subject.namespaces_for_paths(type: :child).
|
||||
map(&:id)
|
||||
|
@ -30,13 +30,13 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do
|
|||
|
||||
context 'for child namespaces' do
|
||||
it 'only returns child namespaces with the correct path' do
|
||||
_root_namespace = create(:namespace, path: 'THE-path')
|
||||
_other_path = create(:namespace,
|
||||
_root_namespace = create(:group, path: 'THE-path')
|
||||
_other_path = create(:group,
|
||||
path: 'other',
|
||||
parent: create(:namespace))
|
||||
namespace = create(:namespace,
|
||||
parent: create(:group))
|
||||
namespace = create(:group,
|
||||
path: 'the-path',
|
||||
parent: create(:namespace))
|
||||
parent: create(:group))
|
||||
|
||||
found_ids = subject.namespaces_for_paths(type: :child).
|
||||
map(&:id)
|
||||
|
@ -45,13 +45,13 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do
|
|||
end
|
||||
|
||||
it 'has no namespaces that look the same' do
|
||||
_root_namespace = create(:namespace, path: 'THE-path')
|
||||
_similar_path = create(:namespace,
|
||||
_root_namespace = create(:group, path: 'THE-path')
|
||||
_similar_path = create(:group,
|
||||
path: 'not-really-the-path',
|
||||
parent: create(:namespace))
|
||||
namespace = create(:namespace,
|
||||
parent: create(:group))
|
||||
namespace = create(:group,
|
||||
path: 'the-path',
|
||||
parent: create(:namespace))
|
||||
parent: create(:group))
|
||||
|
||||
found_ids = subject.namespaces_for_paths(type: :child).
|
||||
map(&:id)
|
||||
|
@ -62,11 +62,11 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do
|
|||
|
||||
context 'for top levelnamespaces' do
|
||||
it 'only returns child namespaces with the correct path' do
|
||||
root_namespace = create(:namespace, path: 'the-path')
|
||||
_other_path = create(:namespace, path: 'other')
|
||||
_child_namespace = create(:namespace,
|
||||
root_namespace = create(:group, path: 'the-path')
|
||||
_other_path = create(:group, path: 'other')
|
||||
_child_namespace = create(:group,
|
||||
path: 'the-path',
|
||||
parent: create(:namespace))
|
||||
parent: create(:group))
|
||||
|
||||
found_ids = subject.namespaces_for_paths(type: :top_level).
|
||||
map(&:id)
|
||||
|
@ -75,11 +75,11 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do
|
|||
end
|
||||
|
||||
it 'has no namespaces that just look the same' do
|
||||
root_namespace = create(:namespace, path: 'the-path')
|
||||
_similar_path = create(:namespace, path: 'not-really-the-path')
|
||||
_child_namespace = create(:namespace,
|
||||
root_namespace = create(:group, path: 'the-path')
|
||||
_similar_path = create(:group, path: 'not-really-the-path')
|
||||
_child_namespace = create(:group,
|
||||
path: 'the-path',
|
||||
parent: create(:namespace))
|
||||
parent: create(:group))
|
||||
|
||||
found_ids = subject.namespaces_for_paths(type: :top_level).
|
||||
map(&:id)
|
||||
|
@ -124,10 +124,10 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do
|
|||
|
||||
describe "#child_ids_for_parent" do
|
||||
it "collects child ids for all levels" do
|
||||
parent = create(:namespace)
|
||||
first_child = create(:namespace, parent: parent)
|
||||
second_child = create(:namespace, parent: parent)
|
||||
third_child = create(:namespace, parent: second_child)
|
||||
parent = create(:group)
|
||||
first_child = create(:group, parent: parent)
|
||||
second_child = create(:group, parent: parent)
|
||||
third_child = create(:group, parent: second_child)
|
||||
all_ids = [parent.id, first_child.id, second_child.id, third_child.id]
|
||||
|
||||
collected_ids = subject.child_ids_for_parent(parent, ids: [parent.id])
|
||||
|
@ -205,9 +205,9 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do
|
|||
end
|
||||
|
||||
describe '#rename_namespaces' do
|
||||
let!(:top_level_namespace) { create(:namespace, path: 'the-path') }
|
||||
let!(:top_level_namespace) { create(:group, path: 'the-path') }
|
||||
let!(:child_namespace) do
|
||||
create(:namespace, path: 'the-path', parent: create(:namespace))
|
||||
create(:group, path: 'the-path', parent: create(:group))
|
||||
end
|
||||
|
||||
it 'renames top level namespaces the namespace' do
|
||||
|
|
|
@ -0,0 +1,384 @@
|
|||
# coding: utf-8
|
||||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::PathRegex, lib: true do
|
||||
# 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`
|
||||
# -> 'builds/artifacts'
|
||||
def path_before_wildcard(path)
|
||||
path = path.gsub(STARTING_WITH_NAMESPACE, "")
|
||||
path_segments = path.split('/').reject(&:empty?)
|
||||
wildcard_index = path_segments.index { |segment| parameter?(segment) }
|
||||
|
||||
segments_before_wildcard = path_segments[0..wildcard_index - 1]
|
||||
|
||||
segments_before_wildcard.join('/')
|
||||
end
|
||||
|
||||
def parameter?(segment)
|
||||
segment =~ /[*:]/
|
||||
end
|
||||
|
||||
# If the path is reserved. Then no conflicting paths can# be created for any
|
||||
# route using this reserved word.
|
||||
#
|
||||
# Both `builds/artifacts` & `build` are covered by reserving the word
|
||||
# `build`
|
||||
def wildcards_include?(path)
|
||||
described_class::PROJECT_WILDCARD_ROUTES.include?(path) ||
|
||||
described_class::PROJECT_WILDCARD_ROUTES.include?(path.split('/').first)
|
||||
end
|
||||
|
||||
def failure_message(missing_words, constant_name, migration_helper)
|
||||
missing_words = Array(missing_words)
|
||||
<<-MSG
|
||||
Found new routes that could cause conflicts with existing namespaced routes
|
||||
for groups or projects.
|
||||
|
||||
Add <#{missing_words.join(', ')}> to `Gitlab::PathRegex::#{constant_name}
|
||||
to make sure no projects or namespaces can be created with those paths.
|
||||
|
||||
To rename any existing records with those paths you can use the
|
||||
`Gitlab::Database::RenameReservedpathsMigration::<VERSION>.#{migration_helper}`
|
||||
migration helper.
|
||||
|
||||
Make sure to make a note of the renamed records in the release blog post.
|
||||
|
||||
MSG
|
||||
end
|
||||
|
||||
let(:all_routes) do
|
||||
route_set = Rails.application.routes
|
||||
routes_collection = route_set.routes
|
||||
routes_array = routes_collection.routes
|
||||
routes_array.map { |route| route.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{^/[:*]} } }
|
||||
|
||||
let(:top_level_words) do
|
||||
routes_not_starting_in_wildcard.map do |route|
|
||||
route.split('/')[1]
|
||||
end.compact.uniq
|
||||
end
|
||||
|
||||
# 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/Rv2pDE5Dvw
|
||||
STARTING_WITH_NAMESPACE = %r{^/\*namespace_id/:(project_)?id}
|
||||
NON_PARAM_PARTS = %r{[^:*][a-z\-_/]*}
|
||||
ANY_OTHER_PATH_PART = %r{[a-z\-_/:]*}
|
||||
WILDCARD_SEGMENT = %r{\*}
|
||||
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
|
||||
|
||||
# This will return all paths that are used in a namespaced route
|
||||
# before another wildcard path:
|
||||
#
|
||||
# /*namespace_id/:project_id/builds/artifacts/*ref_name_and_path
|
||||
# /*namespace_id/:project_id/info/lfs/objects/*oid
|
||||
# /*namespace_id/:project_id/commits/*id
|
||||
# /*namespace_id/:project_id/builds/:build_id/artifacts/file/*path
|
||||
# -> ['builds/artifacts', 'info/lfs/objects', 'commits', 'artifacts/file']
|
||||
let(:all_wildcard_paths) do
|
||||
namespaced_wildcard_routes.map do |route|
|
||||
path_before_wildcard(route)
|
||||
end.uniq
|
||||
end
|
||||
|
||||
STARTING_WITH_GROUP = %r{^/groups/\*(group_)?id/}
|
||||
let(:group_routes) do
|
||||
routes_without_format.select do |path|
|
||||
path =~ STARTING_WITH_GROUP
|
||||
end
|
||||
end
|
||||
|
||||
let(:paths_after_group_id) do
|
||||
group_routes.map do |route|
|
||||
route.gsub(STARTING_WITH_GROUP, '').split('/').first
|
||||
end.uniq
|
||||
end
|
||||
|
||||
describe 'TOP_LEVEL_ROUTES' do
|
||||
it 'includes all the top level namespaces' do
|
||||
failure_block = lambda do
|
||||
missing_words = top_level_words - described_class::TOP_LEVEL_ROUTES
|
||||
failure_message(missing_words, 'TOP_LEVEL_ROUTES', 'rename_root_paths')
|
||||
end
|
||||
|
||||
expect(described_class::TOP_LEVEL_ROUTES)
|
||||
.to include(*top_level_words), failure_block
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GROUP_ROUTES' do
|
||||
it "don't contain a second wildcard" do
|
||||
failure_block = lambda do
|
||||
missing_words = paths_after_group_id - described_class::GROUP_ROUTES
|
||||
failure_message(missing_words, 'GROUP_ROUTES', 'rename_child_paths')
|
||||
end
|
||||
|
||||
expect(described_class::GROUP_ROUTES)
|
||||
.to include(*paths_after_group_id), failure_block
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PROJECT_WILDCARD_ROUTES' do
|
||||
it 'includes all paths that can be used after a namespace/project path' do
|
||||
aggregate_failures do
|
||||
all_wildcard_paths.each do |path|
|
||||
expect(wildcards_include?(path))
|
||||
.to be(true), failure_message(path, 'PROJECT_WILDCARD_ROUTES', 'rename_wildcard_paths')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.root_namespace_path_regex' do
|
||||
subject { described_class.root_namespace_path_regex }
|
||||
|
||||
it 'rejects top level routes' do
|
||||
expect(subject).not_to match('admin/')
|
||||
expect(subject).not_to match('api/')
|
||||
expect(subject).not_to match('.well-known/')
|
||||
end
|
||||
|
||||
it 'accepts project wildcard routes' do
|
||||
expect(subject).to match('blob/')
|
||||
expect(subject).to match('edit/')
|
||||
expect(subject).to match('wikis/')
|
||||
end
|
||||
|
||||
it 'accepts group routes' do
|
||||
expect(subject).to match('activity/')
|
||||
expect(subject).to match('group_members/')
|
||||
expect(subject).to match('subgroups/')
|
||||
end
|
||||
|
||||
it 'is not case sensitive' do
|
||||
expect(subject).not_to match('Users/')
|
||||
end
|
||||
|
||||
it 'does not allow extra slashes' do
|
||||
expect(subject).not_to match('/blob/')
|
||||
expect(subject).not_to match('blob//')
|
||||
end
|
||||
end
|
||||
|
||||
describe '.full_namespace_path_regex' do
|
||||
subject { described_class.full_namespace_path_regex }
|
||||
|
||||
context 'at the top level' do
|
||||
context 'when the final level' do
|
||||
it 'rejects top level routes' do
|
||||
expect(subject).not_to match('admin/')
|
||||
expect(subject).not_to match('api/')
|
||||
expect(subject).not_to match('.well-known/')
|
||||
end
|
||||
|
||||
it 'accepts project wildcard routes' do
|
||||
expect(subject).to match('blob/')
|
||||
expect(subject).to match('edit/')
|
||||
expect(subject).to match('wikis/')
|
||||
end
|
||||
|
||||
it 'accepts group routes' do
|
||||
expect(subject).to match('activity/')
|
||||
expect(subject).to match('group_members/')
|
||||
expect(subject).to match('subgroups/')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when more levels follow' do
|
||||
it 'rejects top level routes' do
|
||||
expect(subject).not_to match('admin/more/')
|
||||
expect(subject).not_to match('api/more/')
|
||||
expect(subject).not_to match('.well-known/more/')
|
||||
end
|
||||
|
||||
it 'accepts project wildcard routes' do
|
||||
expect(subject).to match('blob/more/')
|
||||
expect(subject).to match('edit/more/')
|
||||
expect(subject).to match('wikis/more/')
|
||||
expect(subject).to match('environments/folders/')
|
||||
expect(subject).to match('info/lfs/objects/')
|
||||
end
|
||||
|
||||
it 'accepts group routes' do
|
||||
expect(subject).to match('activity/more/')
|
||||
expect(subject).to match('group_members/more/')
|
||||
expect(subject).to match('subgroups/more/')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'at the second level' do
|
||||
context 'when the final level' do
|
||||
it 'accepts top level routes' do
|
||||
expect(subject).to match('root/admin/')
|
||||
expect(subject).to match('root/api/')
|
||||
expect(subject).to match('root/.well-known/')
|
||||
end
|
||||
|
||||
it 'rejects project wildcard routes' do
|
||||
expect(subject).not_to match('root/blob/')
|
||||
expect(subject).not_to match('root/edit/')
|
||||
expect(subject).not_to match('root/wikis/')
|
||||
expect(subject).not_to match('root/environments/folders/')
|
||||
expect(subject).not_to match('root/info/lfs/objects/')
|
||||
end
|
||||
|
||||
it 'rejects group routes' do
|
||||
expect(subject).not_to match('root/activity/')
|
||||
expect(subject).not_to match('root/group_members/')
|
||||
expect(subject).not_to match('root/subgroups/')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when more levels follow' do
|
||||
it 'accepts top level routes' do
|
||||
expect(subject).to match('root/admin/more/')
|
||||
expect(subject).to match('root/api/more/')
|
||||
expect(subject).to match('root/.well-known/more/')
|
||||
end
|
||||
|
||||
it 'rejects project wildcard routes' do
|
||||
expect(subject).not_to match('root/blob/more/')
|
||||
expect(subject).not_to match('root/edit/more/')
|
||||
expect(subject).not_to match('root/wikis/more/')
|
||||
expect(subject).not_to match('root/environments/folders/more/')
|
||||
expect(subject).not_to match('root/info/lfs/objects/more/')
|
||||
end
|
||||
|
||||
it 'rejects group routes' do
|
||||
expect(subject).not_to match('root/activity/more/')
|
||||
expect(subject).not_to match('root/group_members/more/')
|
||||
expect(subject).not_to match('root/subgroups/more/')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'is not case sensitive' do
|
||||
expect(subject).not_to match('root/Blob/')
|
||||
end
|
||||
|
||||
it 'does not allow extra slashes' do
|
||||
expect(subject).not_to match('/root/admin/')
|
||||
expect(subject).not_to match('root/admin//')
|
||||
end
|
||||
end
|
||||
|
||||
describe '.project_path_regex' do
|
||||
subject { described_class.project_path_regex }
|
||||
|
||||
it 'accepts top level routes' do
|
||||
expect(subject).to match('admin/')
|
||||
expect(subject).to match('api/')
|
||||
expect(subject).to match('.well-known/')
|
||||
end
|
||||
|
||||
it 'rejects project wildcard routes' do
|
||||
expect(subject).not_to match('blob/')
|
||||
expect(subject).not_to match('edit/')
|
||||
expect(subject).not_to match('wikis/')
|
||||
expect(subject).not_to match('environments/folders/')
|
||||
expect(subject).not_to match('info/lfs/objects/')
|
||||
end
|
||||
|
||||
it 'accepts group routes' do
|
||||
expect(subject).to match('activity/')
|
||||
expect(subject).to match('group_members/')
|
||||
expect(subject).to match('subgroups/')
|
||||
end
|
||||
|
||||
it 'is not case sensitive' do
|
||||
expect(subject).not_to match('Blob/')
|
||||
end
|
||||
|
||||
it 'does not allow extra slashes' do
|
||||
expect(subject).not_to match('/admin/')
|
||||
expect(subject).not_to match('admin//')
|
||||
end
|
||||
end
|
||||
|
||||
describe '.full_project_path_regex' do
|
||||
subject { described_class.full_project_path_regex }
|
||||
|
||||
it 'accepts top level routes' do
|
||||
expect(subject).to match('root/admin/')
|
||||
expect(subject).to match('root/api/')
|
||||
expect(subject).to match('root/.well-known/')
|
||||
end
|
||||
|
||||
it 'rejects project wildcard routes' do
|
||||
expect(subject).not_to match('root/blob/')
|
||||
expect(subject).not_to match('root/edit/')
|
||||
expect(subject).not_to match('root/wikis/')
|
||||
expect(subject).not_to match('root/environments/folders/')
|
||||
expect(subject).not_to match('root/info/lfs/objects/')
|
||||
end
|
||||
|
||||
it 'accepts group routes' do
|
||||
expect(subject).to match('root/activity/')
|
||||
expect(subject).to match('root/group_members/')
|
||||
expect(subject).to match('root/subgroups/')
|
||||
end
|
||||
|
||||
it 'is not case sensitive' do
|
||||
expect(subject).not_to match('root/Blob/')
|
||||
end
|
||||
|
||||
it 'does not allow extra slashes' do
|
||||
expect(subject).not_to match('/root/admin/')
|
||||
expect(subject).not_to match('root/admin//')
|
||||
end
|
||||
end
|
||||
|
||||
describe '.namespace_format_regex' do
|
||||
subject { described_class.namespace_format_regex }
|
||||
|
||||
it { is_expected.to match('gitlab-ce') }
|
||||
it { is_expected.to match('gitlab_git') }
|
||||
it { is_expected.to match('_underscore.js') }
|
||||
it { is_expected.to match('100px.com') }
|
||||
it { is_expected.to match('gitlab.org') }
|
||||
it { is_expected.not_to match('?gitlab') }
|
||||
it { is_expected.not_to match('git lab') }
|
||||
it { is_expected.not_to match('gitlab.git') }
|
||||
it { is_expected.not_to match('gitlab.org.') }
|
||||
it { is_expected.not_to match('gitlab.org/') }
|
||||
it { is_expected.not_to match('/gitlab.org') }
|
||||
it { is_expected.not_to match('gitlab git') }
|
||||
end
|
||||
|
||||
describe '.project_path_format_regex' do
|
||||
subject { described_class.project_path_format_regex }
|
||||
|
||||
it { is_expected.to match('gitlab-ce') }
|
||||
it { is_expected.to match('gitlab_git') }
|
||||
it { is_expected.to match('_underscore.js') }
|
||||
it { is_expected.to match('100px.com') }
|
||||
it { is_expected.not_to match('?gitlab') }
|
||||
it { is_expected.not_to match('git lab') }
|
||||
it { is_expected.not_to match('gitlab.git') }
|
||||
end
|
||||
end
|
|
@ -2,386 +2,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Regex, lib: true do
|
||||
# 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`
|
||||
# -> 'builds/artifacts'
|
||||
def path_before_wildcard(path)
|
||||
path = path.gsub(STARTING_WITH_NAMESPACE, "")
|
||||
path_segments = path.split('/').reject(&:empty?)
|
||||
wildcard_index = path_segments.index { |segment| parameter?(segment) }
|
||||
|
||||
segments_before_wildcard = path_segments[0..wildcard_index - 1]
|
||||
|
||||
segments_before_wildcard.join('/')
|
||||
end
|
||||
|
||||
def parameter?(segment)
|
||||
segment =~ /[*:]/
|
||||
end
|
||||
|
||||
# If the path is reserved. Then no conflicting paths can# be created for any
|
||||
# route using this reserved word.
|
||||
#
|
||||
# Both `builds/artifacts` & `build` are covered by reserving the word
|
||||
# `build`
|
||||
def wildcards_include?(path)
|
||||
described_class::PROJECT_WILDCARD_ROUTES.include?(path) ||
|
||||
described_class::PROJECT_WILDCARD_ROUTES.include?(path.split('/').first)
|
||||
end
|
||||
|
||||
def failure_message(missing_words, constant_name, migration_helper)
|
||||
missing_words = Array(missing_words)
|
||||
<<-MSG
|
||||
Found new routes that could cause conflicts with existing namespaced routes
|
||||
for groups or projects.
|
||||
|
||||
Add <#{missing_words.join(', ')}> to `Gitlab::Regex::#{constant_name}
|
||||
to make sure no projects or namespaces can be created with those paths.
|
||||
|
||||
To rename any existing records with those paths you can use the
|
||||
`Gitlab::Database::RenameReservedpathsMigration::<VERSION>.#{migration_helper}`
|
||||
migration helper.
|
||||
|
||||
Make sure to make a note of the renamed records in the release blog post.
|
||||
|
||||
MSG
|
||||
end
|
||||
|
||||
let(:all_routes) do
|
||||
route_set = Rails.application.routes
|
||||
routes_collection = route_set.routes
|
||||
routes_array = routes_collection.routes
|
||||
routes_array.map { |route| route.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{^/[:*]} } }
|
||||
|
||||
let(:top_level_words) do
|
||||
routes_not_starting_in_wildcard.map do |route|
|
||||
route.split('/')[1]
|
||||
end.compact.uniq
|
||||
end
|
||||
|
||||
# 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/Rv2pDE5Dvw
|
||||
STARTING_WITH_NAMESPACE = %r{^/\*namespace_id/:(project_)?id}
|
||||
NON_PARAM_PARTS = %r{[^:*][a-z\-_/]*}
|
||||
ANY_OTHER_PATH_PART = %r{[a-z\-_/:]*}
|
||||
WILDCARD_SEGMENT = %r{\*}
|
||||
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
|
||||
|
||||
# This will return all paths that are used in a namespaced route
|
||||
# before another wildcard path:
|
||||
#
|
||||
# /*namespace_id/:project_id/builds/artifacts/*ref_name_and_path
|
||||
# /*namespace_id/:project_id/info/lfs/objects/*oid
|
||||
# /*namespace_id/:project_id/commits/*id
|
||||
# /*namespace_id/:project_id/builds/:build_id/artifacts/file/*path
|
||||
# -> ['builds/artifacts', 'info/lfs/objects', 'commits', 'artifacts/file']
|
||||
let(:all_wildcard_paths) do
|
||||
namespaced_wildcard_routes.map do |route|
|
||||
path_before_wildcard(route)
|
||||
end.uniq
|
||||
end
|
||||
|
||||
STARTING_WITH_GROUP = %r{^/groups/\*(group_)?id/}
|
||||
let(:group_routes) do
|
||||
routes_without_format.select do |path|
|
||||
path =~ STARTING_WITH_GROUP
|
||||
end
|
||||
end
|
||||
|
||||
let(:paths_after_group_id) do
|
||||
group_routes.map do |route|
|
||||
route.gsub(STARTING_WITH_GROUP, '').split('/').first
|
||||
end.uniq
|
||||
end
|
||||
|
||||
describe 'TOP_LEVEL_ROUTES' do
|
||||
it 'includes all the top level namespaces' do
|
||||
failure_block = lambda do
|
||||
missing_words = top_level_words - described_class::TOP_LEVEL_ROUTES
|
||||
failure_message(missing_words, 'TOP_LEVEL_ROUTES', 'rename_root_paths')
|
||||
end
|
||||
|
||||
expect(described_class::TOP_LEVEL_ROUTES)
|
||||
.to include(*top_level_words), failure_block
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GROUP_ROUTES' do
|
||||
it "don't contain a second wildcard" do
|
||||
failure_block = lambda do
|
||||
missing_words = paths_after_group_id - described_class::GROUP_ROUTES
|
||||
failure_message(missing_words, 'GROUP_ROUTES', 'rename_child_paths')
|
||||
end
|
||||
|
||||
expect(described_class::GROUP_ROUTES)
|
||||
.to include(*paths_after_group_id), failure_block
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PROJECT_WILDCARD_ROUTES' do
|
||||
it 'includes all paths that can be used after a namespace/project path' do
|
||||
aggregate_failures do
|
||||
all_wildcard_paths.each do |path|
|
||||
expect(wildcards_include?(path))
|
||||
.to be(true), failure_message(path, 'PROJECT_WILDCARD_ROUTES', 'rename_wildcard_paths')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.root_namespace_path_regex' do
|
||||
subject { described_class.root_namespace_path_regex }
|
||||
|
||||
it 'rejects top level routes' do
|
||||
expect(subject).not_to match('admin/')
|
||||
expect(subject).not_to match('api/')
|
||||
expect(subject).not_to match('.well-known/')
|
||||
end
|
||||
|
||||
it 'accepts project wildcard routes' do
|
||||
expect(subject).to match('blob/')
|
||||
expect(subject).to match('edit/')
|
||||
expect(subject).to match('wikis/')
|
||||
end
|
||||
|
||||
it 'accepts group routes' do
|
||||
expect(subject).to match('activity/')
|
||||
expect(subject).to match('group_members/')
|
||||
expect(subject).to match('subgroups/')
|
||||
end
|
||||
|
||||
it 'is not case sensitive' do
|
||||
expect(subject).not_to match('Users/')
|
||||
end
|
||||
|
||||
it 'does not allow extra slashes' do
|
||||
expect(subject).not_to match('/blob/')
|
||||
expect(subject).not_to match('blob//')
|
||||
end
|
||||
end
|
||||
|
||||
describe '.full_namespace_path_regex' do
|
||||
subject { described_class.full_namespace_path_regex }
|
||||
|
||||
context 'at the top level' do
|
||||
context 'when the final level' do
|
||||
it 'rejects top level routes' do
|
||||
expect(subject).not_to match('admin/')
|
||||
expect(subject).not_to match('api/')
|
||||
expect(subject).not_to match('.well-known/')
|
||||
end
|
||||
|
||||
it 'accepts project wildcard routes' do
|
||||
expect(subject).to match('blob/')
|
||||
expect(subject).to match('edit/')
|
||||
expect(subject).to match('wikis/')
|
||||
end
|
||||
|
||||
it 'accepts group routes' do
|
||||
expect(subject).to match('activity/')
|
||||
expect(subject).to match('group_members/')
|
||||
expect(subject).to match('subgroups/')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when more levels follow' do
|
||||
it 'rejects top level routes' do
|
||||
expect(subject).not_to match('admin/more/')
|
||||
expect(subject).not_to match('api/more/')
|
||||
expect(subject).not_to match('.well-known/more/')
|
||||
end
|
||||
|
||||
it 'accepts project wildcard routes' do
|
||||
expect(subject).to match('blob/more/')
|
||||
expect(subject).to match('edit/more/')
|
||||
expect(subject).to match('wikis/more/')
|
||||
expect(subject).to match('environments/folders/')
|
||||
expect(subject).to match('info/lfs/objects/')
|
||||
end
|
||||
|
||||
it 'accepts group routes' do
|
||||
expect(subject).to match('activity/more/')
|
||||
expect(subject).to match('group_members/more/')
|
||||
expect(subject).to match('subgroups/more/')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'at the second level' do
|
||||
context 'when the final level' do
|
||||
it 'accepts top level routes' do
|
||||
expect(subject).to match('root/admin/')
|
||||
expect(subject).to match('root/api/')
|
||||
expect(subject).to match('root/.well-known/')
|
||||
end
|
||||
|
||||
it 'rejects project wildcard routes' do
|
||||
expect(subject).not_to match('root/blob/')
|
||||
expect(subject).not_to match('root/edit/')
|
||||
expect(subject).not_to match('root/wikis/')
|
||||
expect(subject).not_to match('root/environments/folders/')
|
||||
expect(subject).not_to match('root/info/lfs/objects/')
|
||||
end
|
||||
|
||||
it 'rejects group routes' do
|
||||
expect(subject).not_to match('root/activity/')
|
||||
expect(subject).not_to match('root/group_members/')
|
||||
expect(subject).not_to match('root/subgroups/')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when more levels follow' do
|
||||
it 'accepts top level routes' do
|
||||
expect(subject).to match('root/admin/more/')
|
||||
expect(subject).to match('root/api/more/')
|
||||
expect(subject).to match('root/.well-known/more/')
|
||||
end
|
||||
|
||||
it 'rejects project wildcard routes' do
|
||||
expect(subject).not_to match('root/blob/more/')
|
||||
expect(subject).not_to match('root/edit/more/')
|
||||
expect(subject).not_to match('root/wikis/more/')
|
||||
expect(subject).not_to match('root/environments/folders/more/')
|
||||
expect(subject).not_to match('root/info/lfs/objects/more/')
|
||||
end
|
||||
|
||||
it 'rejects group routes' do
|
||||
expect(subject).not_to match('root/activity/more/')
|
||||
expect(subject).not_to match('root/group_members/more/')
|
||||
expect(subject).not_to match('root/subgroups/more/')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'is not case sensitive' do
|
||||
expect(subject).not_to match('root/Blob/')
|
||||
end
|
||||
|
||||
it 'does not allow extra slashes' do
|
||||
expect(subject).not_to match('/root/admin/')
|
||||
expect(subject).not_to match('root/admin//')
|
||||
end
|
||||
end
|
||||
|
||||
describe '.project_path_regex' do
|
||||
subject { described_class.project_path_regex }
|
||||
|
||||
it 'accepts top level routes' do
|
||||
expect(subject).to match('admin/')
|
||||
expect(subject).to match('api/')
|
||||
expect(subject).to match('.well-known/')
|
||||
end
|
||||
|
||||
it 'rejects project wildcard routes' do
|
||||
expect(subject).not_to match('blob/')
|
||||
expect(subject).not_to match('edit/')
|
||||
expect(subject).not_to match('wikis/')
|
||||
expect(subject).not_to match('environments/folders/')
|
||||
expect(subject).not_to match('info/lfs/objects/')
|
||||
end
|
||||
|
||||
it 'accepts group routes' do
|
||||
expect(subject).to match('activity/')
|
||||
expect(subject).to match('group_members/')
|
||||
expect(subject).to match('subgroups/')
|
||||
end
|
||||
|
||||
it 'is not case sensitive' do
|
||||
expect(subject).not_to match('Blob/')
|
||||
end
|
||||
|
||||
it 'does not allow extra slashes' do
|
||||
expect(subject).not_to match('/admin/')
|
||||
expect(subject).not_to match('admin//')
|
||||
end
|
||||
end
|
||||
|
||||
describe '.full_project_path_regex' do
|
||||
subject { described_class.full_project_path_regex }
|
||||
|
||||
it 'accepts top level routes' do
|
||||
expect(subject).to match('root/admin/')
|
||||
expect(subject).to match('root/api/')
|
||||
expect(subject).to match('root/.well-known/')
|
||||
end
|
||||
|
||||
it 'rejects project wildcard routes' do
|
||||
expect(subject).not_to match('root/blob/')
|
||||
expect(subject).not_to match('root/edit/')
|
||||
expect(subject).not_to match('root/wikis/')
|
||||
expect(subject).not_to match('root/environments/folders/')
|
||||
expect(subject).not_to match('root/info/lfs/objects/')
|
||||
end
|
||||
|
||||
it 'accepts group routes' do
|
||||
expect(subject).to match('root/activity/')
|
||||
expect(subject).to match('root/group_members/')
|
||||
expect(subject).to match('root/subgroups/')
|
||||
end
|
||||
|
||||
it 'is not case sensitive' do
|
||||
expect(subject).not_to match('root/Blob/')
|
||||
end
|
||||
|
||||
it 'does not allow extra slashes' do
|
||||
expect(subject).not_to match('/root/admin/')
|
||||
expect(subject).not_to match('root/admin//')
|
||||
end
|
||||
end
|
||||
|
||||
describe '.namespace_regex' do
|
||||
subject { described_class.namespace_regex }
|
||||
|
||||
it { is_expected.to match('gitlab-ce') }
|
||||
it { is_expected.to match('gitlab_git') }
|
||||
it { is_expected.to match('_underscore.js') }
|
||||
it { is_expected.to match('100px.com') }
|
||||
it { is_expected.to match('gitlab.org') }
|
||||
it { is_expected.not_to match('?gitlab') }
|
||||
it { is_expected.not_to match('git lab') }
|
||||
it { is_expected.not_to match('gitlab.git') }
|
||||
it { is_expected.not_to match('gitlab.org.') }
|
||||
it { is_expected.not_to match('gitlab.org/') }
|
||||
it { is_expected.not_to match('/gitlab.org') }
|
||||
it { is_expected.not_to match('gitlab git') }
|
||||
end
|
||||
|
||||
describe '.project_path_format_regex' do
|
||||
subject { described_class.project_path_format_regex }
|
||||
|
||||
it { is_expected.to match('gitlab-ce') }
|
||||
it { is_expected.to match('gitlab_git') }
|
||||
it { is_expected.to match('_underscore.js') }
|
||||
it { is_expected.to match('100px.com') }
|
||||
it { is_expected.not_to match('?gitlab') }
|
||||
it { is_expected.not_to match('git lab') }
|
||||
it { is_expected.not_to match('gitlab.git') }
|
||||
end
|
||||
|
||||
describe '.project_name_regex' do
|
||||
subject { described_class.project_name_regex }
|
||||
|
||||
|
@ -412,16 +32,4 @@ describe Gitlab::Regex, lib: true do
|
|||
it { is_expected.not_to match('9foo') }
|
||||
it { is_expected.not_to match('foo-') }
|
||||
end
|
||||
|
||||
describe '.full_namespace_regex' do
|
||||
subject { described_class.full_namespace_regex }
|
||||
|
||||
it { is_expected.to match('gitlab.org') }
|
||||
it { is_expected.to match('gitlab.org/gitlab-git') }
|
||||
it { is_expected.not_to match('gitlab.org.') }
|
||||
it { is_expected.not_to match('gitlab.org/') }
|
||||
it { is_expected.not_to match('/gitlab.org') }
|
||||
it { is_expected.not_to match('gitlab.git') }
|
||||
it { is_expected.not_to match('gitlab git') }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -238,8 +238,8 @@ describe Namespace, models: true do
|
|||
end
|
||||
|
||||
context 'in sub-groups' do
|
||||
let(:parent) { create(:namespace, path: 'parent') }
|
||||
let(:child) { create(:namespace, parent: parent, path: 'child') }
|
||||
let(:parent) { create(:group, path: 'parent') }
|
||||
let(:child) { create(:group, parent: parent, path: 'child') }
|
||||
let!(:project) { create(:project_empty_repo, namespace: child) }
|
||||
let(:path_in_dir) { File.join(repository_storage_path, 'parent', 'child') }
|
||||
let(:deleted_path) { File.join('parent', "child+#{child.id}+deleted") }
|
||||
|
|
|
@ -100,7 +100,7 @@ describe KubernetesService, models: true, caching: true do
|
|||
|
||||
it 'sets the namespace to the default' do
|
||||
expect(kube_namespace).not_to be_nil
|
||||
expect(kube_namespace[:placeholder]).to match(/\A#{Gitlab::Regex::PATH_REGEX_STR}-\d+\z/)
|
||||
expect(kube_namespace[:placeholder]).to match(/\A#{Gitlab::PathRegex::PATH_REGEX_STR}-\d+\z/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -215,7 +215,7 @@ describe KubernetesService, models: true, caching: true do
|
|||
kube_namespace = subject.predefined_variables.find { |h| h[:key] == 'KUBE_NAMESPACE' }
|
||||
|
||||
expect(kube_namespace).not_to be_nil
|
||||
expect(kube_namespace[:value]).to match(/\A#{Gitlab::Regex::PATH_REGEX_STR}-\d+\z/)
|
||||
expect(kube_namespace[:value]).to match(/\A#{Gitlab::PathRegex::PATH_REGEX_STR}-\d+\z/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -287,7 +287,7 @@ describe API::Users do
|
|||
expect(json_response['message']['projects_limit']).
|
||||
to eq(['must be greater than or equal to 0'])
|
||||
expect(json_response['message']['username']).
|
||||
to eq([Gitlab::Regex.namespace_regex_message])
|
||||
to eq([Gitlab::PathRegex.namespace_format_message])
|
||||
end
|
||||
|
||||
it "is not available for non admin users" do
|
||||
|
@ -459,7 +459,7 @@ describe API::Users do
|
|||
expect(json_response['message']['projects_limit']).
|
||||
to eq(['must be greater than or equal to 0'])
|
||||
expect(json_response['message']['username']).
|
||||
to eq([Gitlab::Regex.namespace_regex_message])
|
||||
to eq([Gitlab::PathRegex.namespace_format_message])
|
||||
end
|
||||
|
||||
it 'returns 400 if provider is missing for identity update' do
|
||||
|
|
|
@ -209,9 +209,13 @@ shared_examples 'a GitHub-ish import controller: POST create' do
|
|||
end
|
||||
|
||||
context 'user has chosen a namespace and name for the project' do
|
||||
let(:test_namespace) { create(:namespace, name: 'test_namespace', owner: user) }
|
||||
let(:test_namespace) { create(:group, name: 'test_namespace') }
|
||||
let(:test_name) { 'test_name' }
|
||||
|
||||
before do
|
||||
test_namespace.add_owner(user)
|
||||
end
|
||||
|
||||
it 'takes the selected namespace and name' do
|
||||
expect(Gitlab::GithubImport::ProjectCreator).
|
||||
to receive(:new).with(provider_repo, test_name, test_namespace, user, access_params, type: provider).
|
||||
|
@ -230,10 +234,14 @@ shared_examples 'a GitHub-ish import controller: POST create' do
|
|||
end
|
||||
|
||||
context 'user has chosen an existing nested namespace and name for the project' do
|
||||
let(:parent_namespace) { create(:namespace, name: 'foo', owner: user) }
|
||||
let(:nested_namespace) { create(:namespace, name: 'bar', parent: parent_namespace, owner: user) }
|
||||
let(:parent_namespace) { create(:group, name: 'foo', owner: user) }
|
||||
let(:nested_namespace) { create(:group, name: 'bar', parent: parent_namespace) }
|
||||
let(:test_name) { 'test_name' }
|
||||
|
||||
before do
|
||||
nested_namespace.add_owner(user)
|
||||
end
|
||||
|
||||
it 'takes the selected namespace and name' do
|
||||
expect(Gitlab::GithubImport::ProjectCreator).
|
||||
to receive(:new).with(provider_repo, test_name, nested_namespace, user, access_params, type: provider).
|
||||
|
@ -276,7 +284,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do
|
|||
|
||||
context 'user has chosen existent and non-existent nested namespaces and name for the project' do
|
||||
let(:test_name) { 'test_name' }
|
||||
let!(:parent_namespace) { create(:namespace, name: 'foo', owner: user) }
|
||||
let!(:parent_namespace) { create(:group, name: 'foo', owner: user) }
|
||||
|
||||
it 'takes the selected namespace and name' do
|
||||
expect(Gitlab::GithubImport::ProjectCreator).
|
||||
|
|
|
@ -15,31 +15,31 @@ describe DynamicPathValidator do
|
|||
end
|
||||
|
||||
context 'for group' do
|
||||
it 'calls valid_namespace_path?' do
|
||||
it 'calls valid_group_path?' do
|
||||
group = build(:group, :nested, path: 'activity')
|
||||
|
||||
expect(described_class).to receive(:valid_namespace_path?).with(group.full_path).and_call_original
|
||||
expect(described_class).to receive(:valid_group_path?).with(group.full_path).and_call_original
|
||||
|
||||
expect(validator.path_valid_for_record?(group, 'activity')).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
context 'for user' do
|
||||
it 'calls valid_namespace_path?' do
|
||||
it 'calls valid_user_path?' do
|
||||
user = build(:user, username: 'activity')
|
||||
|
||||
expect(described_class).to receive(:valid_namespace_path?).with(user.full_path).and_call_original
|
||||
expect(described_class).to receive(:valid_user_path?).with(user.full_path).and_call_original
|
||||
|
||||
expect(validator.path_valid_for_record?(user, 'activity')).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context 'for user namespace' do
|
||||
it 'calls valid_namespace_path?' do
|
||||
it 'calls valid_user_path?' do
|
||||
user = create(:user, username: 'activity')
|
||||
namespace = user.namespace
|
||||
|
||||
expect(described_class).to receive(:valid_namespace_path?).with(namespace.full_path).and_call_original
|
||||
expect(described_class).to receive(:valid_user_path?).with(namespace.full_path).and_call_original
|
||||
|
||||
expect(validator.path_valid_for_record?(namespace, 'activity')).to be_truthy
|
||||
end
|
||||
|
@ -52,7 +52,7 @@ describe DynamicPathValidator do
|
|||
|
||||
validator.validate_each(group, :path, "Path with spaces, and comma's!")
|
||||
|
||||
expect(group.errors[:path]).to include(Gitlab::Regex.namespace_regex_message)
|
||||
expect(group.errors[:path]).to include(Gitlab::PathRegex.namespace_format_message)
|
||||
end
|
||||
|
||||
it 'adds a message when the path is not in the correct format' do
|
||||
|
|
Loading…
Reference in New Issue