diff options
Diffstat (limited to 'lib')
50 files changed, 963 insertions, 287 deletions
diff --git a/lib/api/api.rb b/lib/api/api.rb index ce4cc8b34f7..2c7cd9038c3 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -46,5 +46,6 @@ module API mount Commits mount Namespaces mount Branches + mount Labels end end diff --git a/lib/api/branches.rb b/lib/api/branches.rb index b32a4aa7bc2..4db5f61dd28 100644 --- a/lib/api/branches.rb +++ b/lib/api/branches.rb @@ -80,9 +80,17 @@ module API # POST /projects/:id/repository/branches post ":id/repository/branches" do authorize_push_project - @branch = CreateBranchService.new.execute(user_project, params[:branch_name], params[:ref], current_user) - - present @branch, with: Entities::RepoObject, project: user_project + result = CreateBranchService.new.execute(user_project, + params[:branch_name], + params[:ref], + current_user) + if result[:status] == :success + present result[:branch], + with: Entities::RepoObject, + project: user_project + else + render_api_error!(result[:message], 400) + end end # Delete branch @@ -99,7 +107,7 @@ module API if result[:state] == :success true else - render_api_error!(result[:message], 405) + render_api_error!(result[:message], result[:return_code]) end end end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index b190646a1e3..74fdef93543 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -102,6 +102,7 @@ module API class RepoCommit < Grape::Entity expose :id, :short_id, :title, :author_name, :author_email, :created_at + expose :safe_message, as: :message end class RepoCommitDetail < RepoCommit @@ -126,7 +127,7 @@ module API end class Issue < ProjectEntity - expose :label_list, as: :labels + expose :label_names, as: :labels expose :milestone, using: Entities::Milestone expose :assignee, :author, using: Entities::UserBasic end @@ -135,7 +136,9 @@ module API expose :target_branch, :source_branch, :upvotes, :downvotes expose :author, :assignee, using: Entities::UserBasic expose :source_project_id, :target_project_id - expose :label_list, as: :labels + expose :label_names, as: :labels + expose :description + expose :milestone, using: Entities::Milestone end class SSHKey < Grape::Entity @@ -191,7 +194,7 @@ module API end class Label < Grape::Entity - expose :name + expose :name, :color end class RepoDiff < Grape::Entity @@ -201,13 +204,13 @@ module API class Compare < Grape::Entity expose :commit, using: Entities::RepoCommit do |compare, options| - if compare.commit - Commit.new compare.commit - end + Commit.decorate(compare.commits).last end + expose :commits, using: Entities::RepoCommit do |compare, options| - Commit.decorate compare.commits + Commit.decorate(compare.commits) end + expose :diffs, using: Entities::RepoDiff do |compare, options| compare.diffs end @@ -218,5 +221,9 @@ module API expose :same, as: :compare_same_ref end + + class Contributor < Grape::Entity + expose :name, :email, :commits, :additions, :deletions + end end end diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index b6a5806d646..6af0f6d1b25 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -5,6 +5,10 @@ module API SUDO_HEADER ="HTTP_SUDO" SUDO_PARAM = :sudo + def parse_boolean(value) + [ true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON' ].include?(value) + end + def current_user private_token = (params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER]).to_s @current_user ||= User.find_by(authentication_token: private_token) @@ -98,10 +102,33 @@ module API def attributes_for_keys(keys) attrs = {} + keys.each do |key| - attrs[key] = params[key] if params[key].present? or (params.has_key?(key) and params[key] == false) + if params[key].present? or (params.has_key?(key) and params[key] == false) + attrs[key] = params[key] + end + end + + ActionController::Parameters.new(attrs).permit! + end + + # Helper method for validating all labels against its names + def validate_label_params(params) + errors = {} + + if params[:labels].present? + params[:labels].split(',').each do |label_name| + label = user_project.labels.create_with( + color: Label::DEFAULT_COLOR).find_or_initialize_by( + title: label_name.strip) + + if label.invalid? + errors[label.title] = label.errors + end + end end - attrs + + errors end # error helpers diff --git a/lib/api/internal.rb b/lib/api/internal.rb index 5850892df07..5f484f63418 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -12,7 +12,9 @@ module API # ref - branch name # forced_push - forced_push # - get "/allowed" do + post "/allowed" do + status 200 + # Check for *.wiki repositories. # Strip out the .wiki from the pathname before finding the # project. This applies the correct project permissions to @@ -34,10 +36,7 @@ module API actor, params[:action], project, - params[:ref], - params[:oldrev], - params[:newrev], - params[:forced_push] + params[:changes] ) end diff --git a/lib/api/issues.rb b/lib/api/issues.rb index f50be3a815d..5369149cdfc 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -3,13 +3,41 @@ module API class Issues < Grape::API before { authenticate! } + helpers do + def filter_issues_state(issues, state = nil) + case state + when 'opened' then issues.opened + when 'closed' then issues.closed + else issues + end + end + + def filter_issues_labels(issues, labels) + issues.includes(:labels).where("labels.title" => labels.split(',')) + end + end + resource :issues do # Get currently authenticated user's issues # - # Example Request: + # Parameters: + # state (optional) - Return "opened" or "closed" issues + # labels (optional) - Comma-separated list of label names + + # Example Requests: # GET /issues + # GET /issues?state=opened + # GET /issues?state=closed + # GET /issues?labels=foo + # GET /issues?labels=foo,bar + # GET /issues?labels=foo,bar&state=opened get do - present paginate(current_user.issues), with: Entities::Issue + issues = current_user.issues + issues = filter_issues_state(issues, params[:state]) unless params[:state].nil? + issues = filter_issues_labels(issues, params[:labels]) unless params[:labels].nil? + issues = issues.order('issues.id DESC') + + present paginate(issues), with: Entities::Issue end end @@ -18,10 +46,24 @@ module API # # Parameters: # id (required) - The ID of a project - # Example Request: + # state (optional) - Return "opened" or "closed" issues + # labels (optional) - Comma-separated list of label names + # + # Example Requests: + # GET /projects/:id/issues + # GET /projects/:id/issues?state=opened + # GET /projects/:id/issues?state=closed # GET /projects/:id/issues + # GET /projects/:id/issues?labels=foo + # GET /projects/:id/issues?labels=foo,bar + # GET /projects/:id/issues?labels=foo,bar&state=opened get ":id/issues" do - present paginate(user_project.issues), with: Entities::Issue + issues = user_project.issues + issues = filter_issues_state(issues, params[:state]) unless params[:state].nil? + issues = filter_issues_labels(issues, params[:labels]) unless params[:labels].nil? + issues = issues.order('issues.id DESC') + + present paginate(issues), with: Entities::Issue end # Get a single project issue @@ -50,10 +92,21 @@ module API post ":id/issues" do required_attributes! [:title] attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id] - attrs[:label_list] = params[:labels] if params[:labels].present? + + # Validate label names in advance + if (errors = validate_label_params(params)).any? + render_api_error!({ labels: errors }, 400) + end + issue = ::Issues::CreateService.new(user_project, current_user, attrs).execute if issue.valid? + # Find or create labels and attach to issue. Labels are valid because + # we already checked its name, so there can't be an error here + if params[:labels].present? + issue.add_labels_by_names(params[:labels].split(',')) + end + present issue, with: Entities::Issue else not_found! @@ -76,13 +129,24 @@ module API put ":id/issues/:issue_id" do issue = user_project.issues.find(params[:issue_id]) authorize! :modify_issue, issue - attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id, :state_event] - attrs[:label_list] = params[:labels] if params[:labels].present? + + # Validate label names in advance + if (errors = validate_label_params(params)).any? + render_api_error!({ labels: errors }, 400) + end issue = ::Issues::UpdateService.new(user_project, current_user, attrs).execute(issue) if issue.valid? + # Find or create labels and attach to issue. Labels are valid because + # we already checked its name, so there can't be an error here + unless params[:labels].nil? + issue.remove_labels + # Create and add labels to the new created issue + issue.add_labels_by_names(params[:labels].split(',')) + end + present issue, with: Entities::Issue else not_found! diff --git a/lib/api/labels.rb b/lib/api/labels.rb new file mode 100644 index 00000000000..2fdf53ffec2 --- /dev/null +++ b/lib/api/labels.rb @@ -0,0 +1,103 @@ +module API + # Labels API + class Labels < Grape::API + before { authenticate! } + + resource :projects do + # Get all labels of the project + # + # Parameters: + # id (required) - The ID of a project + # Example Request: + # GET /projects/:id/labels + get ':id/labels' do + present user_project.labels, with: Entities::Label + end + + # Creates a new label + # + # Parameters: + # id (required) - The ID of a project + # name (required) - The name of the label to be deleted + # color (required) - Color of the label given in 6-digit hex + # notation with leading '#' sign (e.g. #FFAABB) + # Example Request: + # POST /projects/:id/labels + post ':id/labels' do + authorize! :admin_label, user_project + required_attributes! [:name, :color] + + attrs = attributes_for_keys [:name, :color] + label = user_project.find_label(attrs[:name]) + + if label + return render_api_error!('Label already exists', 409) + end + + label = user_project.labels.create(attrs) + + if label.valid? + present label, with: Entities::Label + else + render_api_error!(label.errors.full_messages.join(', '), 400) + end + end + + # Deletes an existing label + # + # Parameters: + # id (required) - The ID of a project + # name (required) - The name of the label to be deleted + # + # Example Request: + # DELETE /projects/:id/labels + delete ':id/labels' do + authorize! :admin_label, user_project + required_attributes! [:name] + + label = user_project.find_label(params[:name]) + if !label + return render_api_error!('Label not found', 404) + end + + label.destroy + end + + # Updates an existing label. At least one optional parameter is required. + # + # Parameters: + # id (required) - The ID of a project + # name (optional) - The name of the label to be deleted + # color (optional) - Color of the label given in 6-digit hex + # notation with leading '#' sign (e.g. #FFAABB) + # Example Request: + # PUT /projects/:id/labels + put ':id/labels' do + authorize! :admin_label, user_project + required_attributes! [:name] + + label = user_project.find_label(params[:name]) + if !label + return render_api_error!('Label not found', 404) + end + + attrs = attributes_for_keys [:new_name, :color] + + if attrs.empty? + return render_api_error!('Required parameters "name" or "color" ' \ + 'missing', + 400) + end + + # Rename new name to the actual label attribute name + attrs[:name] = attrs.delete(:new_name) if attrs.key?(:new_name) + + if label.update(attrs) + present label, with: Entities::Label + else + render_api_error!(label.errors.full_messages.join(', '), 400) + end + end + end + end +end diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index fc1f1254a9e..8726379bf3c 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -76,10 +76,20 @@ module API authorize! :write_merge_request, user_project required_attributes! [:source_branch, :target_branch, :title] attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :target_project_id, :description] - attrs[:label_list] = params[:labels] if params[:labels].present? + + # Validate label names in advance + if (errors = validate_label_params(params)).any? + render_api_error!({ labels: errors }, 400) + end + merge_request = ::MergeRequests::CreateService.new(user_project, current_user, attrs).execute if merge_request.valid? + # Find or create labels and attach to issue + if params[:labels].present? + merge_request.add_labels_by_names(params[:labels].split(",")) + end + present merge_request, with: Entities::MergeRequest else handle_merge_request_errors! merge_request.errors @@ -103,12 +113,23 @@ module API # put ":id/merge_request/:merge_request_id" do attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :state_event, :description] - attrs[:label_list] = params[:labels] if params[:labels].present? merge_request = user_project.merge_requests.find(params[:merge_request_id]) authorize! :modify_merge_request, merge_request + + # Validate label names in advance + if (errors = validate_label_params(params)).any? + render_api_error!({ labels: errors }, 400) + end + merge_request = ::MergeRequests::UpdateService.new(user_project, current_user, attrs).execute(merge_request) if merge_request.valid? + # Find or create labels and attach to issue + unless params[:labels].nil? + merge_request.remove_labels + merge_request.add_labels_by_names(params[:labels].split(",")) + end + present merge_request, with: Entities::MergeRequest else handle_merge_request_errors! merge_request.errors diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 732c969d7ef..55f7975bbf7 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -7,7 +7,7 @@ module API helpers do def map_public_to_visibility_level(attrs) publik = attrs.delete(:public) - publik = [ true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON' ].include?(publik) + publik = parse_boolean(publik) attrs[:visibility_level] = Gitlab::VisibilityLevel::PUBLIC if !attrs[:visibility_level].present? && publik == true attrs end @@ -15,10 +15,20 @@ module API # Get a projects list for authenticated user # + # Parameters: + # archived (optional) - if passed, limit by archived status + # # Example Request: # GET /projects get do - @projects = paginate current_user.authorized_projects + @projects = current_user.authorized_projects + + # If the archived parameter is passed, limit results accordingly + if params[:archived].present? + @projects = @projects.where(archived: parse_boolean(params[:archived])) + end + + @projects = paginate @projects present @projects, with: Entities::Project end @@ -77,6 +87,7 @@ module API # namespace_id (optional) - defaults to user namespace # public (optional) - if true same as setting visibility_level = 20 # visibility_level (optional) - 0 by default + # import_url (optional) # Example Request # POST /projects post do @@ -117,6 +128,7 @@ module API # snippets_enabled (optional) # public (optional) - if true same as setting visibility_level = 20 # visibility_level (optional) + # import_url (optional) # Example Request # POST /projects/user/:user_id post "user/:user_id" do @@ -130,7 +142,8 @@ module API :wiki_enabled, :snippets_enabled, :public, - :visibility_level] + :visibility_level, + :import_url] attrs = map_public_to_visibility_level(attrs) @project = ::Projects::CreateService.new(user, attrs).execute if @project.saved? @@ -211,17 +224,6 @@ module API @users = paginate @users present @users, with: Entities::UserBasic end - - # Get a project labels - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # GET /projects/:id/labels - get ':id/labels' do - @labels = user_project.issues_labels - present @labels, with: Entities::Label - end end end end diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index 03806d9343b..07c29aa7b4c 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -32,14 +32,23 @@ module API # id (required) - The ID of a project # tag_name (required) - The name of the tag # ref (required) - Create tag from commit sha or branch + # message (optional) - Specifying a message creates an annotated tag. # Example Request: # POST /projects/:id/repository/tags post ':id/repository/tags' do authorize_push_project - @tag = CreateTagService.new.execute(user_project, params[:tag_name], - params[:ref], current_user) - - present @tag, with: Entities::RepoObject, project: user_project + message = params[:message] || nil + result = CreateTagService.new.execute(user_project, params[:tag_name], + params[:ref], message, + current_user) + + if result[:status] == :success + present result[:tag], + with: Entities::RepoObject, + project: user_project + else + render_api_error!(result[:message], 400) + end end # Get a project repository tree @@ -115,21 +124,13 @@ module API # GET /projects/:id/repository/archive get ":id/repository/archive", requirements: { format: Gitlab::Regex.archive_formats_regex } do authorize! :download_code, user_project - repo = user_project.repository - ref = params[:sha] - format = params[:format] - storage_path = Gitlab.config.gitlab.repository_downloads_path + file_path = ArchiveRepositoryService.new.execute(user_project, params[:sha], params[:format]) - file_path = repo.archive_repo(ref, storage_path, format) if file_path && File.exists?(file_path) data = File.open(file_path, 'rb').read - header["Content-Disposition"] = "attachment; filename=\"#{File.basename(file_path)}\"" - content_type MIME::Types.type_for(file_path).first.content_type - env['api.format'] = :binary - present data else not_found! @@ -147,9 +148,21 @@ module API get ':id/repository/compare' do authorize! :download_code, user_project required_attributes! [:from, :to] - compare = Gitlab::Git::Compare.new(user_project.repository.raw_repository, params[:from], params[:to], MergeRequestDiff::COMMITS_SAFE_SIZE) + compare = Gitlab::Git::Compare.new(user_project.repository.raw_repository, params[:from], params[:to]) present compare, with: Entities::Compare end + + # Get repository contributors + # + # Parameters: + # id (required) - The ID of a project + # Example Request: + # GET /projects/:id/repository/contributors + get ':id/repository/contributors' do + authorize! :download_code, user_project + + present user_project.repository.contributors, with: Entities::Contributor + end end end end diff --git a/lib/api/users.rb b/lib/api/users.rb index 92dbe97f0a4..69553f16397 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -59,7 +59,7 @@ module API authenticated_as_admin! required_attributes! [:email, :password, :name, :username] attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :extern_uid, :provider, :bio, :can_create_group, :admin] - user = User.build_user(attrs, as: :admin) + user = User.build_user(attrs) admin = attrs.delete(:admin) user.admin = admin unless admin.nil? if user.save @@ -96,7 +96,7 @@ module API admin = attrs.delete(:admin) user.admin = admin unless admin.nil? - if user.update_attributes(attrs, as: :admin) + if user.update_attributes(attrs) present user, with: Entities::UserFull else not_found! diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb index 6f7c4f7c909..ea05fa2c261 100644 --- a/lib/backup/repository.rb +++ b/lib/backup/repository.rb @@ -69,7 +69,7 @@ module Backup end print 'Put GitLab hooks in repositories dirs'.yellow - if system("#{Gitlab.config.gitlab_shell.path}/support/rewrite-hooks.sh", Gitlab.config.gitlab_shell.repos_path) + if system("#{Gitlab.config.gitlab_shell.path}/bin/create-hooks") puts " [DONE]".green else puts " [FAILED]".red diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb index b93800e235f..907373ab991 100644 --- a/lib/gitlab/backend/shell.rb +++ b/lib/gitlab/backend/shell.rb @@ -27,7 +27,7 @@ module Gitlab # import_repository("gitlab/gitlab-ci", "https://github.com/randx/six.git") # def import_repository(name, url) - system "#{gitlab_shell_path}/bin/gitlab-projects", "import-project", "#{name}.git", url + system "#{gitlab_shell_path}/bin/gitlab-projects", "import-project", "#{name}.git", url, '240' end # Move repository @@ -107,12 +107,17 @@ module Gitlab # path - project path with namespace # tag_name - new tag name # ref - HEAD for new tag + # message - optional message for tag (annotated tag) # # Ex. # add_tag("gitlab/gitlab-ci", "v4.0", "master") + # add_tag("gitlab/gitlab-ci", "v4.0", "master", "message") # - def add_tag(path, tag_name, ref) - system "#{gitlab_shell_path}/bin/gitlab-projects", "create-tag", "#{path}.git", tag_name, ref + def add_tag(path, tag_name, ref, message = nil) + cmd = %W(#{gitlab_shell_path}/bin/gitlab-projects create-tag #{path}.git + #{tag_name} #{ref}) + cmd << message unless message.nil? || message.empty? + system *cmd end # Remove repository tag diff --git a/lib/gitlab/blacklist.rb b/lib/gitlab/blacklist.rb index 6bc2c3b487c..65efb6e4407 100644 --- a/lib/gitlab/blacklist.rb +++ b/lib/gitlab/blacklist.rb @@ -3,7 +3,31 @@ module Gitlab extend self def path - %w(admin dashboard files groups help profile projects search public assets u s teams merge_requests issues users snippets services repository hooks notes) + %w( + admin + dashboard + files + groups + help + profile + projects + search + public + assets + u + s + teams + merge_requests + issues + users + snippets + services + repository + hooks + notes + unsubscribes + all + ) end end end diff --git a/lib/gitlab/compare_result.rb b/lib/gitlab/compare_result.rb new file mode 100644 index 00000000000..d72391dade5 --- /dev/null +++ b/lib/gitlab/compare_result.rb @@ -0,0 +1,9 @@ +module Gitlab + class CompareResult + attr_reader :commits, :diffs + + def initialize(compare) + @commits, @diffs = compare.commits, compare.diffs + end + end +end diff --git a/lib/gitlab/config_helper.rb b/lib/gitlab/config_helper.rb new file mode 100644 index 00000000000..41880069e4c --- /dev/null +++ b/lib/gitlab/config_helper.rb @@ -0,0 +1,9 @@ +module Gitlab::ConfigHelper + def gitlab_config_features + Gitlab.config.gitlab.default_projects_features + end + + def gitlab_config + Gitlab.config.gitlab + end +end diff --git a/lib/gitlab/contributors.rb b/lib/gitlab/contributors.rb new file mode 100644 index 00000000000..c41e92b620f --- /dev/null +++ b/lib/gitlab/contributors.rb @@ -0,0 +1,9 @@ +module Gitlab + class Contributor + attr_accessor :email, :name, :commits, :additions, :deletions + + def initialize + @commits, @additions, @deletions = 0, 0, 0 + end + end +end diff --git a/lib/gitlab/diff_parser.rb b/lib/gitlab/diff_parser.rb index 14bbb328637..b244295027e 100644 --- a/lib/gitlab/diff_parser.rb +++ b/lib/gitlab/diff_parser.rb @@ -30,7 +30,7 @@ module Gitlab line_new = line.match(/\+[0-9]*/)[0].to_i.abs rescue 0 next if line_old == 1 && line_new == 1 #top of file - yield(full_line, type, nil, nil, nil) + yield(full_line, type, nil, line_new, line_old) next else type = identification_type(line) diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index 38b3d82e2f4..e75a5a1d62e 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -5,7 +5,7 @@ module Gitlab attr_reader :params, :project, :git_cmd, :user - def allowed?(actor, cmd, project, ref = nil, oldrev = nil, newrev = nil, forced_push = false) + def allowed?(actor, cmd, project, changes = nil) case cmd when *DOWNLOAD_COMMANDS if actor.is_a? User @@ -19,12 +19,12 @@ module Gitlab end when *PUSH_COMMANDS if actor.is_a? User - push_allowed?(actor, project, ref, oldrev, newrev, forced_push) + push_allowed?(actor, project, changes) elsif actor.is_a? DeployKey # Deploy key not allowed to push return false elsif actor.is_a? Key - push_allowed?(actor.user, project, ref, oldrev, newrev, forced_push) + push_allowed?(actor.user, project, changes) else raise 'Wrong actor' end @@ -41,13 +41,21 @@ module Gitlab end end - def push_allowed?(user, project, ref, oldrev, newrev, forced_push) - if user && user_allowed?(user) + def push_allowed?(user, project, changes) + return false unless user && user_allowed?(user) + return true if changes.blank? + + changes = changes.lines if changes.kind_of?(String) + + # Iterate over all changes to find if user allowed all of them to be applied + changes.each do |change| + oldrev, newrev, ref = changes.split('') + action = if project.protected_branch?(ref) # we dont allow force push to protected branch - if forced_push.to_s == 'true' + if forced_push?(oldrev, newrev) :force_push_code_to_protected_branches - # and we dont allow remove of protected branch + # and we dont allow remove of protected branch elsif newrev =~ /0000000/ :remove_protected_branches else @@ -59,7 +67,22 @@ module Gitlab else :push_code end - user.can?(action, project) + unless user.can?(action, project) + # If user does not have access to make at least one change - cancel all push + return false + end + end + + # If user has access to make all changes + true + end + + def forced_push?(oldrev, newrev) + return false if project.empty_repo? + + if oldrev !~ /00000000/ && newrev !~ /00000000/ + missed_refs = IO.popen(%W(git --git-dir=#{project.repository.path_to_repo} rev-list #{oldrev} ^#{newrev})).read + missed_refs.split("\n").size > 0 else false end diff --git a/lib/gitlab/git_ref_validator.rb b/lib/gitlab/git_ref_validator.rb new file mode 100644 index 00000000000..13cb08948bb --- /dev/null +++ b/lib/gitlab/git_ref_validator.rb @@ -0,0 +1,11 @@ +module Gitlab + module GitRefValidator + extend self + # Validates a given name against the git reference specification + # + # Returns true for a valid reference name, false otherwise + def validate(ref_name) + system *%W(git check-ref-format refs/#{ref_name}) + end + end +end diff --git a/lib/gitlab/issues_labels.rb b/lib/gitlab/issues_labels.rb index bc49d27b521..0d34976736f 100644 --- a/lib/gitlab/issues_labels.rb +++ b/lib/gitlab/issues_labels.rb @@ -1,27 +1,27 @@ module Gitlab class IssuesLabels class << self - def important_labels - %w(bug critical confirmed) - end - - def warning_labels - %w(documentation support) - end - - def neutral_labels - %w(discussion suggestion) - end - - def positive_labels - %w(feature enhancement) - end - def generate(project) - labels = important_labels + warning_labels + neutral_labels + positive_labels + red = '#d9534f' + yellow = '#f0ad4e' + blue = '#428bca' + green = '#5cb85c' + + labels = [ + { title: "bug", color: red }, + { title: "critical", color: red }, + { title: "confirmed", color: red }, + { title: "documentation", color: yellow }, + { title: "support", color: yellow }, + { title: "discussion", color: blue }, + { title: "suggestion", color: blue }, + { title: "feature", color: green }, + { title: "enhancement", color: green } + ] - project.issues_default_label_list = labels - project.save + labels.each do |label| + project.labels.create(label) + end end end end diff --git a/lib/gitlab/ldap/access.rb b/lib/gitlab/ldap/access.rb index 4e48ff11871..c054b6f5865 100644 --- a/lib/gitlab/ldap/access.rb +++ b/lib/gitlab/ldap/access.rb @@ -9,13 +9,26 @@ module Gitlab end end + def self.allowed?(user) + self.open do |access| + if access.allowed?(user) + # GitLab EE LDAP code goes here + user.last_credential_check_at = Time.now + user.save + true + else + false + end + end + end + def initialize(adapter=nil) @adapter = adapter end def allowed?(user) if Gitlab::LDAP::Person.find_by_dn(user.extern_uid, adapter) - !Gitlab::LDAP::Person.active_directory_disabled?(user.extern_uid, adapter) + !Gitlab::LDAP::Person.disabled_via_active_directory?(user.extern_uid, adapter) else false end diff --git a/lib/gitlab/ldap/adapter.rb b/lib/gitlab/ldap/adapter.rb index e36616f0e66..68ac1b22909 100644 --- a/lib/gitlab/ldap/adapter.rb +++ b/lib/gitlab/ldap/adapter.rb @@ -14,7 +14,15 @@ module Gitlab end def self.adapter_options - encryption = config['method'].to_s == 'ssl' ? :simple_tls : nil + encryption = + case config['method'].to_s + when 'ssl' + :simple_tls + when 'tls' + :start_tls + else + nil + end options = { host: config['host'], @@ -78,7 +86,8 @@ module Gitlab end def dn_matches_filter?(dn, filter) - ldap_search(base: dn, filter: filter, scope: Net::LDAP::SearchScope_BaseObject, attributes: %w{dn}).any? + ldap_search(base: dn, filter: filter, + scope: Net::LDAP::SearchScope_BaseObject, attributes: %w{dn}).any? end def ldap_search(*args) diff --git a/lib/gitlab/ldap/person.rb b/lib/gitlab/ldap/person.rb index 9ad6618bd46..87c3d711db4 100644 --- a/lib/gitlab/ldap/person.rb +++ b/lib/gitlab/ldap/person.rb @@ -16,7 +16,7 @@ module Gitlab adapter.user('dn', dn) end - def self.active_directory_disabled?(dn, adapter=nil) + def self.disabled_via_active_directory?(dn, adapter=nil) adapter ||= Gitlab::LDAP::Adapter.new adapter.dn_matches_filter?(dn, AD_USER_DISABLED) end diff --git a/lib/gitlab/ldap/user.rb b/lib/gitlab/ldap/user.rb index be3fcc4f035..e6aa3890992 100644 --- a/lib/gitlab/ldap/user.rb +++ b/lib/gitlab/ldap/user.rb @@ -26,7 +26,7 @@ module Gitlab # * When user already has account and need to link their LDAP account. # * LDAP uid changed for user with same email and we need to update their uid # - user = find_user(email) + user = model.find_by(email: email) if user user.update_attributes(extern_uid: uid, provider: provider) @@ -43,21 +43,6 @@ module Gitlab user end - def find_user(email) - user = model.find_by(email: email) - - # If no user found and allow_username_or_email_login is true - # we look for user by extracting part of their email - if !user && email && ldap_conf['allow_username_or_email_login'] - uname = email.partition('@').first - # Strip apostrophes since they are disallowed as part of username - username = uname.gsub("'", "") - user = model.find_by(username: username) - end - - user - end - def authenticate(login, password) # Check user against LDAP backend if user is not authenticated # Only check with valid login and password to prevent anonymous bind results @@ -92,10 +77,6 @@ module Gitlab model.where("provider = ? and lower(extern_uid) = ?", provider, uid.downcase).last end - def username - auth.info.nickname.to_s.force_encoding("utf-8") - end - def provider 'ldap' end diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb index 50e6b1efca6..6017a4c86c1 100644 --- a/lib/gitlab/markdown.rb +++ b/lib/gitlab/markdown.rb @@ -169,10 +169,13 @@ module Gitlab end def reference_user(identifier, project = @project) - if user = User.find_by(username: identifier) - options = html_options.merge( + options = html_options.merge( class: "gfm gfm-team_member #{html_options[:class]}" ) + + if identifier == "all" + link_to("@all", project_url(project), options) + elsif user = User.find_by(username: identifier) link_to("@#{identifier}", user_url(identifier), options) end end diff --git a/lib/gitlab/markdown_helper.rb b/lib/gitlab/markdown_helper.rb new file mode 100644 index 00000000000..abed12fe570 --- /dev/null +++ b/lib/gitlab/markdown_helper.rb @@ -0,0 +1,25 @@ +module Gitlab + module MarkdownHelper + module_function + + # Public: Determines if a given filename is compatible with GitHub::Markup. + # + # filename - Filename string to check + # + # Returns boolean + def markup?(filename) + filename.downcase.end_with?(*%w(.textile .rdoc .org .creole .wiki + .mediawiki .rst .adoc .asciidoc .asc)) + end + + # Public: Determines if a given filename is compatible with + # GitLab-flavored Markdown. + # + # filename - Filename string to check + # + # Returns boolean + def gitlab_markdown?(filename) + filename.downcase.end_with?(*%w(.mdown .md .markdown)) + end + end +end diff --git a/lib/gitlab/oauth/user.rb b/lib/gitlab/oauth/user.rb index c5be884a895..9670aad2c5d 100644 --- a/lib/gitlab/oauth/user.rb +++ b/lib/gitlab/oauth/user.rb @@ -27,7 +27,7 @@ module Gitlab password_confirmation: password, } - user = model.build_user(opts, as: :admin) + user = model.build_user(opts) user.skip_confirmation! # Services like twitter and github does not return email via oauth @@ -44,7 +44,13 @@ module Gitlab user.username = email_username.gsub("'", "") end - user.save! + begin + user.save! + rescue ActiveRecord::RecordInvalid => e + log.info "(OAuth) Email #{e.record.errors[:email]}. Username #{e.record.errors[:username]}" + return nil, e.record.errors + end + log.info "(OAuth) Creating user #{email} from login with extern_uid => #{uid}" if Gitlab.config.omniauth['block_auto_created_users'] && !ldap? @@ -61,10 +67,11 @@ module Gitlab end def uid - auth.info.uid || auth.uid + auth.uid.to_s end def email + return unless auth.info.respond_to?(:email) auth.info.email.downcase unless auth.info.email.nil? end @@ -77,6 +84,7 @@ module Gitlab end def username + return unless auth.info.respond_to?(:nickname) auth.info.nickname.to_s.force_encoding("utf-8") end diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb new file mode 100644 index 00000000000..90511662b20 --- /dev/null +++ b/lib/gitlab/project_search_results.rb @@ -0,0 +1,52 @@ +module Gitlab + class ProjectSearchResults < SearchResults + attr_reader :project, :repository_ref + + def initialize(project_id, query, repository_ref = nil) + @project = Project.find(project_id) + @repository_ref = repository_ref + @query = Shellwords.shellescape(query) if query.present? + end + + def objects(scope, page = nil) + case scope + when 'notes' + notes.page(page).per(per_page) + when 'blobs' + Kaminari.paginate_array(blobs).page(page).per(per_page) + else + super + end + end + + def total_count + @total_count ||= issues_count + merge_requests_count + blobs_count + notes_count + end + + def blobs_count + @blobs_count ||= blobs.count + end + + def notes_count + @notes_count ||= notes.count + end + + private + + def blobs + if project.empty_repo? + [] + else + project.repository.search_files(query, repository_ref) + end + end + + def notes + Note.where(project_id: limit_project_ids).search(query).order('updated_at DESC') + end + + def limit_project_ids + [project.id] + end + end +end diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index e932b64f4f0..4b8038843b0 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -6,18 +6,35 @@ module Gitlab default_regex end + def username_regex_message + default_regex_message + end + def project_name_regex /\A[a-zA-Z0-9_][a-zA-Z0-9_\-\. ]*\z/ end + def project_regex_message + "can contain only letters, digits, '_', '-' and '.' and space. " \ + "It must start with letter, digit or '_'." + end + def name_regex /\A[a-zA-Z0-9_\-\. ]*\z/ end + def name_regex_message + "can contain only letters, digits, '_', '-' and '.' and space." + end + def path_regex default_regex end + def path_regex_message + default_regex_message + end + def archive_formats_regex #|zip|tar| tar.gz | tar.bz2 | /(zip|tar|tar\.gz|tgz|gz|tar\.bz2|tbz|tbz2|tb2|bz2)/ @@ -48,8 +65,14 @@ module Gitlab protected + def default_regex_message + "can contain only letters, digits, '_', '-' and '.'. " \ + "It must start with letter, digit or '_', optionally preceeded by '.'. " \ + "It must not end in '.git'." + end + def default_regex - /\A[.?]?[a-zA-Z0-9_][a-zA-Z0-9_\-\.]*(?<!\.git)\z/ + /\A[a-zA-Z0-9_.][a-zA-Z0-9_\-\.]*(?<!\.git)\z/ end end end diff --git a/lib/gitlab/satellite/action.rb b/lib/gitlab/satellite/action.rb index 5ea6f956765..be45cb5c98e 100644 --- a/lib/gitlab/satellite/action.rb +++ b/lib/gitlab/satellite/action.rb @@ -1,7 +1,7 @@ module Gitlab module Satellite class Action - DEFAULT_OPTIONS = { git_timeout: 30.seconds } + DEFAULT_OPTIONS = { git_timeout: Gitlab.config.satellites.timeout.seconds } attr_accessor :options, :project, :user diff --git a/lib/gitlab/satellite/compare_action.rb b/lib/gitlab/satellite/compare_action.rb index c923bb9c0f0..46c98a8f4ca 100644 --- a/lib/gitlab/satellite/compare_action.rb +++ b/lib/gitlab/satellite/compare_action.rb @@ -1,5 +1,7 @@ module Gitlab module Satellite + class BranchesWithoutParent < StandardError; end + class CompareAction < Action def initialize(user, target_project, target_branch, source_project, source_branch) super user, target_project @@ -8,34 +10,16 @@ module Gitlab @source_project, @source_branch = source_project, source_branch end - # Only show what is new in the source branch compared to the target branch, not the other way around. - # The line below with merge_base is equivalent to diff with three dots (git diff branch1...branch2) - # From the git documentation: "git diff A...B" is equivalent to "git diff $(git-merge-base A B) B" - def diffs + # Compare 2 repositories and return Gitlab::CompareResult object + def result in_locked_and_timed_satellite do |target_repo| prepare_satellite!(target_repo) update_satellite_source_and_target!(target_repo) - common_commit = target_repo.git.native(:merge_base, default_options, ["origin/#{@target_branch}", "source/#{@source_branch}"]).strip - #this method doesn't take default options - diffs = target_repo.diff(common_commit, "source/#{@source_branch}") - diffs = diffs.map { |diff| Gitlab::Git::Diff.new(diff) } - diffs - end - rescue Grit::Git::CommandFailed => ex - handle_exception(ex) - end - # Retrieve an array of commits between the source and the target - def commits - in_locked_and_timed_satellite do |target_repo| - prepare_satellite!(target_repo) - update_satellite_source_and_target!(target_repo) - commits = target_repo.commits_between("origin/#{@target_branch}", "source/#{@source_branch}") - commits = commits.map { |commit| Gitlab::Git::Commit.new(commit, nil) } - commits + Gitlab::CompareResult.new(compare(target_repo)) end rescue Grit::Git::CommandFailed => ex - handle_exception(ex) + raise BranchesWithoutParent end private @@ -44,10 +28,17 @@ module Gitlab def update_satellite_source_and_target!(target_repo) target_repo.remote_add('source', @source_project.repository.path_to_repo) target_repo.remote_fetch('source') - target_repo.git.checkout(default_options({b: true}), @target_branch, "origin/#{@target_branch}") rescue Grit::Git::CommandFailed => ex handle_exception(ex) end + + def compare(repo) + @compare ||= Gitlab::Git::Compare.new( + Gitlab::Git::Repository.new(repo.path), + "origin/#{@target_branch}", + "source/#{@source_branch}" + ) + end end end end diff --git a/lib/gitlab/satellite/merge_action.rb b/lib/gitlab/satellite/merge_action.rb index 5f17aa60b8b..7c9b2294647 100644 --- a/lib/gitlab/satellite/merge_action.rb +++ b/lib/gitlab/satellite/merge_action.rb @@ -31,8 +31,9 @@ module Gitlab # push merge back to bare repo # will raise CommandFailed when push fails merge_repo.git.push(default_options, :origin, merge_request.target_branch) + # remove source branch - if merge_request.should_remove_source_branch && !project.root_ref?(merge_request.source_branch) + if merge_request.should_remove_source_branch && !project.root_ref?(merge_request.source_branch) && !merge_request.for_fork? # will raise CommandFailed when push fails merge_repo.git.push(default_options, :origin, ":#{merge_request.source_branch}") end @@ -44,27 +45,30 @@ module Gitlab handle_exception(ex) end - # Get a raw diff of the source to the target def diff_in_satellite in_locked_and_timed_satellite do |merge_repo| prepare_satellite!(merge_repo) update_satellite_source_and_target!(merge_repo) - diff = merge_repo.git.native(:diff, default_options, "origin/#{merge_request.target_branch}", "source/#{merge_request.source_branch}") + + # Only show what is new in the source branch compared to the target branch, not the other way around. + # The line below with merge_base is equivalent to diff with three dots (git diff branch1...branch2) + # From the git documentation: "git diff A...B" is equivalent to "git diff $(git-merge-base A B) B" + common_commit = merge_repo.git.native(:merge_base, default_options, ["origin/#{merge_request.target_branch}", "source/#{merge_request.source_branch}"]).strip + merge_repo.git.native(:diff, default_options, common_commit, "source/#{merge_request.source_branch}") end rescue Grit::Git::CommandFailed => ex handle_exception(ex) end - # Only show what is new in the source branch compared to the target branch, not the other way around. - # The line below with merge_base is equivalent to diff with three dots (git diff branch1...branch2) - # From the git documentation: "git diff A...B" is equivalent to "git diff $(git-merge-base A B) B" def diffs_between_satellite in_locked_and_timed_satellite do |merge_repo| prepare_satellite!(merge_repo) update_satellite_source_and_target!(merge_repo) if merge_request.for_fork? + # Only show what is new in the source branch compared to the target branch, not the other way around. + # The line below with merge_base is equivalent to diff with three dots (git diff branch1...branch2) + # From the git documentation: "git diff A...B" is equivalent to "git diff $(git-merge-base A B) B" common_commit = merge_repo.git.native(:merge_base, default_options, ["origin/#{merge_request.target_branch}", "source/#{merge_request.source_branch}"]).strip - #this method doesn't take default options diffs = merge_repo.diff(common_commit, "source/#{merge_request.source_branch}") else raise "Attempt to determine diffs between for a non forked merge request in satellite MergeRequest.id:[#{merge_request.id}]" diff --git a/lib/gitlab/satellite/satellite.rb b/lib/gitlab/satellite/satellite.rb index 05123ad9c41..f34d661c9fc 100644 --- a/lib/gitlab/satellite/satellite.rb +++ b/lib/gitlab/satellite/satellite.rb @@ -53,7 +53,7 @@ module Gitlab File.open(lock_file, "w+") do |f| begin f.flock File::LOCK_EX - Dir.chdir(path) { return yield } + yield ensure f.flock File::LOCK_UN end @@ -121,6 +121,7 @@ module Gitlab # # Note: this will only update remote branches (i.e. origin/*) def update_from_source! + repo.git.remote(default_options, 'set-url', :origin, project.repository.path_to_repo) repo.git.fetch(default_options, :origin) end diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb new file mode 100644 index 00000000000..75a3dfe37c3 --- /dev/null +++ b/lib/gitlab/search_results.rb @@ -0,0 +1,69 @@ +module Gitlab + class SearchResults + attr_reader :query + + # Limit search results by passed project ids + # It allows us to search only for projects user has access to + attr_reader :limit_project_ids + + def initialize(limit_project_ids, query) + @limit_project_ids = limit_project_ids || Project.all + @query = Shellwords.shellescape(query) if query.present? + end + + def objects(scope, page = nil) + case scope + when 'projects' + projects.page(page).per(per_page) + when 'issues' + issues.page(page).per(per_page) + when 'merge_requests' + merge_requests.page(page).per(per_page) + else + Kaminari.paginate_array([]).page(page).per(per_page) + end + end + + def total_count + @total_count ||= projects_count + issues_count + merge_requests_count + end + + def projects_count + @projects_count ||= projects.count + end + + def issues_count + @issues_count ||= issues.count + end + + def merge_requests_count + @merge_requests_count ||= merge_requests.count + end + + def empty? + total_count.zero? + end + + private + + def projects + Project.where(id: limit_project_ids).search(query) + end + + def issues + Issue.where(project_id: limit_project_ids).full_search(query).order('updated_at DESC') + end + + def merge_requests + MergeRequest.in_projects(limit_project_ids).full_search(query).order('updated_at DESC') + end + + def default_scope + 'projects' + end + + def per_page + 20 + end + end +end diff --git a/lib/gitlab/sidekiq_middleware/arguments_logger.rb b/lib/gitlab/sidekiq_middleware/arguments_logger.rb new file mode 100644 index 00000000000..7813091ec7b --- /dev/null +++ b/lib/gitlab/sidekiq_middleware/arguments_logger.rb @@ -0,0 +1,10 @@ +module Gitlab + module SidekiqMiddleware + class ArgumentsLogger + def call(worker, job, queue) + Sidekiq.logger.info "arguments: #{job['args']}" + yield + end + end + end +end diff --git a/lib/gitlab/theme.rb b/lib/gitlab/theme.rb index 44237a062fc..b7c50cb734d 100644 --- a/lib/gitlab/theme.rb +++ b/lib/gitlab/theme.rb @@ -1,10 +1,10 @@ module Gitlab class Theme - BASIC = 1 - MARS = 2 - MODERN = 3 - GRAY = 4 - COLOR = 5 + BASIC = 1 unless const_defined?(:BASIC) + MARS = 2 unless const_defined?(:MARS) + MODERN = 3 unless const_defined?(:MODERN) + GRAY = 4 unless const_defined?(:GRAY) + COLOR = 5 unless const_defined?(:COLOR) def self.css_class_by_id(id) themes = { diff --git a/lib/gitlab/upgrader.rb b/lib/gitlab/upgrader.rb index 0846359f9b1..74b049b5143 100644 --- a/lib/gitlab/upgrader.rb +++ b/lib/gitlab/upgrader.rb @@ -43,7 +43,7 @@ module Gitlab end def latest_version_raw - remote_tags, _ = Gitlab::Popen.popen(%W(git ls-remote --tags origin)) + remote_tags, _ = Gitlab::Popen.popen(%W(git ls-remote --tags https://gitlab.com/gitlab-org/gitlab-ce.git)) git_tags = remote_tags.split("\n").grep(/tags\/v#{current_version.major}/) git_tags = git_tags.select { |version| version =~ /v\d\.\d\.\d\Z/ } last_tag = git_tags.last.match(/v\d\.\d\.\d/).to_s diff --git a/lib/gitlab/url_builder.rb b/lib/gitlab/url_builder.rb new file mode 100644 index 00000000000..de7e0404086 --- /dev/null +++ b/lib/gitlab/url_builder.rb @@ -0,0 +1,25 @@ +module Gitlab + class UrlBuilder + include Rails.application.routes.url_helpers + + def initialize(type) + @type = type + end + + def build(id) + case @type + when :issue + issue_url(id) + end + end + + private + + def issue_url(id) + issue = Issue.find(id) + project_issue_url(id: issue.iid, + project_id: issue.project, + host: Settings.gitlab['url']) + end + end +end diff --git a/lib/gitlab/user_access.rb b/lib/gitlab/user_access.rb index 16df21b49ba..4885baf9526 100644 --- a/lib/gitlab/user_access.rb +++ b/lib/gitlab/user_access.rb @@ -3,13 +3,8 @@ module Gitlab def self.allowed?(user) return false if user.blocked? - if Gitlab.config.ldap.enabled - if user.ldap_user? - # Check if LDAP user exists and match LDAP user_filter - Gitlab::LDAP::Access.open do |adapter| - return false unless adapter.allowed?(user) - end - end + if user.requires_ldap_check? + return false unless Gitlab::LDAP::Access.allowed?(user) end true diff --git a/lib/gitlab/visibility_level.rb b/lib/gitlab/visibility_level.rb index eada9bcddf5..d0b6cde3c7e 100644 --- a/lib/gitlab/visibility_level.rb +++ b/lib/gitlab/visibility_level.rb @@ -5,9 +5,9 @@ # module Gitlab module VisibilityLevel - PRIVATE = 0 - INTERNAL = 10 - PUBLIC = 20 + PRIVATE = 0 unless const_defined?(:PRIVATE) + INTERNAL = 10 unless const_defined?(:INTERNAL) + PUBLIC = 20 unless const_defined?(:PUBLIC) class << self def values @@ -21,9 +21,23 @@ module Gitlab 'Public' => PUBLIC } end - + def allowed_for?(user, level) - user.is_admin? || !Gitlab.config.gitlab.restricted_visibility_levels.include?(level) + user.is_admin? || allowed_level?(level) + end + + # Level can be a string `"public"` or a value `20`, first check if valid, + # then check if the corresponding string appears in the config + def allowed_level?(level) + if options.has_key?(level.to_s) + non_restricted_level?(level) + elsif options.has_value?(level.to_i) + non_restricted_level?(options.key(level.to_i).downcase) + end + end + + def non_restricted_level?(level) + ! Gitlab.config.gitlab.restricted_visibility_levels.include?(level) end end diff --git a/lib/gt_one_coercion.rb b/lib/gt_one_coercion.rb new file mode 100644 index 00000000000..ef2dc09767c --- /dev/null +++ b/lib/gt_one_coercion.rb @@ -0,0 +1,5 @@ +class GtOneCoercion < Virtus::Attribute + def coerce(value) + [1, value.to_i].max + end +end diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab index 36306eeb3a6..16b06fe006e 100644 --- a/lib/support/nginx/gitlab +++ b/lib/support/nginx/gitlab @@ -1,69 +1,85 @@ -# GITLAB -# Maintainer: @randx - -# CHUNKED TRANSFER -# It is a known issue that Git-over-HTTP requires chunked transfer encoding [0] which is not -# supported by Nginx < 1.3.9 [1]. As a result, pushing a large object with Git (i.e. a single large file) -# can lead to a 411 error. In theory you can get around this by tweaking this configuration file and either -# - installing an old version of Nginx with the chunkin module [2] compiled in, or -# - using a newer version of Nginx. -# -# At the time of writing we do not know if either of these theoretical solutions works. As a workaround -# users can use Git over SSH to push large files. -# -# [0] https://git.kernel.org/cgit/git/git.git/tree/Documentation/technical/http-protocol.txt#n99 -# [1] https://github.com/agentzh/chunkin-nginx-module#status -# [2] https://github.com/agentzh/chunkin-nginx-module +## GitLab +## Maintainer: @randx +## +## Lines starting with two hashes (##) are comments with information. +## Lines starting with one hash (#) are configuration parameters that can be uncommented. +## +################################## +## CHUNKED TRANSFER ## +################################## +## +## It is a known issue that Git-over-HTTP requires chunked transfer encoding [0] +## which is not supported by Nginx < 1.3.9 [1]. As a result, pushing a large object +## with Git (i.e. a single large file) can lead to a 411 error. In theory you can get +## around this by tweaking this configuration file and either: +## - installing an old version of Nginx with the chunkin module [2] compiled in, or +## - using a newer version of Nginx. +## +## At the time of writing we do not know if either of these theoretical solutions works. +## As a workaround users can use Git over SSH to push large files. +## +## [0] https://git.kernel.org/cgit/git/git.git/tree/Documentation/technical/http-protocol.txt#n99 +## [1] https://github.com/agentzh/chunkin-nginx-module#status +## [2] https://github.com/agentzh/chunkin-nginx-module +## +################################### +## configuration ## +################################### +## upstream gitlab { server unix:/home/git/gitlab/tmp/sockets/gitlab.socket; } +## Normal HTTP host server { - listen *:80 default_server; # e.g., listen 192.168.1.1:80; In most cases *:80 is a good idea - server_name YOUR_SERVER_FQDN; # e.g., server_name source.example.com; - server_tokens off; # don't show the version number, a security best practice + listen *:80 default_server; + server_name YOUR_SERVER_FQDN; ## Replace this with something like gitlab.example.com + server_tokens off; ## Don't show the nginx version number, a security best practice root /home/git/gitlab/public; - - # Increase this if you want to upload large attachments - # Or if you want to accept large git objects over http + + ## Increase this if you want to upload large attachments + ## Or if you want to accept large git objects over http client_max_body_size 20m; - # individual nginx logs for this gitlab vhost + ## Individual nginx logs for this GitLab vhost access_log /var/log/nginx/gitlab_access.log; error_log /var/log/nginx/gitlab_error.log; location / { - # serve static files from defined root folder;. - # @gitlab is a named location for the upstream fallback, see below + ## Serve static files from defined root folder. + ## @gitlab is a named location for the upstream fallback, see below. try_files $uri $uri/index.html $uri.html @gitlab; } - # if a file, which is not found in the root folder is requested, - # then the proxy pass the request to the upsteam (gitlab unicorn) + ## If a file, which is not found in the root folder is requested, + ## then the proxy passes the request to the upsteam (gitlab unicorn). location @gitlab { - # If you use https make sure you disable gzip compression - # to be safe against BREACH attack + ## If you use HTTPS make sure you disable gzip compression + ## to be safe against BREACH attack. # gzip off; - proxy_read_timeout 300; # Some requests take more than 30 seconds. - proxy_connect_timeout 300; # Some requests take more than 30 seconds. - proxy_redirect off; + ## https://github.com/gitlabhq/gitlabhq/issues/694 + ## Some requests take more than 30 seconds. + proxy_read_timeout 300; + proxy_connect_timeout 300; + proxy_redirect off; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Frame-Options SAMEORIGIN; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Frame-Options SAMEORIGIN; proxy_pass http://gitlab; } - # Enable gzip compression as per rails guide: http://guides.rubyonrails.org/asset_pipeline.html#gzip-compression - # WARNING: If you are using relative urls do remove the block below - # See config/application.rb under "Relative url support" for the list of - # other files that need to be changed for relative url support - location ~ ^/(assets)/ { + ## Enable gzip compression as per rails guide: + ## http://guides.rubyonrails.org/asset_pipeline.html#gzip-compression + ## WARNING: If you are using relative urls remove the block below + ## See config/application.rb under "Relative url support" for the list of + ## other files that need to be changed for relative url support + location ~ ^/(assets)/ { root /home/git/gitlab/public; gzip_static on; # to serve pre-gzipped version expires max; diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl index 22e923b377c..0ba9d055c8c 100644 --- a/lib/support/nginx/gitlab-ssl +++ b/lib/support/nginx/gitlab-ssl @@ -3,33 +3,10 @@ ## ## Modified from nginx http version ## Modified from http://blog.phusion.nl/2012/04/21/tutorial-setting-up-gitlab-on-debian-6/ +## Modified from https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html ## -## Lines starting with two hashes (##) are comments containing information -## for configuration. One hash (#) comments are actual configuration parameters -## which you can comment/uncomment to your liking. -## -################################### -## SSL configuration ## -################################### -## -## Optimal configuration is taken from: -## https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html -## Make sure to read it and understand what each option does. -## -## [Optional] Generate a self-signed ssl certificate: -## mkdir /etc/nginx/ssl/ -## cd /etc/nginx/ssl/ -## sudo openssl req -newkey rsa:2048 -x509 -nodes -days 3560 -out gitlab.crt -keyout gitlab.key -## sudo chmod o-r gitlab.key -## -## Edit `gitlab-shell/config.yml`: -## 1) Set "gitlab_url" param in `gitlab-shell/config.yml` to `https://git.example.com` -## 2) Set "ca_file" to `/etc/nginx/ssl/gitlab.crt` -## 3) Set "self_signed_cert" to `true` -## Edit `gitlab/config/gitlab.yml`: -## 1) Define port for http "port: 443" -## 2) Enable https "https: true" -## 3) Update ssl for gravatar "ssl_url: https://secure.gravatar.com/avatar/%{hash}?s=%{size}&d=mm" +## Lines starting with two hashes (##) are comments with information. +## Lines starting with one hash (#) are configuration parameters that can be uncommented. ## ################################## ## CHUNKED TRANSFER ## @@ -42,39 +19,39 @@ ## - installing an old version of Nginx with the chunkin module [2] compiled in, or ## - using a newer version of Nginx. ## -## At the time of writing we do not know if either of these theoretical solutions works. As a workaround -## users can use Git over SSH to push large files. +## At the time of writing we do not know if either of these theoretical solutions works. +## As a workaround users can use Git over SSH to push large files. ## ## [0] https://git.kernel.org/cgit/git/git.git/tree/Documentation/technical/http-protocol.txt#n99 ## [1] https://github.com/agentzh/chunkin-nginx-module#status ## [2] https://github.com/agentzh/chunkin-nginx-module - +## +## +################################### +## SSL configuration ## +################################### +## +## See installation.md#using-https for additional HTTPS configuration details. upstream gitlab { - - ## Uncomment if you have set up unicorn to listen on a unix socket (recommended). server unix:/home/git/gitlab/tmp/sockets/gitlab.socket; - - ## Uncomment if unicorn is configured to listen on a tcp port. - ## Check the port number in /home/git/gitlab/config/unicorn.rb - # server 127.0.0.1:8080; } -## This is a normal HTTP host which redirects all traffic to the HTTPS host. +## Normal HTTP host server { - listen *:80; - ## Replace git.example.com with your FQDN. - server_name git.example.com; - server_tokens off; - ## root doesn't have to be a valid path since we are redirecting - root /nowhere; - rewrite ^ https://$server_name$request_uri permanent; + listen *:80 default_server; + server_name YOUR_SERVER_FQDN; ## Replace this with something like gitlab.example.com + server_tokens off; ## Don't show the nginx version number, a security best practice + + ## Redirects all traffic to the HTTPS host + root /nowhere; ## root doesn't have to be a valid path since we are redirecting + rewrite ^ https://$server_name$request_uri? permanent; } +## HTTPS host server { listen 443 ssl; - ## Replace git.example.com with your FQDN. - server_name git.example.com; + server_name YOUR_SERVER_FQDN; ## Replace this with something like gitlab.example.com server_tokens off; root /home/git/gitlab/public; @@ -93,27 +70,29 @@ server { ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_session_cache builtin:1000 shared:SSL:10m; - ## Enable OCSP stapling to reduce the overhead and latency of running SSL. + ssl_prefer_server_ciphers on; + + add_header Strict-Transport-Security max-age=63072000; + add_header X-Frame-Options SAMEORIGIN; + add_header X-Content-Type-Options nosniff; + + ## [Optional] If your certficate has OCSP, enable OCSP stapling to reduce the overhead and latency of running SSL. ## Replace with your ssl_trusted_certificate. For more info see: ## - https://medium.com/devops-programming/4445f4862461 ## - https://www.ruby-forum.com/topic/4419319 - ssl_stapling on; - ssl_stapling_verify on; - ssl_trusted_certificate /etc/nginx/ssl/stapling.trusted.crt; - resolver 208.67.222.222 208.67.222.220 valid=300s; - resolver_timeout 10s; - - ssl_prefer_server_ciphers on; - ## [Optional] Generate a stronger DHE parameter (recommended): + ## - https://www.digitalocean.com/community/tutorials/how-to-configure-ocsp-stapling-on-apache-and-nginx + # ssl_stapling on; + # ssl_stapling_verify on; + # ssl_trusted_certificate /etc/nginx/ssl/stapling.trusted.crt; + # resolver 208.67.222.222 208.67.222.220 valid=300s; # Can change to your DNS resolver if desired + # resolver_timeout 10s; + + ## [Optional] Generate a stronger DHE parameter: ## cd /etc/ssl/certs - ## openssl dhparam -out dhparam.pem 2048 + ## sudo openssl dhparam -out dhparam.pem 4096 ## # ssl_dhparam /etc/ssl/certs/dhparam.pem; - add_header Strict-Transport-Security max-age=63072000; - add_header X-Frame-Options DENY; - add_header X-Content-Type-Options nosniff; - ## Individual nginx logs for this GitLab vhost access_log /var/log/nginx/gitlab_access.log; error_log /var/log/nginx/gitlab_error.log; @@ -125,10 +104,9 @@ server { } ## If a file, which is not found in the root folder is requested, - ## then the proxy pass the request to the upsteam (gitlab unicorn). + ## then the proxy passes the request to the upsteam (gitlab unicorn). location @gitlab { - - ## If you use https make sure you disable gzip compression + ## If you use HTTPS make sure you disable gzip compression ## to be safe against BREACH attack. gzip off; @@ -150,7 +128,7 @@ server { ## Enable gzip compression as per rails guide: ## http://guides.rubyonrails.org/asset_pipeline.html#gzip-compression - ## WARNING: If you are using relative urls do remove the block below + ## WARNING: If you are using relative urls remove the block below ## See config/application.rb under "Relative url support" for the list of ## other files that need to be changed for relative url support location ~ ^/(assets)/ { diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index 34116568e99..9ea5c55abd6 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -27,6 +27,7 @@ namespace :gitlab do check_projects_have_namespace check_satellites_exist check_redis_version + check_ruby_version check_git_version finished_checking "GitLab" @@ -216,7 +217,7 @@ namespace :gitlab do puts "" Project.find_each(batch_size: 100) do |project| - print "#{project.name_with_namespace.yellow} ... " + print sanitized_message(project) if project.satellite.exists? puts "yes".green @@ -317,10 +318,11 @@ namespace :gitlab do options = { "user.name" => "GitLab", - "user.email" => Gitlab.config.gitlab.email_from + "user.email" => Gitlab.config.gitlab.email_from, + "core.autocrlf" => "input" } correct_options = options.map do |name, value| - run(%W(git config --global --get #{name})).try(:squish) == value + run(%W(#{Gitlab.config.git.bin_path} config --global --get #{name})).try(:squish) == value end if correct_options.all? @@ -328,8 +330,9 @@ namespace :gitlab do else puts "no".red try_fixing_it( - sudo_gitlab("git config --global user.name \"#{options["user.name"]}\""), - sudo_gitlab("git config --global user.email \"#{options["user.email"]}\"") + sudo_gitlab("\"#{Gitlab.config.git.bin_path}\" config --global user.name \"#{options["user.name"]}\""), + sudo_gitlab("\"#{Gitlab.config.git.bin_path}\" config --global user.email \"#{options["user.email"]}\""), + sudo_gitlab("\"#{Gitlab.config.git.bin_path}\" config --global core.autocrlf \"#{options["core.autocrlf"]}\"") ) for_more_information( see_installation_guide_section "GitLab" @@ -525,7 +528,7 @@ namespace :gitlab do puts "" Project.find_each(batch_size: 100) do |project| - print "#{project.name_with_namespace.yellow} ... " + print sanitized_message(project) if project.empty_repo? puts "repository is empty".magenta @@ -538,7 +541,7 @@ namespace :gitlab do "sudo -u #{gitlab_shell_ssh_user} ln -sf #{gitlab_shell_hook_file} #{project_hook_file}" ) for_more_information( - "#{gitlab_shell_path}/support/rewrite-hooks.sh" + "#{gitlab_shell_path}/bin/create-hooks" ) fix_and_rerun next @@ -553,7 +556,7 @@ namespace :gitlab do "sudo -u #{gitlab_shell_ssh_user} ln -sf #{gitlab_shell_hook_file} #{project_hook_file}" ) for_more_information( - "lib/support/rewrite-hooks.sh" + "#{gitlab_shell_path}/bin/create-hooks" ) fix_and_rerun end @@ -588,7 +591,7 @@ namespace :gitlab do puts "" Project.find_each(batch_size: 100) do |project| - print "#{project.name_with_namespace.yellow} ... " + print sanitized_message(project) if project.namespace puts "yes".green @@ -816,6 +819,23 @@ namespace :gitlab do end end + def check_ruby_version + required_version = Gitlab::VersionInfo.new(2, 0, 0) + current_version = Gitlab::VersionInfo.parse(run(%W(ruby --version))) + + print "Ruby version >= #{required_version} ? ... " + + if current_version.valid? && required_version <= current_version + puts "yes (#{current_version})".green + else + puts "no".red + try_fixing_it( + "Update your ruby to a version >= #{required_version} from #{current_version}" + ) + fix_and_rerun + end + end + def check_git_version required_version = Gitlab::VersionInfo.new(1, 7, 10) current_version = Gitlab::VersionInfo.parse(run(%W(#{Gitlab.config.git.bin_path} --version))) @@ -837,4 +857,20 @@ namespace :gitlab do def omnibus_gitlab? Dir.pwd == '/opt/gitlab/embedded/service/gitlab-rails' end + + def sanitized_message(project) + if sanitize + "#{project.namespace_id.to_s.yellow}/#{project.id.to_s.yellow} ... " + else + "#{project.name_with_namespace.yellow} ... " + end + end + + def sanitize + if ENV['SANITIZE'] == "true" + true + else + false + end + end end diff --git a/lib/tasks/gitlab/cleanup.rake b/lib/tasks/gitlab/cleanup.rake index 4aaab11340f..63dcdc52370 100644 --- a/lib/tasks/gitlab/cleanup.rake +++ b/lib/tasks/gitlab/cleanup.rake @@ -84,5 +84,29 @@ namespace :gitlab do puts "To cleanup this directories run this command with REMOVE=true".yellow end end + + desc "GITLAB | Cleanup | Block users that have been removed in LDAP" + task block_removed_ldap_users: :environment do + warn_user_is_not_gitlab + block_flag = ENV['BLOCK'] + + User.ldap.each do |ldap_user| + print "#{ldap_user.name} (#{ldap_user.extern_uid}) ..." + if Gitlab::LDAP::Access.open { |access| access.allowed?(ldap_user) } + puts " [OK]".green + else + if block_flag + ldap_user.block! + puts " [BLOCKED]".red + else + puts " [NOT IN LDAP]".yellow + end + end + end + + unless block_flag + puts "To block these users run this command with BLOCK=true".yellow + end + end end end diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake index dfc90bb3339..ece3ad58385 100644 --- a/lib/tasks/gitlab/shell.rake +++ b/lib/tasks/gitlab/shell.rake @@ -4,10 +4,11 @@ namespace :gitlab do task :install, [:tag, :repo] => :environment do |t, args| warn_user_is_not_gitlab - args.with_defaults(tag: "v1.9.3", repo: "https://gitlab.com/gitlab-org/gitlab-shell.git") + default_version = File.read(File.join(Rails.root, "GITLAB_SHELL_VERSION")).strip + args.with_defaults(tag: 'v' + default_version, repo: "https://gitlab.com/gitlab-org/gitlab-shell.git") user = Settings.gitlab.user - home_dir = Settings.gitlab.user_home + home_dir = Rails.env.test? ? Rails.root.join('tmp/tests') : Settings.gitlab.user_home gitlab_url = Settings.gitlab.url # gitlab-shell requires a / at the end of the url gitlab_url += "/" unless gitlab_url.match(/\/$/) diff --git a/lib/tasks/gitlab/sidekiq.rake b/lib/tasks/gitlab/sidekiq.rake new file mode 100644 index 00000000000..7e2a6668e59 --- /dev/null +++ b/lib/tasks/gitlab/sidekiq.rake @@ -0,0 +1,47 @@ +namespace :gitlab do + namespace :sidekiq do + QUEUE = 'queue:post_receive' + + desc 'Drop all Sidekiq PostReceive jobs for a given project' + task :drop_post_receive , [:project] => :environment do |t, args| + unless args.project.present? + abort "Please specify the project you want to drop PostReceive jobs for:\n rake gitlab:sidekiq:drop_post_receive[group/project]" + end + project_path = Project.find_with_namespace(args.project).repository.path_to_repo + + Sidekiq.redis do |redis| + unless redis.exists(QUEUE) + abort "Queue #{QUEUE} is empty" + end + + temp_queue = "#{QUEUE}_#{Time.now.to_i}" + redis.rename(QUEUE, temp_queue) + + # At this point, then post_receive queue is empty. It may be receiving + # new jobs already. We will repopulate it with the old jobs, skipping the + # ones we want to drop. + dropped = 0 + while (job = redis.lpop(temp_queue)) do + if repo_path(job) == project_path + dropped += 1 + else + redis.rpush(QUEUE, job) + end + end + # The temp_queue will delete itself after we have popped all elements + # from it + + puts "Dropped #{dropped} jobs containing #{project_path} from #{QUEUE}" + end + end + + def repo_path(job) + job_args = JSON.parse(job)['args'] + if job_args + job_args.first + else + nil + end + end + end +end diff --git a/lib/tasks/gitlab/test.rake b/lib/tasks/gitlab/test.rake index 5b937ce0a28..c01b00bd1c0 100644 --- a/lib/tasks/gitlab/test.rake +++ b/lib/tasks/gitlab/test.rake @@ -11,4 +11,4 @@ namespace :gitlab do system({'RAILS_ENV' => 'test', 'force' => 'yes'}, *cmd) or raise("#{cmd} failed!") end end -end
\ No newline at end of file +end diff --git a/lib/unfold_form.rb b/lib/unfold_form.rb new file mode 100644 index 00000000000..46b12beeaaf --- /dev/null +++ b/lib/unfold_form.rb @@ -0,0 +1,11 @@ +require_relative 'gt_one_coercion' + +class UnfoldForm + include Virtus.model + + attribute :since, GtOneCoercion + attribute :to, GtOneCoercion + attribute :bottom, Boolean + attribute :unfold, Boolean, default: true + attribute :offset, Integer +end |
