Commit 83b643a0 authored by Kamil Trzcinski's avatar Kamil Trzcinski

Merge remote-tracking branch 'origin/lfs-support-for-ssh' into per-build-token

# Conflicts:
#	app/controllers/projects/git_http_client_controller.rb
#	app/helpers/lfs_helper.rb
#	lib/gitlab/auth.rb
#	spec/requests/lfs_http_spec.rb
parents eed5c58d be09bcf0
...@@ -37,6 +37,7 @@ v 8.12.0 (unreleased) ...@@ -37,6 +37,7 @@ v 8.12.0 (unreleased)
- Request only the LDAP attributes we need !6187 - Request only the LDAP attributes we need !6187
- Center build stage columns in pipeline overview (ClemMakesApps) - Center build stage columns in pipeline overview (ClemMakesApps)
- Rename behaviour to behavior in bug issue template for consistency (ClemMakesApps) - Rename behaviour to behavior in bug issue template for consistency (ClemMakesApps)
- Fix bug stopping issue description being scrollable after selecting issue template
- Remove suggested colors hover underline (ClemMakesApps) - Remove suggested colors hover underline (ClemMakesApps)
- Shorten task status phrase (ClemMakesApps) - Shorten task status phrase (ClemMakesApps)
- Fix project visibility level fields on settings - Fix project visibility level fields on settings
...@@ -92,6 +93,7 @@ v 8.12.0 (unreleased) ...@@ -92,6 +93,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)
......
...@@ -13,6 +13,9 @@ ...@@ -13,6 +13,9 @@
this.buildDropdown(); this.buildDropdown();
this.bindEvents(); this.bindEvents();
this.onFilenameUpdate(); this.onFilenameUpdate();
this.autosizeUpdateEvent = document.createEvent('Event');
this.autosizeUpdateEvent.initEvent('autosize:update', true, false);
} }
TemplateSelector.prototype.buildDropdown = function() { TemplateSelector.prototype.buildDropdown = function() {
...@@ -72,6 +75,10 @@ ...@@ -72,6 +75,10 @@
TemplateSelector.prototype.requestFileSuccess = function(file, skipFocus) { TemplateSelector.prototype.requestFileSuccess = function(file, skipFocus) {
this.editor.setValue(file.content, 1); this.editor.setValue(file.content, 1);
if (!skipFocus) this.editor.focus(); if (!skipFocus) this.editor.focus();
if (this.editor instanceof jQuery) {
this.editor.get(0).dispatchEvent(this.autosizeUpdateEvent);
}
}; };
TemplateSelector.prototype.startLoadingSpinner = function() { TemplateSelector.prototype.startLoadingSpinner = function() {
......
...@@ -318,9 +318,17 @@ ...@@ -318,9 +318,17 @@
.build-content { .build-content {
width: 130px; width: 130px;
.ci-status-text {
width: 110px;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
vertical-align: middle;
display: inline-block;
position: relative;
top: -1px;
}
a { a {
color: $layout-link-gray; color: $layout-link-gray;
...@@ -331,13 +339,74 @@ ...@@ -331,13 +339,74 @@
text-decoration: underline; text-decoration: underline;
} }
} }
}
.dropdown-menu-toggle {
border: none;
width: auto;
padding: 0;
color: $layout-link-gray;
.ci-status-text {
width: 80px;
}
}
.grouped-pipeline-dropdown {
padding: 8px 0;
width: 200px;
left: auto;
right: -214px;
top: -9px;
a:hover {
.ci-status-text {
text-decoration: none;
}
}
.ci-status-text {
width: 145px;
}
.arrow {
&:before,
&:after {
content: '';
display: inline-block;
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
top: 18px;
}
&:before {
left: -5px;
margin-top: -6px;
border-width: 7px 5px 7px 0;
border-right-color: $border-color;
}
&:after {
left: -4px;
margin-top: -9px;
border-width: 10px 7px 10px 0;
border-right-color: $white-light;
}
}
}
.badge {
background-color: $gray-dark;
color: $layout-link-gray;
font-weight: normal;
} }
} }
svg { svg {
position: relative; vertical-align: middle;
top: 2px;
margin-right: 5px; margin-right: 5px;
} }
...@@ -442,7 +511,7 @@ ...@@ -442,7 +511,7 @@
width: 21px; width: 21px;
height: 25px; height: 25px;
position: absolute; position: absolute;
top: -28.5px; top: -29px;
border-top: 2px solid $border-color; border-top: 2px solid $border-color;
} }
......
...@@ -334,6 +334,10 @@ a.deploy-project-label { ...@@ -334,6 +334,10 @@ a.deploy-project-label {
a { a {
color: $gl-dark-link-color; color: $gl-dark-link-color;
} }
.dropdown-menu {
width: 240px;
}
} }
.last-push-widget { .last-push-widget {
......
...@@ -4,7 +4,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController ...@@ -4,7 +4,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController
include ActionController::HttpAuthentication::Basic include ActionController::HttpAuthentication::Basic
include KerberosSpnegoHelper include KerberosSpnegoHelper
attr_reader :user, :capabilities attr_reader :actor, :capabilities
# Git clients will not know what authenticity token to send along # Git clients will not know what authenticity token to send along
skip_before_action :verify_authenticity_token skip_before_action :verify_authenticity_token
...@@ -21,31 +21,14 @@ class Projects::GitHttpClientController < Projects::ApplicationController ...@@ -21,31 +21,14 @@ class Projects::GitHttpClientController < Projects::ApplicationController
if allow_basic_auth? && basic_auth_provided? if allow_basic_auth? && basic_auth_provided?
login, password = user_name_and_password(request) login, password = user_name_and_password(request)
auth_result = Gitlab::Auth.find_for_git_client(login, password, project: project, ip: request.ip)
if auth_result.type == :ci && !download_request?
# Not allowed
auth_result = Gitlab::Auth::Result.new
elsif auth_result.type == :oauth && !download_request?
# Not allowed
auth_result = Gitlab::Auth::Result.new
elsif auth_result.type == :missing_personal_token
render_missing_personal_token
return # Render above denied access, nothing left to do
else
@user = auth_result.user
end
@capabilities = auth_result.capabilities || [] if handle_basic_authentication(login, password)
@ci = auth_result.type == :ci
if auth_result.succeeded?
return # Allow access return # Allow access
end end
elsif allow_kerberos_spnego_auth? && spnego_provided? elsif allow_kerberos_spnego_auth? && spnego_provided?
@user = find_kerberos_user @actor = find_kerberos_user
if user if actor
send_final_spnego_response send_final_spnego_response
return # Allow access return # Allow access
end end
...@@ -53,6 +36,8 @@ class Projects::GitHttpClientController < Projects::ApplicationController ...@@ -53,6 +36,8 @@ class Projects::GitHttpClientController < Projects::ApplicationController
send_challenges send_challenges
render plain: "HTTP Basic: Access denied\n", status: 401 render plain: "HTTP Basic: Access denied\n", status: 401
rescue Gitlab::Auth::MissingPersonalTokenError
render_missing_personal_token
end end
def basic_auth_provided? def basic_auth_provided?
...@@ -120,7 +105,49 @@ class Projects::GitHttpClientController < Projects::ApplicationController ...@@ -120,7 +105,49 @@ class Projects::GitHttpClientController < Projects::ApplicationController
end end
def ci? def ci?
@ci.present? @ci
end
def user
@actor
end
def handle_basic_authentication(login, password)
auth_result = Gitlab::Auth.find_for_git_client(login, password, project: project, ip: request.ip)
case auth_result.type
when :ci
if download_request?
@ci = true
else
return false
end
when :oauth
if download_request?
@actor = auth_result.actor
@capabilities = auth_result.capabilities
else
return false
end
when :lfs_deploy_token
if download_request?
@lfs_deploy_key = true
@actor = auth_result.actor
@capabilities = auth_result.capabilities
end
when :lfs_token, :personal_token, :gitlab_or_ldap, :build
@actor = auth_result.actor
@capabilities = auth_result.capabilities
else
# Not allowed
return false
end
true
end
def lfs_deploy_key?
@lfs_deploy_key && actor && actor.projects.include?(project)
end end
def has_capability?(capability) def has_capability?(capability)
......
...@@ -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_key? || user_can_download_code? || build_can_download_code?
end end
def user_can_download_code? def user_can_download_code?
......
...@@ -69,17 +69,15 @@ class CommitStatus < ActiveRecord::Base ...@@ -69,17 +69,15 @@ class CommitStatus < ActiveRecord::Base
commit_status.update_attributes finished_at: Time.now commit_status.update_attributes finished_at: Time.now
end end
# We use around_transition to process pipeline on next stages as soon as possible, before the `after_*` is executed
around_transition any => [:success, :failed, :canceled] do |commit_status, block|
block.call
commit_status.pipeline.try(:process!)
end
after_transition do |commit_status, transition| after_transition do |commit_status, transition|
commit_status.pipeline.try(:build_updated) unless transition.loopback? commit_status.pipeline.try(:build_updated) unless transition.loopback?
end end
after_transition any => [:success, :failed, :canceled] do |commit_status|
commit_status.pipeline.try(:process!)
true
end
after_transition [:created, :pending, :running] => :success do |commit_status| after_transition [:created, :pending, :running] => :success do |commit_status|
MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.pipeline.project, nil).trigger(commit_status) MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.pipeline.project, nil).trigger(commit_status)
end end
...@@ -95,6 +93,10 @@ class CommitStatus < ActiveRecord::Base ...@@ -95,6 +93,10 @@ class CommitStatus < ActiveRecord::Base
pipeline.before_sha || Gitlab::Git::BLANK_SHA pipeline.before_sha || Gitlab::Git::BLANK_SHA
end end
def group_name
name.gsub(/\d+[\s:\/\\]+\d+\s*/, '').strip
end
def self.stages def self.stages
# We group by stage name, but order stages by theirs' index # We group by stage name, but order stages by theirs' index
unscoped.from(all, :sg).group('stage').order('max(stage_idx)', 'stage').pluck('sg.stage') unscoped.from(all, :sg).group('stage').order('max(stage_idx)', 'stage').pluck('sg.stage')
...@@ -113,6 +115,10 @@ class CommitStatus < ActiveRecord::Base ...@@ -113,6 +115,10 @@ class CommitStatus < ActiveRecord::Base
allow_failure? && (failed? || canceled?) allow_failure? && (failed? || canceled?)
end end
def playable?
false
end
def duration def duration
calculate_duration calculate_duration
end end
......
...@@ -8,8 +8,9 @@ module HasStatus ...@@ -8,8 +8,9 @@ module HasStatus
class_methods do class_methods do
def status_sql def status_sql
scope = all.relevant scope = all
builds = scope.select('count(*)').to_sql builds = scope.select('count(*)').to_sql
created = scope.created.select('count(*)').to_sql
success = scope.success.select('count(*)').to_sql success = scope.success.select('count(*)').to_sql
ignored = scope.ignored.select('count(*)').to_sql if scope.respond_to?(:ignored) ignored = scope.ignored.select('count(*)').to_sql if scope.respond_to?(:ignored)
ignored ||= '0' ignored ||= '0'
...@@ -19,12 +20,12 @@ module HasStatus ...@@ -19,12 +20,12 @@ module HasStatus
skipped = scope.skipped.select('count(*)').to_sql skipped = scope.skipped.select('count(*)').to_sql
deduce_status = "(CASE deduce_status = "(CASE
WHEN (#{builds})=0 THEN NULL WHEN (#{builds})=(#{created}) THEN NULL
WHEN (#{builds})=(#{skipped}) THEN 'skipped' WHEN (#{builds})=(#{skipped}) THEN 'skipped'
WHEN (#{builds})=(#{success})+(#{ignored})+(#{skipped}) THEN 'success' WHEN (#{builds})=(#{success})+(#{ignored})+(#{skipped}) THEN 'success'
WHEN (#{builds})=(#{pending})+(#{skipped}) THEN 'pending' WHEN (#{builds})=(#{created})+(#{pending})+(#{skipped}) THEN 'pending'
WHEN (#{builds})=(#{canceled})+(#{success})+(#{ignored})+(#{skipped}) THEN 'canceled' WHEN (#{builds})=(#{canceled})+(#{success})+(#{ignored})+(#{skipped}) THEN 'canceled'
WHEN (#{running})+(#{pending})>0 THEN 'running' WHEN (#{running})+(#{pending})+(#{created})>0 THEN 'running'
ELSE 'failed' ELSE 'failed'
END)" END)"
......
- is_playable = subject.playable? && can?(current_user, :update_build, @project) - is_playable = subject.playable? && can?(current_user, :update_build, @project)
%li.build{class: ("playable" if is_playable)} - if is_playable
.curve
.build-content
- if is_playable
= link_to play_namespace_project_build_path(subject.project.namespace, subject.project, subject, return_to: request.original_url), method: :post, title: 'Play' do = link_to play_namespace_project_build_path(subject.project.namespace, subject.project, subject, return_to: request.original_url), method: :post, title: 'Play' do
= render_status_with_link('build', 'play') = render_status_with_link('build', 'play')
%span.ci-status-text= subject.name .ci-status-text= subject.name
- elsif can?(current_user, :read_build, @project) - elsif can?(current_user, :read_build, @project)
= link_to namespace_project_build_path(subject.project.namespace, subject.project, subject) do = link_to namespace_project_build_path(subject.project.namespace, subject.project, subject) do
= render_status_with_link('build', subject.status) = render_status_with_link('build', subject.status)
%span.ci-status-text= subject.name .ci-status-text= subject.name
- else - else
= render_status_with_link('build', subject.status) = render_status_with_link('build', subject.status)
= ci_icon_for_status(subject.status) = ci_icon_for_status(subject.status)
...@@ -39,8 +39,7 @@ ...@@ -39,8 +39,7 @@
= stage.titleize = stage.titleize
.builds-container .builds-container
%ul %ul
- statuses.each do |status| = render "projects/commit/pipeline_stage", statuses: statuses
= render "projects/#{status.to_partial_path}_pipeline", subject: status
- if pipeline.yaml_errors.present? - if pipeline.yaml_errors.present?
......
- status_groups = statuses.group_by(&:group_name)
- status_groups.each do |group_name, grouped_statuses|
- if grouped_statuses.one?
- status = grouped_statuses.first
- is_playable = status.playable? && can?(current_user, :update_build, @project)
%li.build{ class: ("playable" if is_playable) }
.curve
.build-content
= render "projects/#{status.to_partial_path}_pipeline", subject: status
- else
%li.build
.curve
.build-content
= render "projects/commit/pipeline_status_group", name: group_name, subject: grouped_statuses
- group_status = CommitStatus.where(id: subject).status
= render_status_with_link('build', group_status)
.dropdown.inline
%button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } }
%span.ci-status-text
= name
%span.badge= subject.size
%ul.dropdown-menu.grouped-pipeline-dropdown
.arrow
- subject.each do |status|
= render "projects/#{status.to_partial_path}_pipeline", subject: status
%li.build - if subject.target_url
.curve = link_to subject.target_url do
.build-content
- if subject.target_url
- link_to subject.target_url do
= render_status_with_link('commit status', subject.status) = render_status_with_link('commit status', subject.status)
%span.ci-status-text= subject.name %span.ci-status-text= subject.name
- else - else
= render_status_with_link('commit status', subject.status) = render_status_with_link('commit status', subject.status)
%span.ci-status-text= subject.name %span.ci-status-text= subject.name
...@@ -3,9 +3,13 @@ class Gitlab::Seeder::Pipelines ...@@ -3,9 +3,13 @@ class Gitlab::Seeder::Pipelines
BUILDS = [ BUILDS = [
{ name: 'build:linux', stage: 'build', status: :success }, { name: 'build:linux', stage: 'build', status: :success },
{ name: 'build:osx', stage: 'build', status: :success }, { name: 'build:osx', stage: 'build', status: :success },
{ name: 'rspec:linux', stage: 'test', status: :success }, { name: 'rspec:linux 0 3', stage: 'test', status: :success },
{ name: 'rspec:windows', stage: 'test', status: :success }, { name: 'rspec:linux 1 3', stage: 'test', status: :success },
{ name: 'rspec:windows', stage: 'test', status: :success }, { name: 'rspec:linux 2 3', stage: 'test', status: :success },
{ name: 'rspec:windows 0 3', stage: 'test', status: :success },
{ name: 'rspec:windows 1 3', stage: 'test', status: :success },
{ name: 'rspec:windows 2 3', stage: 'test', status: :success },
{ name: 'rspec:windows 2 3', stage: 'test', status: :success },
{ name: 'rspec:osx', stage: 'test', status_event: :success }, { name: 'rspec:osx', stage: 'test', status_event: :success },
{ name: 'spinach:linux', stage: 'test', status: :success }, { name: 'spinach:linux', stage: 'test', status: :success },
{ name: 'spinach:osx', stage: 'test', status: :failed, allow_failure: true}, { name: 'spinach:osx', stage: 'test', status: :failed, allow_failure: true},
......
# rubocop:disable all # rubocop:disable all
class MigrateRepoSize < ActiveRecord::Migration class MigrateRepoSize < ActiveRecord::Migration
DOWNTIME = false
def up def up
project_data = execute('SELECT projects.id, namespaces.path AS namespace_path, projects.path AS project_path FROM projects LEFT JOIN namespaces ON projects.namespace_id = namespaces.id') project_data = execute('SELECT projects.id, namespaces.path AS namespace_path, projects.path AS project_path FROM projects LEFT JOIN namespaces ON projects.namespace_id = namespaces.id')
project_data.each do |project| project_data.each do |project|
id = project['id'] id = project['id']
namespace_path = project['namespace_path'] || '' namespace_path = project['namespace_path'] || ''
path = File.join(Gitlab.config.gitlab_shell.repos_path, namespace_path, project['project_path'] + '.git') repos_path = Gitlab.config.gitlab_shell['repos_path'] || Gitlab.config.repositories.storages.default
path = File.join(repos_path, namespace_path, project['project_path'] + '.git')
begin begin
repo = Gitlab::Git::Repository.new(path) repo = Gitlab::Git::Repository.new(path)
......
...@@ -105,7 +105,8 @@ What is important is that each job is run independently from each other. ...@@ -105,7 +105,8 @@ What is important is that each job is run independently from each other.
If you want to check whether your `.gitlab-ci.yml` file is valid, there is a If you want to check whether your `.gitlab-ci.yml` file is valid, there is a
Lint tool under the page `/ci/lint` of your GitLab instance. You can also find Lint tool under the page `/ci/lint` of your GitLab instance. You can also find
the link under **Settings > CI settings** in your project. a "CI Lint" button to go to this page under **Pipelines > Pipelines** and
**Pipelines > Builds** in your project.
For more information and a complete `.gitlab-ci.yml` syntax, please read For more information and a complete `.gitlab-ci.yml` syntax, please read
[the documentation on .gitlab-ci.yml](../yaml/README.md). [the documentation on .gitlab-ci.yml](../yaml/README.md).
......
...@@ -63,7 +63,7 @@ The following table depicts the various user permission levels in a project. ...@@ -63,7 +63,7 @@ The following table depicts the various user permission levels in a project.
| Force push to protected branches [^2] | | | | | | | Force push to protected branches [^2] | | | | | |
| Remove protected branches [^2] | | | | | | | Remove protected branches [^2] | | | | | |
[^1]: If **Allow guest to access builds** is enabled in CI settings [^1]: If **Public pipelines** is enabled in **Project Settings > CI/CD Pipelines**
[^2]: Not allowed for Guest, Reporter, Developer, Master, or Owner [^2]: Not allowed for Guest, Reporter, Developer, Master, or Owner
## Group ## Group
......
...@@ -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.
......
...@@ -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
......
module Gitlab module Gitlab
module Auth module Auth
Result = Struct.new(:user, :project, :type, :capabilities) do Result = Struct.new(:actor, :project, :type, :capabilities) do
def succeeded? def success?
user.present? || [:ci].include?(type) actor.present? || type == :ci
end end
end end
class MissingPersonalTokenError < StandardError; end
class << self class << self
def find_for_git_client(login, password, project:, ip:) def find_for_git_client(login, password, project:, ip:)
raise "Must provide an IP for rate limiting" if ip.nil? raise "Must provide an IP for rate limiting" if ip.nil?
result = service_access_token_check(login, password, project) || result =
service_request_check(login, password, project) ||
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) ||
Result.new Result.new
rate_limit!(ip, success: result.succeeded?, login: login) rate_limit!(ip, success: result.success?, login: login)
result result
end end
...@@ -59,7 +64,7 @@ module Gitlab ...@@ -59,7 +64,7 @@ module Gitlab
private private
def service_access_token_check(login, password, project) def service_request_check(login, password, project)
matched_login = /(?<service>^[a-zA-Z]*-ci)-token$/.match(login) matched_login = /(?<service>^[a-zA-Z]*-ci)-token$/.match(login)
return unless project && matched_login.present? return unless project && matched_login.present?
...@@ -81,14 +86,9 @@ module Gitlab ...@@ -81,14 +86,9 @@ module Gitlab
user = find_with_user_password(login, password) user = find_with_user_password(login, password)
return unless user return unless user
type = raise Gitlab::Auth::MissingPersonalTokenError if user.two_factor_enabled?
if user.two_factor_enabled?
:missing_personal_token
else
:gitlab_or_ldap
end
Result.new(user, nil, type, full_capabilities) Result.new(user, nil, :gitlab_or_ldap, full_capabilities)
end end
def oauth_access_token_check(login, password) def oauth_access_token_check(login, password)
...@@ -105,9 +105,24 @@ module Gitlab ...@@ -105,9 +105,24 @@ module Gitlab
if login && password if login && password
user = User.find_by_personal_access_token(password) user = User.find_by_personal_access_token(password)
validation = User.by_login(login) validation = User.by_login(login)
if user && user == validation Result.new(user, nil, :personal_token, full_capabilities) if user.present? && user == validation
Result.new(user, nil, :personal_token, full_capabilities) 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
if actor
token_handler = Gitlab::LfsToken.new(actor)
Result.new(actor, nil, token_handler.type, read_capabilities) if Devise.secure_compare(token_handler.value, password)
end end
end end
......
...@@ -22,10 +22,6 @@ module Gitlab ...@@ -22,10 +22,6 @@ module Gitlab
private private
def repos_path
Gitlab.config.gitlab_shell.repos_path
end
def path_to_repo def path_to_repo
@project.repository.path_to_repo @project.repository.path_to_repo
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 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
...@@ -13,10 +13,12 @@ feature 'issuable templates', feature: true, js: true do ...@@ -13,10 +13,12 @@ feature 'issuable templates', feature: true, js: true do
context 'user creates an issue using templates' do context 'user creates an issue using templates' do
let(:template_content) { 'this is a test "bug" template' } let(:template_content) { 'this is a test "bug" template' }
let(:longtemplate_content) { %Q(this\n\n\n\n\nis\n\n\n\n\na\n\n\n\n\nbug\n\n\n\n\ntemplate) }
let(:issue) { create(:issue, author: user, assignee: user, project: project) } let(:issue) { create(:issue, author: user, assignee: user, project: project) }
background do background do
project.repository.commit_file(user, '.gitlab/issue_templates/bug.md', template_content, 'added issue template', 'master', false) project.repository.commit_file(user, '.gitlab/issue_templates/bug.md', template_content, 'added issue template', 'master', false)
project.repository.commit_file(user, '.gitlab/issue_templates/test.md', longtemplate_content, 'added issue template', 'master', false)
visit edit_namespace_project_issue_path project.namespace, project, issue visit edit_namespace_project_issue_path project.namespace, project, issue
fill_in :'issue[title]', with: 'test issue title' fill_in :'issue[title]', with: 'test issue title'
end end
...@@ -27,6 +29,17 @@ feature 'issuable templates', feature: true, js: true do ...@@ -27,6 +29,17 @@ feature 'issuable templates', feature: true, js: true do
preview_template preview_template
save_changes save_changes
end end
it 'updates height of markdown textarea' do
start_height = page.evaluate_script('$(".markdown-area").outerHeight()')
select_template 'test'
wait_for_ajax
end_height = page.evaluate_script('$(".markdown-area").outerHeight()')
expect(end_height).not_to eq(start_height)
end
end end
context 'user creates a merge request using templates' do context 'user creates a merge request using templates' do
......
...@@ -29,8 +29,11 @@ describe 'Dashboard > User filters todos', feature: true, js: true do ...@@ -29,8 +29,11 @@ describe 'Dashboard > User filters todos', feature: true, js: true do
fill_in 'Search projects', with: project_1.name_with_namespace fill_in 'Search projects', with: project_1.name_with_namespace
click_link project_1.name_with_namespace click_link project_1.name_with_namespace
end end
wait_for_ajax wait_for_ajax
expect('.prepend-top-default').not_to have_content project_2.name_with_namespace
expect(page).to have_content project_1.name_with_namespace
expect(page).not_to have_content project_2.name_with_namespace
end end
it 'filters by author' do it 'filters by author' do
...@@ -39,8 +42,11 @@ describe 'Dashboard > User filters todos', feature: true, js: true do ...@@ -39,8 +42,11 @@ describe 'Dashboard > User filters todos', feature: true, js: true do
fill_in 'Search authors', with: user_1.name fill_in 'Search authors', with: user_1.name
click_link user_1.name click_link user_1.name
end end
wait_for_ajax wait_for_ajax
expect('.prepend-top-default').not_to have_content user_2.name
expect(find('.todos-list')).to have_content user_1.name
expect(find('.todos-list')).not_to have_content user_2.name
end end
it 'filters by type' do it 'filters by type' do
...@@ -48,8 +54,11 @@ describe 'Dashboard > User filters todos', feature: true, js: true do ...@@ -48,8 +54,11 @@ describe 'Dashboard > User filters todos', feature: true, js: true do
within '.dropdown-menu-type' do within '.dropdown-menu-type' do
click_link 'Issue' click_link 'Issue'
end end
wait_for_ajax wait_for_ajax
expect('.prepend-top-default').not_to have_content ' merge request !'
expect(find('.todos-list')).to have_content issue.to_reference
expect(find('.todos-list')).not_to have_content merge_request.to_reference
end end
it 'filters by action' do it 'filters by action' do
...@@ -57,7 +66,10 @@ describe 'Dashboard > User filters todos', feature: true, js: true do ...@@ -57,7 +66,10 @@ describe 'Dashboard > User filters todos', feature: true, js: true do
within '.dropdown-menu-action' do within '.dropdown-menu-action' do
click_link 'Assigned' click_link 'Assigned'
end end
wait_for_ajax wait_for_ajax
expect('.prepend-top-default').not_to have_content ' mentioned '
expect(find('.todos-list')).to have_content ' assigned you '
expect(find('.todos-list')).not_to have_content ' mentioned '
end end
end end
...@@ -59,6 +59,24 @@ describe Gitlab::Auth, lib: true do ...@@ -59,6 +59,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_capabilities)) 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_capabilities))
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, :lfs_token))
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, :lfs_deploy_token))
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)
...@@ -73,7 +91,7 @@ describe Gitlab::Auth, lib: true do ...@@ -73,7 +91,7 @@ describe Gitlab::Auth, lib: true do
login = 'foo' login = 'foo'
ip = 'ip' ip = 'ip'
expect(gl_auth).to receive(:rate_limit!).with(ip, success: false, login: login) expect(gl_auth).to receive(:rate_limit!).with(ip, success: nil, login: login)
expect(gl_auth.find_for_git_client(login, 'bar', project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new) expect(gl_auth.find_for_git_client(login, 'bar', project: nil, ip: ip)).to eq(Gitlab::Auth::Result.new)
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
...@@ -373,8 +373,8 @@ describe Ci::Pipeline, models: true do ...@@ -373,8 +373,8 @@ describe Ci::Pipeline, models: true do
end end
describe '#execute_hooks' do describe '#execute_hooks' do
let!(:build_a) { create_build('a') } let!(:build_a) { create_build('a', 0) }
let!(:build_b) { create_build('b') } let!(:build_b) { create_build('b', 1) }
let!(:hook) do let!(:hook) do
create(:project_hook, project: project, pipeline_events: enabled) create(:project_hook, project: project, pipeline_events: enabled)
...@@ -398,7 +398,7 @@ describe Ci::Pipeline, models: true do ...@@ -398,7 +398,7 @@ describe Ci::Pipeline, models: true do
build_b.enqueue build_b.enqueue
end end
it 'receive a pending event once' do it 'receives a pending event once' do
expect(WebMock).to have_requested_pipeline_hook('pending').once expect(WebMock).to have_requested_pipeline_hook('pending').once
end end
end end
...@@ -411,7 +411,7 @@ describe Ci::Pipeline, models: true do ...@@ -411,7 +411,7 @@ describe Ci::Pipeline, models: true do
build_b.run build_b.run
end end
it 'receive a running event once' do it 'receives a running event once' do
expect(WebMock).to have_requested_pipeline_hook('running').once expect(WebMock).to have_requested_pipeline_hook('running').once
end end
end end
...@@ -422,11 +422,21 @@ describe Ci::Pipeline, models: true do ...@@ -422,11 +422,21 @@ describe Ci::Pipeline, models: true do
build_b.success build_b.success
end end
it 'receive a success event once' do it 'receives a success event once' do
expect(WebMock).to have_requested_pipeline_hook('success').once expect(WebMock).to have_requested_pipeline_hook('success').once
end end
end end
context 'when stage one failed' do
before do
build_a.drop
end
it 'receives a failed event once' do
expect(WebMock).to have_requested_pipeline_hook('failed').once
end
end
def have_requested_pipeline_hook(status) def have_requested_pipeline_hook(status)
have_requested(:post, hook.url).with do |req| have_requested(:post, hook.url).with do |req|
json_body = JSON.parse(req.body) json_body = JSON.parse(req.body)
...@@ -450,8 +460,12 @@ describe Ci::Pipeline, models: true do ...@@ -450,8 +460,12 @@ describe Ci::Pipeline, models: true do
end end
end end
def create_build(name) def create_build(name, stage_idx)
create(:ci_build, :created, pipeline: pipeline, name: name) create(:ci_build,
:created,
pipeline: pipeline,
name: name,
stage_idx: stage_idx)
end end
end end
end end
...@@ -40,7 +40,7 @@ describe CommitStatus, models: true do ...@@ -40,7 +40,7 @@ describe CommitStatus, models: true do
it { is_expected.to be_falsey } it { is_expected.to be_falsey }
end end
%w(running success failed).each do |status| %w[running success failed].each do |status|
context "if commit status is #{status}" do context "if commit status is #{status}" do
before { commit_status.status = status } before { commit_status.status = status }
...@@ -48,7 +48,7 @@ describe CommitStatus, models: true do ...@@ -48,7 +48,7 @@ describe CommitStatus, models: true do
end end
end end
%w(pending canceled).each do |status| %w[pending canceled].each do |status|
context "if commit status is #{status}" do context "if commit status is #{status}" do
before { commit_status.status = status } before { commit_status.status = status }
...@@ -60,7 +60,7 @@ describe CommitStatus, models: true do ...@@ -60,7 +60,7 @@ describe CommitStatus, models: true do
describe '#active?' do describe '#active?' do
subject { commit_status.active? } subject { commit_status.active? }
%w(pending running).each do |state| %w[pending running].each do |state|
context "if commit_status.status is #{state}" do context "if commit_status.status is #{state}" do
before { commit_status.status = state } before { commit_status.status = state }
...@@ -68,7 +68,7 @@ describe CommitStatus, models: true do ...@@ -68,7 +68,7 @@ describe CommitStatus, models: true do
end end
end end
%w(success failed canceled).each do |state| %w[success failed canceled].each do |state|
context "if commit_status.status is #{state}" do context "if commit_status.status is #{state}" do
before { commit_status.status = state } before { commit_status.status = state }
...@@ -80,7 +80,7 @@ describe CommitStatus, models: true do ...@@ -80,7 +80,7 @@ describe CommitStatus, models: true do
describe '#complete?' do describe '#complete?' do
subject { commit_status.complete? } subject { commit_status.complete? }
%w(success failed canceled).each do |state| %w[success failed canceled].each do |state|
context "if commit_status.status is #{state}" do context "if commit_status.status is #{state}" do
before { commit_status.status = state } before { commit_status.status = state }
...@@ -88,7 +88,7 @@ describe CommitStatus, models: true do ...@@ -88,7 +88,7 @@ describe CommitStatus, models: true do
end end
end end
%w(pending running).each do |state| %w[pending running].each do |state|
context "if commit_status.status is #{state}" do context "if commit_status.status is #{state}" do
before { commit_status.status = state } before { commit_status.status = state }
...@@ -187,7 +187,7 @@ describe CommitStatus, models: true do ...@@ -187,7 +187,7 @@ describe CommitStatus, models: true do
subject { CommitStatus.where(pipeline: pipeline).stages } subject { CommitStatus.where(pipeline: pipeline).stages }
it 'returns ordered list of stages' do it 'returns ordered list of stages' do
is_expected.to eq(%w(build test deploy)) is_expected.to eq(%w[build test deploy])
end end
end end
...@@ -223,4 +223,33 @@ describe CommitStatus, models: true do ...@@ -223,4 +223,33 @@ describe CommitStatus, models: true do
expect(commit_status.commit).to eq project.commit expect(commit_status.commit).to eq project.commit
end end
end end
describe '#group_name' do
subject { commit_status.group_name }
tests = {
'rspec:windows' => 'rspec:windows',
'rspec:windows 0' => 'rspec:windows 0',
'rspec:windows 0 test' => 'rspec:windows 0 test',
'rspec:windows 0 1' => 'rspec:windows',
'rspec:windows 0 1 name' => 'rspec:windows name',
'rspec:windows 0/1' => 'rspec:windows',
'rspec:windows 0/1 name' => 'rspec:windows name',
'rspec:windows 0:1' => 'rspec:windows',
'rspec:windows 0:1 name' => 'rspec:windows name',
'rspec:windows 10000 20000' => 'rspec:windows',
'rspec:windows 0 : / 1' => 'rspec:windows',
'rspec:windows 0 : / 1 name' => 'rspec:windows name',
'0 1 name ruby' => 'name ruby',
'0 :/ 1 name ruby' => 'name ruby'
}
tests.each do |name, group_name|
it "'#{name}' puts in '#{group_name}'" do
commit_status.name = name
is_expected.to eq(group_name)
end
end
end
end end
...@@ -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
...@@ -246,6 +246,18 @@ describe 'Git LFS API and storage' do ...@@ -246,6 +246,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' do context 'when build is authorized' do
let(:authorization) { authorize_ci_project } let(:authorization) { authorize_ci_project }
...@@ -906,6 +918,10 @@ describe 'Git LFS API and storage' do ...@@ -906,6 +918,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
......
require 'spec_helper'
describe 'projects/pipelines/show' do
include Devise::TestHelpers
let(:project) { create(:project) }
let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.id) }
before do
controller.prepend_view_path('app/views/projects')
create_build('build', 0, 'build')
create_build('test', 1, 'rspec 0:2')
create_build('test', 1, 'rspec 1:2')
create_build('test', 1, 'audit')
create_build('deploy', 2, 'production')
create(:generic_commit_status, pipeline: pipeline, stage: 'external', name: 'jenkins', stage_idx: 3)
assign(:project, project)
assign(:pipeline, pipeline)
allow(view).to receive(:can?).and_return(true)
end
it 'shows a graph with grouped stages' do
render
expect(rendered).to have_css('.pipeline-graph')
expect(rendered).to have_css('.grouped-pipeline-dropdown')
# stages
expect(rendered).to have_text('Build')
expect(rendered).to have_text('Test')
expect(rendered).to have_text('Deploy')
expect(rendered).to have_text('External')
# builds
expect(rendered).to have_text('rspec')
expect(rendered).to have_text('rspec 0:2')
expect(rendered).to have_text('production')
expect(rendered).to have_text('jenkins')
end
private
def create_build(stage, stage_idx, name)
create(:ci_build, pipeline: pipeline, stage: stage, stage_idx: stage_idx, name: name)
end
end
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