summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBryce Johnson <bryce@gitlab.com>2016-09-20 12:18:20 +0200
committerBryce Johnson <bryce@gitlab.com>2016-09-20 12:18:20 +0200
commit2872c75266a2cffbde77a0b8a14e22effedd00ce (patch)
treeab5a15874f7415be8517511cf543d8c087d7f56e
parent812806e6afbc92715b02d73385d820d3eabab2d1 (diff)
parent667d2350911b2c3cca0545897eb67fda4b7d4b80 (diff)
downloadgitlab-ce-2872c75266a2cffbde77a0b8a14e22effedd00ce.tar.gz
Merge branch 'master' into merge-request-push-compare-ui
-rw-r--r--CHANGELOG14
-rw-r--r--GITLAB_WORKHORSE_VERSION2
-rw-r--r--app/assets/javascripts/build.js13
-rw-r--r--app/assets/javascripts/gl_dropdown.js16
-rw-r--r--app/assets/javascripts/labels_select.js2
-rw-r--r--app/assets/stylesheets/pages/diff.scss9
-rw-r--r--app/controllers/projects/git_http_client_controller.rb8
-rw-r--r--app/helpers/lfs_helper.rb2
-rw-r--r--app/models/event.rb18
-rw-r--r--app/views/projects/buttons/_download.html.haml2
-rw-r--r--app/views/shared/issuable/_label_dropdown.html.haml2
-rw-r--r--doc/install/installation.md2
-rw-r--r--doc/update/8.11-to-8.12.md2
-rw-r--r--doc/workflow/lfs/lfs_administration.md4
-rw-r--r--doc/workflow/lfs/manage_large_binaries_with_git_lfs.md8
-rw-r--r--lib/api/access_requests.rb2
-rw-r--r--lib/api/entities.rb6
-rw-r--r--lib/api/internal.rb13
-rw-r--r--lib/api/members.rb8
-rw-r--r--lib/gitlab/auth.rb25
-rw-r--r--lib/gitlab/auth/result.rb12
-rw-r--r--lib/gitlab/backend/shell.rb13
-rw-r--r--lib/gitlab/ldap/adapter.rb6
-rw-r--r--lib/gitlab/lfs_token.rb54
-rw-r--r--lib/gitlab/regex.rb6
-rwxr-xr-xscripts/lint-doc.sh9
-rw-r--r--spec/factories/events.rb5
-rw-r--r--spec/features/issues/filter_issues_spec.rb31
-rw-r--r--spec/features/projects/branches/download_buttons_spec.rb6
-rw-r--r--spec/features/projects/files/download_buttons_spec.rb6
-rw-r--r--spec/features/projects/main/download_buttons_spec.rb6
-rw-r--r--spec/features/projects/tags/download_buttons_spec.rb6
-rw-r--r--spec/lib/gitlab/auth_spec.rb18
-rw-r--r--spec/lib/gitlab/backend/shell_spec.rb32
-rw-r--r--spec/lib/gitlab/lfs_token_spec.rb51
-rw-r--r--spec/models/project_spec.rb13
-rw-r--r--spec/requests/api/internal_spec.rb46
-rw-r--r--spec/requests/api/members_spec.rb27
-rw-r--r--spec/requests/lfs_http_spec.rb16
39 files changed, 443 insertions, 78 deletions
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.
+ 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
-
- project.update_column(:last_activity_at, created_at)
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\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
- expect(io).to have_received(:puts).with("key-42\tsha-rsa foo bar baz")
+ 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