Commit 2872c752 authored by Bryce Johnson's avatar Bryce Johnson

Merge branch 'master' into merge-request-push-compare-ui

parents 812806e6 667d2350
...@@ -15,6 +15,7 @@ v 8.12.0 (unreleased) ...@@ -15,6 +15,7 @@ v 8.12.0 (unreleased)
- Update gitlab shell secret file also when it is empty. !3774 (glensc) - Update gitlab shell secret file also when it is empty. !3774 (glensc)
- Give project selection dropdowns responsive width, make non-wrapping. - Give project selection dropdowns responsive width, make non-wrapping.
- Make push events have equal vertical spacing. - 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 - Add two-factor recovery endpoint to internal API !5510
- Pass the "Remember me" value to the U2F authentication form - Pass the "Remember me" value to the U2F authentication form
- Only update projects.last_activity_at once per hour when creating a new event - Only update projects.last_activity_at once per hour when creating a new event
...@@ -24,9 +25,11 @@ v 8.12.0 (unreleased) ...@@ -24,9 +25,11 @@ v 8.12.0 (unreleased)
- Change logo animation to CSS (ClemMakesApps) - Change logo animation to CSS (ClemMakesApps)
- Instructions for enabling Git packfile bitmaps !6104 - Instructions for enabling Git packfile bitmaps !6104
- Use Search::GlobalService.new in the `GET /projects/search/:query` endpoint - 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 - Fix pagination on user snippets page
- Run CI builds with the permissions of users !5735 - Run CI builds with the permissions of users !5735
- Fix sorting of issues in API - Fix sorting of issues in API
- Fix download artifacts button links !6407
- Sort project variables by key. !6275 (Diego Souza) - Sort project variables by key. !6275 (Diego Souza)
- Ensure specs on sorting of issues in API are deterministic on MySQL - Ensure specs on sorting of issues in API are deterministic on MySQL
- Added ability to use predefined CI variables for environment name - Added ability to use predefined CI variables for environment name
...@@ -56,6 +59,7 @@ v 8.12.0 (unreleased) ...@@ -56,6 +59,7 @@ v 8.12.0 (unreleased)
- Add hover color to emoji icon (ClemMakesApps) - Add hover color to emoji icon (ClemMakesApps)
- Increase ci_builds artifacts_size column to 8-byte integer to allow larger files - Increase ci_builds artifacts_size column to 8-byte integer to allow larger files
- Add textarea autoresize after comment (ClemMakesApps) - 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 - Refresh todos count cache when an Issue/MR is deleted
- Fix branches page dropdown sort alignment (ClemMakesApps) - Fix branches page dropdown sort alignment (ClemMakesApps)
- Hides merge request button on branches page is user doesn't have permissions - Hides merge request button on branches page is user doesn't have permissions
...@@ -109,6 +113,7 @@ v 8.12.0 (unreleased) ...@@ -109,6 +113,7 @@ v 8.12.0 (unreleased)
- Remove green outline from `New branch unavailable` button on issue page !5858 (winniehell) - Remove green outline from `New branch unavailable` button on issue page !5858 (winniehell)
- Fix repo title alignment (ClemMakesApps) - Fix repo title alignment (ClemMakesApps)
- Change update interval of contacted_at - Change update interval of contacted_at
- Add LFS support to SSH !6043
- Fix branch title trailing space on hover (ClemMakesApps) - 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) - 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) - Award emoji tooltips containing more than 10 usernames are now truncated !4780 (jlogandavison)
...@@ -604,6 +609,7 @@ v 8.10.0 ...@@ -604,6 +609,7 @@ v 8.10.0
- Export and import avatar as part of project import/export - Export and import avatar as part of project import/export
- Fix migration corrupting import data for old version upgrades - Fix migration corrupting import data for old version upgrades
- Show tooltip on GitLab export link in new project page - 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 v 8.9.9
- Exclude some pending or inactivated rows in Member scopes - Exclude some pending or inactivated rows in Member scopes
...@@ -624,12 +630,6 @@ v 8.9.6 ...@@ -624,12 +630,6 @@ v 8.9.6
- Keeps issue number when importing from Gitlab.com - Keeps issue number when importing from Gitlab.com
- Add Pending tab for Builds (Katarzyna Kobierska, Urszula Budziszewska) - 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 v 8.9.5
- Add more debug info to import/export and memory killer. !5108 - Add more debug info to import/export and memory killer. !5108
- Fixed avatar alignment in new MR view. !5095 - Fixed avatar alignment in new MR view. !5095
...@@ -1895,7 +1895,7 @@ v 8.1.3 ...@@ -1895,7 +1895,7 @@ v 8.1.3
- Use issue editor as cross reference comment author when issue is edited with a new mention - Use issue editor as cross reference comment author when issue is edited with a new mention
- Add Facebook authentication - Add Facebook authentication
v 8.1.1 v 8.1.2
- Fix cloning Wiki repositories via HTTP (Stan Hu) - Fix cloning Wiki repositories via HTTP (Stan Hu)
- Add migration to remove satellites directory - Add migration to remove satellites directory
- Fix specific runners visibility - Fix specific runners visibility
......
...@@ -27,10 +27,11 @@ ...@@ -27,10 +27,11 @@
$(document).off('click', '.js-sidebar-build-toggle').on('click', '.js-sidebar-build-toggle', this.toggleSidebar); $(document).off('click', '.js-sidebar-build-toggle').on('click', '.js-sidebar-build-toggle', this.toggleSidebar);
$(window).off('resize.build').on('resize.build', this.hideSidebar); $(window).off('resize.build').on('resize.build', this.hideSidebar);
$(document).off('click', '.stage-item').on('click', '.stage-item', this.updateDropdown); $(document).off('click', '.stage-item').on('click', '.stage-item', this.updateDropdown);
$('#js-build-scroll > a').off('click').on('click', this.stepTrace);
this.updateArtifactRemoveDate(); this.updateArtifactRemoveDate();
if ($('#build-trace').length) { if ($('#build-trace').length) {
this.getInitialBuildTrace(); this.getInitialBuildTrace();
this.initScrollButtonAffix(); this.initScrollButtons();
} }
if (this.build_status === "running" || this.build_status === "pending") { if (this.build_status === "running" || this.build_status === "pending") {
$('#autoscroll-button').on('click', function() { $('#autoscroll-button').on('click', function() {
...@@ -106,7 +107,7 @@ ...@@ -106,7 +107,7 @@
} }
}; };
Build.prototype.initScrollButtonAffix = function() { Build.prototype.initScrollButtons = function() {
var $body, $buildScroll, $buildTrace; var $body, $buildScroll, $buildTrace;
$buildScroll = $('#js-build-scroll'); $buildScroll = $('#js-build-scroll');
$body = $('body'); $body = $('body');
...@@ -165,6 +166,14 @@ ...@@ -165,6 +166,14 @@
this.populateJobs(stage); 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; return Build;
})(); })();
......
...@@ -607,13 +607,15 @@ ...@@ -607,13 +607,15 @@
selectedObject = this.renderedData[selectedIndex]; 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; value = this.options.id ? this.options.id(selectedObject, el) : selectedObject.id;
if (isInput) { if (isInput) {
field = $(this.el); field = $(this.el);
} else { } else if(value) {
field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + escape(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); el.removeClass(ACTIVE_CLASS);
if (isInput) { if (isInput) {
field.val(''); field.val('');
...@@ -623,7 +625,7 @@ ...@@ -623,7 +625,7 @@
} else if (el.hasClass(INDETERMINATE_CLASS)) { } else if (el.hasClass(INDETERMINATE_CLASS)) {
el.addClass(ACTIVE_CLASS); el.addClass(ACTIVE_CLASS);
el.removeClass(INDETERMINATE_CLASS); el.removeClass(INDETERMINATE_CLASS);
if (value == null) { if (field.length && value == null) {
field.remove(); field.remove();
} }
if (!field.length && fieldName) { if (!field.length && fieldName) {
...@@ -636,7 +638,7 @@ ...@@ -636,7 +638,7 @@
this.dropdown.parent().find("input[name='" + fieldName + "']").remove(); this.dropdown.parent().find("input[name='" + fieldName + "']").remove();
} }
} }
if (value == null) { if (field.length && value == null) {
field.remove(); field.remove();
} }
// Toggle active class for the tick mark // Toggle active class for the tick mark
...@@ -644,7 +646,7 @@ ...@@ -644,7 +646,7 @@
if (value != null) { if (value != null) {
if (!field.length && fieldName) { if (!field.length && fieldName) {
this.addInput(fieldName, value, selectedObject); this.addInput(fieldName, value, selectedObject);
} else { } else if (field.length) {
field.val(value).trigger('change'); field.val(value).trigger('change');
} }
} }
......
...@@ -166,7 +166,7 @@ ...@@ -166,7 +166,7 @@
instance.addInput(this.fieldName, label.id); 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'); selectedClass.push('is-active');
} }
if ($dropdown.hasClass('js-multiselect') && removesAll) { if ($dropdown.hasClass('js-multiselect') && removesAll) {
......
...@@ -68,6 +68,11 @@ ...@@ -68,6 +68,11 @@
border-collapse: separate; border-collapse: separate;
margin: 0; margin: 0;
padding: 0; padding: 0;
table-layout: fixed;
.diff-line-num {
width: 50px;
}
.line_holder td { .line_holder td {
line-height: $code_line_height; line-height: $code_line_height;
...@@ -98,10 +103,6 @@ ...@@ -98,10 +103,6 @@
} }
tr.line_holder.parallel { tr.line_holder.parallel {
.old_line, .new_line {
min-width: 50px;
}
td.line_content.parallel { td.line_content.parallel {
width: 46%; width: 46%;
} }
......
...@@ -127,9 +127,11 @@ class Projects::GitHttpClientController < Projects::ApplicationController ...@@ -127,9 +127,11 @@ class Projects::GitHttpClientController < Projects::ApplicationController
end end
def ci? def ci?
authentication_result.ci? && authentication_result.ci?(project)
authentication_project && end
authentication_project == project
def lfs_deploy_token?
authentication_result.lfs_deploy_token?(project)
end end
def authentication_has_download_access? def authentication_has_download_access?
......
...@@ -25,7 +25,7 @@ module LfsHelper ...@@ -25,7 +25,7 @@ module LfsHelper
def lfs_download_access? def lfs_download_access?
return false unless project.lfs_enabled? 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 end
def user_can_download_code? def user_can_download_code?
......
...@@ -330,13 +330,23 @@ class Event < ActiveRecord::Base ...@@ -330,13 +330,23 @@ class Event < ActiveRecord::Base
# Don't even bother obtaining a lock if the last update happened less than # Don't even bother obtaining a lock if the last update happened less than
# 60 minutes ago. # 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}", new("project:update_last_activity_at:#{project.id}",
timeout: RESET_PROJECT_ACTIVITY_INTERVAL.to_i). timeout: RESET_PROJECT_ACTIVITY_INTERVAL.to_i).
try_obtain try_obtain
project.update_column(:last_activity_at, created_at)
end end
end end
...@@ -37,6 +37,6 @@ ...@@ -37,6 +37,6 @@
%li.dropdown-header Previous Artifacts %li.dropdown-header Previous Artifacts
- artifacts.each do |job| - artifacts.each do |job|
%li %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 %i.fa.fa-download
%span Download '#{job.name}' %span Download '#{job.name}'
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
- if params[:label_name].present? - if params[:label_name].present?
- if params[:label_name].respond_to?('any?') - if params[:label_name].respond_to?('any?')
- params[:label_name].each do |label| - params[:label_name].each do |label|
= hidden_field_tag "label_name[]", u(label), id: nil = hidden_field_tag "label_name[]", label, id: nil
.dropdown .dropdown
%button.dropdown-menu-toggle.js-label-select.js-multiselect{class: classes.join(' '), type: "button", data: dropdown_data} %button.dropdown-menu-toggle.js-label-select.js-multiselect{class: classes.join(' '), type: "button", data: dropdown_data}
%span.dropdown-toggle-text %span.dropdown-toggle-text
......
...@@ -400,7 +400,7 @@ If you are not using Linux you may have to run `gmake` instead of ...@@ -400,7 +400,7 @@ If you are not using Linux you may have to run `gmake` instead of
cd /home/git cd /home/git
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git
cd gitlab-workhorse 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 sudo -u git -H make
### Initialize Database and Activate Advanced Features ### Initialize Database and Activate Advanced Features
......
...@@ -82,7 +82,7 @@ GitLab 8.1. ...@@ -82,7 +82,7 @@ GitLab 8.1.
```bash ```bash
cd /home/git/gitlab-workhorse cd /home/git/gitlab-workhorse
sudo -u git -H git fetch --all 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 sudo -u git -H make
``` ```
......
...@@ -45,5 +45,5 @@ In `config/gitlab.yml`: ...@@ -45,5 +45,5 @@ In `config/gitlab.yml`:
* Currently, storing GitLab Git LFS objects on a non-local storage (like S3 buckets) * Currently, storing GitLab Git LFS objects on a non-local storage (like S3 buckets)
is not supported is not supported
* Currently, removing LFS objects from GitLab Git LFS storage 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 * LFS authentications via SSH was added with GitLab 8.12
* Only compatible with the GitLFS client versions 1.1.0 or 1.0.2. * Only compatible with the GitLFS client versions 1.1.0 and up, or 1.0.2.
...@@ -36,6 +36,10 @@ Documentation for GitLab instance administrators is under [LFS administration do ...@@ -36,6 +36,10 @@ Documentation for GitLab instance administrators is under [LFS administration do
* Git LFS always assumes HTTPS so if you have GitLab server on HTTP you will have * 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) 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 ## Using Git LFS
Lets take a look at the workflow when you need to check large files into your Git Lets take a look at the workflow when you need to check large files into your Git
...@@ -132,6 +136,10 @@ git config --add lfs.url "http://gitlab.example.com/group/project.git/info/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 ### 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 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. the LFS object on every push for every object, user HTTPS credentials are required.
......
...@@ -20,7 +20,7 @@ module API ...@@ -20,7 +20,7 @@ module API
access_requesters = paginate(source.requesters.includes(:user)) 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 end
# Request access to the group/project # Request access to the group/project
......
...@@ -105,18 +105,18 @@ module API ...@@ -105,18 +105,18 @@ module API
class Member < UserBasic class Member < UserBasic
expose :access_level do |user, options| 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 member.access_level
end end
expose :expires_at do |user, options| 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 member.expires_at
end end
end end
class AccessRequester < UserBasic class AccessRequester < UserBasic
expose :requested_at do |user, options| 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 access_requester.requested_at
end end
end end
......
...@@ -82,6 +82,19 @@ module API ...@@ -82,6 +82,19 @@ module API
response response
end 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 get "/merge_request_urls" do
::MergeRequests::GetUrlsService.new(project).execute(params[:changes]) ::MergeRequests::GetUrlsService.new(project).execute(params[:changes])
end end
......
...@@ -18,11 +18,11 @@ module API ...@@ -18,11 +18,11 @@ module API
get ":id/members" do get ":id/members" do
source = find_source(source_type, params[:id]) source = find_source(source_type, params[:id])
members = source.members.includes(:user) users = source.users
members = members.joins(:user).merge(User.search(params[:query])) if params[:query] users = users.merge(User.search(params[:query])) if params[:query]
members = paginate(members) users = paginate(users)
present members.map(&:user), with: Entities::Member, members: members present users, with: Entities::Member, source: source
end end
# Get a group/project member # Get a group/project member
......
...@@ -11,6 +11,7 @@ module Gitlab ...@@ -11,6 +11,7 @@ module Gitlab
build_access_token_check(login, password) || build_access_token_check(login, password) ||
user_with_password_for_git(login, password) || user_with_password_for_git(login, password) ||
oauth_access_token_check(login, password) || oauth_access_token_check(login, password) ||
lfs_token_check(login, password) ||
personal_access_token_check(login, password) || personal_access_token_check(login, password) ||
Gitlab::Auth::Result.new Gitlab::Auth::Result.new
...@@ -102,6 +103,30 @@ module Gitlab ...@@ -102,6 +103,30 @@ module Gitlab
end end
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) def build_access_token_check(login, password)
return unless login == 'gitlab-ci-token' return unless login == 'gitlab-ci-token'
return unless password return unless password
......
module Gitlab module Gitlab
module Auth module Auth
Result = Struct.new(:actor, :project, :type, :authentication_abilities) do Result = Struct.new(:actor, :project, :type, :authentication_abilities) do
def ci? def ci?(for_project)
type == :ci type == :ci &&
project &&
project == for_project
end
def lfs_deploy_token?(for_project)
type == :lfs_deploy_token &&
actor &&
actor.projects.include?(for_project)
end end
def success? def success?
......
...@@ -6,7 +6,12 @@ module Gitlab ...@@ -6,7 +6,12 @@ module Gitlab
KeyAdder = Struct.new(:io) do KeyAdder = Struct.new(:io) do
def add_key(id, key) 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}") io.puts("#{id}\t#{key}")
end end
end end
...@@ -16,6 +21,10 @@ module Gitlab ...@@ -16,6 +21,10 @@ module Gitlab
@version_required ||= File.read(Rails.root. @version_required ||= File.read(Rails.root.
join('GITLAB_SHELL_VERSION')).strip join('GITLAB_SHELL_VERSION')).strip
end end
def strip_key(key)
key.split(/ /)[0, 2].join(' ')
end
end end
# Init new repository # Init new repository
...@@ -107,7 +116,7 @@ module Gitlab ...@@ -107,7 +116,7 @@ module Gitlab
# #
def add_key(key_id, key_content) def add_key(key_id, key_content)
Gitlab::Utils.system_silent([gitlab_shell_keys_path, 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 end
# Batch-add keys to authorized_keys # Batch-add keys to authorized_keys
......
...@@ -70,7 +70,7 @@ module Gitlab ...@@ -70,7 +70,7 @@ module Gitlab
private private
def user_options(field, value, limit) def user_options(field, value, limit)
options = { attributes: %W(#{config.uid} cn mail dn) } options = { attributes: user_attributes }
options[:size] = limit if limit options[:size] = limit if limit
if field.to_sym == :dn if field.to_sym == :dn
...@@ -98,6 +98,10 @@ module Gitlab ...@@ -98,6 +98,10 @@ module Gitlab
filter filter
end end
end end
def user_attributes
%W(#{config.uid} cn mail dn)
end
end end
end end
end end
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
...@@ -44,7 +44,7 @@ module Gitlab ...@@ -44,7 +44,7 @@ module Gitlab
end end
def file_name_regex_message def file_name_regex_message
"can contain only letters, digits, '_', '-', '@' and '.'. " "can contain only letters, digits, '_', '-', '@' and '.'."
end end
def file_path_regex def file_path_regex
...@@ -52,7 +52,7 @@ module Gitlab ...@@ -52,7 +52,7 @@ module Gitlab
end end
def file_path_regex_message 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 end
def directory_traversal_regex def directory_traversal_regex
...@@ -60,7 +60,7 @@ module Gitlab ...@@ -60,7 +60,7 @@ module Gitlab
end end
def directory_traversal_regex_message def directory_traversal_regex_message
"cannot include directory traversal. " "cannot include directory traversal."
end end
def archive_formats_regex def archive_formats_regex
......
...@@ -10,6 +10,15 @@ then ...@@ -10,6 +10,15 @@ then
exit 1 exit 1
fi 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" echo "✔ Linting passed"
exit 0 exit 0
FactoryGirl.define do FactoryGirl.define do
factory :event do factory :event do
factory :closed_issue_event do
project project
author factory: :user
factory :closed_issue_event do
action { Event::CLOSED } action { Event::CLOSED }
target factory: :closed_issue target factory: :closed_issue
author factory: :user
end end
end end
end end
...@@ -101,7 +101,7 @@ describe 'Filter issues', feature: true do ...@@ -101,7 +101,7 @@ describe 'Filter issues', feature: true do
expect(find('.js-label-select .dropdown-toggle-text')).to have_content('No Label') expect(find('.js-label-select .dropdown-toggle-text')).to have_content('No Label')
end end
it 'filters by no label' do it 'filters by a label' do
find('.dropdown-menu-labels a', text: label.title).click find('.dropdown-menu-labels a', text: label.title).click
page.within '.labels-filter' do page.within '.labels-filter' do
expect(page).to have_content label.title expect(page).to have_content label.title
...@@ -109,7 +109,7 @@ describe 'Filter issues', feature: true do ...@@ -109,7 +109,7 @@ describe 'Filter issues', feature: true do
expect(find('.js-label-select .dropdown-toggle-text')).to have_content(label.title) expect(find('.js-label-select .dropdown-toggle-text')).to have_content(label.title)
end 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 find('.dropdown-menu-labels a', text: label.title).click
page.within '.labels-filter' do page.within '.labels-filter' do
expect(page).to have_content wontfix.title expect(page).to have_content wontfix.title
...@@ -117,6 +117,33 @@ describe 'Filter issues', feature: true do ...@@ -117,6 +117,33 @@ describe 'Filter issues', feature: true do
end end
expect(find('.js-label-select .dropdown-toggle-text')).to have_content(wontfix.title) expect(find('.js-label-select .dropdown-toggle-text')).to have_content(wontfix.title)
end 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 end
describe 'Filter issues for assignee and label from issues#index' do describe 'Filter issues for assignee and label from issues#index' do
......
...@@ -33,7 +33,11 @@ feature 'Download buttons in branches page', feature: true do ...@@ -33,7 +33,11 @@ feature 'Download buttons in branches page', feature: true do
end end
scenario 'shows download artifacts button' do 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 end
end end
......
...@@ -34,7 +34,11 @@ feature 'Download buttons in files tree', feature: true do ...@@ -34,7 +34,11 @@ feature 'Download buttons in files tree', feature: true do
end end
scenario 'shows download artifacts button' do 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 end
end end
......
...@@ -33,7 +33,11 @@ feature 'Download buttons in project main page', feature: true do ...@@ -33,7 +33,11 @@ feature 'Download buttons in project main page', feature: true do
end end
scenario 'shows download artifacts button' do 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 end
end end
......
...@@ -34,7 +34,11 @@ feature 'Download buttons in tags page', feature: true do ...@@ -34,7 +34,11 @@ feature 'Download buttons in tags page', feature: true do
end end
scenario 'shows download artifacts button' do 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 end
end end
......
...@@ -61,6 +61,24 @@ describe Gitlab::Auth, lib: true do ...@@ -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)) 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 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 it 'recognizes OAuth tokens' do
user = create(:user) user = create(:user)
application = Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user) application = Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user)
......
require 'spec_helper' require 'spec_helper'
require 'stringio'
describe Gitlab::Shell, lib: true do describe Gitlab::Shell, lib: true do
let(:project) { double('Project', id: 7, path: 'diaspora') } let(:project) { double('Project', id: 7, path: 'diaspora') }
...@@ -44,15 +45,38 @@ describe Gitlab::Shell, lib: true do ...@@ -44,15 +45,38 @@ describe Gitlab::Shell, lib: true do
end end
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 Gitlab::Shell::KeyAdder, lib: true do
describe '#add_key' do describe '#add_key' do
it 'normalizes space characters in the key' do it 'removes trailing garbage' do
io = spy io = spy(:io)
adder = described_class.new(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 end
end end
......
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
...@@ -308,20 +308,23 @@ describe Project, models: true do ...@@ -308,20 +308,23 @@ describe Project, models: true do
end end
describe 'last_activity methods' do describe 'last_activity methods' do
let(:project) { create(:project) } let(:timestamp) { Time.now - 2.hours }
let(:last_event) { double(created_at: Time.now) } let(:project) { create(:project, created_at: timestamp, updated_at: timestamp) }
describe 'last_activity' do describe 'last_activity' do
it 'alias last_activity to last_event' 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) expect(project.last_activity).to eq(last_event)
end end
end end
describe 'last_activity_date' do describe 'last_activity_date' do
it 'returns the creation date of the project\'s last event if present' do it 'returns the creation date of the project\'s last event if present' do
create(:event, project: project) expect_any_instance_of(Event).to receive(:try_obtain_lease).and_return(true)
expect(project.last_activity_at.to_i).to eq(last_event.created_at.to_i) 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 end
it 'returns the project\'s last update date if it has no events' do it 'returns the project\'s last update date if it has no events' do
......
...@@ -100,6 +100,43 @@ describe API::API, api: true do ...@@ -100,6 +100,43 @@ describe API::API, api: true do
end end
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 describe "GET /internal/discover" do
it do it do
get(api("/internal/discover"), key_id: key.id, secret_token: secret_token) get(api("/internal/discover"), key_id: key.id, secret_token: secret_token)
...@@ -389,4 +426,13 @@ describe API::API, api: true do ...@@ -389,4 +426,13 @@ describe API::API, api: true do
protocol: 'ssh' protocol: 'ssh'
) )
end 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 end
...@@ -30,18 +30,27 @@ describe API::Members, api: true do ...@@ -30,18 +30,27 @@ describe API::Members, api: true do
let(:route) { get api("/#{source_type.pluralize}/#{source.id}/members", stranger) } let(:route) { get api("/#{source_type.pluralize}/#{source.id}/members", stranger) }
end end
context 'when authenticated as a non-member' do %i[master developer access_requester stranger].each do |type|
%i[access_requester stranger].each do |type| context "when authenticated as a #{type}" do
context "as a #{type}" do
it 'returns 200' do it 'returns 200' do
user = public_send(type) user = public_send(type)
get api("/#{source_type.pluralize}/#{source.id}/members", user) get api("/#{source_type.pluralize}/#{source.id}/members", user)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response.size).to eq(2) expect(json_response.size).to eq(2)
expect(json_response.map { |u| u['id'] }).to match_array [master.id, developer.id]
end end
end 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 end
it 'finds members with query string' do it 'finds members with query string' do
......
...@@ -245,6 +245,18 @@ describe 'Git LFS API and storage' do ...@@ -245,6 +245,18 @@ describe 'Git LFS API and storage' do
end end
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 context 'when build is authorized as' do
let(:authorization) { authorize_ci_project } let(:authorization) { authorize_ci_project }
...@@ -1097,6 +1109,10 @@ describe 'Git LFS API and storage' do ...@@ -1097,6 +1109,10 @@ describe 'Git LFS API and storage' do
ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password) ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password)
end 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) def fork_project(project, user, object = nil)
allow(RepositoryForkWorker).to receive(:perform_async).and_return(true) allow(RepositoryForkWorker).to receive(:perform_async).and_return(true)
Projects::ForkService.new(project, user, {}).execute Projects::ForkService.new(project, user, {}).execute
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment