Commit 57d4a172 authored by Rémy Coutable's avatar Rémy Coutable

Merge remote-tracking branch 'origin/master' into 8-10-stable

parents 7ce5bb18 240a4aa6
...@@ -31,6 +31,7 @@ v 8.10.0 (unreleased) ...@@ -31,6 +31,7 @@ v 8.10.0 (unreleased)
- Support U2F devices in Firefox. !5177 - Support U2F devices in Firefox. !5177
- Fix issue, preventing users w/o push access to sort tags !5105 (redetection) - Fix issue, preventing users w/o push access to sort tags !5105 (redetection)
- Add Spring EmojiOne updates. - Add Spring EmojiOne updates.
- Fix fetching LFS objects for private CI projects
- Add syntax for multiline blockquote using `>>>` fence !3954 - Add syntax for multiline blockquote using `>>>` fence !3954
- Fix viewing notification settings when a project is pending deletion - Fix viewing notification settings when a project is pending deletion
- Updated compare dropdown menus to use GL dropdown - Updated compare dropdown menus to use GL dropdown
...@@ -47,6 +48,7 @@ v 8.10.0 (unreleased) ...@@ -47,6 +48,7 @@ v 8.10.0 (unreleased)
- Render inline diffs for multiple changed lines following eachother - Render inline diffs for multiple changed lines following eachother
- Wildcards for protected branches. !4665 - Wildcards for protected branches. !4665
- Allow importing from Github using Personal Access Tokens. (Eric K Idema) - Allow importing from Github using Personal Access Tokens. (Eric K Idema)
- API: Expose `due_date` for issues (Robert Schilling)
- API: Todos !3188 (Robert Schilling) - API: Todos !3188 (Robert Schilling)
- API: Expose shared groups for projects and shared projects for groups !5050 (Robert Schilling) - API: Expose shared groups for projects and shared projects for groups !5050 (Robert Schilling)
- Add "Enabled Git access protocols" to Application Settings - Add "Enabled Git access protocols" to Application Settings
...@@ -55,13 +57,16 @@ v 8.10.0 (unreleased) ...@@ -55,13 +57,16 @@ v 8.10.0 (unreleased)
- Fix user creation with stronger minimum password requirements !4054 (nathan-pmt) - Fix user creation with stronger minimum password requirements !4054 (nathan-pmt)
- Only show New Snippet button to users that can create snippets. - Only show New Snippet button to users that can create snippets.
- PipelinesFinder uses git cache data - PipelinesFinder uses git cache data
- Track a user who created a pipeline
- Actually render old and new sections of parallel diff next to each other - Actually render old and new sections of parallel diff next to each other
- Throttle the update of `project.pushes_since_gc` to 1 minute. - Throttle the update of `project.pushes_since_gc` to 1 minute.
- Allow expanding and collapsing files in diff view (!4990) - Allow expanding and collapsing files in diff view (!4990)
- Collapse large diffs by default (!4990) - Collapse large diffs by default (!4990)
- Fix mentioned users list on diff notes - Fix mentioned users list on diff notes
- Fix creation of deployment on build that is retried, redeployed or rollback
- Check for conflicts with existing Project's wiki path when creating a new project. - Check for conflicts with existing Project's wiki path when creating a new project.
- Show last push widget in upstream after push to fork - Show last push widget in upstream after push to fork
- Fix stage status shown for pipelines
- Cache todos pending/done dashboard query counts. - Cache todos pending/done dashboard query counts.
- Don't instantiate a git tree on Projects show default view - Don't instantiate a git tree on Projects show default view
- Bump Rinku to 2.0.0 - Bump Rinku to 2.0.0
...@@ -82,7 +87,9 @@ v 8.10.0 (unreleased) ...@@ -82,7 +87,9 @@ v 8.10.0 (unreleased)
- Add basic system information like memory and disk usage to the admin panel - Add basic system information like memory and disk usage to the admin panel
- Don't garbage collect commits that have related DB records like comments - Don't garbage collect commits that have related DB records like comments
- More descriptive message for git hooks and file locks - More descriptive message for git hooks and file locks
- Aliases of award emoji should be stored as original name. !5060 (dixpac)
- Handle custom Git hook result in GitLab UI - Handle custom Git hook result in GitLab UI
- Allow to access Container Registry for Public and Internal projects
- Allow '?', or '&' for label names - Allow '?', or '&' for label names
- Fix importer for GitHub Pull Requests when a branch was reused across Pull Requests - Fix importer for GitHub Pull Requests when a branch was reused across Pull Requests
- Add date when user joined the team on the member page - Add date when user joined the team on the member page
...@@ -103,6 +110,7 @@ v 8.10.0 (unreleased) ...@@ -103,6 +110,7 @@ v 8.10.0 (unreleased)
- Fix issues importing projects from EE to CE - Fix issues importing projects from EE to CE
- Fix creating group with space in group path - Fix creating group with space in group path
- Create Todos for Issue author when assign or mention himself (Katarzyna Kobierska) - Create Todos for Issue author when assign or mention himself (Katarzyna Kobierska)
- Limit the number of retries on error to 3 for exporting projects
v 8.9.6 v 8.9.6
- Fix importing of events under notes for GitLab projects. !5154 - Fix importing of events under notes for GitLab projects. !5154
......
...@@ -129,6 +129,8 @@ ...@@ -129,6 +129,8 @@
} }
.cancel-retry-btns { .cancel-retry-btns {
vertical-align: middle;
.btn:not(:first-child) { .btn:not(:first-child) {
margin-left: 8px; margin-left: 8px;
} }
......
...@@ -204,7 +204,8 @@ class Ability ...@@ -204,7 +204,8 @@ class Ability
:download_code, :download_code,
:fork_project, :fork_project,
:read_commit_status, :read_commit_status,
:read_pipeline :read_pipeline,
:read_container_image
] ]
end end
......
...@@ -53,6 +53,7 @@ module Ci ...@@ -53,6 +53,7 @@ module Ci
new_build.stage_idx = build.stage_idx new_build.stage_idx = build.stage_idx
new_build.trigger_request = build.trigger_request new_build.trigger_request = build.trigger_request
new_build.user = user new_build.user = user
new_build.environment = build.environment
new_build.save new_build.save
MergeRequests::AddTodoWhenBuildFailsService.new(build.project, nil).close(new_build) MergeRequests::AddTodoWhenBuildFailsService.new(build.project, nil).close(new_build)
new_build new_build
......
...@@ -6,6 +6,8 @@ module Ci ...@@ -6,6 +6,8 @@ module Ci
self.table_name = 'ci_commits' self.table_name = 'ci_commits'
belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
belongs_to :user
has_many :statuses, class_name: 'CommitStatus', foreign_key: :commit_id has_many :statuses, class_name: 'CommitStatus', foreign_key: :commit_id
has_many :builds, class_name: 'Ci::Build', foreign_key: :commit_id has_many :builds, class_name: 'Ci::Build', foreign_key: :commit_id
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest', foreign_key: :commit_id has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest', foreign_key: :commit_id
......
...@@ -65,8 +65,7 @@ module Awardable ...@@ -65,8 +65,7 @@ module Awardable
def create_award_emoji(name, current_user) def create_award_emoji(name, current_user)
return unless emoji_awardable? return unless emoji_awardable?
award_emoji.create(name: normalize_name(name), user: current_user)
award_emoji.create(name: name, user: current_user)
end end
def remove_award_emoji(name, current_user) def remove_award_emoji(name, current_user)
...@@ -80,4 +79,10 @@ module Awardable ...@@ -80,4 +79,10 @@ module Awardable
create_award_emoji(emoji_name, current_user) create_award_emoji(emoji_name, current_user)
end end
end end
private
def normalize_name(name)
Gitlab::AwardEmoji.normalize_emoji_name(name)
end
end end
...@@ -229,8 +229,7 @@ class Note < ActiveRecord::Base ...@@ -229,8 +229,7 @@ class Note < ActiveRecord::Base
end end
def award_emoji_name def award_emoji_name
original_name = note.match(Banzai::Filter::EmojiFilter.emoji_pattern)[1] note.match(Banzai::Filter::EmojiFilter.emoji_pattern)[1]
Gitlab::AwardEmoji.normalize_emoji_name(original_name)
end end
private private
......
...@@ -162,7 +162,7 @@ class Project < ActiveRecord::Base ...@@ -162,7 +162,7 @@ class Project < ActiveRecord::Base
validates :namespace, presence: true validates :namespace, presence: true
validates_uniqueness_of :name, scope: :namespace_id validates_uniqueness_of :name, scope: :namespace_id
validates_uniqueness_of :path, scope: :namespace_id validates_uniqueness_of :path, scope: :namespace_id
validates :import_url, addressable_url: true, if: :import_url validates :import_url, addressable_url: true, if: :external_import?
validates :star_count, numericality: { greater_than_or_equal_to: 0 } validates :star_count, numericality: { greater_than_or_equal_to: 0 }
validate :check_limit, on: :create validate :check_limit, on: :create
validate :avatar_type, validate :avatar_type,
...@@ -482,7 +482,7 @@ class Project < ActiveRecord::Base ...@@ -482,7 +482,7 @@ class Project < ActiveRecord::Base
end end
def create_or_update_import_data(data: nil, credentials: nil) def create_or_update_import_data(data: nil, credentials: nil)
return unless valid_import_url? return unless import_url.present? && valid_import_url?
project_import_data = import_data || build_import_data project_import_data = import_data || build_import_data
if data if data
...@@ -1038,8 +1038,8 @@ class Project < ActiveRecord::Base ...@@ -1038,8 +1038,8 @@ class Project < ActiveRecord::Base
pipelines.order(id: :desc).find_by(sha: sha, ref: ref) pipelines.order(id: :desc).find_by(sha: sha, ref: ref)
end end
def ensure_pipeline(sha, ref) def ensure_pipeline(sha, ref, current_user = nil)
pipeline(sha, ref) || pipelines.create(sha: sha, ref: ref) pipeline(sha, ref) || pipelines.create(sha: sha, ref: ref, user: current_user)
end end
def enable_ci def enable_ci
......
...@@ -85,6 +85,7 @@ class User < ActiveRecord::Base ...@@ -85,6 +85,7 @@ class User < ActiveRecord::Base
has_one :abuse_report, dependent: :destroy has_one :abuse_report, dependent: :destroy
has_many :spam_logs, dependent: :destroy has_many :spam_logs, dependent: :destroy
has_many :builds, dependent: :nullify, class_name: 'Ci::Build' has_many :builds, dependent: :nullify, class_name: 'Ci::Build'
has_many :pipelines, dependent: :nullify, class_name: 'Ci::Pipeline'
has_many :todos, dependent: :destroy has_many :todos, dependent: :destroy
has_many :notification_settings, dependent: :destroy has_many :notification_settings, dependent: :destroy
has_many :award_emoji, dependent: :destroy has_many :award_emoji, dependent: :destroy
......
...@@ -2,6 +2,7 @@ module Ci ...@@ -2,6 +2,7 @@ module Ci
class CreatePipelineService < BaseService class CreatePipelineService < BaseService
def execute def execute
pipeline = project.pipelines.new(params) pipeline = project.pipelines.new(params)
pipeline.user = current_user
unless ref_names.include?(params[:ref]) unless ref_names.include?(params[:ref])
pipeline.errors.add(:base, 'Reference not found') pipeline.errors.add(:base, 'Reference not found')
......
...@@ -14,7 +14,13 @@ class CreateCommitBuildsService ...@@ -14,7 +14,13 @@ class CreateCommitBuildsService
return false return false
end end
@pipeline = Ci::Pipeline.new(project: project, sha: sha, ref: ref, before_sha: before_sha, tag: tag) @pipeline = Ci::Pipeline.new(
project: project,
sha: sha,
ref: ref,
before_sha: before_sha,
tag: tag,
user: user)
## ##
# Skip creating pipeline if no gitlab-ci.yml is found # Skip creating pipeline if no gitlab-ci.yml is found
......
...@@ -8,7 +8,6 @@ module Notes ...@@ -8,7 +8,6 @@ module Notes
if note.award_emoji? if note.award_emoji?
noteable = note.noteable noteable = note.noteable
todo_service.new_award_emoji(noteable, current_user) todo_service.new_award_emoji(noteable, current_user)
return noteable.create_award_emoji(note.award_emoji_name, current_user) return noteable.create_award_emoji(note.award_emoji_name, current_user)
end end
......
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
Cant find HEAD commit for this branch Cant find HEAD commit for this branch
- stages_status = pipeline.statuses.stages_status - stages_status = pipeline.statuses.latest.stages_status
- stages.each do |stage| - stages.each do |stage|
%td %td
- status = stages_status[stage] - status = stages_status[stage]
...@@ -58,16 +58,7 @@ ...@@ -58,16 +58,7 @@
.controls.hidden-xs.pull-right .controls.hidden-xs.pull-right
- artifacts = pipeline.builds.latest.select { |b| b.artifacts? } - artifacts = pipeline.builds.latest.select { |b| b.artifacts? }
- if artifacts.present? - if artifacts.present?
.btn-group.inline .inline
.btn-group
%a.dropdown-toggle.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'}
= icon("play")
%b.caret
%ul.dropdown-menu.dropdown-menu-align-right
%li
= link_to '#' do
= icon("play")
%span Deploy to production
.btn-group .btn-group
%a.dropdown-toggle.btn.btn-default.build-artifacts{type: 'button', 'data-toggle' => 'dropdown'} %a.dropdown-toggle.btn.btn-default.build-artifacts{type: 'button', 'data-toggle' => 'dropdown'}
= icon("download") = icon("download")
...@@ -80,7 +71,7 @@ ...@@ -80,7 +71,7 @@
%span Download '#{build.name}' artifacts %span Download '#{build.name}' artifacts
- if can?(current_user, :update_pipeline, @project) - if can?(current_user, :update_pipeline, @project)
.cancel-retry-btns .cancel-retry-btns.inline
- if pipeline.retryable? - if pipeline.retryable?
= link_to retry_namespace_project_pipeline_path(@project.namespace, @project, pipeline.id), class: 'btn has-tooltip', title: "Retry", method: :post do = link_to retry_namespace_project_pipeline_path(@project.namespace, @project, pipeline.id), class: 'btn has-tooltip', title: "Retry", method: :post do
= icon("repeat") = icon("repeat")
......
class ProjectExportWorker class ProjectExportWorker
include Sidekiq::Worker include Sidekiq::Worker
sidekiq_options queue: :gitlab_shell, retry: true sidekiq_options queue: :gitlab_shell, retry: 3
def perform(current_user_id, project_id) def perform(current_user_id, project_id)
current_user = User.find(current_user_id) current_user = User.find(current_user_id)
......
class AddUserIdToPipeline < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
def change
add_column :ci_commits, :user_id, :integer
end
end
class AddIndexForPipelineUserId < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
def change
add_concurrent_index :ci_commits, :user_id
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20160712171823) do ActiveRecord::Schema.define(version: 20160715134306) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -199,6 +199,7 @@ ActiveRecord::Schema.define(version: 20160712171823) do ...@@ -199,6 +199,7 @@ ActiveRecord::Schema.define(version: 20160712171823) do
t.datetime "started_at" t.datetime "started_at"
t.datetime "finished_at" t.datetime "finished_at"
t.integer "duration" t.integer "duration"
t.integer "user_id"
end end
add_index "ci_commits", ["gl_project_id", "sha"], name: "index_ci_commits_on_gl_project_id_and_sha", using: :btree add_index "ci_commits", ["gl_project_id", "sha"], name: "index_ci_commits_on_gl_project_id_and_sha", using: :btree
...@@ -210,6 +211,7 @@ ActiveRecord::Schema.define(version: 20160712171823) do ...@@ -210,6 +211,7 @@ ActiveRecord::Schema.define(version: 20160712171823) do
add_index "ci_commits", ["project_id"], name: "index_ci_commits_on_project_id", using: :btree add_index "ci_commits", ["project_id"], name: "index_ci_commits_on_project_id", using: :btree
add_index "ci_commits", ["sha"], name: "index_ci_commits_on_sha", using: :btree add_index "ci_commits", ["sha"], name: "index_ci_commits_on_sha", using: :btree
add_index "ci_commits", ["status"], name: "index_ci_commits_on_status", using: :btree add_index "ci_commits", ["status"], name: "index_ci_commits_on_status", using: :btree
add_index "ci_commits", ["user_id"], name: "index_ci_commits_on_user_id", using: :btree
create_table "ci_events", force: :cascade do |t| create_table "ci_events", force: :cascade do |t|
t.integer "project_id" t.integer "project_id"
......
...@@ -78,7 +78,8 @@ Example response: ...@@ -78,7 +78,8 @@ Example response:
"iid" : 6, "iid" : 6,
"labels" : [], "labels" : [],
"subscribed" : false, "subscribed" : false,
"user_notes_count": 1 "user_notes_count": 1,
"due_date": "2016-07-22"
} }
] ]
``` ```
...@@ -154,7 +155,8 @@ Example response: ...@@ -154,7 +155,8 @@ Example response:
"updated_at" : "2016-01-04T15:31:46.176Z", "updated_at" : "2016-01-04T15:31:46.176Z",
"created_at" : "2016-01-04T15:31:46.176Z", "created_at" : "2016-01-04T15:31:46.176Z",
"subscribed" : false, "subscribed" : false,
"user_notes_count": 1 "user_notes_count": 1,
"due_date": null
} }
] ]
``` ```
...@@ -232,7 +234,8 @@ Example response: ...@@ -232,7 +234,8 @@ Example response:
"updated_at" : "2016-01-04T15:31:46.176Z", "updated_at" : "2016-01-04T15:31:46.176Z",
"created_at" : "2016-01-04T15:31:46.176Z", "created_at" : "2016-01-04T15:31:46.176Z",
"subscribed" : false, "subscribed" : false,
"user_notes_count": 1 "user_notes_count": 1,
"due_date": "2016-07-22"
} }
] ]
``` ```
...@@ -295,7 +298,8 @@ Example response: ...@@ -295,7 +298,8 @@ Example response:
"updated_at" : "2016-01-04T15:31:46.176Z", "updated_at" : "2016-01-04T15:31:46.176Z",
"created_at" : "2016-01-04T15:31:46.176Z", "created_at" : "2016-01-04T15:31:46.176Z",
"subscribed": false, "subscribed": false,
"user_notes_count": 1 "user_notes_count": 1,
"due_date": null
} }
``` ```
...@@ -320,6 +324,7 @@ POST /projects/:id/issues ...@@ -320,6 +324,7 @@ POST /projects/:id/issues
| `milestone_id` | integer | no | The ID of a milestone to assign issue | | `milestone_id` | integer | no | The ID of a milestone to assign issue |
| `labels` | string | no | Comma-separated label names for an issue | | `labels` | string | no | Comma-separated label names for an issue |
| `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. `2016-03-11T03:45:40Z` | | `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. `2016-03-11T03:45:40Z` |
| `due_date` | string | no | Date time string in the format YEAR-MONTH-DAY, e.g. `2016-03-11` |
```bash ```bash
curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues?title=Issues%20with%20auth&labels=bug curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues?title=Issues%20with%20auth&labels=bug
...@@ -351,7 +356,8 @@ Example response: ...@@ -351,7 +356,8 @@ Example response:
"updated_at" : "2016-01-07T12:44:33.959Z", "updated_at" : "2016-01-07T12:44:33.959Z",
"milestone" : null, "milestone" : null,
"subscribed" : true, "subscribed" : true,
"user_notes_count": 0 "user_notes_count": 0,
"due_date": null
} }
``` ```
...@@ -379,6 +385,7 @@ PUT /projects/:id/issues/:issue_id ...@@ -379,6 +385,7 @@ PUT /projects/:id/issues/:issue_id
| `labels` | string | no | Comma-separated label names for an issue | | `labels` | string | no | Comma-separated label names for an issue |
| `state_event` | string | no | The state event of an issue. Set `close` to close the issue and `reopen` to reopen it | | `state_event` | string | no | The state event of an issue. Set `close` to close the issue and `reopen` to reopen it |
| `updated_at` | string | no | Date time string, ISO 8601 formatted, e.g. `2016-03-11T03:45:40Z` | | `updated_at` | string | no | Date time string, ISO 8601 formatted, e.g. `2016-03-11T03:45:40Z` |
| `due_date` | string | no | Date time string in the format YEAR-MONTH-DAY, e.g. `2016-03-11` |
```bash ```bash
curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues/85?state_event=close curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/4/issues/85?state_event=close
...@@ -410,7 +417,8 @@ Example response: ...@@ -410,7 +417,8 @@ Example response:
"assignee" : null, "assignee" : null,
"milestone" : null, "milestone" : null,
"subscribed" : true, "subscribed" : true,
"user_notes_count": 0 "user_notes_count": 0,
"due_date": "2016-07-22"
} }
``` ```
...@@ -487,7 +495,8 @@ Example response: ...@@ -487,7 +495,8 @@ Example response:
"state": "active", "state": "active",
"avatar_url": "http://www.gravatar.com/avatar/7a190fecbaa68212a4b68aeb6e3acd10?s=80&d=identicon", "avatar_url": "http://www.gravatar.com/avatar/7a190fecbaa68212a4b68aeb6e3acd10?s=80&d=identicon",
"web_url": "https://gitlab.example.com/u/solon.cremin" "web_url": "https://gitlab.example.com/u/solon.cremin"
} },
"due_date": null
} }
``` ```
...@@ -541,7 +550,8 @@ Example response: ...@@ -541,7 +550,8 @@ Example response:
"state": "active", "state": "active",
"avatar_url": "http://www.gravatar.com/avatar/7a190fecbaa68212a4b68aeb6e3acd10?s=80&d=identicon", "avatar_url": "http://www.gravatar.com/avatar/7a190fecbaa68212a4b68aeb6e3acd10?s=80&d=identicon",
"web_url": "https://gitlab.example.com/u/solon.cremin" "web_url": "https://gitlab.example.com/u/solon.cremin"
} },
"due_date": null
} }
``` ```
...@@ -596,7 +606,8 @@ Example response: ...@@ -596,7 +606,8 @@ Example response:
"avatar_url": "http://www.gravatar.com/avatar/5224fd70153710e92fb8bcf79ac29d67?s=80&d=identicon", "avatar_url": "http://www.gravatar.com/avatar/5224fd70153710e92fb8bcf79ac29d67?s=80&d=identicon",
"web_url": "https://gitlab.example.com/u/orville" "web_url": "https://gitlab.example.com/u/orville"
}, },
"subscribed": false "subscribed": false,
"due_date": null
} }
``` ```
......
...@@ -985,11 +985,11 @@ directive defined in `.postgres_services` and `.mysql_services` respectively: ...@@ -985,11 +985,11 @@ directive defined in `.postgres_services` and `.mysql_services` respectively:
- ruby - ruby
test:postgres: test:postgres:
<< *job_definition <<: *job_definition
services: *postgres_definition services: *postgres_definition
test:mysql: test:mysql:
<< *job_definition <<: *job_definition
services: *mysql_definition services: *mysql_definition
``` ```
......
...@@ -56,9 +56,9 @@ module API ...@@ -56,9 +56,9 @@ module API
not_found!('Award Emoji') unless can_read_awardable? not_found!('Award Emoji') unless can_read_awardable?
award = awardable.award_emoji.new(name: params[:name], user: current_user) award = awardable.create_award_emoji(params[:name], current_user)
if award.save if award.persisted?
present award, with: Entities::AwardEmoji present award, with: Entities::AwardEmoji
else else
not_found!("Award Emoji #{award.errors.messages}") not_found!("Award Emoji #{award.errors.messages}")
......
...@@ -64,7 +64,7 @@ module API ...@@ -64,7 +64,7 @@ module API
ref = branches.first ref = branches.first
end end
pipeline = @project.ensure_pipeline(commit.sha, ref) pipeline = @project.ensure_pipeline(commit.sha, ref, current_user)
name = params[:name] || params[:context] name = params[:name] || params[:context]
status = GenericCommitStatus.running_or_pending.find_by(pipeline: pipeline, name: name, ref: params[:ref]) status = GenericCommitStatus.running_or_pending.find_by(pipeline: pipeline, name: name, ref: params[:ref])
......
...@@ -187,6 +187,7 @@ module API ...@@ -187,6 +187,7 @@ module API
end end
expose :user_notes_count expose :user_notes_count
expose :upvotes, :downvotes expose :upvotes, :downvotes
expose :due_date
end end
class ExternalIssue < Grape::Entity class ExternalIssue < Grape::Entity
......
...@@ -63,7 +63,12 @@ module API ...@@ -63,7 +63,12 @@ module API
if access_status.status if access_status.status
# Return the repository full path so that gitlab-shell has it when # Return the repository full path so that gitlab-shell has it when
# handling ssh commands # handling ssh commands
response[:repository_path] = project.repository.path_to_repo response[:repository_path] =
if wiki?
project.wiki.repository.path_to_repo
else
project.repository.path_to_repo
end
end end
response response
......
...@@ -152,12 +152,13 @@ module API ...@@ -152,12 +152,13 @@ module API
# milestone_id (optional) - The ID of a milestone to assign issue # milestone_id (optional) - The ID of a milestone to assign issue
# labels (optional) - The labels of an issue # labels (optional) - The labels of an issue
# created_at (optional) - Date time string, ISO 8601 formatted # created_at (optional) - Date time string, ISO 8601 formatted
# due_date (optional) - Date time string in the format YEAR-MONTH-DAY
# Example Request: # Example Request:
# POST /projects/:id/issues # POST /projects/:id/issues
post ":id/issues" do post ':id/issues' do
required_attributes! [:title] required_attributes! [:title]
keys = [:title, :description, :assignee_id, :milestone_id] keys = [:title, :description, :assignee_id, :milestone_id, :due_date]
keys << :created_at if current_user.admin? || user_project.owner == current_user keys << :created_at if current_user.admin? || user_project.owner == current_user
attrs = attributes_for_keys(keys) attrs = attributes_for_keys(keys)
...@@ -201,12 +202,13 @@ module API ...@@ -201,12 +202,13 @@ module API
# labels (optional) - The labels of an issue # labels (optional) - The labels of an issue
# state_event (optional) - The state event of an issue (close|reopen) # state_event (optional) - The state event of an issue (close|reopen)
# updated_at (optional) - Date time string, ISO 8601 formatted # updated_at (optional) - Date time string, ISO 8601 formatted
# due_date (optional) - Date time string in the format YEAR-MONTH-DAY
# Example Request: # Example Request:
# PUT /projects/:id/issues/:issue_id # PUT /projects/:id/issues/:issue_id
put ":id/issues/:issue_id" do put ':id/issues/:issue_id' do
issue = user_project.issues.find(params[:issue_id]) issue = user_project.issues.find(params[:issue_id])
authorize! :update_issue, issue authorize! :update_issue, issue
keys = [:title, :description, :assignee_id, :milestone_id, :state_event] keys = [:title, :description, :assignee_id, :milestone_id, :state_event, :due_date]
keys << :updated_at if current_user.admin? || user_project.owner == current_user keys << :updated_at if current_user.admin? || user_project.owner == current_user
attrs = attributes_for_keys(keys) attrs = attributes_for_keys(keys)
......
...@@ -19,21 +19,22 @@ module Banzai ...@@ -19,21 +19,22 @@ module Banzai
language = node.attr('class') language = node.attr('class')
code = node.text code = node.text
lexer = Rouge::Lexer.find_fancy(language) css_classes = "code highlight"
lexer = Rouge::Lexer.find_fancy(language) || Rouge::Lexers::PlainText
formatter = Rouge::Formatters::HTML.new formatter = Rouge::Formatters::HTML.new
css_classes = "code highlight js-syntax-highlight #{lexer.tag}"
begin begin
highlighted = '' code = formatter.format(lexer.lex(code))
highlighted << %(<pre class="#{css_classes}"><code>)
highlighted << formatter.format(lexer.lex(code)) css_classes << " js-syntax-highlight #{lexer.tag}"
highlighted << %(</code></pre>)
rescue rescue
# Gracefully handle syntax highlighter bugs/errors to ensure # Gracefully handle syntax highlighter bugs/errors to ensure
# users can still access an issue/comment/etc. # users can still access an issue/comment/etc.
highlighted = "<pre>#{code}</pre>"
end end
highlighted = %(<pre class="#{css_classes}"><code>#{code}</code></pre>)
# Extracted to a method to measure it # Extracted to a method to measure it
replace_parent_pre_element(node, highlighted) replace_parent_pre_element(node, highlighted)
end end
......
...@@ -63,7 +63,7 @@ module Grack ...@@ -63,7 +63,7 @@ module Grack
def ci_request?(login, password) def ci_request?(login, password)
matched_login = /(?<s>^[a-zA-Z]*-ci)-token$/.match(login) matched_login = /(?<s>^[a-zA-Z]*-ci)-token$/.match(login)
if project && matched_login.present? && git_cmd == 'git-upload-pack' if project && matched_login.present?
underscored_service = matched_login['s'].underscore underscored_service = matched_login['s'].underscore
if underscored_service == 'gitlab_ci' if underscored_service == 'gitlab_ci'
......
...@@ -47,6 +47,8 @@ module Gitlab ...@@ -47,6 +47,8 @@ module Gitlab
end end
def render_storage_upload_store_response(oid, size, tmp_file_name) def render_storage_upload_store_response(oid, size, tmp_file_name)
return render_forbidden unless tmp_file_name
render_response_to_push do render_response_to_push do
render_lfs_upload_ok(oid, size, tmp_file_name) render_lfs_upload_ok(oid, size, tmp_file_name)
end end
......
...@@ -74,8 +74,6 @@ module Gitlab ...@@ -74,8 +74,6 @@ module Gitlab
lfs.render_storage_upload_authorize_response(oid, size) lfs.render_storage_upload_authorize_response(oid, size)
else else
tmp_file_name = sanitize_tmp_filename(@request.env['HTTP_X_GITLAB_LFS_TMP']) tmp_file_name = sanitize_tmp_filename(@request.env['HTTP_X_GITLAB_LFS_TMP'])
return nil unless tmp_file_name
lfs.render_storage_upload_store_response(oid, size, tmp_file_name) lfs.render_storage_upload_store_response(oid, size, tmp_file_name)
end end
end end
......
...@@ -426,4 +426,23 @@ describe "Internal Project Access", feature: true do ...@@ -426,4 +426,23 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :external }
it { is_expected.to be_denied_for :visitor } it { is_expected.to be_denied_for :visitor }
end end
describe "GET /:project_path/container_registry" do
before do
stub_container_registry_tags('latest')
stub_container_registry_config(enabled: true)
end
subject { namespace_project_container_registry_index_path(project.namespace, project) }
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
it { is_expected.to be_denied_for :external }
it { is_expected.to be_denied_for :visitor }
end
end end
...@@ -362,4 +362,23 @@ describe "Private Project Access", feature: true do ...@@ -362,4 +362,23 @@ describe "Private Project Access", feature: true do
it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :external }
it { is_expected.to be_denied_for :visitor } it { is_expected.to be_denied_for :visitor }
end end
describe "GET /:project_path/container_registry" do
before do
stub_container_registry_tags('latest')
stub_container_registry_config(enabled: true)
end
subject { namespace_project_container_registry_index_path(project.namespace, project) }
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
it { is_expected.to be_denied_for guest }
it { is_expected.to be_denied_for :user }
it { is_expected.to be_denied_for :external }
it { is_expected.to be_denied_for :visitor }
end
end end
...@@ -426,4 +426,23 @@ describe "Public Project Access", feature: true do ...@@ -426,4 +426,23 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :external }
it { is_expected.to be_denied_for :visitor } it { is_expected.to be_denied_for :visitor }
end end
describe "GET /:project_path/container_registry" do
before do
stub_container_registry_tags('latest')
stub_container_registry_config(enabled: true)
end
subject { namespace_project_container_registry_index_path(project.namespace, project) }
it { is_expected.to be_allowed_for :admin }
it { is_expected.to be_allowed_for owner }
it { is_expected.to be_allowed_for master }
it { is_expected.to be_allowed_for developer }
it { is_expected.to be_allowed_for reporter }
it { is_expected.to be_allowed_for guest }
it { is_expected.to be_allowed_for :user }
it { is_expected.to be_allowed_for :external }
it { is_expected.to be_allowed_for :visitor }
end
end end
...@@ -3,15 +3,35 @@ require 'spec_helper' ...@@ -3,15 +3,35 @@ require 'spec_helper'
describe Banzai::Filter::SyntaxHighlightFilter, lib: true do describe Banzai::Filter::SyntaxHighlightFilter, lib: true do
include FilterSpecHelper include FilterSpecHelper
it 'highlights valid code blocks' do context "when no language is specified" do
result = filter('<pre><code>def fun end</code>') it "highlights as plaintext" do
expect(result.to_html).to eq("<pre class=\"code highlight js-syntax-highlight plaintext\"><code>def fun end</code></pre>") result = filter('<pre><code>def fun end</code></pre>')
expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext"><code>def fun end</code></pre>')
end
end end
it 'passes through invalid code blocks' do context "when a valid language is specified" do
allow_any_instance_of(Rouge::Formatter).to receive(:format).and_raise(StandardError) it "highlights as that language" do
result = filter('<pre><code class="ruby">def fun end</code></pre>')
expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight ruby"><code><span class="k">def</span> <span class="nf">fun</span> <span class="k">end</span></code></pre>')
end
end
context "when an invalid language is specified" do
it "highlights as plaintext" do
result = filter('<pre><code class="gnuplot">This is a test</code></pre>')
expect(result.to_html).to eq('<pre class="code highlight js-syntax-highlight plaintext"><code>This is a test</code></pre>')
end
end
context "when Rouge formatting fails" do
before do
allow_any_instance_of(Rouge::Formatter).to receive(:format).and_raise(StandardError)
end
result = filter('<pre><code>This is a test</code></pre>') it "highlights as plaintext" do
expect(result.to_html).to eq('<pre>This is a test</pre>') result = filter('<pre><code class="ruby">This is a test</code></pre>')
expect(result.to_html).to eq('<pre class="code highlight"><code>This is a test</code></pre>')
end
end end
end end
...@@ -54,12 +54,12 @@ describe Gitlab::BitbucketImport::Client, lib: true do ...@@ -54,12 +54,12 @@ describe Gitlab::BitbucketImport::Client, lib: true do
context 'project import' do context 'project import' do
it 'calls .from_project with no errors' do it 'calls .from_project with no errors' do
project = create(:empty_project) project = create(:empty_project)
project.import_url = "ssh://git@bitbucket.org/test/test.git"
project.create_or_update_import_data(credentials: project.create_or_update_import_data(credentials:
{ user: "git", { user: "git",
password: nil, password: nil,
bb_session: { bitbucket_access_token: "test", bb_session: { bitbucket_access_token: "test",
bitbucket_access_token_secret: "test" } }) bitbucket_access_token_secret: "test" } })
project.import_url = "ssh://git@bitbucket.org/test/test.git"
expect { described_class.from_project(project) }.not_to raise_error expect { described_class.from_project(project) }.not_to raise_error
end end
......
...@@ -5,9 +5,12 @@ describe Ci::Pipeline, models: true do ...@@ -5,9 +5,12 @@ describe Ci::Pipeline, models: true do
let(:pipeline) { FactoryGirl.create :ci_pipeline, project: project } let(:pipeline) { FactoryGirl.create :ci_pipeline, project: project }
it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:user) }
it { is_expected.to have_many(:statuses) } it { is_expected.to have_many(:statuses) }
it { is_expected.to have_many(:trigger_requests) } it { is_expected.to have_many(:trigger_requests) }
it { is_expected.to have_many(:builds) } it { is_expected.to have_many(:builds) }
it { is_expected.to validate_presence_of :sha } it { is_expected.to validate_presence_of :sha }
it { is_expected.to validate_presence_of :status } it { is_expected.to validate_presence_of :status }
......
...@@ -177,10 +177,10 @@ describe CommitStatus, models: true do ...@@ -177,10 +177,10 @@ describe CommitStatus, models: true do
describe '#stages' do describe '#stages' do
before do before do
FactoryGirl.create :commit_status, pipeline: pipeline, stage: 'build', stage_idx: 0, status: 'success' create :commit_status, pipeline: pipeline, stage: 'build', name: 'linux', stage_idx: 0, status: 'success'
FactoryGirl.create :commit_status, pipeline: pipeline, stage: 'build', stage_idx: 0, status: 'failed' create :commit_status, pipeline: pipeline, stage: 'build', name: 'mac', stage_idx: 0, status: 'failed'
FactoryGirl.create :commit_status, pipeline: pipeline, stage: 'deploy', stage_idx: 2, status: 'running' create :commit_status, pipeline: pipeline, stage: 'deploy', name: 'staging', stage_idx: 2, status: 'running'
FactoryGirl.create :commit_status, pipeline: pipeline, stage: 'test', stage_idx: 1, status: 'success' create :commit_status, pipeline: pipeline, stage: 'test', name: 'rspec', stage_idx: 1, status: 'success'
end end
context 'stages list' do context 'stages list' do
...@@ -192,7 +192,7 @@ describe CommitStatus, models: true do ...@@ -192,7 +192,7 @@ describe CommitStatus, models: true do
end end
context 'stages with statuses' do context 'stages with statuses' do
subject { CommitStatus.where(pipeline: pipeline).stages_status } subject { CommitStatus.where(pipeline: pipeline).latest.stages_status }
it 'return list of stages with statuses' do it 'return list of stages with statuses' do
is_expected.to eq({ is_expected.to eq({
...@@ -201,6 +201,20 @@ describe CommitStatus, models: true do ...@@ -201,6 +201,20 @@ describe CommitStatus, models: true do
'deploy' => 'running' 'deploy' => 'running'
}) })
end end
context 'when build is retried' do
before do
create :commit_status, pipeline: pipeline, stage: 'build', name: 'mac', stage_idx: 0, status: 'success'
end
it 'ignores a previous state' do
is_expected.to eq({
'build' => 'success',
'test' => 'success',
'deploy' => 'running'
})
end
end
end end
end end
......
...@@ -142,10 +142,10 @@ describe Project, models: true do ...@@ -142,10 +142,10 @@ describe Project, models: true do
expect(project2).to be_valid expect(project2).to be_valid
end end
it 'does not allow to introduce an empty URI' do it 'allows an empty URI' do
project2 = build(:project, import_url: '') project2 = build(:project, import_url: '')
expect(project2).not_to be_valid expect(project2).to be_valid
end end
it 'does not produce import data on an empty URI' do it 'does not produce import data on an empty URI' do
......
...@@ -31,6 +31,8 @@ describe User, models: true do ...@@ -31,6 +31,8 @@ describe User, models: true do
it { is_expected.to have_many(:spam_logs).dependent(:destroy) } it { is_expected.to have_many(:spam_logs).dependent(:destroy) }
it { is_expected.to have_many(:todos).dependent(:destroy) } it { is_expected.to have_many(:todos).dependent(:destroy) }
it { is_expected.to have_many(:award_emoji).dependent(:destroy) } it { is_expected.to have_many(:award_emoji).dependent(:destroy) }
it { is_expected.to have_many(:builds).dependent(:nullify) }
it { is_expected.to have_many(:pipelines).dependent(:nullify) }
describe '#group_members' do describe '#group_members' do
it 'does not include group memberships for which user is a requester' do it 'does not include group memberships for which user is a requester' do
......
...@@ -135,6 +135,22 @@ describe API::API, api: true do ...@@ -135,6 +135,22 @@ describe API::API, api: true do
expect(response).to have_http_status(401) expect(response).to have_http_status(401)
end end
it "normalizes +1 as thumbsup award" do
post api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user), name: '+1'
expect(issue.award_emoji.last.name).to eq("thumbsup")
end
context 'when the emoji already has been awarded' do
it 'returns a 404 status code' do
post api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user), name: 'thumbsup'
post api("/projects/#{project.id}/issues/#{issue.id}/award_emoji", user), name: 'thumbsup'
expect(response).to have_http_status(404)
expect(json_response["message"]).to match("has already been taken")
end
end
end end
end end
...@@ -147,6 +163,22 @@ describe API::API, api: true do ...@@ -147,6 +163,22 @@ describe API::API, api: true do
expect(response).to have_http_status(201) expect(response).to have_http_status(201)
expect(json_response['user']['username']).to eq(user.username) expect(json_response['user']['username']).to eq(user.username)
end end
it "normalizes +1 as thumbsup award" do
post api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user), name: '+1'
expect(note.award_emoji.last.name).to eq("thumbsup")
end
context 'when the emoji already has been awarded' do
it 'returns a 404 status code' do
post api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user), name: 'rocket'
post api("/projects/#{project.id}/issues/#{issue.id}/notes/#{note.id}/award_emoji", user), name: 'rocket'
expect(response).to have_http_status(404)
expect(json_response["message"]).to match("has already been taken")
end
end
end end
describe 'DELETE /projects/:id/awardable/:awardable_id/award_emoji/:award_id' do describe 'DELETE /projects/:id/awardable/:awardable_id/award_emoji/:award_id' do
......
...@@ -56,13 +56,21 @@ describe API::API, api: true do ...@@ -56,13 +56,21 @@ describe API::API, api: true do
context "git push with project.wiki" do context "git push with project.wiki" do
it 'responds with success' do it 'responds with success' do
project_wiki = create(:project, name: 'my.wiki', path: 'my.wiki') push(key, project.wiki)
project_wiki.team << [user, :developer]
push(key, project_wiki) expect(response).to have_http_status(200)
expect(json_response["status"]).to be_truthy
expect(json_response["repository_path"]).to eq(project.wiki.repository.path_to_repo)
end
end
context "git pull with project.wiki" do
it 'responds with success' do
pull(key, project.wiki)
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(json_response["status"]).to be_truthy expect(json_response["status"]).to be_truthy
expect(json_response["repository_path"]).to eq(project.wiki.repository.path_to_repo)
end end
end end
......
...@@ -503,6 +503,20 @@ describe API::API, api: true do ...@@ -503,6 +503,20 @@ describe API::API, api: true do
]) ])
end end
context 'with due date' do
it 'creates a new project issue' do
due_date = 2.weeks.from_now.strftime('%Y-%m-%d')
post api("/projects/#{project.id}/issues", user),
title: 'new issue', due_date: due_date
expect(response).to have_http_status(201)
expect(json_response['title']).to eq('new issue')
expect(json_response['description']).to be_nil
expect(json_response['due_date']).to eq(due_date)
end
end
context 'when an admin or owner makes the request' do context 'when an admin or owner makes the request' do
it 'accepts the creation date to be set' do it 'accepts the creation date to be set' do
creation_time = 2.weeks.ago creation_time = 2.weeks.ago
...@@ -683,6 +697,17 @@ describe API::API, api: true do ...@@ -683,6 +697,17 @@ describe API::API, api: true do
end end
end end
describe 'PUT /projects/:id/issues/:issue_id to update due date' do
it 'creates a new project issue' do
due_date = 2.weeks.from_now.strftime('%Y-%m-%d')
put api("/projects/#{project.id}/issues/#{issue.id}", user), due_date: due_date
expect(response).to have_http_status(200)
expect(json_response['due_date']).to eq(due_date)
end
end
describe "DELETE /projects/:id/issues/:issue_id" do describe "DELETE /projects/:id/issues/:issue_id" do
it "rejects a non member from deleting an issue" do it "rejects a non member from deleting an issue" do
delete api("/projects/#{project.id}/issues/#{issue.id}", non_member) delete api("/projects/#{project.id}/issues/#{issue.id}", non_member)
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::Lfs::Router, lib: true do describe Gitlab::Lfs::Router do
let(:project) { create(:project) }
let(:public_project) { create(:project, :public) }
let(:forked_project) { fork_project(public_project, user) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:user_two) { create(:user) }
let!(:lfs_object) { create(:lfs_object, :with_file) } let!(:lfs_object) { create(:lfs_object, :with_file) }
let(:request) { Rack::Request.new(env) } let(:headers) do
let(:env) do
{ {
'rack.input' => '', 'Authorization' => authorization,
'REQUEST_METHOD' => 'GET', 'X-Sendfile-Type' => sendfile
} }.compact
end end
let(:authorization) { }
let(:sendfile) { }
let(:lfs_router_auth) { new_lfs_router(project, user: user) } let(:sample_oid) { lfs_object.oid }
let(:lfs_router_ci_auth) { new_lfs_router(project, ci: true) } let(:sample_size) { lfs_object.size }
let(:lfs_router_noauth) { new_lfs_router(project) }
let(:lfs_router_public_auth) { new_lfs_router(public_project, user: user) }
let(:lfs_router_public_ci_auth) { new_lfs_router(public_project, ci: true) }
let(:lfs_router_public_noauth) { new_lfs_router(public_project) }
let(:lfs_router_forked_noauth) { new_lfs_router(forked_project) }
let(:lfs_router_forked_auth) { new_lfs_router(forked_project, user: user_two) }
let(:lfs_router_forked_ci_auth) { new_lfs_router(forked_project, ci: true) }
let(:sample_oid) { "b68143e6463773b1b6c6fd009a76c32aeec041faff32ba2ed42fd7f708a17f80" }
let(:sample_size) { 499013 }
let(:respond_with_deprecated) {[ 501, { "Content-Type" => "application/json; charset=utf-8" }, ["{\"message\":\"Server supports batch API only, please update your Git LFS client to version 1.0.1 and up.\",\"documentation_url\":\"#{Gitlab.config.gitlab.url}/help\"}"]]}
let(:respond_with_disabled) {[ 501, { "Content-Type" => "application/json; charset=utf-8" }, ["{\"message\":\"Git LFS is not enabled on this GitLab server, contact your admin.\",\"documentation_url\":\"#{Gitlab.config.gitlab.url}/help\"}"]]}
describe 'when lfs is disabled' do describe 'when lfs is disabled' do
let(:project) { create(:empty_project) }
let(:body) do
{
'objects' => [
{ 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
'size' => 1575078
},
{ 'oid' => sample_oid,
'size' => sample_size
}
],
'operation' => 'upload'
}
end
before do before do
allow(Gitlab.config.lfs).to receive(:enabled).and_return(false) allow(Gitlab.config.lfs).to receive(:enabled).and_return(false)
env['REQUEST_METHOD'] = 'POST' post_json "#{project.http_url_to_repo}/info/lfs/objects/batch", body, headers
body = {
'objects' => [
{ 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
'size' => 1575078
},
{ 'oid' => sample_oid,
'size' => sample_size
}
],
'operation' => 'upload'
}.to_json
env['rack.input'] = StringIO.new(body)
env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/info/lfs/objects/batch"
end end
it 'responds with 501' do it 'responds with 501' do
expect(lfs_router_auth.try_call).to match_array(respond_with_disabled) expect(response).to have_http_status(501)
expect(json_response).to include('message' => 'Git LFS is not enabled on this GitLab server, contact your admin.')
end end
end end
describe 'when fetching lfs object using deprecated API' do describe 'deprecated API' do
let(:project) { create(:empty_project) }
before do before do
enable_lfs enable_lfs
env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/info/lfs/objects/#{sample_oid}"
end end
it 'responds with 501' do shared_examples 'a deprecated' do
expect(lfs_router_auth.try_call).to match_array(respond_with_deprecated) it 'responds with 501' do
expect(response).to have_http_status(501)
end
it 'returns deprecated message' do
expect(json_response).to include('message' => 'Server supports batch API only, please update your Git LFS client to version 1.0.1 and up.')
end
end
context 'when fetching lfs object using deprecated API' do
let(:authorization) { authorize_user }
before do
get "#{project.http_url_to_repo}/info/lfs/objects/#{sample_oid}", nil, headers
end
it_behaves_like 'a deprecated'
end
context 'when handling lfs request using deprecated API' do
before do
post_json "#{project.http_url_to_repo}/info/lfs/objects", nil, headers
end
it_behaves_like 'a deprecated'
end end
end end
describe 'when fetching lfs object' do describe 'when fetching lfs object' do
let(:project) { create(:empty_project) }
let(:update_permissions) { }
before do before do
enable_lfs enable_lfs
env['HTTP_ACCEPT'] = "application/vnd.git-lfs+json; charset=utf-8" update_permissions
env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}" get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", nil, headers
end end
describe 'and request comes from gitlab-workhorse' do context 'and request comes from gitlab-workhorse' do
context 'without user being authorized' do context 'without user being authorized' do
it "responds with status 401" do it 'responds with status 401' do
expect(lfs_router_noauth.try_call.first).to eq(401) expect(response).to have_http_status(401)
end end
end end
context 'with required headers' do context 'with required headers' do
before do shared_examples 'responds with a file' do
project.lfs_objects << lfs_object let(:sendfile) { 'X-Sendfile' }
env['HTTP_X_SENDFILE_TYPE'] = "X-Sendfile"
end
context 'when user does not have project access' do it 'responds with status 200' do
it "responds with status 403" do expect(response).to have_http_status(200)
expect(lfs_router_auth.try_call.first).to eq(403)
end end
end
context 'when user has project access' do it 'responds with the file location' do
before do expect(response.headers['Content-Type']).to eq('application/octet-stream')
project.team << [user, :master] expect(response.headers['X-Sendfile']).to eq(lfs_object.file.path)
end end
end
context 'with user is authorized' do
let(:authorization) { authorize_user }
context 'and does not have project access' do
let(:update_permissions) do
project.lfs_objects << lfs_object
end
it "responds with status 200" do it 'responds with status 403' do
expect(lfs_router_auth.try_call.first).to eq(200) expect(response).to have_http_status(403)
end
end end
it "responds with the file location" do context 'and does have project access' do
expect(lfs_router_auth.try_call[1]['Content-Type']).to eq("application/octet-stream") let(:update_permissions) do
expect(lfs_router_auth.try_call[1]['X-Sendfile']).to eq(lfs_object.file.path) project.team << [user, :master]
project.lfs_objects << lfs_object
end
it_behaves_like 'responds with a file'
end end
end end
context 'when CI is authorized' do context 'when CI is authorized' do
it "responds with status 200" do let(:authorization) { authorize_ci_project }
expect(lfs_router_ci_auth.try_call.first).to eq(200)
end
it "responds with the file location" do let(:update_permissions) do
expect(lfs_router_ci_auth.try_call[1]['Content-Type']).to eq("application/octet-stream") project.lfs_objects << lfs_object
expect(lfs_router_ci_auth.try_call[1]['X-Sendfile']).to eq(lfs_object.file.path)
end end
it_behaves_like 'responds with a file'
end end
end end
context 'without required headers' do context 'without required headers' do
it "responds with status 403" do let(:authorization) { authorize_user }
expect(lfs_router_auth.try_call.first).to eq(403)
it 'responds with status 403' do
expect(response).to have_http_status(403)
end end
end end
end end
end end
describe 'when handling lfs request using deprecated API' do
before do
enable_lfs
env['REQUEST_METHOD'] = 'POST'
env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/info/lfs/objects"
end
it 'responds with 501' do
expect(lfs_router_auth.try_call).to match_array(respond_with_deprecated)
end
end
describe 'when handling lfs batch request' do describe 'when handling lfs batch request' do
let(:update_lfs_permissions) { }
let(:update_user_permissions) { }
before do before do
enable_lfs enable_lfs
env['REQUEST_METHOD'] = 'POST' update_lfs_permissions
env['PATH_INFO'] = "#{project.repository.path_with_namespace}.git/info/lfs/objects/batch" update_user_permissions
post_json "#{project.http_url_to_repo}/info/lfs/objects/batch", body, headers
end end
describe 'download' do describe 'download' do
before do let(:project) { create(:empty_project) }
body = { 'operation' => 'download', let(:body) do
'objects' => [ { 'operation' => 'download',
{ 'oid' => sample_oid, 'objects' => [
'size' => sample_size { 'oid' => sample_oid,
}] 'size' => sample_size
}.to_json }]
env['rack.input'] = StringIO.new(body) }
end end
shared_examples 'an authorized requests' do shared_examples 'an authorized requests' do
context 'when downloading an lfs object that is assigned to our project' do context 'when downloading an lfs object that is assigned to our project' do
before do let(:update_lfs_permissions) do
project.lfs_objects << lfs_object project.lfs_objects << lfs_object
end end
it 'responds with status 200 and href to download' do it 'responds with status 200' do
response = router.try_call expect(response).to have_http_status(200)
expect(response.first).to eq(200) end
response_body = ActiveSupport::JSON.decode(response.last.first)
expect(response_body).to eq('objects' => [ it 'with href to download' do
expect(json_response).to eq('objects' => [
{ 'oid' => sample_oid, { 'oid' => sample_oid,
'size' => sample_size, 'size' => sample_size,
'actions' => { 'actions' => {
'download' => { 'download' => {
'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}",
'header' => { 'Authorization' => auth } 'header' => { 'Authorization' => authorization }
} }
} }
}]) }])
...@@ -183,16 +201,17 @@ describe Gitlab::Lfs::Router, lib: true do ...@@ -183,16 +201,17 @@ describe Gitlab::Lfs::Router, lib: true do
end end
context 'when downloading an lfs object that is assigned to other project' do context 'when downloading an lfs object that is assigned to other project' do
before do let(:other_project) { create(:empty_project) }
public_project.lfs_objects << lfs_object let(:update_lfs_permissions) do
other_project.lfs_objects << lfs_object
end end
it 'responds with status 200 and error message' do it 'responds with status 200' do
response = router.try_call expect(response).to have_http_status(200)
expect(response.first).to eq(200) end
response_body = ActiveSupport::JSON.decode(response.last.first)
expect(response_body).to eq('objects' => [ it 'with href to download' do
expect(json_response).to eq('objects' => [
{ 'oid' => sample_oid, { 'oid' => sample_oid,
'size' => sample_size, 'size' => sample_size,
'error' => { 'error' => {
...@@ -204,22 +223,21 @@ describe Gitlab::Lfs::Router, lib: true do ...@@ -204,22 +223,21 @@ describe Gitlab::Lfs::Router, lib: true do
end end
context 'when downloading a lfs object that does not exist' do context 'when downloading a lfs object that does not exist' do
before do let(:body) do
body = { 'operation' => 'download', { 'operation' => 'download',
'objects' => [ 'objects' => [
{ 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
'size' => 1575078 'size' => 1575078
}] }]
}.to_json }
env['rack.input'] = StringIO.new(body)
end end
it "responds with status 200 and error message" do it 'responds with status 200' do
response = router.try_call expect(response).to have_http_status(200)
expect(response.first).to eq(200) end
response_body = ActiveSupport::JSON.decode(response.last.first)
expect(response_body).to eq('objects' => [ it 'with an 404 for specific object' do
expect(json_response).to eq('objects' => [
{ 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
'size' => 1575078, 'size' => 1575078,
'error' => { 'error' => {
...@@ -231,27 +249,29 @@ describe Gitlab::Lfs::Router, lib: true do ...@@ -231,27 +249,29 @@ describe Gitlab::Lfs::Router, lib: true do
end end
context 'when downloading one new and one existing lfs object' do context 'when downloading one new and one existing lfs object' do
before do let(:body) do
body = { 'operation' => 'download', { 'operation' => 'download',
'objects' => [ 'objects' => [
{ 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
'size' => 1575078 'size' => 1575078
}, },
{ 'oid' => sample_oid, { 'oid' => sample_oid,
'size' => sample_size 'size' => sample_size
} }
] ]
}.to_json }
env['rack.input'] = StringIO.new(body) end
let(:update_lfs_permissions) do
project.lfs_objects << lfs_object project.lfs_objects << lfs_object
end end
it "responds with status 200 with upload hypermedia link for the new object" do it 'responds with status 200' do
response = router.try_call expect(response).to have_http_status(200)
expect(response.first).to eq(200) end
response_body = ActiveSupport::JSON.decode(response.last.first)
expect(response_body).to eq('objects' => [ it 'responds with upload hypermedia link for the new object' do
expect(json_response).to eq('objects' => [
{ 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
'size' => 1575078, 'size' => 1575078,
'error' => { 'error' => {
...@@ -264,7 +284,7 @@ describe Gitlab::Lfs::Router, lib: true do ...@@ -264,7 +284,7 @@ describe Gitlab::Lfs::Router, lib: true do
'actions' => { 'actions' => {
'download' => { 'download' => {
'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}",
'header' => { 'Authorization' => auth } 'header' => { 'Authorization' => authorization }
} }
} }
}]) }])
...@@ -273,23 +293,21 @@ describe Gitlab::Lfs::Router, lib: true do ...@@ -273,23 +293,21 @@ describe Gitlab::Lfs::Router, lib: true do
end end
context 'when user is authenticated' do context 'when user is authenticated' do
let(:auth) { authorize(user) } let(:authorization) { authorize_user }
before do let(:update_user_permissions) do
env["HTTP_AUTHORIZATION"] = auth
project.team << [user, role] project.team << [user, role]
end end
it_behaves_like 'an authorized requests' do it_behaves_like 'an authorized requests' do
let(:role) { :reporter } let(:role) { :reporter }
let(:router) { lfs_router_auth }
end end
context 'when user does is not member of the project' do context 'when user does is not member of the project' do
let(:role) { :guest } let(:role) { :guest }
it 'responds with 403' do it 'responds with 403' do
expect(lfs_router_auth.try_call.first).to eq(403) expect(response).to have_http_status(403)
end end
end end
...@@ -297,40 +315,36 @@ describe Gitlab::Lfs::Router, lib: true do ...@@ -297,40 +315,36 @@ describe Gitlab::Lfs::Router, lib: true do
let(:role) { :guest } let(:role) { :guest }
it 'responds with 403' do it 'responds with 403' do
expect(lfs_router_auth.try_call.first).to eq(403) expect(response).to have_http_status(403)
end end
end end
end end
context 'when CI is authorized' do context 'when CI is authorized' do
let(:auth) { 'gitlab-ci-token:password' } let(:authorization) { authorize_ci_project }
before do
env["HTTP_AUTHORIZATION"] = auth
end
it_behaves_like 'an authorized requests' do it_behaves_like 'an authorized requests'
let(:router) { lfs_router_ci_auth }
end
end end
context 'when user is not authenticated' do context 'when user is not authenticated' do
describe 'is accessing public project' do describe 'is accessing public project' do
before do let(:project) { create(:project, :public) }
public_project.lfs_objects << lfs_object
let(:update_lfs_permissions) do
project.lfs_objects << lfs_object
end end
it 'responds with status 200 and href to download' do it 'responds with status 200 and href to download' do
response = lfs_router_public_noauth.try_call expect(response).to have_http_status(200)
expect(response.first).to eq(200) end
response_body = ActiveSupport::JSON.decode(response.last.first)
expect(response_body).to eq('objects' => [ it 'responds with status 200 and href to download' do
expect(json_response).to eq('objects' => [
{ 'oid' => sample_oid, { 'oid' => sample_oid,
'size' => sample_size, 'size' => sample_size,
'actions' => { 'actions' => {
'download' => { 'download' => {
'href' => "#{public_project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}",
'header' => {} 'header' => {}
} }
} }
...@@ -339,172 +353,174 @@ describe Gitlab::Lfs::Router, lib: true do ...@@ -339,172 +353,174 @@ describe Gitlab::Lfs::Router, lib: true do
end end
describe 'is accessing non-public project' do describe 'is accessing non-public project' do
before do let(:update_lfs_permissions) do
project.lfs_objects << lfs_object project.lfs_objects << lfs_object
end end
it 'responds with authorization required' do it 'responds with authorization required' do
expect(lfs_router_noauth.try_call.first).to eq(401) expect(response).to have_http_status(401)
end end
end end
end end
end end
describe 'upload' do describe 'upload' do
before do let(:project) { create(:project, :public) }
body = { 'operation' => 'upload', let(:body) do
'objects' => [ { 'operation' => 'upload',
{ 'oid' => sample_oid, 'objects' => [
'size' => sample_size { 'oid' => sample_oid,
}] 'size' => sample_size
}.to_json }]
env['rack.input'] = StringIO.new(body) }
end end
describe 'when request is authenticated' do describe 'when request is authenticated' do
describe 'when user has project push access' do describe 'when user has project push access' do
before do let(:authorization) { authorize_user }
@auth = authorize(user)
env["HTTP_AUTHORIZATION"] = @auth let(:update_user_permissions) do
project.team << [user, :developer] project.team << [user, :developer]
end end
context 'when pushing an lfs object that already exists' do context 'when pushing an lfs object that already exists' do
before do let(:other_project) { create(:empty_project) }
public_project.lfs_objects << lfs_object let(:update_lfs_permissions) do
other_project.lfs_objects << lfs_object
end end
it "responds with status 200 and links the object to the project" do it 'responds with status 200' do
response_body = lfs_router_auth.try_call.last expect(response).to have_http_status(200)
response = ActiveSupport::JSON.decode(response_body.first) end
expect(response['objects']).to be_kind_of(Array) it 'responds with links the object to the project' do
expect(response['objects'].first['oid']).to eq(sample_oid) expect(json_response['objects']).to be_kind_of(Array)
expect(response['objects'].first['size']).to eq(sample_size) expect(json_response['objects'].first['oid']).to eq(sample_oid)
expect(json_response['objects'].first['size']).to eq(sample_size)
expect(lfs_object.projects.pluck(:id)).not_to include(project.id) expect(lfs_object.projects.pluck(:id)).not_to include(project.id)
expect(lfs_object.projects.pluck(:id)).to include(public_project.id) expect(lfs_object.projects.pluck(:id)).to include(other_project.id)
expect(response['objects'].first['actions']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}") expect(json_response['objects'].first['actions']['upload']['href']).to eq("#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}")
expect(response['objects'].first['actions']['upload']['header']).to eq('Authorization' => @auth) expect(json_response['objects'].first['actions']['upload']['header']).to eq('Authorization' => authorization)
end end
end end
context 'when pushing a lfs object that does not exist' do context 'when pushing a lfs object that does not exist' do
before do let(:body) do
body = { 'operation' => 'upload', { 'operation' => 'upload',
'objects' => [ 'objects' => [
{ 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
'size' => 1575078 'size' => 1575078
}] }]
}.to_json }
env['rack.input'] = StringIO.new(body)
end end
it "responds with status 200 and upload hypermedia link" do it 'responds with status 200' do
response = lfs_router_auth.try_call expect(response).to have_http_status(200)
expect(response.first).to eq(200) end
response_body = ActiveSupport::JSON.decode(response.last.first) it 'responds with upload hypermedia link' do
expect(response_body['objects']).to be_kind_of(Array) expect(json_response['objects']).to be_kind_of(Array)
expect(response_body['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897") expect(json_response['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897")
expect(response_body['objects'].first['size']).to eq(1575078) expect(json_response['objects'].first['size']).to eq(1575078)
expect(lfs_object.projects.pluck(:id)).not_to include(project.id) expect(json_response['objects'].first['actions']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078")
expect(response_body['objects'].first['actions']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078") expect(json_response['objects'].first['actions']['upload']['header']).to eq('Authorization' => authorization)
expect(response_body['objects'].first['actions']['upload']['header']).to eq('Authorization' => @auth)
end end
end end
context 'when pushing one new and one existing lfs object' do context 'when pushing one new and one existing lfs object' do
before do let(:body) do
body = { 'operation' => 'upload', { 'operation' => 'upload',
'objects' => [ 'objects' => [
{ 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
'size' => 1575078 'size' => 1575078
}, },
{ 'oid' => sample_oid, { 'oid' => sample_oid,
'size' => sample_size 'size' => sample_size
} }
] ]
}.to_json }
env['rack.input'] = StringIO.new(body) end
let(:update_lfs_permissions) do
project.lfs_objects << lfs_object project.lfs_objects << lfs_object
end end
it "responds with status 200 with upload hypermedia link for the new object" do it 'responds with status 200' do
response = lfs_router_auth.try_call expect(response).to have_http_status(200)
expect(response.first).to eq(200) end
response_body = ActiveSupport::JSON.decode(response.last.first) it 'responds with upload hypermedia link for the new object' do
expect(response_body['objects']).to be_kind_of(Array) expect(json_response['objects']).to be_kind_of(Array)
expect(response_body['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897") expect(json_response['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897")
expect(response_body['objects'].first['size']).to eq(1575078) expect(json_response['objects'].first['size']).to eq(1575078)
expect(response_body['objects'].first['actions']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078") expect(json_response['objects'].first['actions']['upload']['href']).to eq("#{project.http_url_to_repo}/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078")
expect(response_body['objects'].first['actions']['upload']['header']).to eq("Authorization" => @auth) expect(json_response['objects'].first['actions']['upload']['header']).to eq("Authorization" => authorization)
expect(response_body['objects'].last['oid']).to eq(sample_oid) expect(json_response['objects'].last['oid']).to eq(sample_oid)
expect(response_body['objects'].last['size']).to eq(sample_size) expect(json_response['objects'].last['size']).to eq(sample_size)
expect(response_body['objects'].last).not_to have_key('actions') expect(json_response['objects'].last).not_to have_key('actions')
end end
end end
end end
context 'when user does not have push access' do context 'when user does not have push access' do
let(:authorization) { authorize_user }
it 'responds with 403' do it 'responds with 403' do
expect(lfs_router_auth.try_call.first).to eq(403) expect(response).to have_http_status(403)
end end
end end
context 'when CI is authorized' do context 'when CI is authorized' do
let(:authorization) { authorize_ci_project }
it 'responds with 401' do it 'responds with 401' do
expect(lfs_router_ci_auth.try_call.first).to eq(401) expect(response).to have_http_status(401)
end end
end end
end end
context 'when user is not authenticated' do context 'when user is not authenticated' do
context 'when user has push access' do context 'when user has push access' do
before do let(:update_user_permissions) do
project.team << [user, :master] project.team << [user, :master]
end end
it "responds with status 401" do it 'responds with status 401' do
expect(lfs_router_public_noauth.try_call.first).to eq(401) expect(response).to have_http_status(401)
end end
end end
context 'when user does not have push access' do context 'when user does not have push access' do
it "responds with status 401" do it 'responds with status 401' do
expect(lfs_router_public_noauth.try_call.first).to eq(401) expect(response).to have_http_status(401)
end end
end end
end end
context 'when CI is authorized' do context 'when CI is authorized' do
let(:auth) { 'gitlab-ci-token:password' } let(:authorization) { authorize_ci_project }
before do it 'responds with status 403' do
env["HTTP_AUTHORIZATION"] = auth expect(response).to have_http_status(401)
end
it "responds with status 403" do
expect(lfs_router_public_ci_auth.try_call.first).to eq(401)
end end
end end
end end
describe 'unsupported' do describe 'unsupported' do
before do let(:project) { create(:empty_project) }
body = { 'operation' => 'other', let(:body) do
'objects' => [ { 'operation' => 'other',
{ 'oid' => sample_oid, 'objects' => [
'size' => sample_size { 'oid' => sample_oid,
}] 'size' => sample_size
}.to_json }]
env['rack.input'] = StringIO.new(body) }
end end
it 'responds with status 404' do it 'responds with status 404' do
expect(lfs_router_public_noauth.try_call.first).to eq(404) expect(response).to have_http_status(404)
end end
end end
end end
...@@ -512,38 +528,36 @@ describe Gitlab::Lfs::Router, lib: true do ...@@ -512,38 +528,36 @@ describe Gitlab::Lfs::Router, lib: true do
describe 'when pushing a lfs object' do describe 'when pushing a lfs object' do
before do before do
enable_lfs enable_lfs
env['REQUEST_METHOD'] = 'PUT'
end end
shared_examples 'unauthorized' do shared_examples 'unauthorized' do
context 'and request is sent by gitlab-workhorse to authorize the request' do context 'and request is sent by gitlab-workhorse to authorize the request' do
before do before do
header_for_upload_authorize(router.project) put_authorize
end end
it 'responds with status 401' do it 'responds with status 401' do
expect(router.try_call.first).to eq(401) expect(response).to have_http_status(401)
end end
end end
context 'and request is sent by gitlab-workhorse to finalize the upload' do context 'and request is sent by gitlab-workhorse to finalize the upload' do
before do before do
headers_for_upload_finalize(router.project) put_finalize
end end
it 'responds with status 401' do it 'responds with status 401' do
expect(router.try_call.first).to eq(401) expect(response).to have_http_status(401)
end end
end end
context 'and request is sent with a malformed headers' do context 'and request is sent with a malformed headers' do
before do before do
env["PATH_INFO"] = "#{router.project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}" put_finalize('cat /etc/passwd')
env["HTTP_X_GITLAB_LFS_TMP"] = "cat /etc/passwd"
end end
it 'does not recognize it as a valid lfs command' do it 'does not recognize it as a valid lfs command' do
expect(router.try_call).to eq(nil) expect(response).to have_http_status(403)
end end
end end
end end
...@@ -551,27 +565,31 @@ describe Gitlab::Lfs::Router, lib: true do ...@@ -551,27 +565,31 @@ describe Gitlab::Lfs::Router, lib: true do
shared_examples 'forbidden' do shared_examples 'forbidden' do
context 'and request is sent by gitlab-workhorse to authorize the request' do context 'and request is sent by gitlab-workhorse to authorize the request' do
before do before do
header_for_upload_authorize(router.project) put_authorize
end end
it 'responds with 403' do it 'responds with 403' do
expect(router.try_call.first).to eq(403) expect(response).to have_http_status(403)
end end
end end
context 'and request is sent by gitlab-workhorse to finalize the upload' do context 'and request is sent by gitlab-workhorse to finalize the upload' do
before do before do
headers_for_upload_finalize(router.project) put_finalize
end end
it 'responds with 403' do it 'responds with 403' do
expect(router.try_call.first).to eq(403) expect(response).to have_http_status(403)
end end
end end
end end
describe 'to one project' do describe 'to one project' do
let(:project) { create(:empty_project) }
describe 'when user is authenticated' do describe 'when user is authenticated' do
let(:authorization) { authorize_user }
describe 'when user has push access to the project' do describe 'when user has push access to the project' do
before do before do
project.team << [user, :developer] project.team << [user, :developer]
...@@ -579,13 +597,14 @@ describe Gitlab::Lfs::Router, lib: true do ...@@ -579,13 +597,14 @@ describe Gitlab::Lfs::Router, lib: true do
context 'and request is sent by gitlab-workhorse to authorize the request' do context 'and request is sent by gitlab-workhorse to authorize the request' do
before do before do
header_for_upload_authorize(project) put_authorize
end end
it 'responds with status 200, location of lfs store and object details' do it 'responds with status 200' do
json_response = ActiveSupport::JSON.decode(lfs_router_auth.try_call.last.first) expect(response).to have_http_status(200)
end
expect(lfs_router_auth.try_call.first).to eq(200) it 'responds with status 200, location of lfs store and object details' do
expect(json_response['StoreLFSPath']).to eq("#{Gitlab.config.shared.path}/lfs-objects/tmp/upload") expect(json_response['StoreLFSPath']).to eq("#{Gitlab.config.shared.path}/lfs-objects/tmp/upload")
expect(json_response['LfsOid']).to eq(sample_oid) expect(json_response['LfsOid']).to eq(sample_oid)
expect(json_response['LfsSize']).to eq(sample_size) expect(json_response['LfsSize']).to eq(sample_size)
...@@ -594,54 +613,58 @@ describe Gitlab::Lfs::Router, lib: true do ...@@ -594,54 +613,58 @@ describe Gitlab::Lfs::Router, lib: true do
context 'and request is sent by gitlab-workhorse to finalize the upload' do context 'and request is sent by gitlab-workhorse to finalize the upload' do
before do before do
headers_for_upload_finalize(project) put_finalize
end
it 'responds with status 200' do
expect(response).to have_http_status(200)
end end
it 'responds with status 200 and lfs object is linked to the project' do it 'lfs object is linked to the project' do
expect(lfs_router_auth.try_call.first).to eq(200)
expect(lfs_object.projects.pluck(:id)).to include(project.id) expect(lfs_object.projects.pluck(:id)).to include(project.id)
end end
end end
end end
describe 'and user does not have push access' do describe 'and user does not have push access' do
let(:router) { lfs_router_auth }
it_behaves_like 'forbidden' it_behaves_like 'forbidden'
end end
end end
context 'when CI is authenticated' do context 'when CI is authenticated' do
let(:router) { lfs_router_ci_auth } let(:authorization) { authorize_ci_project }
it_behaves_like 'unauthorized' it_behaves_like 'unauthorized'
end end
context 'for unauthenticated' do context 'for unauthenticated' do
let(:router) { new_lfs_router(project) }
it_behaves_like 'unauthorized' it_behaves_like 'unauthorized'
end end
end end
describe 'to a forked project' do describe 'to a forked project' do
let(:forked_project) { fork_project(public_project, user) } let(:upstream_project) { create(:project, :public) }
let(:project_owner) { create(:user) }
let(:project) { fork_project(upstream_project, project_owner) }
describe 'when user is authenticated' do describe 'when user is authenticated' do
let(:authorization) { authorize_user }
describe 'when user has push access to the project' do describe 'when user has push access to the project' do
before do before do
forked_project.team << [user_two, :developer] project.team << [user, :developer]
end end
context 'and request is sent by gitlab-workhorse to authorize the request' do context 'and request is sent by gitlab-workhorse to authorize the request' do
before do before do
header_for_upload_authorize(forked_project) put_authorize
end end
it 'responds with status 200, location of lfs store and object details' do it 'responds with status 200' do
json_response = ActiveSupport::JSON.decode(lfs_router_forked_auth.try_call.last.first) expect(response).to have_http_status(200)
end
expect(lfs_router_forked_auth.try_call.first).to eq(200) it 'with location of lfs store and object details' do
expect(json_response['StoreLFSPath']).to eq("#{Gitlab.config.shared.path}/lfs-objects/tmp/upload") expect(json_response['StoreLFSPath']).to eq("#{Gitlab.config.shared.path}/lfs-objects/tmp/upload")
expect(json_response['LfsOid']).to eq(sample_oid) expect(json_response['LfsOid']).to eq(sample_oid)
expect(json_response['LfsSize']).to eq(sample_size) expect(json_response['LfsSize']).to eq(sample_size)
...@@ -650,81 +673,96 @@ describe Gitlab::Lfs::Router, lib: true do ...@@ -650,81 +673,96 @@ describe Gitlab::Lfs::Router, lib: true do
context 'and request is sent by gitlab-workhorse to finalize the upload' do context 'and request is sent by gitlab-workhorse to finalize the upload' do
before do before do
headers_for_upload_finalize(forked_project) put_finalize
end
it 'responds with status 200' do
expect(response).to have_http_status(200)
end end
it 'responds with status 200 and lfs object is linked to the source project' do it 'lfs object is linked to the source project' do
expect(lfs_router_forked_auth.try_call.first).to eq(200) expect(lfs_object.projects.pluck(:id)).to include(upstream_project.id)
expect(lfs_object.projects.pluck(:id)).to include(public_project.id)
end end
end end
end end
describe 'and user does not have push access' do describe 'and user does not have push access' do
let(:router) { lfs_router_forked_auth }
it_behaves_like 'forbidden' it_behaves_like 'forbidden'
end end
end end
context 'when CI is authenticated' do context 'when CI is authenticated' do
let(:router) { lfs_router_forked_ci_auth } let(:authorization) { authorize_ci_project }
it_behaves_like 'unauthorized' it_behaves_like 'unauthorized'
end end
context 'for unauthenticated' do context 'for unauthenticated' do
let(:router) { lfs_router_forked_noauth }
it_behaves_like 'unauthorized' it_behaves_like 'unauthorized'
end end
describe 'and second project not related to fork or a source project' do describe 'and second project not related to fork or a source project' do
let(:second_project) { create(:project) } let(:second_project) { create(:empty_project) }
let(:lfs_router_second_project) { new_lfs_router(second_project, user: user) } let(:authorization) { authorize_user }
before do before do
public_project.lfs_objects << lfs_object second_project.team << [user, :master]
headers_for_upload_finalize(second_project) upstream_project.lfs_objects << lfs_object
end end
context 'when pushing the same lfs object to the second project' do context 'when pushing the same lfs object to the second project' do
before do before do
second_project.team << [user, :master] put "#{second_project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}", nil,
headers.merge('X-Gitlab-Lfs-Tmp' => lfs_tmp_file).compact
end
it 'responds with status 200' do
expect(response).to have_http_status(200)
end end
it 'responds with 200 and links the lfs object to the project' do it 'links the lfs object to the project' do
expect(lfs_router_second_project.try_call.first).to eq(200) expect(lfs_object.projects.pluck(:id)).to include(second_project.id, upstream_project.id)
expect(lfs_object.projects.pluck(:id)).to include(second_project.id, public_project.id)
end end
end end
end end
end end
def put_authorize
put "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}/authorize", nil, headers
end
def put_finalize(lfs_tmp = lfs_tmp_file)
put "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}", nil,
headers.merge('X-Gitlab-Lfs-Tmp' => lfs_tmp).compact
end
def lfs_tmp_file
"#{sample_oid}012345678"
end
end end
def enable_lfs def enable_lfs
allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
end end
def authorize(user) def authorize_ci_project
ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password) ActionController::HttpAuthentication::Basic.encode_credentials('gitlab-ci-token', project.runners_token)
end end
def new_lfs_router(project, user: nil, ci: false) def authorize_user
Gitlab::Lfs::Router.new(project, user, ci, request) ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password)
end end
def header_for_upload_authorize(project) def fork_project(project, user, object = nil)
env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}/authorize" allow(RepositoryForkWorker).to receive(:perform_async).and_return(true)
Projects::ForkService.new(project, user, {}).execute
end end
def headers_for_upload_finalize(project) def post_json(url, body = nil, headers = nil)
env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}" post(url, body.try(:to_json), (headers || {}).merge('Content-Type' => 'application/json'))
env["HTTP_X_GITLAB_LFS_TMP"] = "#{sample_oid}6e561c9d4"
end end
def fork_project(project, user, object = nil) def json_response
allow(RepositoryForkWorker).to receive(:perform_async).and_return(true) @json_response ||= JSON.parse(response.body)
Projects::ForkService.new(project, user, {}).execute
end end
end end
...@@ -87,51 +87,105 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do ...@@ -87,51 +87,105 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do
end end
context 'user authorization' do context 'user authorization' do
let(:project) { create(:project) }
let(:current_user) { create(:user) } let(:current_user) { create(:user) }
context 'allow to use scope-less authentication' do context 'for private project' do
it_behaves_like 'a valid token' let(:project) { create(:empty_project) }
end
context 'allow developer to push images' do context 'allow to use scope-less authentication' do
before { project.team << [current_user, :developer] } it_behaves_like 'a valid token'
end
let(:current_params) do context 'allow developer to push images' do
{ scope: "repository:#{project.path_with_namespace}:push" } before { project.team << [current_user, :developer] }
let(:current_params) do
{ scope: "repository:#{project.path_with_namespace}:push" }
end
it_behaves_like 'a pushable'
end end
it_behaves_like 'a pushable' context 'allow reporter to pull images' do
end before { project.team << [current_user, :reporter] }
let(:current_params) do
{ scope: "repository:#{project.path_with_namespace}:pull" }
end
context 'allow reporter to pull images' do it_behaves_like 'a pullable'
before { project.team << [current_user, :reporter] } end
let(:current_params) do context 'return a least of privileges' do
{ scope: "repository:#{project.path_with_namespace}:pull" } before { project.team << [current_user, :reporter] }
let(:current_params) do
{ scope: "repository:#{project.path_with_namespace}:push,pull" }
end
it_behaves_like 'a pullable'
end end
it_behaves_like 'a pullable' context 'disallow guest to pull or push images' do
before { project.team << [current_user, :guest] }
let(:current_params) do
{ scope: "repository:#{project.path_with_namespace}:pull,push" }
end
it_behaves_like 'an inaccessible'
end
end end
context 'return a least of privileges' do context 'for public project' do
before { project.team << [current_user, :reporter] } let(:project) { create(:empty_project, :public) }
let(:current_params) do context 'allow anyone to pull images' do
{ scope: "repository:#{project.path_with_namespace}:push,pull" } let(:current_params) do
{ scope: "repository:#{project.path_with_namespace}:pull" }
end
it_behaves_like 'a pullable'
end end
it_behaves_like 'a pullable' context 'disallow anyone to push images' do
let(:current_params) do
{ scope: "repository:#{project.path_with_namespace}:push" }
end
it_behaves_like 'an inaccessible'
end
end end
context 'disallow guest to pull or push images' do context 'for internal project' do
before { project.team << [current_user, :guest] } let(:project) { create(:empty_project, :internal) }
let(:current_params) do context 'for internal user' do
{ scope: "repository:#{project.path_with_namespace}:pull,push" } context 'allow anyone to pull images' do
let(:current_params) do
{ scope: "repository:#{project.path_with_namespace}:pull" }
end
it_behaves_like 'a pullable'
end
context 'disallow anyone to push images' do
let(:current_params) do
{ scope: "repository:#{project.path_with_namespace}:push" }
end
it_behaves_like 'an inaccessible'
end
end end
it_behaves_like 'an inaccessible' context 'for external user' do
let(:current_user) { create(:user, external: true) }
let(:current_params) do
{ scope: "repository:#{project.path_with_namespace}:pull,push" }
end
it_behaves_like 'an inaccessible'
end
end end
end end
......
...@@ -3,7 +3,7 @@ require 'spec_helper' ...@@ -3,7 +3,7 @@ require 'spec_helper'
describe CreateCommitBuildsService, services: true do describe CreateCommitBuildsService, services: true do
let(:service) { CreateCommitBuildsService.new } let(:service) { CreateCommitBuildsService.new }
let(:project) { FactoryGirl.create(:empty_project) } let(:project) { FactoryGirl.create(:empty_project) }
let(:user) { nil } let(:user) { create(:user) }
before do before do
stub_ci_pipeline_to_return_yaml_file stub_ci_pipeline_to_return_yaml_file
...@@ -24,6 +24,7 @@ describe CreateCommitBuildsService, services: true do ...@@ -24,6 +24,7 @@ describe CreateCommitBuildsService, services: true do
it { expect(pipeline).to be_valid } it { expect(pipeline).to be_valid }
it { expect(pipeline).to be_persisted } it { expect(pipeline).to be_persisted }
it { expect(pipeline).to eq(project.pipelines.last) } it { expect(pipeline).to eq(project.pipelines.last) }
it { expect(pipeline).to have_attributes(user: user) }
it { expect(pipeline.builds.first).to be_kind_of(Ci::Build) } it { expect(pipeline.builds.first).to be_kind_of(Ci::Build) }
end end
......
...@@ -89,6 +89,12 @@ describe CreateDeploymentService, services: true do ...@@ -89,6 +89,12 @@ describe CreateDeploymentService, services: true do
expect_any_instance_of(described_class).to receive(:execute) expect_any_instance_of(described_class).to receive(:execute)
subject subject
end end
it 'is set as deployable' do
subject
expect(Deployment.last.deployable).to eq(deployable)
end
end end
context 'without environment specified' do context 'without environment specified' do
...@@ -105,6 +111,8 @@ describe CreateDeploymentService, services: true do ...@@ -105,6 +111,8 @@ describe CreateDeploymentService, services: true do
context 'when build succeeds' do context 'when build succeeds' do
it_behaves_like 'does create environment and deployment' do it_behaves_like 'does create environment and deployment' do
let(:deployable) { build }
subject { build.success } subject { build.success }
end end
end end
...@@ -114,6 +122,14 @@ describe CreateDeploymentService, services: true do ...@@ -114,6 +122,14 @@ describe CreateDeploymentService, services: true do
subject { build.drop } subject { build.drop }
end end
end end
context 'when build is retried' do
it_behaves_like 'does create environment and deployment' do
let(:deployable) { Ci::Build.retry(build) }
subject { deployable.success }
end
end
end end
end end
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