diff --git a/CHANGELOG b/CHANGELOG index e69bab51dd5..857b787e173 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -15,6 +15,7 @@ v 8.12.0 (unreleased) - Update gitlab shell secret file also when it is empty. !3774 (glensc) - Give project selection dropdowns responsive width, make non-wrapping. - Make push events have equal vertical spacing. + - API: Ensure invitees are not returned in Members API. - Add two-factor recovery endpoint to internal API !5510 - Pass the "Remember me" value to the U2F authentication form - Only update projects.last_activity_at once per hour when creating a new event @@ -24,9 +25,11 @@ v 8.12.0 (unreleased) - Change logo animation to CSS (ClemMakesApps) - Instructions for enabling Git packfile bitmaps !6104 - Use Search::GlobalService.new in the `GET /projects/search/:query` endpoint + - Fix long comments in diffs messing with table width - Fix pagination on user snippets page - Run CI builds with the permissions of users !5735 - Fix sorting of issues in API + - Fix download artifacts button links !6407 - Sort project variables by key. !6275 (Diego Souza) - Ensure specs on sorting of issues in API are deterministic on MySQL - Added ability to use predefined CI variables for environment name @@ -56,6 +59,7 @@ v 8.12.0 (unreleased) - Add hover color to emoji icon (ClemMakesApps) - Increase ci_builds artifacts_size column to 8-byte integer to allow larger files - Add textarea autoresize after comment (ClemMakesApps) + - Do not write SSH public key 'comments' to authorized_keys !6381 - Refresh todos count cache when an Issue/MR is deleted - Fix branches page dropdown sort alignment (ClemMakesApps) - Hides merge request button on branches page is user doesn't have permissions @@ -109,6 +113,7 @@ v 8.12.0 (unreleased) - Remove green outline from `New branch unavailable` button on issue page !5858 (winniehell) - Fix repo title alignment (ClemMakesApps) - Change update interval of contacted_at + - Add LFS support to SSH !6043 - Fix branch title trailing space on hover (ClemMakesApps) - Don't include 'Created By' tag line when importing from GitHub if there is a linked GitLab account (EspadaV8) - Award emoji tooltips containing more than 10 usernames are now truncated !4780 (jlogandavison) @@ -604,6 +609,7 @@ v 8.10.0 - Export and import avatar as part of project import/export - Fix migration corrupting import data for old version upgrades - Show tooltip on GitLab export link in new project page + - Fix import_data wrongly saved as a result of an invalid import_url !5206 v 8.9.9 - Exclude some pending or inactivated rows in Member scopes @@ -624,12 +630,6 @@ v 8.9.6 - Keeps issue number when importing from Gitlab.com - Add Pending tab for Builds (Katarzyna Kobierska, Urszula Budziszewska) -v 8.9.7 (unreleased) - - Fix import_data wrongly saved as a result of an invalid import_url - -v 8.9.6 - - Fix importing of events under notes for GitLab projects - v 8.9.5 - Add more debug info to import/export and memory killer. !5108 - Fixed avatar alignment in new MR view. !5095 @@ -1895,7 +1895,7 @@ v 8.1.3 - Use issue editor as cross reference comment author when issue is edited with a new mention - Add Facebook authentication -v 8.1.1 +v 8.1.2 - Fix cloning Wiki repositories via HTTP (Stan Hu) - Add migration to remove satellites directory - Fix specific runners visibility diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION index 6f4eebdf6f6..100435be135 100644 --- a/GITLAB_WORKHORSE_VERSION +++ b/GITLAB_WORKHORSE_VERSION @@ -1 +1 @@ -0.8.1 +0.8.2 diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js index 10abeb50f4b..78d21c0552a 100644 --- a/app/assets/javascripts/build.js +++ b/app/assets/javascripts/build.js @@ -27,10 +27,11 @@ $(document).off('click', '.js-sidebar-build-toggle').on('click', '.js-sidebar-build-toggle', this.toggleSidebar); $(window).off('resize.build').on('resize.build', this.hideSidebar); $(document).off('click', '.stage-item').on('click', '.stage-item', this.updateDropdown); + $('#js-build-scroll > a').off('click').on('click', this.stepTrace); this.updateArtifactRemoveDate(); if ($('#build-trace').length) { this.getInitialBuildTrace(); - this.initScrollButtonAffix(); + this.initScrollButtons(); } if (this.build_status === "running" || this.build_status === "pending") { $('#autoscroll-button').on('click', function() { @@ -106,7 +107,7 @@ } }; - Build.prototype.initScrollButtonAffix = function() { + Build.prototype.initScrollButtons = function() { var $body, $buildScroll, $buildTrace; $buildScroll = $('#js-build-scroll'); $body = $('body'); @@ -165,6 +166,14 @@ this.populateJobs(stage); }; + Build.prototype.stepTrace = function(e) { + e.preventDefault(); + $currentTarget = $(e.currentTarget); + $.scrollTo($currentTarget.attr('href'), { + offset: -($('.navbar-gitlab').outerHeight() + $('.layout-nav').outerHeight()) + }); + }; + return Build; })(); diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index 087f27d9f4c..c05cda25bbd 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -607,13 +607,15 @@ selectedObject = this.renderedData[selectedIndex]; } } + field = []; + fieldName = typeof this.options.fieldName === 'function' ? this.options.fieldName(selectedObject) : this.options.fieldName; value = this.options.id ? this.options.id(selectedObject, el) : selectedObject.id; if (isInput) { field = $(this.el); - } else { - field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + escape(value) + "']"); + } else if(value) { + field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value.toString().replace(/'/g, '\\\'') + "']"); } - if (el.hasClass(ACTIVE_CLASS)) { + if (field.length && el.hasClass(ACTIVE_CLASS)) { el.removeClass(ACTIVE_CLASS); if (isInput) { field.val(''); @@ -623,7 +625,7 @@ } else if (el.hasClass(INDETERMINATE_CLASS)) { el.addClass(ACTIVE_CLASS); el.removeClass(INDETERMINATE_CLASS); - if (value == null) { + if (field.length && value == null) { field.remove(); } if (!field.length && fieldName) { @@ -636,7 +638,7 @@ this.dropdown.parent().find("input[name='" + fieldName + "']").remove(); } } - if (value == null) { + if (field.length && value == null) { field.remove(); } // Toggle active class for the tick mark @@ -644,7 +646,7 @@ if (value != null) { if (!field.length && fieldName) { this.addInput(fieldName, value, selectedObject); - } else { + } else if (field.length) { field.val(value).trigger('change'); } } @@ -794,4 +796,4 @@ }); }; -}).call(this); +}).call(this); \ No newline at end of file diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index 29a967a35a0..3f15a117ca8 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -166,7 +166,7 @@ instance.addInput(this.fieldName, label.id); } } - if ($form.find("input[type='hidden'][name='" + ($dropdown.data('fieldName')) + "'][value='" + escape(this.id(label)) + "']").length) { + if (this.id(label) && $form.find("input[type='hidden'][name='" + ($dropdown.data('fieldName')) + "'][value='" + this.id(label).toString().replace(/'/g, '\\\'') + "']").length) { selectedClass.push('is-active'); } if ($dropdown.hasClass('js-multiselect') && removesAll) { diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 21cee2e3a70..b8ef76cc74e 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -68,6 +68,11 @@ border-collapse: separate; margin: 0; padding: 0; + table-layout: fixed; + + .diff-line-num { + width: 50px; + } .line_holder td { line-height: $code_line_height; @@ -98,10 +103,6 @@ } tr.line_holder.parallel { - .old_line, .new_line { - min-width: 50px; - } - td.line_content.parallel { width: 46%; } diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb index d1a2c52d80a..cbfd3cab3dd 100644 --- a/app/controllers/projects/git_http_client_controller.rb +++ b/app/controllers/projects/git_http_client_controller.rb @@ -127,9 +127,11 @@ class Projects::GitHttpClientController < Projects::ApplicationController end def ci? - authentication_result.ci? && - authentication_project && - authentication_project == project + authentication_result.ci?(project) + end + + def lfs_deploy_token? + authentication_result.lfs_deploy_token?(project) end def authentication_has_download_access? diff --git a/app/helpers/lfs_helper.rb b/app/helpers/lfs_helper.rb index 8e827664681..c15ecc8f86e 100644 --- a/app/helpers/lfs_helper.rb +++ b/app/helpers/lfs_helper.rb @@ -25,7 +25,7 @@ module LfsHelper def lfs_download_access? return false unless project.lfs_enabled? - project.public? || ci? || user_can_download_code? || build_can_download_code? + project.public? || ci? || lfs_deploy_token? || user_can_download_code? || build_can_download_code? end def user_can_download_code? diff --git a/app/models/event.rb b/app/models/event.rb index b6e8bef3f67..55a76e26f3c 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -330,13 +330,23 @@ class Event < ActiveRecord::Base # Don't even bother obtaining a lock if the last update happened less than # 60 minutes ago. - return if project.last_activity_at > RESET_PROJECT_ACTIVITY_INTERVAL.ago + return if recent_update? - return unless Gitlab::ExclusiveLease. - new("project:update_last_activity_at:#{project.id}", - timeout: RESET_PROJECT_ACTIVITY_INTERVAL.to_i). - try_obtain + return unless try_obtain_lease project.update_column(:last_activity_at, created_at) end + + private + + def recent_update? + project.last_activity_at > RESET_PROJECT_ACTIVITY_INTERVAL.ago + end + + def try_obtain_lease + Gitlab::ExclusiveLease. + new("project:update_last_activity_at:#{project.id}", + timeout: RESET_PROJECT_ACTIVITY_INTERVAL.to_i). + try_obtain + end end diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml index 5f5e071eb40..24de020917a 100644 --- a/app/views/projects/buttons/_download.html.haml +++ b/app/views/projects/buttons/_download.html.haml @@ -37,6 +37,6 @@ %li.dropdown-header Previous Artifacts - artifacts.each do |job| %li - = link_to latest_succeeded_namespace_project_artifacts_path(project.namespace, project, ref, 'download', job: job.name), rel: 'nofollow' do + = link_to latest_succeeded_namespace_project_artifacts_path(project.namespace, project, "#{ref}/download", job: job.name), rel: 'nofollow' do %i.fa.fa-download %span Download '#{job.name}' diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index 24a1a616919..d34d28f6736 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -12,7 +12,7 @@ - if params[:label_name].present? - if params[:label_name].respond_to?('any?') - params[:label_name].each do |label| - = hidden_field_tag "label_name[]", u(label), id: nil + = hidden_field_tag "label_name[]", label, id: nil .dropdown %button.dropdown-menu-toggle.js-label-select.js-multiselect{class: classes.join(' '), type: "button", data: dropdown_data} %span.dropdown-toggle-text diff --git a/doc/install/installation.md b/doc/install/installation.md index df98655c396..3ac813aa914 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -400,7 +400,7 @@ If you are not using Linux you may have to run `gmake` instead of cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git cd gitlab-workhorse - sudo -u git -H git checkout v0.8.1 + sudo -u git -H git checkout v0.8.2 sudo -u git -H make ### Initialize Database and Activate Advanced Features diff --git a/doc/update/8.11-to-8.12.md b/doc/update/8.11-to-8.12.md index 8017c36587e..686c7e8e7b5 100644 --- a/doc/update/8.11-to-8.12.md +++ b/doc/update/8.11-to-8.12.md @@ -82,7 +82,7 @@ GitLab 8.1. ```bash cd /home/git/gitlab-workhorse sudo -u git -H git fetch --all -sudo -u git -H git checkout v0.8.1 +sudo -u git -H git checkout v0.8.2 sudo -u git -H make ``` diff --git a/doc/workflow/lfs/lfs_administration.md b/doc/workflow/lfs/lfs_administration.md index 9dc1e9b47e3..b3c73e947f0 100644 --- a/doc/workflow/lfs/lfs_administration.md +++ b/doc/workflow/lfs/lfs_administration.md @@ -45,5 +45,5 @@ In `config/gitlab.yml`: * Currently, storing GitLab Git LFS objects on a non-local storage (like S3 buckets) is not supported * Currently, removing LFS objects from GitLab Git LFS storage is not supported -* LFS authentications via SSH is not supported for the time being -* Only compatible with the GitLFS client versions 1.1.0 or 1.0.2. +* LFS authentications via SSH was added with GitLab 8.12 +* Only compatible with the GitLFS client versions 1.1.0 and up, or 1.0.2. diff --git a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md index 9fe065fa680..1a4f213a792 100644 --- a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md +++ b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md @@ -35,6 +35,10 @@ Documentation for GitLab instance administrators is under [LFS administration do credentials store is recommended * Git LFS always assumes HTTPS so if you have GitLab server on HTTP you will have to add the URL to Git config manually (see #troubleshooting) + +>**Note**: With 8.12 GitLab added LFS support to SSH. The Git LFS communication + still goes over HTTP, but now the SSH client passes the correct credentials + to the Git LFS client, so no action is required by the user. ## Using Git LFS @@ -132,6 +136,10 @@ git config --add lfs.url "http://gitlab.example.com/group/project.git/info/lfs" ### Credentials are always required when pushing an object +>**Note**: With 8.12 GitLab added LFS support to SSH. The Git LFS communication + still goes over HTTP, but now the SSH client passes the correct credentials + to the Git LFS client, so no action is required by the user. + Given that Git LFS uses HTTP Basic Authentication to authenticate the user pushing the LFS object on every push for every object, user HTTPS credentials are required. diff --git a/lib/api/access_requests.rb b/lib/api/access_requests.rb index d02b469dac8..29a97ccbd75 100644 --- a/lib/api/access_requests.rb +++ b/lib/api/access_requests.rb @@ -20,7 +20,7 @@ module API access_requesters = paginate(source.requesters.includes(:user)) - present access_requesters.map(&:user), with: Entities::AccessRequester, access_requesters: access_requesters + present access_requesters.map(&:user), with: Entities::AccessRequester, source: source end # Request access to the group/project diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 0235ba3d580..92a6f29adb0 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -105,18 +105,18 @@ module API class Member < UserBasic expose :access_level do |user, options| - member = options[:member] || options[:members].find { |m| m.user_id == user.id } + member = options[:member] || options[:source].members.find_by(user_id: user.id) member.access_level end expose :expires_at do |user, options| - member = options[:member] || options[:members].find { |m| m.user_id == user.id } + member = options[:member] || options[:source].members.find_by(user_id: user.id) member.expires_at end end class AccessRequester < UserBasic expose :requested_at do |user, options| - access_requester = options[:access_requester] || options[:access_requesters].find { |m| m.user_id == user.id } + access_requester = options[:access_requester] || options[:source].requesters.find_by(user_id: user.id) access_requester.requested_at end end diff --git a/lib/api/internal.rb b/lib/api/internal.rb index 1114fd21784..090d04544da 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -82,6 +82,19 @@ module API response end + post "/lfs_authenticate" do + status 200 + + key = Key.find(params[:key_id]) + token_handler = Gitlab::LfsToken.new(key) + + { + username: token_handler.actor_name, + lfs_token: token_handler.generate, + repository_http_path: project.http_url_to_repo + } + end + get "/merge_request_urls" do ::MergeRequests::GetUrlsService.new(project).execute(params[:changes]) end diff --git a/lib/api/members.rb b/lib/api/members.rb index 94c16710d9a..37f0a6512f4 100644 --- a/lib/api/members.rb +++ b/lib/api/members.rb @@ -18,11 +18,11 @@ module API get ":id/members" do source = find_source(source_type, params[:id]) - members = source.members.includes(:user) - members = members.joins(:user).merge(User.search(params[:query])) if params[:query] - members = paginate(members) + users = source.users + users = users.merge(User.search(params[:query])) if params[:query] + users = paginate(users) - present members.map(&:user), with: Entities::Member, members: members + present users, with: Entities::Member, source: source end # Get a group/project member diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 0a0f1c3b17b..7c0f2115d43 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -11,6 +11,7 @@ module Gitlab build_access_token_check(login, password) || user_with_password_for_git(login, password) || oauth_access_token_check(login, password) || + lfs_token_check(login, password) || personal_access_token_check(login, password) || Gitlab::Auth::Result.new @@ -102,6 +103,30 @@ module Gitlab end end + def lfs_token_check(login, password) + deploy_key_matches = login.match(/\Alfs\+deploy-key-(\d+)\z/) + + actor = + if deploy_key_matches + DeployKey.find(deploy_key_matches[1]) + else + User.by_login(login) + end + + return unless actor + + token_handler = Gitlab::LfsToken.new(actor) + + authentication_abilities = + if token_handler.user? + full_authentication_abilities + else + read_authentication_abilities + end + + Result.new(actor, nil, token_handler.type, authentication_abilities) if Devise.secure_compare(token_handler.value, password) + end + def build_access_token_check(login, password) return unless login == 'gitlab-ci-token' return unless password diff --git a/lib/gitlab/auth/result.rb b/lib/gitlab/auth/result.rb index bf625649cbf..6be7f690676 100644 --- a/lib/gitlab/auth/result.rb +++ b/lib/gitlab/auth/result.rb @@ -1,8 +1,16 @@ module Gitlab module Auth Result = Struct.new(:actor, :project, :type, :authentication_abilities) do - def ci? - type == :ci + def ci?(for_project) + type == :ci && + project && + project == for_project + end + + def lfs_deploy_token?(for_project) + type == :lfs_deploy_token && + actor && + actor.projects.include?(for_project) end def success? diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb index c412249a01e..79eac66b364 100644 --- a/lib/gitlab/backend/shell.rb +++ b/lib/gitlab/backend/shell.rb @@ -6,7 +6,12 @@ module Gitlab KeyAdder = Struct.new(:io) do def add_key(id, key) - key.gsub!(/[[:space:]]+/, ' ').strip! + key = Gitlab::Shell.strip_key(key) + # Newline and tab are part of the 'protocol' used to transmit id+key to the other end + if key.include?("\t") || key.include?("\n") + raise Error.new("Invalid key: #{key.inspect}") + end + io.puts("#{id}\t#{key}") end end @@ -16,6 +21,10 @@ module Gitlab @version_required ||= File.read(Rails.root. join('GITLAB_SHELL_VERSION')).strip end + + def strip_key(key) + key.split(/ /)[0, 2].join(' ') + end end # Init new repository @@ -107,7 +116,7 @@ module Gitlab # def add_key(key_id, key_content) Gitlab::Utils.system_silent([gitlab_shell_keys_path, - 'add-key', key_id, key_content]) + 'add-key', key_id, self.class.strip_key(key_content)]) end # Batch-add keys to authorized_keys diff --git a/lib/gitlab/ldap/adapter.rb b/lib/gitlab/ldap/adapter.rb index 9100719da87..82cb8cef754 100644 --- a/lib/gitlab/ldap/adapter.rb +++ b/lib/gitlab/ldap/adapter.rb @@ -70,7 +70,7 @@ module Gitlab private def user_options(field, value, limit) - options = { attributes: %W(#{config.uid} cn mail dn) } + options = { attributes: user_attributes } options[:size] = limit if limit if field.to_sym == :dn @@ -98,6 +98,10 @@ module Gitlab filter end end + + def user_attributes + %W(#{config.uid} cn mail dn) + end end end end diff --git a/lib/gitlab/lfs_token.rb b/lib/gitlab/lfs_token.rb new file mode 100644 index 00000000000..d089a2f9b0b --- /dev/null +++ b/lib/gitlab/lfs_token.rb @@ -0,0 +1,54 @@ +module Gitlab + class LfsToken + attr_accessor :actor + + TOKEN_LENGTH = 50 + EXPIRY_TIME = 1800 + + def initialize(actor) + @actor = + case actor + when DeployKey, User + actor + when Key + actor.user + else + raise 'Bad Actor' + end + end + + def generate + token = Devise.friendly_token(TOKEN_LENGTH) + + Gitlab::Redis.with do |redis| + redis.set(redis_key, token, ex: EXPIRY_TIME) + end + + token + end + + def value + Gitlab::Redis.with do |redis| + redis.get(redis_key) + end + end + + def user? + actor.is_a?(User) + end + + def type + actor.is_a?(User) ? :lfs_token : :lfs_deploy_token + end + + def actor_name + actor.is_a?(User) ? actor.username : "lfs+deploy-key-#{actor.id}" + end + + private + + def redis_key + "gitlab:lfs_token:#{actor.class.name.underscore}_#{actor.id}" if actor + end + end +end diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index bc8bbf337f3..776bbcbb5d0 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -44,7 +44,7 @@ module Gitlab end def file_name_regex_message - "can contain only letters, digits, '_', '-', '@' and '.'. " + "can contain only letters, digits, '_', '-', '@' and '.'." end def file_path_regex @@ -52,7 +52,7 @@ module Gitlab end def file_path_regex_message - "can contain only letters, digits, '_', '-', '@' and '.'. Separate directories with a '/'. " + "can contain only letters, digits, '_', '-', '@' and '.'. Separate directories with a '/'." end def directory_traversal_regex @@ -60,7 +60,7 @@ module Gitlab end def directory_traversal_regex_message - "cannot include directory traversal. " + "cannot include directory traversal." end def archive_formats_regex diff --git a/scripts/lint-doc.sh b/scripts/lint-doc.sh index bc6e4d94061..fb4d8463981 100755 --- a/scripts/lint-doc.sh +++ b/scripts/lint-doc.sh @@ -10,6 +10,15 @@ then exit 1 fi +# Ensure that the CHANGELOG does not contain duplicate versions +DUPLICATE_CHANGELOG_VERSIONS=$(grep --extended-regexp '^v [0-9.]+' CHANGELOG | sed 's| (unreleased)||' | sort | uniq -d) +if [ "${DUPLICATE_CHANGELOG_VERSIONS}" != "" ] +then + echo '✖ ERROR: Duplicate versions in CHANGELOG:' >&2 + echo "${DUPLICATE_CHANGELOG_VERSIONS}" >&2 + exit 1 +fi + echo "✔ Linting passed" exit 0 diff --git a/spec/factories/events.rb b/spec/factories/events.rb index 90788f30ac9..8820d527c61 100644 --- a/spec/factories/events.rb +++ b/spec/factories/events.rb @@ -1,10 +1,11 @@ FactoryGirl.define do factory :event do + project + author factory: :user + factory :closed_issue_event do - project action { Event::CLOSED } target factory: :closed_issue - author factory: :user end end end diff --git a/spec/features/issues/filter_issues_spec.rb b/spec/features/issues/filter_issues_spec.rb index 0e9f814044e..69fda27cc61 100644 --- a/spec/features/issues/filter_issues_spec.rb +++ b/spec/features/issues/filter_issues_spec.rb @@ -101,7 +101,7 @@ describe 'Filter issues', feature: true do expect(find('.js-label-select .dropdown-toggle-text')).to have_content('No Label') end - it 'filters by no label' do + it 'filters by a label' do find('.dropdown-menu-labels a', text: label.title).click page.within '.labels-filter' do expect(page).to have_content label.title @@ -109,7 +109,7 @@ describe 'Filter issues', feature: true do expect(find('.js-label-select .dropdown-toggle-text')).to have_content(label.title) end - it 'filters by wont fix labels' do + it "filters by `won't fix` and another label" do find('.dropdown-menu-labels a', text: label.title).click page.within '.labels-filter' do expect(page).to have_content wontfix.title @@ -117,6 +117,33 @@ describe 'Filter issues', feature: true do end expect(find('.js-label-select .dropdown-toggle-text')).to have_content(wontfix.title) end + + it "filters by `won't fix` label followed by another label after page load" do + find('.dropdown-menu-labels a', text: wontfix.title).click + # Close label dropdown to load + find('body').click + expect(find('.filtered-labels')).to have_content(wontfix.title) + + find('.js-label-select').click + wait_for_ajax + find('.dropdown-menu-labels a', text: label.title).click + # Close label dropdown to load + find('body').click + expect(find('.filtered-labels')).to have_content(label.title) + + find('.js-label-select').click + wait_for_ajax + expect(find('.dropdown-menu-labels li', text: wontfix.title)).to have_css('.is-active') + expect(find('.dropdown-menu-labels li', text: label.title)).to have_css('.is-active') + end + + it "selects and unselects `won't fix`" do + find('.dropdown-menu-labels a', text: wontfix.title).click + find('.dropdown-menu-labels a', text: wontfix.title).click + # Close label dropdown to load + find('body').click + expect(page).not_to have_css('.filtered-labels') + end end describe 'Filter issues for assignee and label from issues#index' do diff --git a/spec/features/projects/branches/download_buttons_spec.rb b/spec/features/projects/branches/download_buttons_spec.rb index 04058300570..92028c19361 100644 --- a/spec/features/projects/branches/download_buttons_spec.rb +++ b/spec/features/projects/branches/download_buttons_spec.rb @@ -33,7 +33,11 @@ feature 'Download buttons in branches page', feature: true do end scenario 'shows download artifacts button' do - expect(page).to have_link "Download '#{build.name}'" + href = latest_succeeded_namespace_project_artifacts_path( + project.namespace, project, 'binary-encoding/download', + job: 'build') + + expect(page).to have_link "Download '#{build.name}'", href: href end end end diff --git a/spec/features/projects/files/download_buttons_spec.rb b/spec/features/projects/files/download_buttons_spec.rb index be5cebcd7c9..d7c29a7e074 100644 --- a/spec/features/projects/files/download_buttons_spec.rb +++ b/spec/features/projects/files/download_buttons_spec.rb @@ -34,7 +34,11 @@ feature 'Download buttons in files tree', feature: true do end scenario 'shows download artifacts button' do - expect(page).to have_link "Download '#{build.name}'" + href = latest_succeeded_namespace_project_artifacts_path( + project.namespace, project, "#{project.default_branch}/download", + job: 'build') + + expect(page).to have_link "Download '#{build.name}'", href: href end end end diff --git a/spec/features/projects/main/download_buttons_spec.rb b/spec/features/projects/main/download_buttons_spec.rb index b26c0ea7a14..227ccf9459c 100644 --- a/spec/features/projects/main/download_buttons_spec.rb +++ b/spec/features/projects/main/download_buttons_spec.rb @@ -33,7 +33,11 @@ feature 'Download buttons in project main page', feature: true do end scenario 'shows download artifacts button' do - expect(page).to have_link "Download '#{build.name}'" + href = latest_succeeded_namespace_project_artifacts_path( + project.namespace, project, "#{project.default_branch}/download", + job: 'build') + + expect(page).to have_link "Download '#{build.name}'", href: href end end end diff --git a/spec/features/projects/tags/download_buttons_spec.rb b/spec/features/projects/tags/download_buttons_spec.rb index 6e0022c179f..dd93d25c2c6 100644 --- a/spec/features/projects/tags/download_buttons_spec.rb +++ b/spec/features/projects/tags/download_buttons_spec.rb @@ -34,7 +34,11 @@ feature 'Download buttons in tags page', feature: true do end scenario 'shows download artifacts button' do - expect(page).to have_link "Download '#{build.name}'" + href = latest_succeeded_namespace_project_artifacts_path( + project.namespace, project, "#{tag}/download", + job: 'build') + + expect(page).to have_link "Download '#{build.name}'", href: href end end end diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index 8807a68a0a2..745fbc0df45 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -61,6 +61,24 @@ describe Gitlab::Auth, lib: true do expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities)) end + it 'recognizes user lfs tokens' do + user = create(:user) + ip = 'ip' + token = Gitlab::LfsToken.new(user).generate + + expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: user.username) + expect(gl_auth.find_for_git_client(user.username, token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(user, nil, :lfs_token, full_authentication_abilities)) + end + + it 'recognizes deploy key lfs tokens' do + key = create(:deploy_key) + ip = 'ip' + token = Gitlab::LfsToken.new(key).generate + + expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: "lfs+deploy-key-#{key.id}") + expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new(key, nil, :lfs_deploy_token, read_authentication_abilities)) + end + it 'recognizes OAuth tokens' do user = create(:user) application = Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user) diff --git a/spec/lib/gitlab/backend/shell_spec.rb b/spec/lib/gitlab/backend/shell_spec.rb index 6e5ba211382..07407f212aa 100644 --- a/spec/lib/gitlab/backend/shell_spec.rb +++ b/spec/lib/gitlab/backend/shell_spec.rb @@ -1,4 +1,5 @@ require 'spec_helper' +require 'stringio' describe Gitlab::Shell, lib: true do let(:project) { double('Project', id: 7, path: 'diaspora') } @@ -44,15 +45,38 @@ describe Gitlab::Shell, lib: true do end end + describe '#add_key' do + it 'removes trailing garbage' do + allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path) + expect(Gitlab::Utils).to receive(:system_silent).with( + [:gitlab_shell_keys_path, 'add-key', 'key-123', 'ssh-rsa foobar'] + ) + + gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage') + end + end + describe Gitlab::Shell::KeyAdder, lib: true do describe '#add_key' do - it 'normalizes space characters in the key' do - io = spy + it 'removes trailing garbage' do + io = spy(:io) adder = described_class.new(io) - adder.add_key('key-42', "sha-rsa foo\tbar\tbaz") + adder.add_key('key-42', "ssh-rsa foo bar\tbaz") - expect(io).to have_received(:puts).with("key-42\tsha-rsa foo bar baz") + expect(io).to have_received(:puts).with("key-42\tssh-rsa foo") + end + + it 'raises an exception if the key contains a tab' do + expect do + described_class.new(StringIO.new).add_key('key-42', "ssh-rsa\tfoobar") + end.to raise_error(Gitlab::Shell::Error) + end + + it 'raises an exception if the key contains a newline' do + expect do + described_class.new(StringIO.new).add_key('key-42', "ssh-rsa foobar\nssh-rsa pawned") + end.to raise_error(Gitlab::Shell::Error) end end end diff --git a/spec/lib/gitlab/lfs_token_spec.rb b/spec/lib/gitlab/lfs_token_spec.rb new file mode 100644 index 00000000000..9f04f67e0a8 --- /dev/null +++ b/spec/lib/gitlab/lfs_token_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +describe Gitlab::LfsToken, lib: true do + describe '#generate and #value' do + shared_examples 'an LFS token generator' do + it 'returns a randomly generated token' do + token = handler.generate + + expect(token).not_to be_nil + expect(token).to be_a String + expect(token.length).to eq 50 + end + + it 'returns the correct token based on the key' do + token = handler.generate + + expect(handler.value).to eq(token) + end + end + + context 'when the actor is a user' do + let(:actor) { create(:user) } + let(:handler) { described_class.new(actor) } + + it_behaves_like 'an LFS token generator' + + it 'returns the correct username' do + expect(handler.actor_name).to eq(actor.username) + end + + it 'returns the correct token type' do + expect(handler.type).to eq(:lfs_token) + end + end + + context 'when the actor is a deploy key' do + let(:actor) { create(:deploy_key) } + let(:handler) { described_class.new(actor) } + + it_behaves_like 'an LFS token generator' + + it 'returns the correct username' do + expect(handler.actor_name).to eq("lfs+deploy-key-#{actor.id}") + end + + it 'returns the correct token type' do + expect(handler.type).to eq(:lfs_deploy_token) + end + end + end +end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 7ca1bd1e5c9..a388ff703a6 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -308,20 +308,23 @@ describe Project, models: true do end describe 'last_activity methods' do - let(:project) { create(:project) } - let(:last_event) { double(created_at: Time.now) } + let(:timestamp) { Time.now - 2.hours } + let(:project) { create(:project, created_at: timestamp, updated_at: timestamp) } describe 'last_activity' do it 'alias last_activity to last_event' do - allow(project).to receive(:last_event).and_return(last_event) + last_event = create(:event, project: project) + expect(project.last_activity).to eq(last_event) end end describe 'last_activity_date' do it 'returns the creation date of the project\'s last event if present' do - create(:event, project: project) - expect(project.last_activity_at.to_i).to eq(last_event.created_at.to_i) + expect_any_instance_of(Event).to receive(:try_obtain_lease).and_return(true) + new_event = create(:event, project: project, created_at: Time.now) + + expect(project.last_activity_at.to_i).to eq(new_event.created_at.to_i) end it 'returns the project\'s last update date if it has no events' do diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 46d1b868782..46e8e6f1169 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -100,6 +100,43 @@ describe API::API, api: true do end end + describe "POST /internal/lfs_authenticate" do + before do + project.team << [user, :developer] + end + + context 'user key' do + it 'returns the correct information about the key' do + lfs_auth(key.id, project) + + expect(response).to have_http_status(200) + expect(json_response['username']).to eq(user.username) + expect(json_response['lfs_token']).to eq(Gitlab::LfsToken.new(key).value) + + expect(json_response['repository_http_path']).to eq(project.http_url_to_repo) + end + + it 'returns a 404 when the wrong key is provided' do + lfs_auth(nil, project) + + expect(response).to have_http_status(404) + end + end + + context 'deploy key' do + let(:key) { create(:deploy_key) } + + it 'returns the correct information about the key' do + lfs_auth(key.id, project) + + expect(response).to have_http_status(200) + expect(json_response['username']).to eq("lfs+deploy-key-#{key.id}") + expect(json_response['lfs_token']).to eq(Gitlab::LfsToken.new(key).value) + expect(json_response['repository_http_path']).to eq(project.http_url_to_repo) + end + end + end + describe "GET /internal/discover" do it do get(api("/internal/discover"), key_id: key.id, secret_token: secret_token) @@ -389,4 +426,13 @@ describe API::API, api: true do protocol: 'ssh' ) end + + def lfs_auth(key_id, project) + post( + api("/internal/lfs_authenticate"), + key_id: key_id, + secret_token: secret_token, + project: project.path_with_namespace + ) + end end diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb index 1e365bf353a..92032f09b17 100644 --- a/spec/requests/api/members_spec.rb +++ b/spec/requests/api/members_spec.rb @@ -30,20 +30,29 @@ describe API::Members, api: true do let(:route) { get api("/#{source_type.pluralize}/#{source.id}/members", stranger) } end - context 'when authenticated as a non-member' do - %i[access_requester stranger].each do |type| - context "as a #{type}" do - it 'returns 200' do - user = public_send(type) - get api("/#{source_type.pluralize}/#{source.id}/members", user) + %i[master developer access_requester stranger].each do |type| + context "when authenticated as a #{type}" do + it 'returns 200' do + user = public_send(type) + get api("/#{source_type.pluralize}/#{source.id}/members", user) - expect(response).to have_http_status(200) - expect(json_response.size).to eq(2) - end + expect(response).to have_http_status(200) + expect(json_response.size).to eq(2) + expect(json_response.map { |u| u['id'] }).to match_array [master.id, developer.id] end end end + it 'does not return invitees' do + create(:"#{source_type}_member", invite_token: '123', invite_email: 'test@abc.com', source: source, user: nil) + + get api("/#{source_type.pluralize}/#{source.id}/members", developer) + + expect(response).to have_http_status(200) + expect(json_response.size).to eq(2) + expect(json_response.map { |u| u['id'] }).to match_array [master.id, developer.id] + end + it 'finds members with query string' do get api("/#{source_type.pluralize}/#{source.id}/members", developer), query: master.username diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb index b58d410b7a3..09e4e265dd1 100644 --- a/spec/requests/lfs_http_spec.rb +++ b/spec/requests/lfs_http_spec.rb @@ -245,6 +245,18 @@ describe 'Git LFS API and storage' do end end + context 'when deploy key is authorized' do + let(:key) { create(:deploy_key) } + let(:authorization) { authorize_deploy_key } + + let(:update_permissions) do + project.deploy_keys << key + project.lfs_objects << lfs_object + end + + it_behaves_like 'responds with a file' + end + context 'when build is authorized as' do let(:authorization) { authorize_ci_project } @@ -1097,6 +1109,10 @@ describe 'Git LFS API and storage' do ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password) end + def authorize_deploy_key + ActionController::HttpAuthentication::Basic.encode_credentials("lfs+deploy-key-#{key.id}", Gitlab::LfsToken.new(key).generate) + end + def fork_project(project, user, object = nil) allow(RepositoryForkWorker).to receive(:perform_async).and_return(true) Projects::ForkService.new(project, user, {}).execute