diff options
author | Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> | 2015-06-05 18:19:17 +0200 |
---|---|---|
committer | Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> | 2015-06-05 18:19:17 +0200 |
commit | b6aa7cbccd90680fd13916846af40b9628e16657 (patch) | |
tree | 2899b1c0bcf3897ddc5670e7b2b5e74389576cfd | |
parent | ef7c0945c33f7a4832473ce96596f832f97244eb (diff) | |
parent | 87f9c475db9d5431073a9ec3568dcb7ff368c729 (diff) | |
download | gitlab-ce-b6aa7cbccd90680fd13916846af40b9628e16657.tar.gz |
Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce
45 files changed, 570 insertions, 280 deletions
diff --git a/CHANGELOG b/CHANGELOG index 3df8e7ea516..624dd3a5c48 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,8 +1,10 @@ Please view this file on the master branch, on stable branches it's out of date. v 7.12.0 (unreleased) + - Fix timeout when rendering file with thousands of lines. - Don't notify users mentioned in code blocks or blockquotes. - Omit link to generate labels if user does not have access to create them (Stan Hu) + - Show warning when a comment will add 10 or more people to the discussion. - Disable changing of the source branch in merge request update API (Stan Hu) - Shorten merge request WIP text. - Add option to disallow users from registering any application to use GitLab as an OAuth provider @@ -47,6 +49,7 @@ v 7.12.0 (unreleased) - When remove project - move repository and schedule it removal - Improve group removing logic - Trigger create-hooks on backup restore task + - Add option to automatically link omniauth and LDAP identities v 7.11.4 - Fix missing bullets when creating lists @@ -34,7 +34,7 @@ gem "browser" # Extracting information from a git repository # Provide access to Gitlab::Git library -gem "gitlab_git", '~> 7.2.2' +gem "gitlab_git", '~> 7.2.3' # Ruby/Rack Git Smart-HTTP Server Handler # GitLab fork with a lot of changes (improved thread-safety, better memory usage etc) diff --git a/Gemfile.lock b/Gemfile.lock index a341a5df409..9d87de7d4e0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -225,7 +225,7 @@ GEM mime-types (~> 1.19) gitlab_emoji (0.1.0) gemojione (~> 2.0) - gitlab_git (7.2.2) + gitlab_git (7.2.3) activesupport (~> 4.0) charlock_holmes (~> 0.6) gitlab-linguist (~> 3.0) @@ -713,7 +713,7 @@ DEPENDENCIES gitlab-grack (~> 2.0.2) gitlab-linguist (~> 3.0.1) gitlab_emoji (~> 0.1) - gitlab_git (~> 7.2.2) + gitlab_git (~> 7.2.3) gitlab_meta (= 7.0) gitlab_omniauth-ldap (= 1.2.1) gollum-lib (~> 4.0.2) diff --git a/app/assets/javascripts/dropzone_input.js.coffee b/app/assets/javascripts/dropzone_input.js.coffee index fca2a290e2d..a7476146010 100644 --- a/app/assets/javascripts/dropzone_input.js.coffee +++ b/app/assets/javascripts/dropzone_input.js.coffee @@ -10,12 +10,17 @@ class @DropzoneInput iconSpinner = "<i class=\"fa fa-spinner fa-spin div-dropzone-icon\"></i>" btnAlert = "<button type=\"button\"" + alertAttr + ">×</button>" project_uploads_path = window.project_uploads_path or null + markdown_preview_path = window.markdown_preview_path or null max_file_size = gon.max_file_size or 10 form_textarea = $(form).find("textarea.markdown-area") form_textarea.wrap "<div class=\"div-dropzone\"></div>" - form_textarea.bind 'paste', (event) => + form_textarea.on 'paste', (event) => handlePaste(event) + form_textarea.on "input", -> + hideReferencedUsers() + form_textarea.on "blur", -> + renderMarkdown() form_dropzone = $(form).find('.div-dropzone') form_dropzone.parent().addClass "div-dropzone-wrapper" @@ -45,16 +50,7 @@ class @DropzoneInput form.find(".md-write-holder").hide() form.find(".md-preview-holder").show() - preview = form.find(".js-md-preview") - mdText = form.find(".markdown-area").val() - if mdText.trim().length is 0 - preview.text "Nothing to preview." - else - preview.text "Loading..." - $.post($(this).data("url"), - md_text: mdText - ).success (previewData) -> - preview.html previewData + renderMarkdown() # Write button $(document).off "click", ".js-md-write-button" @@ -133,6 +129,40 @@ class @DropzoneInput child = $(dropzone[0]).children("textarea") + hideReferencedUsers = -> + referencedUsers = form.find(".referenced-users") + referencedUsers.hide() + + renderReferencedUsers = (users) -> + referencedUsers = form.find(".referenced-users") + + if referencedUsers.length + if users.length >= 10 + referencedUsers.show() + referencedUsers.find(".js-referenced-users-count").text users.length + else + referencedUsers.hide() + + renderMarkdown = -> + preview = form.find(".js-md-preview") + mdText = form.find(".markdown-area").val() + if mdText.trim().length is 0 + preview.text "Nothing to preview." + hideReferencedUsers() + else + preview.text "Loading..." + $.ajax( + type: "POST", + url: markdown_preview_path, + data: { + text: mdText + }, + dataType: "json" + ).success (data) -> + preview.html data.body + + renderReferencedUsers data.references.users + formatLink = (link) -> text = "[#{link.alt}](#{link.url})" text = "!#{text}" if link.is_image diff --git a/app/assets/javascripts/gfm_auto_complete.js.coffee b/app/assets/javascripts/gfm_auto_complete.js.coffee index 4eb3f3c03f3..7967892f856 100644 --- a/app/assets/javascripts/gfm_auto_complete.js.coffee +++ b/app/assets/javascripts/gfm_auto_complete.js.coffee @@ -10,7 +10,7 @@ GitLab.GfmAutoComplete = # Team Members Members: - template: '<li>${username} <small>${name}</small></li>' + template: '<li>${username} <small>${title}</small></li>' # Issues and MergeRequests Issues: @@ -34,7 +34,13 @@ GitLab.GfmAutoComplete = searchKey: 'search' callbacks: beforeSave: (members) -> - $.map members, (m) -> name: m.name, username: m.username, search: "#{m.username} #{m.name}" + $.map members, (m) -> + title = m.name + title += " (#{m.count})" if m.count + + username: m.username + title: sanitize(title) + search: sanitize("#{m.username} #{m.name}") input.atwho at: '#' @@ -44,7 +50,10 @@ GitLab.GfmAutoComplete = insertTpl: '${atwho-at}${id}' callbacks: beforeSave: (issues) -> - $.map issues, (i) -> id: i.iid, title: sanitize(i.title), search: "#{i.iid} #{i.title}" + $.map issues, (i) -> + id: i.iid + title: sanitize(i.title) + search: "#{i.iid} #{i.title}" input.atwho at: '!' @@ -54,7 +63,10 @@ GitLab.GfmAutoComplete = insertTpl: '${atwho-at}${id}' callbacks: beforeSave: (merges) -> - $.map merges, (m) -> id: m.iid, title: sanitize(m.title), search: "#{m.iid} #{m.title}" + $.map merges, (m) -> + id: m.iid + title: sanitize(m.title) + search: "#{m.iid} #{m.title}" input.one 'focus', => $.getJSON(@dataSource).done (data) -> diff --git a/app/assets/stylesheets/generic/header.scss b/app/assets/stylesheets/generic/header.scss index 71afccba001..3e738929a78 100644 --- a/app/assets/stylesheets/generic/header.scss +++ b/app/assets/stylesheets/generic/header.scss @@ -8,6 +8,11 @@ header { &.navbar-empty { background: #FFF; border-bottom: 1px solid #EEE; + + .center-logo { + margin: 8px 0; + text-align: center; + } } &.navbar-gitlab { diff --git a/app/assets/stylesheets/generic/markdown_area.scss b/app/assets/stylesheets/generic/markdown_area.scss index eb39b6bb7e9..f94677d1925 100644 --- a/app/assets/stylesheets/generic/markdown_area.scss +++ b/app/assets/stylesheets/generic/markdown_area.scss @@ -52,6 +52,22 @@ transition: opacity 200ms ease-in-out; } +.md-area { + position: relative; +} + +.md-header ul { + float: left; +} + +.referenced-users { + padding: 10px 0; + color: #999; + margin-left: 10px; + margin-top: 1px; + margin-right: 130px; +} + .md-preview-holder { background: #FFF; border: 1px solid #ddd; diff --git a/app/assets/stylesheets/generic/mobile.scss b/app/assets/stylesheets/generic/mobile.scss index 74108c1f086..e61b39f1ac3 100644 --- a/app/assets/stylesheets/generic/mobile.scss +++ b/app/assets/stylesheets/generic/mobile.scss @@ -19,6 +19,10 @@ } } + .referenced-users { + margin-right: 0; + } + .issues-filters, .dash-projects-filters, .check-all-holder { diff --git a/app/assets/stylesheets/generic/zen.scss b/app/assets/stylesheets/generic/zen.scss index bcb8bbe3134..7ab01187a02 100644 --- a/app/assets/stylesheets/generic/zen.scss +++ b/app/assets/stylesheets/generic/zen.scss @@ -1,6 +1,4 @@ .zennable { - position: relative; - .zen-toggle-comment { display: none; } @@ -8,8 +6,9 @@ .zen-enter-link { color: #888; position: absolute; - top: -26px; + top: 0px; right: 4px; + line-height: 40px; } .zen-leave-link { diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index a0522030785..203f9374cee 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -39,11 +39,8 @@ .new_note, .edit_note { .buttons { - float: left; margin-top: 8px; - } - .clearfix { - margin-bottom: 0; + margin-bottom: 3px; } .note-preview-holder { @@ -82,7 +79,6 @@ .note-form-actions { background: #F9F9F9; - height: 45px; .note-form-option { margin-top: 8px; diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index b762518d377..100d3d3b317 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -13,27 +13,20 @@ class Projects::BlobController < Projects::ApplicationController before_action :commit, except: [:new, :create] before_action :blob, except: [:new, :create] before_action :from_merge_request, only: [:edit, :update] - before_action :after_edit_path, only: [:edit, :update] before_action :require_branch_head, only: [:edit, :update] + before_action :editor_variables, except: [:show, :preview, :diff] + before_action :after_edit_path, only: [:edit, :update] def new commit unless @repository.empty? end def create - file_path = File.join(@path, File.basename(params[:file_name])) - result = Files::CreateService.new( - @project, - current_user, - params.merge(new_branch: sanitized_new_branch_name), - @ref, - file_path - ).execute + result = Files::CreateService.new(@project, current_user, @commit_params).execute if result[:status] == :success flash[:notice] = "Your changes have been successfully committed" - ref = sanitized_new_branch_name.presence || @ref - redirect_to namespace_project_blob_path(@project.namespace, @project, File.join(ref, file_path)) + redirect_to namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path)) else flash[:alert] = result[:message] render :new @@ -48,22 +41,10 @@ class Projects::BlobController < Projects::ApplicationController end def update - result = Files::UpdateService. - new( - @project, - current_user, - params.merge(new_branch: sanitized_new_branch_name), - @ref, - @path - ).execute + result = Files::UpdateService.new(@project, current_user, @commit_params).execute if result[:status] == :success flash[:notice] = "Your changes have been successfully committed" - - if from_merge_request - from_merge_request.reload_code - end - redirect_to after_edit_path else flash[:alert] = result[:message] @@ -80,12 +61,11 @@ class Projects::BlobController < Projects::ApplicationController end def destroy - result = Files::DeleteService.new(@project, current_user, params, @ref, @path).execute + result = Files::DeleteService.new(@project, current_user, @commit_params).execute if result[:status] == :success flash[:notice] = "Your changes have been successfully committed" - redirect_to namespace_project_tree_path(@project.namespace, @project, - @ref) + redirect_to namespace_project_tree_path(@project.namespace, @project, @target_branch) else flash[:alert] = result[:message] render :show @@ -135,7 +115,6 @@ class Projects::BlobController < Projects::ApplicationController @id = params[:id] @ref, @path = extract_ref(@id) - rescue InvalidPathError not_found! end @@ -145,8 +124,8 @@ class Projects::BlobController < Projects::ApplicationController if from_merge_request diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) + "#file-path-#{hexdigest(@path)}" - elsif sanitized_new_branch_name.present? - namespace_project_blob_path(@project.namespace, @project, File.join(sanitized_new_branch_name, @path)) + elsif @target_branch.present? + namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @path)) else namespace_project_blob_path(@project.namespace, @project, @id) end @@ -160,4 +139,25 @@ class Projects::BlobController < Projects::ApplicationController def sanitized_new_branch_name @new_branch ||= sanitize(strip_tags(params[:new_branch])) end + + def editor_variables + @current_branch = @ref + @target_branch = (sanitized_new_branch_name || @ref) + + @file_path = + if action_name.to_s == 'create' + File.join(@path, File.basename(params[:file_name])) + else + @path + end + + @commit_params = { + file_path: @file_path, + current_branch: @current_branch, + target_branch: @target_branch, + commit_message: params[:commit_message], + file_content: params[:content], + file_content_encoding: params[:encoding] + } + end end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 4ca5fc65459..be5968cd7b0 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -151,7 +151,17 @@ class ProjectsController < ApplicationController end def markdown_preview - render text: view_context.markdown(params[:md_text]) + text = params[:text] + + ext = Gitlab::ReferenceExtractor.new(@project, current_user) + ext.analyze(text) + + render json: { + body: view_context.markdown(text), + references: { + users: ext.users.map(&:username) + } + } end private diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb index 4d02752454e..f587ee266da 100644 --- a/app/services/files/base_service.rb +++ b/app/services/files/base_service.rb @@ -1,11 +1,34 @@ module Files class BaseService < ::BaseService - attr_reader :ref, :path + class ValidationError < StandardError; end - def initialize(project, user, params, ref, path = nil) - @project, @current_user, @params = project, user, params.dup - @ref = ref - @path = path + def execute + @current_branch = params[:current_branch] + @target_branch = params[:target_branch] + @commit_message = params[:commit_message] + @file_path = params[:file_path] + @file_content = if params[:file_content_encoding] == 'base64' + Base64.decode64(params[:file_content]) + else + params[:file_content] + end + + # Validate parameters + validate + + # Create new branch if it different from current_branch + if @target_branch != @current_branch + create_target_branch + end + + if sha = commit + after_commit(sha, @target_branch) + success + else + error("Something went wrong. Your changes were not committed") + end + rescue ValidationError => ex + error(ex.message) end private @@ -14,11 +37,51 @@ module Files project.repository end - def after_commit(sha) + def after_commit(sha, branch) commit = repository.commit(sha) - full_ref = 'refs/heads/' + (params[:new_branch] || ref) + full_ref = 'refs/heads/' + branch old_sha = commit.parent_id || Gitlab::Git::BLANK_SHA GitPushService.new.execute(project, current_user, old_sha, sha, full_ref) end + + def current_branch + @current_branch ||= params[:current_branch] + end + + def target_branch + @target_branch ||= params[:target_branch] + end + + def raise_error(message) + raise ValidationError.new(message) + end + + def validate + allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(@target_branch) + + unless allowed + raise_error("You are not allowed to push into this branch") + end + + unless project.empty_repo? + unless repository.branch_names.include?(@current_branch) + raise_error("You can only create files if you are on top of a branch") + end + + if @current_branch != @target_branch + if repository.branch_names.include?(@target_branch) + raise_error("Branch with such name already exists. You need to switch to this branch in order to make changes") + end + end + end + end + + def create_target_branch + result = CreateBranchService.new(project, current_user).execute(@target_branch, @current_branch) + + unless result[:status] == :success + raise_error("Something went wrong when we tried to create #{@target_branch} for you") + end + end end end diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb index 0a80455bc6b..91d715b2d63 100644 --- a/app/services/files/create_service.rb +++ b/app/services/files/create_service.rb @@ -2,58 +2,28 @@ require_relative "base_service" module Files class CreateService < Files::BaseService - def execute - allowed = Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(ref) + def commit + repository.commit_file(current_user, @file_path, @file_content, @commit_message, @target_branch) + end - unless allowed - return error("You are not allowed to create file in this branch") - end + def validate + super - file_name = File.basename(path) - file_path = path + file_name = File.basename(@file_path) unless file_name =~ Gitlab::Regex.file_name_regex - return error( + raise_error( 'Your changes could not be committed, because the file name ' + Gitlab::Regex.file_name_regex_message ) end - if project.empty_repo? - # everything is ok because repo does not have a commits yet - else - unless repository.branch_names.include?(ref) - return error("You can only create files if you are on top of a branch") - end - - blob = repository.blob_at_branch(ref, file_path) + unless project.empty_repo? + blob = repository.blob_at_branch(@current_branch, @file_path) if blob - return error("Your changes could not be committed, because file with such name exists") - end - end - - content = - if params[:encoding] == 'base64' - Base64.decode64(params[:content]) - else - params[:content] + raise_error("Your changes could not be committed, because file with such name exists") end - - sha = repository.commit_file( - current_user, - file_path, - content, - params[:commit_message], - params[:new_branch] || ref - ) - - - if sha - after_commit(sha) - success - else - error("Your changes could not be committed, because the file has been changed") end end end diff --git a/app/services/files/delete_service.rb b/app/services/files/delete_service.rb index 2281777604c..27c881c3430 100644 --- a/app/services/files/delete_service.rb +++ b/app/services/files/delete_service.rb @@ -2,36 +2,8 @@ require_relative "base_service" module Files class DeleteService < Files::BaseService - def execute - allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(ref) - - unless allowed - return error("You are not allowed to push into this branch") - end - - unless repository.branch_names.include?(ref) - return error("You can only create files if you are on top of a branch") - end - - blob = repository.blob_at_branch(ref, path) - - unless blob - return error("You can only edit text files") - end - - sha = repository.remove_file( - current_user, - path, - params[:commit_message], - ref - ) - - if sha - after_commit(sha) - success - else - error("Your changes could not be committed, because the file has been changed") - end + def commit + repository.remove_file(current_user, @file_path, @commit_message, @target_branch) end end end diff --git a/app/services/files/update_service.rb b/app/services/files/update_service.rb index 013cc1ee322..a20903c6f02 100644 --- a/app/services/files/update_service.rb +++ b/app/services/files/update_service.rb @@ -2,46 +2,8 @@ require_relative "base_service" module Files class UpdateService < Files::BaseService - def execute - allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(ref) - - unless allowed - return error("You are not allowed to push into this branch") - end - - unless repository.branch_names.include?(ref) - return error("You can only create files if you are on top of a branch") - end - - blob = repository.blob_at_branch(ref, path) - - unless blob - return error("You can only edit text files") - end - - content = - if params[:encoding] == 'base64' - Base64.decode64(params[:content]) - else - params[:content] - end - - sha = repository.commit_file( - current_user, - path, - content, - params[:commit_message], - params[:new_branch] || ref - ) - - after_commit(sha) - success - rescue Gitlab::Satellite::CheckoutFailed => ex - error("Your changes could not be committed because ref '#{ref}' could not be checked out", 400) - rescue Gitlab::Satellite::CommitFailed => ex - error("Your changes could not be committed. Maybe there was nothing to commit?", 409) - rescue Gitlab::Satellite::PushFailed => ex - error("Your changes could not be committed. Maybe the file was changed by another process?", 409) + def commit + repository.commit_file(current_user, @file_path, @file_content, @commit_message, @target_branch) end end end diff --git a/app/services/projects/participants_service.rb b/app/services/projects/participants_service.rb index b91590a1a90..0004a399f47 100644 --- a/app/services/projects/participants_service.rb +++ b/app/services/projects/participants_service.rb @@ -38,13 +38,13 @@ module Projects def groups current_user.authorized_groups.sort_by(&:path).map do |group| count = group.users.count - { username: group.path, name: "#{group.name} (#{count})" } + { username: group.path, name: group.name, count: count } end end def all_members count = project.team.members.flatten.count - [{ username: "all", name: "All Project and Group Members (#{count})" }] + [{ username: "all", name: "All Project and Group Members", count: count }] end end end diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index b1a57d9824e..4d8c5656a25 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -15,8 +15,10 @@ %meta{name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1'} %meta{name: 'theme-color', content: '#474D57'} - = yield(:meta_tags) + = yield :meta_tags = render 'layouts/google_analytics' if extra_config.has_key?('google_analytics_id') = render 'layouts/piwik' if extra_config.has_key?('piwik_url') && extra_config.has_key?('piwik_site_id') = render 'layouts/bootlint' if Rails.env.development? + + = yield :scripts_head diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index c1283734d25..54fcbca6bc6 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -22,5 +22,3 @@ = render "layouts/flash" .clearfix = yield - -= yield :embedded_scripts diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 155825cc4c2..ff23913d7d6 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -8,3 +8,5 @@ = render "layouts/header/public", title: header_title = render 'layouts/page', sidebar: sidebar + + = yield :scripts_body diff --git a/app/views/layouts/header/_empty.html.haml b/app/views/layouts/header/_empty.html.haml index 16fbf6d4020..a52a3c8f0ef 100644 --- a/app/views/layouts/header/_empty.html.haml +++ b/app/views/layouts/header/_empty.html.haml @@ -1,4 +1,4 @@ %header.navbar.navbar-fixed-top.navbar-empty .container - %h4.center + .center-logo = image_tag 'logo-white.png', width: 32, height: 32 diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml index 4aeb9d397d2..03c7ba8c73f 100644 --- a/app/views/layouts/project.html.haml +++ b/app/views/layouts/project.html.haml @@ -2,7 +2,13 @@ - header_title project_title(@project) - sidebar "project" unless sidebar -- content_for :embedded_scripts do +- content_for :scripts_head do + -if current_user + :javascript + window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}"; + window.markdown_preview_path = "#{markdown_preview_namespace_project_path(@project.namespace, @project)}"; + +- content_for :scripts_body do = render "layouts/init_auto_complete" if current_user = render template: "layouts/application" diff --git a/app/views/projects/_issuable_form.html.haml b/app/views/projects/_issuable_form.html.haml index 2c5b24b8130..491e2107da4 100644 --- a/app/views/projects/_issuable_form.html.haml +++ b/app/views/projects/_issuable_form.html.haml @@ -24,7 +24,7 @@ = f.label :description, 'Description', class: 'control-label' .col-sm-10 - = render layout: 'projects/md_preview', locals: { preview_class: "wiki" } do + = render layout: 'projects/md_preview', locals: { preview_class: "wiki", referenced_users: true } do = render 'projects/zen', f: f, attr: :description, classes: 'description form-control' .col-sm-12.hint diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml index b869fd6e12a..a831481cf80 100644 --- a/app/views/projects/_md_preview.html.haml +++ b/app/views/projects/_md_preview.html.haml @@ -1,13 +1,24 @@ -%ul.nav.nav-tabs - %li.active - = link_to '#md-write-holder', class: 'js-md-write-button' do - Write - %li - = link_to '#md-preview-holder', class: 'js-md-preview-button', - data: { url: markdown_preview_namespace_project_path(@project.namespace, @project) } do - Preview -%div - .md-write-holder - = yield - .md.md-preview-holder.hide - .js-md-preview{class: (preview_class if defined?(preview_class))} +.md-area + .md-header.clearfix + %ul.nav.nav-tabs + %li.active + = link_to '#md-write-holder', class: 'js-md-write-button' do + Write + %li + = link_to '#md-preview-holder', class: 'js-md-preview-button' do + Preview + + - if defined?(referenced_users) && referenced_users + %span.referenced-users.pull-left.hide + = icon('exclamation-triangle') + You are about to add + %strong + %span.js-referenced-users-count 0 + people + to the discussion. Proceed with caution. + + %div + .md-write-holder + = yield + .md.md-preview-holder.hide + .js-md-preview{class: (preview_class if defined?(preview_class))} diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml index 9b1d03b820e..f7ddf74b4fc 100644 --- a/app/views/projects/blob/new.html.haml +++ b/app/views/projects/blob/new.html.haml @@ -6,11 +6,12 @@ = render 'shared/commit_message_container', params: params, placeholder: 'Add new file' - .form-group.branch - = label_tag 'branch', class: 'control-label' do - Branch - .col-sm-10 - = text_field_tag 'new_branch', @ref, class: "form-control" + - unless @project.empty_repo? + .form-group.branch + = label_tag 'branch', class: 'control-label' do + Branch + .col-sm-10 + = text_field_tag 'new_branch', @ref, class: "form-control" = hidden_field_tag 'content', '', id: 'file-content' = render 'projects/commit_button', ref: @ref, diff --git a/app/views/projects/issues/_form.html.haml b/app/views/projects/issues/_form.html.haml index 7d7217eb2a8..8d2564be55e 100644 --- a/app/views/projects/issues/_form.html.haml +++ b/app/views/projects/issues/_form.html.haml @@ -10,5 +10,3 @@ $('#issue_assignee_id').val("#{current_user.id}").trigger("change"); e.preventDefault(); }); - - window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}"; diff --git a/app/views/projects/merge_requests/_form.html.haml b/app/views/projects/merge_requests/_form.html.haml index 1c7160bce5f..be73f087449 100644 --- a/app/views/projects/merge_requests/_form.html.haml +++ b/app/views/projects/merge_requests/_form.html.haml @@ -8,5 +8,3 @@ $('#merge_request_assignee_id').val("#{current_user.id}").trigger("change"); e.preventDefault(); }); - - window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}"; diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index 9a2edbf0a8c..6792104569b 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -51,8 +51,6 @@ e.preventDefault(); }); - window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}"; - :javascript var merge_request merge_request = new MergeRequest({ diff --git a/app/views/projects/merge_requests/show/_mr_accept.html.haml b/app/views/projects/merge_requests/show/_mr_accept.html.haml index 906cc11dc67..bfd4ab6f3d8 100644 --- a/app/views/projects/merge_requests/show/_mr_accept.html.haml +++ b/app/views/projects/merge_requests/show/_mr_accept.html.haml @@ -49,7 +49,7 @@ .automerge_widget.cannot_be_merged.hide %h4 - This pull request contains merge conflicts that must be resolved. + This merge request contains merge conflicts that must be resolved. You can try it manually on the %strong = link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal" @@ -63,14 +63,14 @@ .automerge_widget.work_in_progress.hide %h4 - This request cannot be merged because it is marked as <strong>Work In Progress</strong>. + This merge request cannot be accepted because it is marked as Work In Progress. %p %button.btn.disabled{:type => 'button'} %i.fa.fa-warning Accept Merge Request - When the merge request is ready, remove the "WIP" prefix from the title to allow merging. + When the merge request is ready, remove the "WIP" prefix from the title to allow it to be accepted. .automerge_widget.unchecked %p diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml index 95b7070ce5c..5650607f31f 100644 --- a/app/views/projects/milestones/_form.html.haml +++ b/app/views/projects/milestones/_form.html.haml @@ -50,5 +50,3 @@ dateFormat: "yy-mm-dd", onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) } }).datepicker("setDate", $.datepicker.parseDate('yy-mm-dd', $('#milestone_due_date').val())); - - window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}"; diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml index 2ada6cb6700..f28b3e9b508 100644 --- a/app/views/projects/notes/_form.html.haml +++ b/app/views/projects/notes/_form.html.haml @@ -5,7 +5,7 @@ = f.hidden_field :noteable_id = f.hidden_field :noteable_type - = render layout: 'projects/md_preview', locals: { preview_class: "note-text" } do + = render layout: 'projects/md_preview', locals: { preview_class: "note-text", referenced_users: true } do = render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text' @@ -15,10 +15,7 @@ .error-alert .note-form-actions - .buttons + .buttons.clearfix = f.submit 'Add Comment', class: "btn comment-btn btn-grouped js-comment-button" = yield(:note_actions) %a.btn.grouped.js-close-discussion-note-form Cancel - -:javascript - window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}"; diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml index 9fbfa0b1aeb..2a8ceaa2844 100644 --- a/app/views/projects/wikis/_form.html.haml +++ b/app/views/projects/wikis/_form.html.haml @@ -41,6 +41,3 @@ - else = f.submit 'Create page', class: "btn-create btn" = link_to "Cancel", namespace_project_wiki_path(@project.namespace, @project, :home), class: "btn btn-cancel" - -:javascript - window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}"; diff --git a/app/views/shared/_file_highlight.html.haml b/app/views/shared/_file_highlight.html.haml index fba69dd0f3f..86921f0a777 100644 --- a/app/views/shared/_file_highlight.html.haml +++ b/app/views/shared/_file_highlight.html.haml @@ -4,7 +4,8 @@ - blob.data.lines.to_a.size.times do |index| - offset = defined?(first_line_number) ? first_line_number : 1 - i = index + offset - = link_to "#L#{i}", id: "L#{i}", rel: "#L#{i}" do + / We're not using `link_to` because it is too slow once we get to thousands of lines. + %a{href: "#L#{i}", id: "L#{i}", rel: "#L#{i}"} %i.fa.fa-link = i :preserve diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index c7f22b9388b..787b3ccfc56 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -192,6 +192,9 @@ production: &base allow_single_sign_on: false # Locks down those users until they have been cleared by the admin (default: true). block_auto_created_users: true + # Look up new users in LDAP servers. If a match is found (same uid), automatically + # link the omniauth identity with the LDAP account. (default: false) + auto_link_ldap_user: false ## Auth providers # Uncomment the following lines and fill in the data of the auth provider you want to use diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index c234bd69e9a..1bd14a3a89f 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -88,6 +88,9 @@ end Settings['omniauth'] ||= Settingslogic.new({}) Settings.omniauth['enabled'] = false if Settings.omniauth['enabled'].nil? Settings.omniauth['auto_sign_in_with_provider'] = false if Settings.omniauth['auto_sign_in_with_provider'].nil? +Settings.omniauth['allow_single_sign_on'] = false if Settings.omniauth['allow_single_sign_on'].nil? +Settings.omniauth['block_auto_created_users'] = true if Settings.omniauth['block_auto_created_users'].nil? +Settings.omniauth['auto_link_ldap_user'] = false if Settings.omniauth['auto_link_ldap_user'].nil? Settings.omniauth['providers'] ||= [] diff --git a/doc/integration/bitbucket.md b/doc/integration/bitbucket.md index d82e1f8b41b..6a0fa4ce015 100644 --- a/doc/integration/bitbucket.md +++ b/doc/integration/bitbucket.md @@ -68,6 +68,8 @@ Bitbucket will generate an application ID and secret key for you to use. 1. Save the configuration file. +1. If you're using the omnibus package, reconfigure GitLab (```gitlab-ctl reconfigure```). + 1. Restart GitLab for the changes to take effect. On the sign in page there should now be a Bitbucket icon below the regular sign in form. @@ -80,43 +82,59 @@ To allow projects to be imported directly into GitLab, Bitbucket requires two ex Bitbucket doesn't allow OAuth applications to clone repositories over HTTPS, and instead requires GitLab to use SSH and identify itself using your GitLab server's SSH key. -### Step 1: Known hosts +### Step 1: Public key -To allow GitLab to connect to Bitbucket over SSH, you need to add 'bitbucket.org' to your GitLab server's known SSH hosts. Take the following steps to do so: +To be able to access repositories on Bitbucket, GitLab will automatically register your public key with Bitbucket as a deploy key for the repositories to be imported. Your public key needs to be at `~/.ssh/bitbucket_rsa.pub`, which will expand to `/home/git/.ssh/bitbucket_rsa.pub` in most configurations. -1. Manually connect to 'bitbucket.org' over SSH, while logged in as the `git` account that GitLab will use: +If you have that file in place, you're all set and should see the "Import projects from Bitbucket" option enabled. If you don't, do the following: + +1. Create a new SSH key: ```sh - ssh git@bitbucket.org + sudo -u git -H ssh-keygen ``` -1. Verify the RSA key fingerprint you'll see in the response matches the one in the [Bitbucket documentation](https://confluence.atlassian.com/display/BITBUCKET/Use+the+SSH+protocol+with+Bitbucket#UsetheSSHprotocolwithBitbucket-KnownhostorBitbucket'spublickeyfingerprints) (the specific IP address doesn't matter): + When asked `Enter file in which to save the key` specify the correct path, eg. `/home/git/.ssh/bitbucket_rsa`. + Make sure to use an **empty passphrase**. + +1. Configure SSH client to use your new key: + + Open the SSH configuration file of the git user. ```sh - The authenticity of host 'bitbucket.org (207.223.240.182)' can't be established. - RSA key fingerprint is 97:8c:1b:f2:6f:14:6b:5c:3b:ec:aa:46:46:74:7c:40. - Are you sure you want to continue connecting (yes/no)? + sudo editor /home/git/.ssh/config ``` -1. If the fingerprint matches, type `yes` to continue connecting and have 'bitbucket.org' be added to your known hosts. + Add a host configuration for `bitbucket.org`. + + ```sh + Host bitbucket.org + IdentityFile ~/.ssh/bitbucket_rsa + User git + ``` -1. Your GitLab server is now able to connect to Bitbucket over SSH. Continue to step 2: +### Step 2: Known hosts -### Step 2: Public key +To allow GitLab to connect to Bitbucket over SSH, you need to add 'bitbucket.org' to your GitLab server's known SSH hosts. Take the following steps to do so: -To be able to access repositories on Bitbucket, GitLab will automatically register your public key with Bitbucket as a deploy key for the repositories to be imported. Your public key needs to be at `~/.ssh/bitbucket_rsa.pub`, which will expand to `/home/git/.ssh/bitbucket_rsa.pub` in most configurations. +1. Manually connect to 'bitbucket.org' over SSH, while logged in as the `git` account that GitLab will use: -If you have that file in place, you're all set and should see the "Import projects from Bitbucket" option enabled. If you don't, do the following: + ```sh + sudo -u git -H ssh bitbucket.org + ``` -1. Create a new SSH key: +1. Verify the RSA key fingerprint you'll see in the response matches the one in the [Bitbucket documentation](https://confluence.atlassian.com/display/BITBUCKET/Use+the+SSH+protocol+with+Bitbucket#UsetheSSHprotocolwithBitbucket-KnownhostorBitbucket'spublickeyfingerprints) (the specific IP address doesn't matter): ```sh - sudo -u git -H ssh-keygen + The authenticity of host 'bitbucket.org (207.223.240.182)' can't be established. + RSA key fingerprint is 97:8c:1b:f2:6f:14:6b:5c:3b:ec:aa:46:46:74:7c:40. + Are you sure you want to continue connecting (yes/no)? ``` - When asked `Enter file in which to save the key` specify the correct path, eg. `/home/git/.ssh/bitbucket_rsa`. - Make sure to use an **empty passphrase**. +1. If the fingerprint matches, type `yes` to continue connecting and have 'bitbucket.org' be added to your known hosts. + +1. Your GitLab server is now able to connect to Bitbucket over SSH. -2. Restart GitLab to allow it to find the new public key. +1. Restart GitLab to allow it to find the new public key. You should now see the "Import projects from Bitbucket" option on the New Project page enabled. diff --git a/doc/workflow/README.md b/doc/workflow/README.md index 89005e51958..4775be15040 100644 --- a/doc/workflow/README.md +++ b/doc/workflow/README.md @@ -16,3 +16,4 @@ - [Change your time zone](timezone.md) - [Keyboard shortcuts](shortcuts.md) - [Web Editor](web_editor.md) +- ["Work In Progress" Merge Requests](wip_merge_requests.md) diff --git a/doc/workflow/wip_merge_requests.md b/doc/workflow/wip_merge_requests.md new file mode 100644 index 00000000000..46035a5e6b6 --- /dev/null +++ b/doc/workflow/wip_merge_requests.md @@ -0,0 +1,13 @@ +# "Work In Progress" Merge Requests + +To prevent merge requests from accidentally being accepted before they're completely ready, GitLab blocks the "Accept" button for merge requests that have been marked a **Work In Progress**. + +![Blocked Accept Button](wip_merge_requests/blocked_accept_button.png) + +To mark a merge request a Work In Progress, simply start its title with `[WIP]` or `WIP:`. + +![Mark as WIP](wip_merge_requests/mark_as_wip.png) + +To allow a Work In Progress merge request to be accepted again when it's ready, simply remove the `WIP` prefix. + +![Unark as WIP](wip_merge_requests/unmark_as_wip.png) diff --git a/doc/workflow/wip_merge_requests/blocked_accept_button.png b/doc/workflow/wip_merge_requests/blocked_accept_button.png Binary files differnew file mode 100644 index 00000000000..4791e5de972 --- /dev/null +++ b/doc/workflow/wip_merge_requests/blocked_accept_button.png diff --git a/doc/workflow/wip_merge_requests/mark_as_wip.png b/doc/workflow/wip_merge_requests/mark_as_wip.png Binary files differnew file mode 100644 index 00000000000..8fa83a201ac --- /dev/null +++ b/doc/workflow/wip_merge_requests/mark_as_wip.png diff --git a/doc/workflow/wip_merge_requests/unmark_as_wip.png b/doc/workflow/wip_merge_requests/unmark_as_wip.png Binary files differnew file mode 100644 index 00000000000..d45e68f31c5 --- /dev/null +++ b/doc/workflow/wip_merge_requests/unmark_as_wip.png diff --git a/docker/README.md b/docker/README.md index a73ccd0dba0..46b21348364 100644 --- a/docker/README.md +++ b/docker/README.md @@ -122,7 +122,7 @@ You can find all available options in [Omnibus GitLab documentation](https://git ### Upgrade GitLab with app and data images -To updgrade GitLab to new versions, stop running container, create new docker image and container from that image. +To upgrade GitLab to new versions, stop running container, create new docker image and container from that image. It Assumes that you're upgrading from 7.8.1 to 7.10.1 and you're in the updated GitLab repo root directory: @@ -141,10 +141,12 @@ sudo docker rmi gitlab-app:7.8.1 ### Publish images to Dockerhub -Login to Dockerhub with `sudo docker login` and run the following (replace '7.9.2' with the version you're using and 'Sytse Sijbrandij' with your name): +- Ensure the containers are running +- Login to Dockerhub with `sudo docker login` +- Run the following (replace '7.9.2' with the version you're using and 'Sytse Sijbrandij' with your name): ```bash -sudo docker commit -m "Initial commit" -a "Sytse Sijbrandij" gitlab-app:7.10.1 sytse/gitlab-app:7.10.1 +sudo docker commit -m "Initial commit" -a "Sytse Sijbrandij" gitlab-app sytse/gitlab-app:7.10.1 sudo docker push sytse/gitlab-app:7.10.1 sudo docker commit -m "Initial commit" -a "Sytse Sijbrandij" gitlab_data sytse/gitlab_data sudo docker push sytse/gitlab_data diff --git a/lib/api/files.rb b/lib/api/files.rb index e0ea6d7dd1d..c7b30cf2f07 100644 --- a/lib/api/files.rb +++ b/lib/api/files.rb @@ -3,6 +3,26 @@ module API class Files < Grape::API before { authenticate! } + helpers do + def commit_params(attrs) + { + file_path: attrs[:file_path], + current_branch: attrs[:branch_name], + target_branch: attrs[:branch_name], + commit_message: attrs[:commit_message], + file_content: attrs[:content], + file_content_encoding: attrs[:encoding] + } + end + + def commit_response(attrs) + { + file_path: attrs[:file_path], + branch_name: attrs[:branch_name], + } + end + end + resource :projects do # Get file from repository # File content is Base64 encoded @@ -73,17 +93,11 @@ module API required_attributes! [:file_path, :branch_name, :content, :commit_message] attrs = attributes_for_keys [:file_path, :branch_name, :content, :commit_message, :encoding] - branch_name = attrs.delete(:branch_name) - file_path = attrs.delete(:file_path) - result = ::Files::CreateService.new(user_project, current_user, attrs, branch_name, file_path).execute + result = ::Files::CreateService.new(user_project, current_user, commit_params(attrs)).execute if result[:status] == :success status(201) - - { - file_path: file_path, - branch_name: branch_name - } + commit_response(attrs) else render_api_error!(result[:message], 400) end @@ -105,17 +119,11 @@ module API required_attributes! [:file_path, :branch_name, :content, :commit_message] attrs = attributes_for_keys [:file_path, :branch_name, :content, :commit_message, :encoding] - branch_name = attrs.delete(:branch_name) - file_path = attrs.delete(:file_path) - result = ::Files::UpdateService.new(user_project, current_user, attrs, branch_name, file_path).execute + result = ::Files::UpdateService.new(user_project, current_user, commit_params(attrs)).execute if result[:status] == :success status(200) - - { - file_path: file_path, - branch_name: branch_name - } + commit_response(attrs) else http_status = result[:http_status] || 400 render_api_error!(result[:message], http_status) @@ -138,17 +146,11 @@ module API required_attributes! [:file_path, :branch_name, :commit_message] attrs = attributes_for_keys [:file_path, :branch_name, :commit_message] - branch_name = attrs.delete(:branch_name) - file_path = attrs.delete(:file_path) - result = ::Files::DeleteService.new(user_project, current_user, attrs, branch_name, file_path).execute + result = ::Files::DeleteService.new(user_project, current_user, commit_params(attrs)).execute if result[:status] == :success status(200) - - { - file_path: file_path, - branch_name: branch_name - } + commit_response(attrs) else render_api_error!(result[:message], 400) end diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb index ba5caed6131..c4971b5bcc6 100644 --- a/lib/gitlab/o_auth/user.rb +++ b/lib/gitlab/o_auth/user.rb @@ -46,6 +46,10 @@ module Gitlab def gl_user @user ||= find_by_uid_and_provider + if auto_link_ldap_user? + @user ||= find_or_create_ldap_user + end + if signup_enabled? @user ||= build_new_user end @@ -55,6 +59,46 @@ module Gitlab protected + def find_or_create_ldap_user + return unless ldap_person + + # If a corresponding person exists with same uid in a LDAP server, + # set up a Gitlab user with dual LDAP and Omniauth identities. + if user = Gitlab::LDAP::User.find_by_uid_and_provider(ldap_person.dn.downcase, ldap_person.provider) + # Case when a LDAP user already exists in Gitlab. Add the Omniauth identity to existing account. + user.identities.build(extern_uid: auth_hash.uid, provider: auth_hash.provider) + else + # No account in Gitlab yet: create it and add the LDAP identity + user = build_new_user + user.identities.new(provider: ldap_person.provider, extern_uid: ldap_person.dn) + end + + user + end + + def auto_link_ldap_user? + Gitlab.config.omniauth.auto_link_ldap_user + end + + def creating_linked_ldap_user? + auto_link_ldap_user? && ldap_person + end + + def ldap_person + return @ldap_person if defined?(@ldap_person) + + # looks for a corresponding person with same uid in any of the configured LDAP providers + @ldap_person = Gitlab::LDAP::Config.providers.find do |provider| + adapter = Gitlab::LDAP::Adapter.new(provider) + + Gitlab::LDAP::Person.find_by_uid(auth_hash.uid, adapter) + end + end + + def ldap_config + Gitlab::LDAP::Config.new(ldap_person.provider) if ldap_person + end + def needs_blocking? new? && block_after_signup? end @@ -64,7 +108,11 @@ module Gitlab end def block_after_signup? - Gitlab.config.omniauth.block_auto_created_users + if creating_linked_ldap_user? + ldap_config.block_auto_created_users + else + Gitlab.config.omniauth.block_auto_created_users + end end def auth_hash=(auth_hash) @@ -84,10 +132,19 @@ module Gitlab end def user_attributes + # Give preference to LDAP for sensitive information when creating a linked account + if creating_linked_ldap_user? + username = ldap_person.username + email = ldap_person.email.first + else + username = auth_hash.username + email = auth_hash.email + end + { name: auth_hash.name, - username: ::Namespace.clean_path(auth_hash.username), - email: auth_hash.email, + username: ::Namespace.clean_path(username), + email: email, password: auth_hash.password, password_confirmation: auth_hash.password, password_automatically_set: true diff --git a/spec/lib/gitlab/o_auth/user_spec.rb b/spec/lib/gitlab/o_auth/user_spec.rb index 44cdd1e4fab..2a982e8b107 100644 --- a/spec/lib/gitlab/o_auth/user_spec.rb +++ b/spec/lib/gitlab/o_auth/user_spec.rb @@ -13,6 +13,7 @@ describe Gitlab::OAuth::User do email: 'john@mail.com' } end + let(:ldap_user) { Gitlab::LDAP::Person.new(Net::LDAP::Entry.new, 'ldapmain') } describe :persisted? do let!(:existing_user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'my-provider') } @@ -32,31 +33,94 @@ describe Gitlab::OAuth::User do let(:provider) { 'twitter' } describe 'signup' do - context "with allow_single_sign_on enabled" do - before { Gitlab.config.omniauth.stub allow_single_sign_on: true } + shared_examples "to verify compliance with allow_single_sign_on" do + context "with allow_single_sign_on enabled" do + before { Gitlab.config.omniauth.stub allow_single_sign_on: true } - it "creates a user from Omniauth" do - oauth_user.save + it "creates a user from Omniauth" do + oauth_user.save - expect(gl_user).to be_valid - identity = gl_user.identities.first - expect(identity.extern_uid).to eql uid - expect(identity.provider).to eql 'twitter' + expect(gl_user).to be_valid + identity = gl_user.identities.first + expect(identity.extern_uid).to eql uid + expect(identity.provider).to eql 'twitter' + end + end + + context "with allow_single_sign_on disabled (Default)" do + before { Gitlab.config.omniauth.stub allow_single_sign_on: false } + it "throws an error" do + expect{ oauth_user.save }.to raise_error StandardError + end end end - context "with allow_single_sign_on disabled (Default)" do - it "throws an error" do - expect{ oauth_user.save }.to raise_error StandardError + context "with auto_link_ldap_user disabled (default)" do + before { Gitlab.config.omniauth.stub auto_link_ldap_user: false } + include_examples "to verify compliance with allow_single_sign_on" + end + + context "with auto_link_ldap_user enabled" do + before { Gitlab.config.omniauth.stub auto_link_ldap_user: true } + + context "and a corresponding LDAP person" do + before do + ldap_user.stub(:uid) { uid } + ldap_user.stub(:username) { uid } + ldap_user.stub(:email) { ['johndoe@example.com','john2@example.com'] } + ldap_user.stub(:dn) { 'uid=user1,ou=People,dc=example' } + allow(oauth_user).to receive(:ldap_person).and_return(ldap_user) + end + + context "and no account for the LDAP user" do + + it "creates a user with dual LDAP and omniauth identities" do + oauth_user.save + + expect(gl_user).to be_valid + expect(gl_user.username).to eql uid + expect(gl_user.email).to eql 'johndoe@example.com' + expect(gl_user.identities.length).to eql 2 + identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } } + expect(identities_as_hash).to match_array( + [ { provider: 'ldapmain', extern_uid: 'uid=user1,ou=People,dc=example' }, + { provider: 'twitter', extern_uid: uid } + ]) + end + end + + context "and LDAP user has an account already" do + let!(:existing_user) { create(:omniauth_user, email: 'john@example.com', extern_uid: 'uid=user1,ou=People,dc=example', provider: 'ldapmain', username: 'john') } + it "adds the omniauth identity to the LDAP account" do + oauth_user.save + + expect(gl_user).to be_valid + expect(gl_user.username).to eql 'john' + expect(gl_user.email).to eql 'john@example.com' + expect(gl_user.identities.length).to eql 2 + identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } } + expect(identities_as_hash).to match_array( + [ { provider: 'ldapmain', extern_uid: 'uid=user1,ou=People,dc=example' }, + { provider: 'twitter', extern_uid: uid } + ]) + end + end + end + + context "and no corresponding LDAP person" do + before { allow(oauth_user).to receive(:ldap_person).and_return(nil) } + + include_examples "to verify compliance with allow_single_sign_on" end end + end describe 'blocking' do let(:provider) { 'twitter' } before { Gitlab.config.omniauth.stub allow_single_sign_on: true } - context 'signup' do + context 'signup with omniauth only' do context 'dont block on create' do before { Gitlab.config.omniauth.stub block_auto_created_users: false } @@ -78,6 +142,64 @@ describe Gitlab::OAuth::User do end end + context 'signup with linked omniauth and LDAP account' do + before do + Gitlab.config.omniauth.stub auto_link_ldap_user: true + ldap_user.stub(:uid) { uid } + ldap_user.stub(:username) { uid } + ldap_user.stub(:email) { ['johndoe@example.com','john2@example.com'] } + ldap_user.stub(:dn) { 'uid=user1,ou=People,dc=example' } + allow(oauth_user).to receive(:ldap_person).and_return(ldap_user) + end + + context "and no account for the LDAP user" do + context 'dont block on create (LDAP)' do + before { Gitlab::LDAP::Config.any_instance.stub block_auto_created_users: false } + + it do + oauth_user.save + expect(gl_user).to be_valid + expect(gl_user).not_to be_blocked + end + end + + context 'block on create (LDAP)' do + before { Gitlab::LDAP::Config.any_instance.stub block_auto_created_users: true } + + it do + oauth_user.save + expect(gl_user).to be_valid + expect(gl_user).to be_blocked + end + end + end + + context 'and LDAP user has an account already' do + let!(:existing_user) { create(:omniauth_user, email: 'john@example.com', extern_uid: 'uid=user1,ou=People,dc=example', provider: 'ldapmain', username: 'john') } + + context 'dont block on create (LDAP)' do + before { Gitlab::LDAP::Config.any_instance.stub block_auto_created_users: false } + + it do + oauth_user.save + expect(gl_user).to be_valid + expect(gl_user).not_to be_blocked + end + end + + context 'block on create (LDAP)' do + before { Gitlab::LDAP::Config.any_instance.stub block_auto_created_users: true } + + it do + oauth_user.save + expect(gl_user).to be_valid + expect(gl_user).not_to be_blocked + end + end + end + end + + context 'sign-in' do before do oauth_user.save @@ -103,6 +225,26 @@ describe Gitlab::OAuth::User do expect(gl_user).not_to be_blocked end end + + context 'dont block on create (LDAP)' do + before { Gitlab::LDAP::Config.any_instance.stub block_auto_created_users: false } + + it do + oauth_user.save + expect(gl_user).to be_valid + expect(gl_user).not_to be_blocked + end + end + + context 'block on create (LDAP)' do + before { Gitlab::LDAP::Config.any_instance.stub block_auto_created_users: true } + + it do + oauth_user.save + expect(gl_user).to be_valid + expect(gl_user).not_to be_blocked + end + end end end end |