Commit b0498c17 authored by Douwe Maan's avatar Douwe Maan

Remove changes that are not absolutely necessary

parent ed16c351
...@@ -74,6 +74,6 @@ class Projects::RefsController < Projects::ApplicationController ...@@ -74,6 +74,6 @@ class Projects::RefsController < Projects::ApplicationController
private private
def validate_ref_id def validate_ref_id
return not_found! if params[:id].present? && params[:id] !~ Gitlab::PathRegex.git_reference_regex return not_found! if params[:id].present? && params[:id] !~ Gitlab::Regex.git_reference_regex
end end
end end
...@@ -205,8 +205,8 @@ class Project < ActiveRecord::Base ...@@ -205,8 +205,8 @@ class Project < ActiveRecord::Base
presence: true, presence: true,
dynamic_path: true, dynamic_path: true,
length: { maximum: 255 }, length: { maximum: 255 },
format: { with: Gitlab::PathRegex.project_path_format_regex, format: { with: Gitlab::Regex.project_path_format_regex,
message: Gitlab::PathRegex.project_path_format_message }, message: Gitlab::Regex.project_path_regex_message },
uniqueness: { scope: :namespace_id } uniqueness: { scope: :namespace_id }
validates :namespace, presence: true validates :namespace, presence: true
...@@ -380,9 +380,11 @@ class Project < ActiveRecord::Base ...@@ -380,9 +380,11 @@ class Project < ActiveRecord::Base
end end
def reference_pattern def reference_pattern
name_pattern = Gitlab::Regex::FULL_NAMESPACE_REGEX_STR
%r{ %r{
((?<namespace>#{Gitlab::PathRegex::FULL_NAMESPACE_FORMAT_REGEX})\/)? ((?<namespace>#{name_pattern})\/)?
(?<project>#{Gitlab::PathRegex::PROJECT_PATH_FORMAT_REGEX}) (?<project>#{name_pattern})
}x }x
end end
......
...@@ -367,7 +367,7 @@ class User < ActiveRecord::Base ...@@ -367,7 +367,7 @@ class User < ActiveRecord::Base
def reference_pattern def reference_pattern
%r{ %r{
#{Regexp.escape(reference_prefix)} #{Regexp.escape(reference_prefix)}
(?<user>#{Gitlab::PathRegex::FULL_NAMESPACE_FORMAT_REGEX}) (?<user>#{Gitlab::Regex::FULL_NAMESPACE_REGEX_STR})
}x }x
end end
......
...@@ -3,20 +3,16 @@ ...@@ -3,20 +3,16 @@
# Custom validator for GitLab path values. # Custom validator for GitLab path values.
# These paths are assigned to `Namespace` (& `Group` as a subclass) & `Project` # These paths are assigned to `Namespace` (& `Group` as a subclass) & `Project`
# #
# Values are checked for formatting and exclusion from a list of illegal path # Values are checked for formatting and exclusion from a list of reserved path
# names. # names.
class DynamicPathValidator < ActiveModel::EachValidator class DynamicPathValidator < ActiveModel::EachValidator
class << self class << self
def valid_user_path?(path) def valid_namespace_path?(path)
"#{path}/" =~ Gitlab::PathRegex.root_namespace_path_regex "#{path}/" =~ Gitlab::Regex.full_namespace_path_regex
end
def valid_group_path?(path)
"#{path}/" =~ Gitlab::PathRegex.full_namespace_path_regex
end end
def valid_project_path?(path) def valid_project_path?(path)
"#{path}/" =~ Gitlab::PathRegex.full_project_path_regex "#{path}/" =~ Gitlab::Regex.full_project_path_regex
end end
end end
...@@ -28,16 +24,14 @@ class DynamicPathValidator < ActiveModel::EachValidator ...@@ -28,16 +24,14 @@ class DynamicPathValidator < ActiveModel::EachValidator
case record case record
when Project when Project
self.class.valid_project_path?(full_path) self.class.valid_project_path?(full_path)
when Group else
self.class.valid_group_path?(full_path) self.class.valid_namespace_path?(full_path)
else # User or non-Group Namespace
self.class.valid_user_path?(full_path)
end end
end end
def validate_each(record, attribute, value) def validate_each(record, attribute, value)
unless value =~ Gitlab::PathRegex.namespace_format_regex unless value =~ Gitlab::Regex.namespace_regex
record.errors.add(attribute, Gitlab::PathRegex.namespace_format_message) record.errors.add(attribute, Gitlab::Regex.namespace_regex_message)
return return
end end
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
= f.text_field :name, class: "form-control top", required: true, title: "This field is required." = f.text_field :name, class: "form-control top", required: true, title: "This field is required."
.username.form-group .username.form-group
= f.label :username = f.label :username
= 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.' = 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.'
%p.validation-error.hide Username is already taken. %p.validation-error.hide Username is already taken.
%p.validation-success.hide Username is available. %p.validation-success.hide Username is available.
%p.validation-pending.hide Checking username availability... %p.validation-pending.hide Checking username availability...
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
%strong= parent.full_path + '/' %strong= parent.full_path + '/'
= f.text_field :path, placeholder: 'open-source', class: 'form-control', = f.text_field :path, placeholder: 'open-source', class: 'form-control',
autofocus: local_assigns[:autofocus] || false, required: true, autofocus: local_assigns[:autofocus] || false, required: true,
pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, pattern: Gitlab::Regex::NAMESPACE_REGEX_STR_JS,
title: 'Please choose a group path with no special characters.', title: 'Please choose a group path with no special characters.',
"data-bind-in" => "#{'create_chat_team' if Gitlab.config.mattermost.enabled}" "data-bind-in" => "#{'create_chat_team' if Gitlab.config.mattermost.enabled}"
- if parent - if parent
......
require 'sidekiq/web' require 'sidekiq/web'
require 'sidekiq/cron/web' require 'sidekiq/cron/web'
require 'constraints/group_url_constrainer'
Rails.application.routes.draw do Rails.application.routes.draw do
concern :access_requestable do concern :access_requestable do
...@@ -84,6 +85,20 @@ Rails.application.routes.draw do ...@@ -84,6 +85,20 @@ Rails.application.routes.draw do
root to: "root#index" 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? draw :test if Rails.env.test?
get '*unmatched_route', to: 'application#route_not_found' get '*unmatched_route', to: 'application#route_not_found'
......
...@@ -36,7 +36,7 @@ namespace :admin do ...@@ -36,7 +36,7 @@ namespace :admin do
scope(path: 'groups/*id', scope(path: 'groups/*id',
controller: :groups, controller: :groups,
constraints: { id: Gitlab::PathRegex.full_namespace_route_regex, format: /(html|json|atom)/ }) do constraints: { id: Gitlab::Regex.namespace_route_regex, format: /(html|json|atom)/ }) do
scope(as: :group) do scope(as: :group) do
put :members_update put :members_update
...@@ -70,10 +70,10 @@ namespace :admin do ...@@ -70,10 +70,10 @@ namespace :admin do
scope(path: 'projects/*namespace_id', scope(path: 'projects/*namespace_id',
as: :namespace, as: :namespace,
constraints: { namespace_id: Gitlab::PathRegex.full_namespace_route_regex }) do constraints: { namespace_id: Gitlab::Regex.namespace_route_regex }) do
resources(:projects, resources(:projects,
path: '/', path: '/',
constraints: { id: Gitlab::PathRegex.project_route_regex }, constraints: { id: Gitlab::Regex.project_route_regex },
only: [:show]) do only: [:show]) do
member do member do
......
scope(path: '*namespace_id/:project_id', scope(path: '*namespace_id/:project_id',
format: nil, format: nil,
constraints: { namespace_id: Gitlab::PathRegex.full_namespace_route_regex }) do constraints: { namespace_id: Gitlab::Regex.namespace_route_regex }) do
scope(constraints: { project_id: Gitlab::PathRegex.project_git_route_regex }, module: :projects) do scope(constraints: { project_id: Gitlab::Regex.project_git_route_regex }, module: :projects) do
# Git HTTP clients ('git clone' etc.) # Git HTTP clients ('git clone' etc.)
scope(controller: :git_http) do scope(controller: :git_http) do
get '/info/refs', action: :info_refs get '/info/refs', action: :info_refs
...@@ -28,7 +28,7 @@ scope(path: '*namespace_id/:project_id', ...@@ -28,7 +28,7 @@ scope(path: '*namespace_id/:project_id',
end end
# Redirect /group/project/info/refs to /group/project.git/info/refs # Redirect /group/project/info/refs to /group/project.git/info/refs
scope(constraints: { project_id: Gitlab::PathRegex.project_route_regex }) do scope(constraints: { project_id: Gitlab::Regex.project_route_regex }) do
# Allow /info/refs, /info/refs?service=git-upload-pack, and # Allow /info/refs, /info/refs?service=git-upload-pack, and
# /info/refs?service=git-receive-pack, but nothing else. # /info/refs?service=git-receive-pack, but nothing else.
# #
......
require 'constraints/group_url_constrainer'
resources :groups, only: [:index, :new, :create] resources :groups, only: [:index, :new, :create]
scope(path: 'groups/*group_id', scope(path: 'groups/*group_id',
module: :groups, module: :groups,
as: :group, as: :group,
constraints: { group_id: Gitlab::PathRegex.full_namespace_route_regex }) do constraints: { group_id: Gitlab::Regex.namespace_route_regex }) do
resources :group_members, only: [:index, :create, :update, :destroy], concerns: :access_requestable do resources :group_members, only: [:index, :create, :update, :destroy], concerns: :access_requestable do
post :resend_invite, on: :member post :resend_invite, on: :member
delete :leave, on: :collection delete :leave, on: :collection
...@@ -27,7 +25,7 @@ end ...@@ -27,7 +25,7 @@ end
scope(path: 'groups/*id', scope(path: 'groups/*id',
controller: :groups, controller: :groups,
constraints: { id: Gitlab::PathRegex.full_namespace_route_regex, format: /(html|json|atom)/ }) do constraints: { id: Gitlab::Regex.namespace_route_regex, format: /(html|json|atom)/ }) do
get :edit, as: :edit_group get :edit, as: :edit_group
get :issues, as: :issues_group get :issues, as: :issues_group
get :merge_requests, as: :merge_requests_group get :merge_requests, as: :merge_requests_group
...@@ -36,15 +34,3 @@ scope(path: 'groups/*id', ...@@ -36,15 +34,3 @@ scope(path: 'groups/*id',
get :subgroups, as: :subgroups_group get :subgroups, as: :subgroups_group
get '/', action: :show, as: :group_canonical get '/', action: :show, as: :group_canonical
end 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 ...@@ -13,16 +13,16 @@ constraints(ProjectUrlConstrainer.new) do
# Otherwise, Rails will overwrite the constraint with `/.+?/`, # Otherwise, Rails will overwrite the constraint with `/.+?/`,
# which breaks some of our wildcard routes like `/blob/*id` # which breaks some of our wildcard routes like `/blob/*id`
# and `/tree/*id` that depend on the negative lookahead inside # and `/tree/*id` that depend on the negative lookahead inside
# `Gitlab::PathRegex.full_namespace_route_regex`, which helps the router # `Gitlab::Regex.namespace_route_regex`, which helps the router
# determine whether a certain path segment is part of `*namespace_id`, # determine whether a certain path segment is part of `*namespace_id`,
# `:project_id`, or `*id`. # `:project_id`, or `*id`.
# #
# See https://github.com/rails/rails/blob/v4.2.8/actionpack/lib/action_dispatch/routing/mapper.rb#L155 # See https://github.com/rails/rails/blob/v4.2.8/actionpack/lib/action_dispatch/routing/mapper.rb#L155
scope(path: '*namespace_id', scope(path: '*namespace_id',
as: :namespace, as: :namespace,
namespace_id: Gitlab::PathRegex.full_namespace_route_regex) do namespace_id: Gitlab::Regex.namespace_route_regex) do
scope(path: ':project_id', scope(path: ':project_id',
constraints: { project_id: Gitlab::PathRegex.project_route_regex }, constraints: { project_id: Gitlab::Regex.project_route_regex },
module: :projects, module: :projects,
as: :project) do as: :project) do
...@@ -329,7 +329,7 @@ constraints(ProjectUrlConstrainer.new) do ...@@ -329,7 +329,7 @@ constraints(ProjectUrlConstrainer.new) do
resources :runner_projects, only: [:create, :destroy] resources :runner_projects, only: [:create, :destroy]
resources :badges, only: [:index] do resources :badges, only: [:index] do
collection do collection do
scope '*ref', constraints: { ref: Gitlab::PathRegex.git_reference_regex } do scope '*ref', constraints: { ref: Gitlab::Regex.git_reference_regex } do
constraints format: /svg/ do constraints format: /svg/ do
get :build get :build
get :coverage get :coverage
...@@ -352,7 +352,7 @@ constraints(ProjectUrlConstrainer.new) do ...@@ -352,7 +352,7 @@ constraints(ProjectUrlConstrainer.new) do
resources(:projects, resources(:projects,
path: '/', path: '/',
constraints: { id: Gitlab::PathRegex.project_route_regex }, constraints: { id: Gitlab::Regex.project_route_regex },
only: [:edit, :show, :update, :destroy]) do only: [:edit, :show, :update, :destroy]) do
member do member do
put :transfer put :transfer
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
resource :repository, only: [:create] do resource :repository, only: [:create] do
member do member do
get 'archive', constraints: { format: Gitlab::PathRegex.archive_formats_regex } get 'archive', constraints: { format: Gitlab::Regex.archive_formats_regex }
end end
end end
...@@ -24,7 +24,7 @@ scope format: false do ...@@ -24,7 +24,7 @@ scope format: false do
member do member do
# tree viewer logs # tree viewer logs
get 'logs_tree', constraints: { id: Gitlab::PathRegex.git_reference_regex } get 'logs_tree', constraints: { id: Gitlab::Regex.git_reference_regex }
# Directories with leading dots erroneously get rejected if git # Directories with leading dots erroneously get rejected if git
# ref regex used in constraints. Regex verification now done in controller. # ref regex used in constraints. Regex verification now done in controller.
get 'logs_tree/*path', action: :logs_tree, as: :logs_file, format: false, constraints: { get 'logs_tree/*path', action: :logs_tree, as: :logs_file, format: false, constraints: {
...@@ -34,7 +34,7 @@ scope format: false do ...@@ -34,7 +34,7 @@ scope format: false do
end end
end end
scope constraints: { id: Gitlab::PathRegex.git_reference_regex } do scope constraints: { id: Gitlab::Regex.git_reference_regex } do
resources :network, only: [:show] resources :network, only: [:show]
resources :graphs, only: [:show] do resources :graphs, only: [:show] do
......
...@@ -11,7 +11,19 @@ devise_scope :user do ...@@ -11,7 +11,19 @@ devise_scope :user do
get '/users/almost_there' => 'confirmations#almost_there' get '/users/almost_there' => 'confirmations#almost_there'
end end
scope(constraints: { username: Gitlab::PathRegex.root_namespace_route_regex }) do 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(path: 'users/:username', scope(path: 'users/:username',
as: :user, as: :user,
controller: :users) do controller: :users) do
...@@ -22,7 +34,7 @@ scope(constraints: { username: Gitlab::PathRegex.root_namespace_route_regex }) d ...@@ -22,7 +34,7 @@ scope(constraints: { username: Gitlab::PathRegex.root_namespace_route_regex }) d
get :contributed, as: :contributed_projects get :contributed, as: :contributed_projects
get :snippets get :snippets
get :exists get :exists
get '/', to: redirect('/%{username}'), as: nil get '/', to: redirect('/%{username}')
end end
# Compatibility with old routing # Compatibility with old routing
...@@ -34,15 +46,3 @@ scope(constraints: { username: Gitlab::PathRegex.root_namespace_route_regex }) d ...@@ -34,15 +46,3 @@ scope(constraints: { username: Gitlab::PathRegex.root_namespace_route_regex }) d
get '/u/:username/snippets', to: redirect('/users/%{username}/snippets') get '/u/:username/snippets', to: redirect('/users/%{username}/snippets')
get '/u/:username/contributed', to: redirect('/users/%{username}/contributed') get '/u/:username/contributed', to: redirect('/users/%{username}/contributed')
end 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. ...@@ -71,7 +71,7 @@ structure.
- You need to be an Owner of a group in order to be able to create - 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]. 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 - For a list of words that are not allowed to be used as group names see the
[`path_regex.rb` file][reserved] under the `TOP_LEVEL_ROUTES`, `PROJECT_WILDCARD_ROUTES` and `GROUP_ROUTES` lists: [`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 - `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. - `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. - `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: ...@@ -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 [ce-2772]: https://gitlab.com/gitlab-org/gitlab-ce/issues/2772
[permissions]: ../../permissions.md#group [permissions]: ../../permissions.md#group
[reserved]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/path_regex.rb [reserved]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/regex.rb
...@@ -85,7 +85,7 @@ module API ...@@ -85,7 +85,7 @@ module API
optional :sha, type: String, desc: 'The commit sha of the archive to be downloaded' optional :sha, type: String, desc: 'The commit sha of the archive to be downloaded'
optional :format, type: String, desc: 'The archive format' optional :format, type: String, desc: 'The archive format'
end end
get ':id/repository/archive', requirements: { format: Gitlab::PathRegex.archive_formats_regex } do get ':id/repository/archive', requirements: { format: Gitlab::Regex.archive_formats_regex } do
begin begin
send_git_archive user_project.repository, ref: params[:sha], format: params[:format] send_git_archive user_project.repository, ref: params[:sha], format: params[:format]
rescue rescue
......
...@@ -72,7 +72,7 @@ module API ...@@ -72,7 +72,7 @@ module API
optional :sha, type: String, desc: 'The commit sha of the archive to be downloaded' optional :sha, type: String, desc: 'The commit sha of the archive to be downloaded'
optional :format, type: String, desc: 'The archive format' optional :format, type: String, desc: 'The archive format'
end end
get ':id/repository/archive', requirements: { format: Gitlab::PathRegex.archive_formats_regex } do get ':id/repository/archive', requirements: { format: Gitlab::Regex.archive_formats_regex } do
begin begin
send_git_archive user_project.repository, ref: params[:sha], format: params[:format] send_git_archive user_project.repository, ref: params[:sha], format: params[:format]
rescue rescue
......
class GroupUrlConstrainer class GroupUrlConstrainer
def matches?(request) def matches?(request)
full_path = request.params[:group_id] || request.params[:id] id = request.params[:group_id] || request.params[:id]
return false unless DynamicPathValidator.valid_group_path?(full_path) return false unless DynamicPathValidator.valid_namespace_path?(id)
Group.find_by_full_path(full_path, follow_redirects: request.get?).present? Group.find_by_full_path(id, follow_redirects: request.get?).present?
end end
end end
...@@ -2,7 +2,7 @@ class ProjectUrlConstrainer ...@@ -2,7 +2,7 @@ class ProjectUrlConstrainer
def matches?(request) def matches?(request)
namespace_path = request.params[:namespace_id] namespace_path = request.params[:namespace_id]
project_path = request.params[:project_id] || request.params[:id] project_path = request.params[:project_id] || request.params[:id]
full_path = [namespace_path, project_path].join('/') full_path = namespace_path + '/' + project_path
return false unless DynamicPathValidator.valid_project_path?(full_path) return false unless DynamicPathValidator.valid_project_path?(full_path)
......
class UserUrlConstrainer class UserUrlConstrainer
def matches?(request) def matches?(request)
full_path = request.params[:username] User.find_by_full_path(request.params[:username], follow_redirects: request.get?).present?
return false unless DynamicPathValidator.valid_user_path?(full_path)
User.find_by_full_path(full_path, follow_redirects: request.get?).present?
end end
end end
...@@ -10,7 +10,7 @@ module Gitlab ...@@ -10,7 +10,7 @@ module Gitlab
# - Ending in `issues/id`/realtime_changes` for the `issue_title` route # - Ending in `issues/id`/realtime_changes` for the `issue_title` route
USED_IN_ROUTES = %w[noteable issue notes issues realtime_changes USED_IN_ROUTES = %w[noteable issue notes issues realtime_changes
commit pipelines merge_requests new].freeze commit pipelines merge_requests new].freeze
RESERVED_WORDS = Gitlab::PathRegex::ILLEGAL_PROJECT_PATH_WORDS - USED_IN_ROUTES RESERVED_WORDS = Gitlab::Regex::ILLEGAL_PROJECT_PATH_WORDS - USED_IN_ROUTES
RESERVED_WORDS_REGEX = Regexp.union(*RESERVED_WORDS) RESERVED_WORDS_REGEX = Regexp.union(*RESERVED_WORDS)
ROUTES = [ ROUTES = [
Gitlab::EtagCaching::Router::Route.new( Gitlab::EtagCaching::Router::Route.new(
......
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,6 +2,203 @@ module Gitlab ...@@ -2,6 +2,203 @@ module Gitlab
module Regex module Regex
extend self 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 def namespace_name_regex
@namespace_name_regex ||= /\A[\p{Alnum}\p{Pd}_\. ]*\z/.freeze @namespace_name_regex ||= /\A[\p{Alnum}\p{Pd}_\. ]*\z/.freeze
end end
...@@ -19,6 +216,34 @@ module Gitlab ...@@ -19,6 +216,34 @@ module Gitlab
"It must start with letter, digit, emoji or '_'." "It must start with letter, digit, emoji or '_'."
end 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 def file_name_regex
@file_name_regex ||= /\A[[[:alnum:]]_\-\.\@\+]*\z/.freeze @file_name_regex ||= /\A[[[:alnum:]]_\-\.\@\+]*\z/.freeze
end end
...@@ -27,8 +252,36 @@ module Gitlab ...@@ -27,8 +252,36 @@ module Gitlab
"can contain only letters, digits, '_', '-', '@', '+' and '.'." "can contain only letters, digits, '_', '-', '@', '+' and '.'."
end 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 def container_registry_reference_regex
Gitlab::PathRegex.git_reference_regex git_reference_regex
end end
## ##
...@@ -62,5 +315,13 @@ module Gitlab ...@@ -62,5 +315,13 @@ module Gitlab
"can contain only lowercase letters, digits, and '-'. " \ "can contain only lowercase letters, digits, and '-'. " \
"Must start with a letter, and cannot end with '-'" "Must start with a letter, and cannot end with '-'"
end 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
end end
...@@ -133,13 +133,9 @@ describe Import::BitbucketController do ...@@ -133,13 +133,9 @@ describe Import::BitbucketController do
end end
context "when a namespace with the Bitbucket user's username already exists" do context "when a namespace with the Bitbucket user's username already exists" do
let!(:existing_namespace) { create(:group, name: other_username) } let!(:existing_namespace) { create(:namespace, name: other_username, owner: user) }
context "when the namespace is owned by the GitLab user" do 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 it "takes the existing namespace" do
expect(Gitlab::BitbucketImport::ProjectCreator). expect(Gitlab::BitbucketImport::ProjectCreator).
to receive(:new).with(bitbucket_repo, bitbucket_repo.name, existing_namespace, user, access_params). to receive(:new).with(bitbucket_repo, bitbucket_repo.name, existing_namespace, user, access_params).
...@@ -150,6 +146,11 @@ describe Import::BitbucketController do ...@@ -150,6 +146,11 @@ describe Import::BitbucketController do
end end
context "when the namespace is not owned by the GitLab user" do 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 it "doesn't create a project" do
expect(Gitlab::BitbucketImport::ProjectCreator). expect(Gitlab::BitbucketImport::ProjectCreator).
not_to receive(:new) not_to receive(:new)
...@@ -201,14 +202,10 @@ describe Import::BitbucketController do ...@@ -201,14 +202,10 @@ describe Import::BitbucketController do
end end
context 'user has chosen an existing nested namespace and name for the project' do context 'user has chosen an existing nested namespace and name for the project' do
let(:parent_namespace) { create(:group, name: 'foo', owner: user) } let(:parent_namespace) { create(:namespace, name: 'foo', owner: user) }
let(:nested_namespace) { create(:group, name: 'bar', parent: parent_namespace) } let(:nested_namespace) { create(:namespace, name: 'bar', parent: parent_namespace, owner: user) }
let(:test_name) { 'test_name' } let(:test_name) { 'test_name' }
before do
nested_namespace.add_owner(user)
end
it 'takes the selected namespace and name' do it 'takes the selected namespace and name' do
expect(Gitlab::BitbucketImport::ProjectCreator). expect(Gitlab::BitbucketImport::ProjectCreator).
to receive(:new).with(bitbucket_repo, test_name, nested_namespace, user, access_params). to receive(:new).with(bitbucket_repo, test_name, nested_namespace, user, access_params).
...@@ -251,7 +248,7 @@ describe Import::BitbucketController do ...@@ -251,7 +248,7 @@ describe Import::BitbucketController do
context 'user has chosen existent and non-existent nested namespaces and name for the project' do context 'user has chosen existent and non-existent nested namespaces and name for the project' do
let(:test_name) { 'test_name' } let(:test_name) { 'test_name' }
let!(:parent_namespace) { create(:group, name: 'foo', owner: user) } let!(:parent_namespace) { create(:namespace, name: 'foo', owner: user) }
it 'takes the selected namespace and name' do it 'takes the selected namespace and name' do
expect(Gitlab::BitbucketImport::ProjectCreator). expect(Gitlab::BitbucketImport::ProjectCreator).
......
...@@ -108,13 +108,9 @@ describe Import::GitlabController do ...@@ -108,13 +108,9 @@ describe Import::GitlabController do
end end
context "when a namespace with the GitLab.com user's username already exists" do context "when a namespace with the GitLab.com user's username already exists" do
let!(:existing_namespace) { create(:group, name: other_username) } let!(:existing_namespace) { create(:namespace, name: other_username, owner: user) }
context "when the namespace is owned by the GitLab server user" do 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 it "takes the existing namespace" do
expect(Gitlab::GitlabImport::ProjectCreator). expect(Gitlab::GitlabImport::ProjectCreator).
to receive(:new).with(gitlab_repo, existing_namespace, user, access_params). to receive(:new).with(gitlab_repo, existing_namespace, user, access_params).
...@@ -125,6 +121,11 @@ describe Import::GitlabController do ...@@ -125,6 +121,11 @@ describe Import::GitlabController do
end end
context "when the namespace is not owned by the GitLab server user" do 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 it "doesn't create a project" do
expect(Gitlab::GitlabImport::ProjectCreator). expect(Gitlab::GitlabImport::ProjectCreator).
not_to receive(:new) not_to receive(:new)
...@@ -175,12 +176,8 @@ describe Import::GitlabController do ...@@ -175,12 +176,8 @@ describe Import::GitlabController do
end end
context 'user has chosen an existing nested namespace for the project' do context 'user has chosen an existing nested namespace for the project' do
let(:parent_namespace) { create(:group, name: 'foo', owner: user) } let(:parent_namespace) { create(:namespace, name: 'foo', owner: user) }
let(:nested_namespace) { create(:group, name: 'bar', parent: parent_namespace) } let(:nested_namespace) { create(:namespace, name: 'bar', parent: parent_namespace, owner: user) }
before do
nested_namespace.add_owner(user)
end
it 'takes the selected namespace and name' do it 'takes the selected namespace and name' do
expect(Gitlab::GitlabImport::ProjectCreator). expect(Gitlab::GitlabImport::ProjectCreator).
...@@ -224,7 +221,7 @@ describe Import::GitlabController do ...@@ -224,7 +221,7 @@ describe Import::GitlabController do
context 'user has chosen existent and non-existent nested namespaces and name for the project' do context 'user has chosen existent and non-existent nested namespaces and name for the project' do
let(:test_name) { 'test_name' } let(:test_name) { 'test_name' }
let!(:parent_namespace) { create(:group, name: 'foo', owner: user) } let!(:parent_namespace) { create(:namespace, name: 'foo', owner: user) }
it 'takes the selected namespace and name' do it 'takes the selected namespace and name' do
expect(Gitlab::GitlabImport::ProjectCreator). expect(Gitlab::GitlabImport::ProjectCreator).
......
...@@ -18,8 +18,8 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do ...@@ -18,8 +18,8 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do
let(:subject) { described_class.new(['parent/the-Path'], migration) } let(:subject) { described_class.new(['parent/the-Path'], migration) }
it 'includes the namespace' do it 'includes the namespace' do
parent = create(:group, path: 'parent') parent = create(:namespace, path: 'parent')
child = create(:group, path: 'the-path', parent: parent) child = create(:namespace, path: 'the-path', parent: parent)
found_ids = subject.namespaces_for_paths(type: :child). found_ids = subject.namespaces_for_paths(type: :child).
map(&:id) map(&:id)
...@@ -30,13 +30,13 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do ...@@ -30,13 +30,13 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do
context 'for child namespaces' do context 'for child namespaces' do
it 'only returns child namespaces with the correct path' do it 'only returns child namespaces with the correct path' do
_root_namespace = create(:group, path: 'THE-path') _root_namespace = create(:namespace, path: 'THE-path')
_other_path = create(:group, _other_path = create(:namespace,
path: 'other', path: 'other',
parent: create(:group)) parent: create(:namespace))
namespace = create(:group, namespace = create(:namespace,
path: 'the-path', path: 'the-path',
parent: create(:group)) parent: create(:namespace))
found_ids = subject.namespaces_for_paths(type: :child). found_ids = subject.namespaces_for_paths(type: :child).
map(&:id) map(&:id)
...@@ -45,13 +45,13 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do ...@@ -45,13 +45,13 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do
end end
it 'has no namespaces that look the same' do it 'has no namespaces that look the same' do
_root_namespace = create(:group, path: 'THE-path') _root_namespace = create(:namespace, path: 'THE-path')
_similar_path = create(:group, _similar_path = create(:namespace,
path: 'not-really-the-path', path: 'not-really-the-path',
parent: create(:group)) parent: create(:namespace))
namespace = create(:group, namespace = create(:namespace,
path: 'the-path', path: 'the-path',
parent: create(:group)) parent: create(:namespace))
found_ids = subject.namespaces_for_paths(type: :child). found_ids = subject.namespaces_for_paths(type: :child).
map(&:id) map(&:id)
...@@ -62,11 +62,11 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do ...@@ -62,11 +62,11 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do
context 'for top levelnamespaces' do context 'for top levelnamespaces' do
it 'only returns child namespaces with the correct path' do it 'only returns child namespaces with the correct path' do
root_namespace = create(:group, path: 'the-path') root_namespace = create(:namespace, path: 'the-path')
_other_path = create(:group, path: 'other') _other_path = create(:namespace, path: 'other')
_child_namespace = create(:group, _child_namespace = create(:namespace,
path: 'the-path', path: 'the-path',
parent: create(:group)) parent: create(:namespace))
found_ids = subject.namespaces_for_paths(type: :top_level). found_ids = subject.namespaces_for_paths(type: :top_level).
map(&:id) map(&:id)
...@@ -75,11 +75,11 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do ...@@ -75,11 +75,11 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do
end end
it 'has no namespaces that just look the same' do it 'has no namespaces that just look the same' do
root_namespace = create(:group, path: 'the-path') root_namespace = create(:namespace, path: 'the-path')
_similar_path = create(:group, path: 'not-really-the-path') _similar_path = create(:namespace, path: 'not-really-the-path')
_child_namespace = create(:group, _child_namespace = create(:namespace,
path: 'the-path', path: 'the-path',
parent: create(:group)) parent: create(:namespace))
found_ids = subject.namespaces_for_paths(type: :top_level). found_ids = subject.namespaces_for_paths(type: :top_level).
map(&:id) map(&:id)
...@@ -124,10 +124,10 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do ...@@ -124,10 +124,10 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do
describe "#child_ids_for_parent" do describe "#child_ids_for_parent" do
it "collects child ids for all levels" do it "collects child ids for all levels" do
parent = create(:group) parent = create(:namespace)
first_child = create(:group, parent: parent) first_child = create(:namespace, parent: parent)
second_child = create(:group, parent: parent) second_child = create(:namespace, parent: parent)
third_child = create(:group, parent: second_child) third_child = create(:namespace, parent: second_child)
all_ids = [parent.id, first_child.id, second_child.id, third_child.id] all_ids = [parent.id, first_child.id, second_child.id, third_child.id]
collected_ids = subject.child_ids_for_parent(parent, ids: [parent.id]) collected_ids = subject.child_ids_for_parent(parent, ids: [parent.id])
...@@ -205,9 +205,9 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do ...@@ -205,9 +205,9 @@ describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces do
end end
describe '#rename_namespaces' do describe '#rename_namespaces' do
let!(:top_level_namespace) { create(:group, path: 'the-path') } let!(:top_level_namespace) { create(:namespace, path: 'the-path') }
let!(:child_namespace) do let!(:child_namespace) do
create(:group, path: 'the-path', parent: create(:group)) create(:namespace, path: 'the-path', parent: create(:namespace))
end end
it 'renames top level namespaces the namespace' do it 'renames top level namespaces the namespace' do
......
# 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,6 +2,386 @@ ...@@ -2,6 +2,386 @@
require 'spec_helper' require 'spec_helper'
describe Gitlab::Regex, lib: true do 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 describe '.project_name_regex' do
subject { described_class.project_name_regex } subject { described_class.project_name_regex }
...@@ -32,4 +412,16 @@ describe Gitlab::Regex, lib: true do ...@@ -32,4 +412,16 @@ describe Gitlab::Regex, lib: true do
it { is_expected.not_to match('9foo') } it { is_expected.not_to match('9foo') }
it { is_expected.not_to match('foo-') } it { is_expected.not_to match('foo-') }
end 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 end
...@@ -238,8 +238,8 @@ describe Namespace, models: true do ...@@ -238,8 +238,8 @@ describe Namespace, models: true do
end end
context 'in sub-groups' do context 'in sub-groups' do
let(:parent) { create(:group, path: 'parent') } let(:parent) { create(:namespace, path: 'parent') }
let(:child) { create(:group, parent: parent, path: 'child') } let(:child) { create(:namespace, parent: parent, path: 'child') }
let!(:project) { create(:project_empty_repo, namespace: child) } let!(:project) { create(:project_empty_repo, namespace: child) }
let(:path_in_dir) { File.join(repository_storage_path, 'parent', 'child') } let(:path_in_dir) { File.join(repository_storage_path, 'parent', 'child') }
let(:deleted_path) { File.join('parent', "child+#{child.id}+deleted") } let(:deleted_path) { File.join('parent', "child+#{child.id}+deleted") }
......
...@@ -100,7 +100,7 @@ describe KubernetesService, models: true, caching: true do ...@@ -100,7 +100,7 @@ describe KubernetesService, models: true, caching: true do
it 'sets the namespace to the default' do it 'sets the namespace to the default' do
expect(kube_namespace).not_to be_nil expect(kube_namespace).not_to be_nil
expect(kube_namespace[:placeholder]).to match(/\A#{Gitlab::PathRegex::PATH_REGEX_STR}-\d+\z/) expect(kube_namespace[:placeholder]).to match(/\A#{Gitlab::Regex::PATH_REGEX_STR}-\d+\z/)
end end
end end
end end
...@@ -187,7 +187,7 @@ describe KubernetesService, models: true, caching: true do ...@@ -187,7 +187,7 @@ describe KubernetesService, models: true, caching: true do
kube_namespace = subject.predefined_variables.find { |h| h[:key] == 'KUBE_NAMESPACE' } kube_namespace = subject.predefined_variables.find { |h| h[:key] == 'KUBE_NAMESPACE' }
expect(kube_namespace).not_to be_nil expect(kube_namespace).not_to be_nil
expect(kube_namespace[:value]).to match(/\A#{Gitlab::PathRegex::PATH_REGEX_STR}-\d+\z/) expect(kube_namespace[:value]).to match(/\A#{Gitlab::Regex::PATH_REGEX_STR}-\d+\z/)
end end
end end
end end
......
...@@ -287,7 +287,7 @@ describe API::Users do ...@@ -287,7 +287,7 @@ describe API::Users do
expect(json_response['message']['projects_limit']). expect(json_response['message']['projects_limit']).
to eq(['must be greater than or equal to 0']) to eq(['must be greater than or equal to 0'])
expect(json_response['message']['username']). expect(json_response['message']['username']).
to eq([Gitlab::PathRegex.namespace_format_message]) to eq([Gitlab::Regex.namespace_regex_message])
end end
it "is not available for non admin users" do it "is not available for non admin users" do
...@@ -459,7 +459,7 @@ describe API::Users do ...@@ -459,7 +459,7 @@ describe API::Users do
expect(json_response['message']['projects_limit']). expect(json_response['message']['projects_limit']).
to eq(['must be greater than or equal to 0']) to eq(['must be greater than or equal to 0'])
expect(json_response['message']['username']). expect(json_response['message']['username']).
to eq([Gitlab::PathRegex.namespace_format_message]) to eq([Gitlab::Regex.namespace_regex_message])
end end
it 'returns 400 if provider is missing for identity update' do it 'returns 400 if provider is missing for identity update' do
......
...@@ -209,13 +209,9 @@ shared_examples 'a GitHub-ish import controller: POST create' do ...@@ -209,13 +209,9 @@ shared_examples 'a GitHub-ish import controller: POST create' do
end end
context 'user has chosen a namespace and name for the project' do context 'user has chosen a namespace and name for the project' do
let(:test_namespace) { create(:group, name: 'test_namespace') } let(:test_namespace) { create(:namespace, name: 'test_namespace', owner: user) }
let(:test_name) { 'test_name' } let(:test_name) { 'test_name' }
before do
test_namespace.add_owner(user)
end
it 'takes the selected namespace and name' do it 'takes the selected namespace and name' do
expect(Gitlab::GithubImport::ProjectCreator). expect(Gitlab::GithubImport::ProjectCreator).
to receive(:new).with(provider_repo, test_name, test_namespace, user, access_params, type: provider). to receive(:new).with(provider_repo, test_name, test_namespace, user, access_params, type: provider).
...@@ -234,14 +230,10 @@ shared_examples 'a GitHub-ish import controller: POST create' do ...@@ -234,14 +230,10 @@ shared_examples 'a GitHub-ish import controller: POST create' do
end end
context 'user has chosen an existing nested namespace and name for the project' do context 'user has chosen an existing nested namespace and name for the project' do
let(:parent_namespace) { create(:group, name: 'foo', owner: user) } let(:parent_namespace) { create(:namespace, name: 'foo', owner: user) }
let(:nested_namespace) { create(:group, name: 'bar', parent: parent_namespace) } let(:nested_namespace) { create(:namespace, name: 'bar', parent: parent_namespace, owner: user) }
let(:test_name) { 'test_name' } let(:test_name) { 'test_name' }
before do
nested_namespace.add_owner(user)
end
it 'takes the selected namespace and name' do it 'takes the selected namespace and name' do
expect(Gitlab::GithubImport::ProjectCreator). expect(Gitlab::GithubImport::ProjectCreator).
to receive(:new).with(provider_repo, test_name, nested_namespace, user, access_params, type: provider). to receive(:new).with(provider_repo, test_name, nested_namespace, user, access_params, type: provider).
...@@ -284,7 +276,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do ...@@ -284,7 +276,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 context 'user has chosen existent and non-existent nested namespaces and name for the project' do
let(:test_name) { 'test_name' } let(:test_name) { 'test_name' }
let!(:parent_namespace) { create(:group, name: 'foo', owner: user) } let!(:parent_namespace) { create(:namespace, name: 'foo', owner: user) }
it 'takes the selected namespace and name' do it 'takes the selected namespace and name' do
expect(Gitlab::GithubImport::ProjectCreator). expect(Gitlab::GithubImport::ProjectCreator).
......
...@@ -15,31 +15,31 @@ describe DynamicPathValidator do ...@@ -15,31 +15,31 @@ describe DynamicPathValidator do
end end
context 'for group' do context 'for group' do
it 'calls valid_group_path?' do it 'calls valid_namespace_path?' do
group = build(:group, :nested, path: 'activity') group = build(:group, :nested, path: 'activity')
expect(described_class).to receive(:valid_group_path?).with(group.full_path).and_call_original expect(described_class).to receive(:valid_namespace_path?).with(group.full_path).and_call_original
expect(validator.path_valid_for_record?(group, 'activity')).to be_falsey expect(validator.path_valid_for_record?(group, 'activity')).to be_falsey
end end
end end
context 'for user' do context 'for user' do
it 'calls valid_user_path?' do it 'calls valid_namespace_path?' do
user = build(:user, username: 'activity') user = build(:user, username: 'activity')
expect(described_class).to receive(:valid_user_path?).with(user.full_path).and_call_original expect(described_class).to receive(:valid_namespace_path?).with(user.full_path).and_call_original
expect(validator.path_valid_for_record?(user, 'activity')).to be_truthy expect(validator.path_valid_for_record?(user, 'activity')).to be_truthy
end end
end end
context 'for user namespace' do context 'for user namespace' do
it 'calls valid_user_path?' do it 'calls valid_namespace_path?' do
user = create(:user, username: 'activity') user = create(:user, username: 'activity')
namespace = user.namespace namespace = user.namespace
expect(described_class).to receive(:valid_user_path?).with(namespace.full_path).and_call_original expect(described_class).to receive(:valid_namespace_path?).with(namespace.full_path).and_call_original
expect(validator.path_valid_for_record?(namespace, 'activity')).to be_truthy expect(validator.path_valid_for_record?(namespace, 'activity')).to be_truthy
end end
...@@ -52,7 +52,7 @@ describe DynamicPathValidator do ...@@ -52,7 +52,7 @@ describe DynamicPathValidator do
validator.validate_each(group, :path, "Path with spaces, and comma's!") validator.validate_each(group, :path, "Path with spaces, and comma's!")
expect(group.errors[:path]).to include(Gitlab::PathRegex.namespace_format_message) expect(group.errors[:path]).to include(Gitlab::Regex.namespace_regex_message)
end end
it 'adds a message when the path is not in the correct format' do it 'adds a message when the path is not in the correct format' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment