Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRémy Coutable <remy@rymai.me>2016-07-18 12:28:43 +0300
committerRémy Coutable <remy@rymai.me>2016-07-18 12:28:43 +0300
commit57d4a1725e777095f6eb0fa821105574b0a6a02d (patch)
treeb1ab757009dce95c600e68913119162bebb02fe0
parent7ce5bb18d32e89ec9f16e09f4edff1d7646d4d62 (diff)
parent240a4aa62ab24a8a7b5545dee6bd2cf00ad7596e (diff)
Merge remote-tracking branch 'origin/master' into 8-10-stable
-rw-r--r--CHANGELOG8
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss2
-rw-r--r--app/models/ability.rb3
-rw-r--r--app/models/ci/build.rb1
-rw-r--r--app/models/ci/pipeline.rb2
-rw-r--r--app/models/concerns/awardable.rb9
-rw-r--r--app/models/note.rb3
-rw-r--r--app/models/project.rb8
-rw-r--r--app/models/user.rb1
-rw-r--r--app/services/ci/create_pipeline_service.rb1
-rw-r--r--app/services/create_commit_builds_service.rb8
-rw-r--r--app/services/notes/create_service.rb1
-rw-r--r--app/views/projects/ci/pipelines/_pipeline.html.haml15
-rw-r--r--app/workers/project_export_worker.rb2
-rw-r--r--db/migrate/20160715132507_add_user_id_to_pipeline.rb7
-rw-r--r--db/migrate/20160715134306_add_index_for_pipeline_user_id.rb7
-rw-r--r--db/schema.rb4
-rw-r--r--doc/api/issues.md29
-rw-r--r--doc/ci/yaml/README.md4
-rw-r--r--lib/api/award_emoji.rb4
-rw-r--r--lib/api/commit_statuses.rb2
-rw-r--r--lib/api/entities.rb1
-rw-r--r--lib/api/internal.rb7
-rw-r--r--lib/api/issues.rb10
-rw-r--r--lib/banzai/filter/syntax_highlight_filter.rb15
-rw-r--r--lib/gitlab/backend/grack_auth.rb2
-rw-r--r--lib/gitlab/lfs/response.rb2
-rw-r--r--lib/gitlab/lfs/router.rb2
-rw-r--r--spec/features/security/project/internal_access_spec.rb19
-rw-r--r--spec/features/security/project/private_access_spec.rb19
-rw-r--r--spec/features/security/project/public_access_spec.rb19
-rw-r--r--spec/lib/banzai/filter/syntax_highlight_filter_spec.rb34
-rw-r--r--spec/lib/gitlab/bitbucket_import/client_spec.rb2
-rw-r--r--spec/lib/gitlab/lfs/lfs_router_spec.rb730
-rw-r--r--spec/models/ci/pipeline_spec.rb3
-rw-r--r--spec/models/commit_status_spec.rb24
-rw-r--r--spec/models/project_spec.rb4
-rw-r--r--spec/models/user_spec.rb2
-rw-r--r--spec/requests/api/award_emoji_spec.rb32
-rw-r--r--spec/requests/api/internal_spec.rb14
-rw-r--r--spec/requests/api/issues_spec.rb25
-rw-r--r--spec/requests/lfs_http_spec.rb768
-rw-r--r--spec/services/auth/container_registry_authentication_service_spec.rb104
-rw-r--r--spec/services/create_commit_builds_service_spec.rb3
-rw-r--r--spec/services/create_deployment_service_spec.rb16
45 files changed, 1150 insertions, 828 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 5518429e1b5..94f212324f7 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -31,6 +31,7 @@ v 8.10.0 (unreleased)
- Support U2F devices in Firefox. !5177
- Fix issue, preventing users w/o push access to sort tags !5105 (redetection)
- Add Spring EmojiOne updates.
+ - Fix fetching LFS objects for private CI projects
- Add syntax for multiline blockquote using `>>>` fence !3954
- Fix viewing notification settings when a project is pending deletion
- Updated compare dropdown menus to use GL dropdown
@@ -47,6 +48,7 @@ v 8.10.0 (unreleased)
- Render inline diffs for multiple changed lines following eachother
- Wildcards for protected branches. !4665
- 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: Expose shared groups for projects and shared projects for groups !5050 (Robert Schilling)
- Add "Enabled Git access protocols" to Application Settings
@@ -55,13 +57,16 @@ v 8.10.0 (unreleased)
- Fix user creation with stronger minimum password requirements !4054 (nathan-pmt)
- Only show New Snippet button to users that can create snippets.
- 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
- Throttle the update of `project.pushes_since_gc` to 1 minute.
- Allow expanding and collapsing files in diff view (!4990)
- Collapse large diffs by default (!4990)
- 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.
- Show last push widget in upstream after push to fork
+ - Fix stage status shown for pipelines
- Cache todos pending/done dashboard query counts.
- Don't instantiate a git tree on Projects show default view
- Bump Rinku to 2.0.0
@@ -82,7 +87,9 @@ v 8.10.0 (unreleased)
- 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
- 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
+ - Allow to access Container Registry for Public and Internal projects
- Allow '?', or '&' for label names
- 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
@@ -103,6 +110,7 @@ v 8.10.0 (unreleased)
- Fix issues importing projects from EE to CE
- Fix creating group with space in group path
- 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
- Fix importing of events under notes for GitLab projects. !5154
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 08da4e290dc..a0334207c68 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -129,6 +129,8 @@
}
.cancel-retry-btns {
+ vertical-align: middle;
+
.btn:not(:first-child) {
margin-left: 8px;
}
diff --git a/app/models/ability.rb b/app/models/ability.rb
index eeb0ceba081..6fd18f2ee24 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -204,7 +204,8 @@ class Ability
:download_code,
:fork_project,
:read_commit_status,
- :read_pipeline
+ :read_pipeline,
+ :read_container_image
]
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index e189dbac285..b24527247e0 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -53,6 +53,7 @@ module Ci
new_build.stage_idx = build.stage_idx
new_build.trigger_request = build.trigger_request
new_build.user = user
+ new_build.environment = build.environment
new_build.save
MergeRequests::AddTodoWhenBuildFailsService.new(build.project, nil).close(new_build)
new_build
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 7d743ce99f0..b468434866b 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -6,6 +6,8 @@ module Ci
self.table_name = 'ci_commits'
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 :builds, class_name: 'Ci::Build', foreign_key: :commit_id
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest', foreign_key: :commit_id
diff --git a/app/models/concerns/awardable.rb b/app/models/concerns/awardable.rb
index 06beff177b1..800a16ab246 100644
--- a/app/models/concerns/awardable.rb
+++ b/app/models/concerns/awardable.rb
@@ -65,8 +65,7 @@ module Awardable
def create_award_emoji(name, current_user)
return unless emoji_awardable?
-
- award_emoji.create(name: name, user: current_user)
+ award_emoji.create(name: normalize_name(name), user: current_user)
end
def remove_award_emoji(name, current_user)
@@ -80,4 +79,10 @@ module Awardable
create_award_emoji(emoji_name, current_user)
end
end
+
+ private
+
+ def normalize_name(name)
+ Gitlab::AwardEmoji.normalize_emoji_name(name)
+ end
end
diff --git a/app/models/note.rb b/app/models/note.rb
index 8dca2ef09a8..0ce10c77de9 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -229,8 +229,7 @@ class Note < ActiveRecord::Base
end
def award_emoji_name
- original_name = note.match(Banzai::Filter::EmojiFilter.emoji_pattern)[1]
- Gitlab::AwardEmoji.normalize_emoji_name(original_name)
+ note.match(Banzai::Filter::EmojiFilter.emoji_pattern)[1]
end
private
diff --git a/app/models/project.rb b/app/models/project.rb
index e7b9835692d..1cdf22f6a51 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -162,7 +162,7 @@ class Project < ActiveRecord::Base
validates :namespace, presence: true
validates_uniqueness_of :name, 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 }
validate :check_limit, on: :create
validate :avatar_type,
@@ -482,7 +482,7 @@ class Project < ActiveRecord::Base
end
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
if data
@@ -1038,8 +1038,8 @@ class Project < ActiveRecord::Base
pipelines.order(id: :desc).find_by(sha: sha, ref: ref)
end
- def ensure_pipeline(sha, ref)
- pipeline(sha, ref) || pipelines.create(sha: sha, ref: ref)
+ def ensure_pipeline(sha, ref, current_user = nil)
+ pipeline(sha, ref) || pipelines.create(sha: sha, ref: ref, user: current_user)
end
def enable_ci
diff --git a/app/models/user.rb b/app/models/user.rb
index 7a72c202150..3d0a033785c 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -85,6 +85,7 @@ class User < ActiveRecord::Base
has_one :abuse_report, dependent: :destroy
has_many :spam_logs, dependent: :destroy
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 :notification_settings, dependent: :destroy
has_many :award_emoji, dependent: :destroy
diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb
index b1ee6874190..be91bf0db85 100644
--- a/app/services/ci/create_pipeline_service.rb
+++ b/app/services/ci/create_pipeline_service.rb
@@ -2,6 +2,7 @@ module Ci
class CreatePipelineService < BaseService
def execute
pipeline = project.pipelines.new(params)
+ pipeline.user = current_user
unless ref_names.include?(params[:ref])
pipeline.errors.add(:base, 'Reference not found')
diff --git a/app/services/create_commit_builds_service.rb b/app/services/create_commit_builds_service.rb
index f947e8f452e..0b66b854dea 100644
--- a/app/services/create_commit_builds_service.rb
+++ b/app/services/create_commit_builds_service.rb
@@ -14,7 +14,13 @@ class CreateCommitBuildsService
return false
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
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index 02fca5c0ea3..18971bd0be3 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -8,7 +8,6 @@ module Notes
if note.award_emoji?
noteable = note.noteable
todo_service.new_award_emoji(noteable, current_user)
-
return noteable.create_award_emoji(note.award_emoji_name, current_user)
end
diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml
index b53a8633937..0557d384e33 100644
--- a/app/views/projects/ci/pipelines/_pipeline.html.haml
+++ b/app/views/projects/ci/pipelines/_pipeline.html.haml
@@ -32,7 +32,7 @@
Cant find HEAD commit for this branch
- - stages_status = pipeline.statuses.stages_status
+ - stages_status = pipeline.statuses.latest.stages_status
- stages.each do |stage|
%td
- status = stages_status[stage]
@@ -58,16 +58,7 @@
.controls.hidden-xs.pull-right
- artifacts = pipeline.builds.latest.select { |b| b.artifacts? }
- if artifacts.present?
- .btn-group.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
+ .inline
.btn-group
%a.dropdown-toggle.btn.btn-default.build-artifacts{type: 'button', 'data-toggle' => 'dropdown'}
= icon("download")
@@ -80,7 +71,7 @@
%span Download '#{build.name}' artifacts
- if can?(current_user, :update_pipeline, @project)
- .cancel-retry-btns
+ .cancel-retry-btns.inline
- if pipeline.retryable?
= link_to retry_namespace_project_pipeline_path(@project.namespace, @project, pipeline.id), class: 'btn has-tooltip', title: "Retry", method: :post do
= icon("repeat")
diff --git a/app/workers/project_export_worker.rb b/app/workers/project_export_worker.rb
index 39f6037e077..615311e63f5 100644
--- a/app/workers/project_export_worker.rb
+++ b/app/workers/project_export_worker.rb
@@ -1,7 +1,7 @@
class ProjectExportWorker
include Sidekiq::Worker
- sidekiq_options queue: :gitlab_shell, retry: true
+ sidekiq_options queue: :gitlab_shell, retry: 3
def perform(current_user_id, project_id)
current_user = User.find(current_user_id)
diff --git a/db/migrate/20160715132507_add_user_id_to_pipeline.rb b/db/migrate/20160715132507_add_user_id_to_pipeline.rb
new file mode 100644
index 00000000000..af0461c4daf
--- /dev/null
+++ b/db/migrate/20160715132507_add_user_id_to_pipeline.rb
@@ -0,0 +1,7 @@
+class AddUserIdToPipeline < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ def change
+ add_column :ci_commits, :user_id, :integer
+ end
+end
diff --git a/db/migrate/20160715134306_add_index_for_pipeline_user_id.rb b/db/migrate/20160715134306_add_index_for_pipeline_user_id.rb
new file mode 100644
index 00000000000..e09caa0e6d7
--- /dev/null
+++ b/db/migrate/20160715134306_add_index_for_pipeline_user_id.rb
@@ -0,0 +1,7 @@
+class AddIndexForPipelineUserId < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ def change
+ add_concurrent_index :ci_commits, :user_id
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 8c12898eec9..48ecdc6ba1b 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# 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
enable_extension "plpgsql"
@@ -199,6 +199,7 @@ ActiveRecord::Schema.define(version: 20160712171823) do
t.datetime "started_at"
t.datetime "finished_at"
t.integer "duration"
+ t.integer "user_id"
end
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
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", ["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|
t.integer "project_id"
diff --git a/doc/api/issues.md b/doc/api/issues.md
index 3ced787b23e..419fb8f85d8 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -78,7 +78,8 @@ Example response:
"iid" : 6,
"labels" : [],
"subscribed" : false,
- "user_notes_count": 1
+ "user_notes_count": 1,
+ "due_date": "2016-07-22"
}
]
```
@@ -154,7 +155,8 @@ Example response:
"updated_at" : "2016-01-04T15:31:46.176Z",
"created_at" : "2016-01-04T15:31:46.176Z",
"subscribed" : false,
- "user_notes_count": 1
+ "user_notes_count": 1,
+ "due_date": null
}
]
```
@@ -232,7 +234,8 @@ Example response:
"updated_at" : "2016-01-04T15:31:46.176Z",
"created_at" : "2016-01-04T15:31:46.176Z",
"subscribed" : false,
- "user_notes_count": 1
+ "user_notes_count": 1,
+ "due_date": "2016-07-22"
}
]
```
@@ -295,7 +298,8 @@ Example response:
"updated_at" : "2016-01-04T15:31:46.176Z",
"created_at" : "2016-01-04T15:31:46.176Z",
"subscribed": false,
- "user_notes_count": 1
+ "user_notes_count": 1,
+ "due_date": null
}
```
@@ -320,6 +324,7 @@ POST /projects/:id/issues
| `milestone_id` | integer | no | The ID of a milestone to assign 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` |
+| `due_date` | string | no | Date time string in the format YEAR-MONTH-DAY, e.g. `2016-03-11` |
```bash
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:
"updated_at" : "2016-01-07T12:44:33.959Z",
"milestone" : null,
"subscribed" : true,
- "user_notes_count": 0
+ "user_notes_count": 0,
+ "due_date": null
}
```
@@ -379,6 +385,7 @@ PUT /projects/:id/issues/:issue_id
| `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 |
| `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
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:
"assignee" : null,
"milestone" : null,
"subscribed" : true,
- "user_notes_count": 0
+ "user_notes_count": 0,
+ "due_date": "2016-07-22"
}
```
@@ -487,7 +495,8 @@ Example response:
"state": "active",
"avatar_url": "http://www.gravatar.com/avatar/7a190fecbaa68212a4b68aeb6e3acd10?s=80&d=identicon",
"web_url": "https://gitlab.example.com/u/solon.cremin"
- }
+ },
+ "due_date": null
}
```
@@ -541,7 +550,8 @@ Example response:
"state": "active",
"avatar_url": "http://www.gravatar.com/avatar/7a190fecbaa68212a4b68aeb6e3acd10?s=80&d=identicon",
"web_url": "https://gitlab.example.com/u/solon.cremin"
- }
+ },
+ "due_date": null
}
```
@@ -596,7 +606,8 @@ Example response:
"avatar_url": "http://www.gravatar.com/avatar/5224fd70153710e92fb8bcf79ac29d67?s=80&d=identicon",
"web_url": "https://gitlab.example.com/u/orville"
},
- "subscribed": false
+ "subscribed": false,
+ "due_date": null
}
```
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 16a1461a7e4..50fa263f693 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -985,11 +985,11 @@ directive defined in `.postgres_services` and `.mysql_services` respectively:
- ruby
test:postgres:
- << *job_definition
+ <<: *job_definition
services: *postgres_definition
test:mysql:
- << *job_definition
+ <<: *job_definition
services: *mysql_definition
```
diff --git a/lib/api/award_emoji.rb b/lib/api/award_emoji.rb
index c4fa1838b5a..2efe7e3adf3 100644
--- a/lib/api/award_emoji.rb
+++ b/lib/api/award_emoji.rb
@@ -56,9 +56,9 @@ module API
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
else
not_found!("Award Emoji #{award.errors.messages}")
diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb
index 323a7086890..acb4812b5cf 100644
--- a/lib/api/commit_statuses.rb
+++ b/lib/api/commit_statuses.rb
@@ -64,7 +64,7 @@ module API
ref = branches.first
end
- pipeline = @project.ensure_pipeline(commit.sha, ref)
+ pipeline = @project.ensure_pipeline(commit.sha, ref, current_user)
name = params[:name] || params[:context]
status = GenericCommitStatus.running_or_pending.find_by(pipeline: pipeline, name: name, ref: params[:ref])
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 8e03c08f47b..3c79a00eb8c 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -187,6 +187,7 @@ module API
end
expose :user_notes_count
expose :upvotes, :downvotes
+ expose :due_date
end
class ExternalIssue < Grape::Entity
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index d5dfba5e0cc..959b700de78 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -63,7 +63,12 @@ module API
if access_status.status
# Return the repository full path so that gitlab-shell has it when
# 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
response
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 8a03a41e9c5..c588103e517 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -152,12 +152,13 @@ module API
# milestone_id (optional) - The ID of a milestone to assign issue
# labels (optional) - The labels of an issue
# created_at (optional) - Date time string, ISO 8601 formatted
+ # due_date (optional) - Date time string in the format YEAR-MONTH-DAY
# Example Request:
# POST /projects/:id/issues
- post ":id/issues" do
+ post ':id/issues' do
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
attrs = attributes_for_keys(keys)
@@ -201,12 +202,13 @@ module API
# labels (optional) - The labels of an issue
# state_event (optional) - The state event of an issue (close|reopen)
# updated_at (optional) - Date time string, ISO 8601 formatted
+ # due_date (optional) - Date time string in the format YEAR-MONTH-DAY
# Example Request:
# 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])
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
attrs = attributes_for_keys(keys)
diff --git a/lib/banzai/filter/syntax_highlight_filter.rb b/lib/banzai/filter/syntax_highlight_filter.rb
index 028edef704b..91f0159f9a1 100644
--- a/lib/banzai/filter/syntax_highlight_filter.rb
+++ b/lib/banzai/filter/syntax_highlight_filter.rb
@@ -19,21 +19,22 @@ module Banzai
language = node.attr('class')
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
- css_classes = "code highlight js-syntax-highlight #{lexer.tag}"
begin
- highlighted = ''
- highlighted << %(<pre class="#{css_classes}"><code>)
- highlighted << formatter.format(lexer.lex(code))
- highlighted << %(</code></pre>)
+ code = formatter.format(lexer.lex(code))
+
+ css_classes << " js-syntax-highlight #{lexer.tag}"
rescue
# Gracefully handle syntax highlighter bugs/errors to ensure
# users can still access an issue/comment/etc.
- highlighted = "<pre>#{code}</pre>"
end
+ highlighted = %(<pre class="#{css_classes}"><code>#{code}</code></pre>)
+
# Extracted to a method to measure it
replace_parent_pre_element(node, highlighted)
end
diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb
index 478f145bfed..ab94abeda77 100644
--- a/lib/gitlab/backend/grack_auth.rb
+++ b/lib/gitlab/backend/grack_auth.rb
@@ -63,7 +63,7 @@ module Grack
def ci_request?(login, password)
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
if underscored_service == 'gitlab_ci'
diff --git a/lib/gitlab/lfs/response.rb b/lib/gitlab/lfs/response.rb
index 811363405a8..a1ee1aa81ff 100644
--- a/lib/gitlab/lfs/response.rb
+++ b/lib/gitlab/lfs/response.rb
@@ -47,6 +47,8 @@ module Gitlab
end
def render_storage_upload_store_response(oid, size, tmp_file_name)
+ return render_forbidden unless tmp_file_name
+
render_response_to_push do
render_lfs_upload_ok(oid, size, tmp_file_name)
end
diff --git a/lib/gitlab/lfs/router.rb b/lib/gitlab/lfs/router.rb
index 69bd5e62305..f2a76a56b8f 100644
--- a/lib/gitlab/lfs/router.rb
+++ b/lib/gitlab/lfs/router.rb
@@ -74,8 +74,6 @@ module Gitlab
lfs.render_storage_upload_authorize_response(oid, size)
else
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)
end
end
diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb
index 13d980a326f..b6acc509342 100644
--- a/spec/features/security/project/internal_access_spec.rb
+++ b/spec/features/security/project/internal_access_spec.rb
@@ -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 :visitor }
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
diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb
index ac9690cc127..ccb5c06dab0 100644
--- a/spec/features/security/project/private_access_spec.rb
+++ b/spec/features/security/project/private_access_spec.rb
@@ -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 :visitor }
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
diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb
index 737897de52b..985663e7c98 100644
--- a/spec/features/security/project/public_access_spec.rb
+++ b/spec/features/security/project/public_access_spec.rb
@@ -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 :visitor }
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
diff --git a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
index 48ebc81cf82..b1370bca833 100644
--- a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
+++ b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
@@ -3,15 +3,35 @@ require 'spec_helper'
describe Banzai::Filter::SyntaxHighlightFilter, lib: true do
include FilterSpecHelper
- it 'highlights valid code blocks' do
- result = filter('<pre><code>def fun end</code>')
- expect(result.to_html).to eq("<pre class=\"code highlight js-syntax-highlight plaintext\"><code>def fun end</code></pre>")
+ context "when no language is specified" do
+ it "highlights as plaintext" do
+ 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
- it 'passes through invalid code blocks' do
- allow_any_instance_of(Rouge::Formatter).to receive(:format).and_raise(StandardError)
+ context "when a valid language is specified" do
+ 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>')
- expect(result.to_html).to eq('<pre>This is a test</pre>')
+ it "highlights as plaintext" do
+ 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
diff --git a/spec/lib/gitlab/bitbucket_import/client_spec.rb b/spec/lib/gitlab/bitbucket_import/client_spec.rb
index 760d66a1488..7543c29bcc4 100644
--- a/spec/lib/gitlab/bitbucket_import/client_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/client_spec.rb
@@ -54,12 +54,12 @@ describe Gitlab::BitbucketImport::Client, lib: true do
context 'project import' do
it 'calls .from_project with no errors' do
project = create(:empty_project)
+ project.import_url = "ssh://git@bitbucket.org/test/test.git"
project.create_or_update_import_data(credentials:
{ user: "git",
password: nil,
bb_session: { bitbucket_access_token: "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
end
diff --git a/spec/lib/gitlab/lfs/lfs_router_spec.rb b/spec/lib/gitlab/lfs/lfs_router_spec.rb
deleted file mode 100644
index 659facd6c19..00000000000
--- a/spec/lib/gitlab/lfs/lfs_router_spec.rb
+++ /dev/null
@@ -1,730 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::Lfs::Router, lib: true 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_two) { create(:user) }
- let!(:lfs_object) { create(:lfs_object, :with_file) }
-
- let(:request) { Rack::Request.new(env) }
- let(:env) do
- {
- 'rack.input' => '',
- 'REQUEST_METHOD' => 'GET',
- }
- end
-
- let(:lfs_router_auth) { new_lfs_router(project, user: user) }
- let(:lfs_router_ci_auth) { new_lfs_router(project, ci: true) }
- 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
- before do
- allow(Gitlab.config.lfs).to receive(:enabled).and_return(false)
- env['REQUEST_METHOD'] = 'POST'
- 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
-
- it 'responds with 501' do
- expect(lfs_router_auth.try_call).to match_array(respond_with_disabled)
- end
- end
-
- describe 'when fetching lfs object using deprecated API' do
- before do
- enable_lfs
- env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/info/lfs/objects/#{sample_oid}"
- end
-
- it 'responds with 501' do
- expect(lfs_router_auth.try_call).to match_array(respond_with_deprecated)
- end
- end
-
- describe 'when fetching lfs object' do
- before do
- enable_lfs
- env['HTTP_ACCEPT'] = "application/vnd.git-lfs+json; charset=utf-8"
- env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}"
- end
-
- describe 'and request comes from gitlab-workhorse' do
- context 'without user being authorized' do
- it "responds with status 401" do
- expect(lfs_router_noauth.try_call.first).to eq(401)
- end
- end
-
- context 'with required headers' do
- before do
- project.lfs_objects << lfs_object
- env['HTTP_X_SENDFILE_TYPE'] = "X-Sendfile"
- end
-
- context 'when user does not have project access' do
- it "responds with status 403" do
- expect(lfs_router_auth.try_call.first).to eq(403)
- end
- end
-
- context 'when user has project access' do
- before do
- project.team << [user, :master]
- end
-
- it "responds with status 200" do
- expect(lfs_router_auth.try_call.first).to eq(200)
- end
-
- it "responds with the file location" do
- expect(lfs_router_auth.try_call[1]['Content-Type']).to eq("application/octet-stream")
- expect(lfs_router_auth.try_call[1]['X-Sendfile']).to eq(lfs_object.file.path)
- end
- end
-
- context 'when CI is authorized' do
- it "responds with status 200" do
- expect(lfs_router_ci_auth.try_call.first).to eq(200)
- end
-
- it "responds with the file location" do
- expect(lfs_router_ci_auth.try_call[1]['Content-Type']).to eq("application/octet-stream")
- expect(lfs_router_ci_auth.try_call[1]['X-Sendfile']).to eq(lfs_object.file.path)
- end
- end
- end
-
- context 'without required headers' do
- it "responds with status 403" do
- expect(lfs_router_auth.try_call.first).to eq(403)
- 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
- before do
- enable_lfs
- env['REQUEST_METHOD'] = 'POST'
- env['PATH_INFO'] = "#{project.repository.path_with_namespace}.git/info/lfs/objects/batch"
- end
-
- describe 'download' do
- before do
- body = { 'operation' => 'download',
- 'objects' => [
- { 'oid' => sample_oid,
- 'size' => sample_size
- }]
- }.to_json
- env['rack.input'] = StringIO.new(body)
- end
-
- shared_examples 'an authorized requests' do
- context 'when downloading an lfs object that is assigned to our project' do
- before do
- project.lfs_objects << lfs_object
- end
-
- it 'responds with status 200 and href to download' do
- response = router.try_call
- expect(response.first).to eq(200)
- response_body = ActiveSupport::JSON.decode(response.last.first)
-
- expect(response_body).to eq('objects' => [
- { 'oid' => sample_oid,
- 'size' => sample_size,
- 'actions' => {
- 'download' => {
- 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}",
- 'header' => { 'Authorization' => auth }
- }
- }
- }])
- end
- end
-
- context 'when downloading an lfs object that is assigned to other project' do
- before do
- public_project.lfs_objects << lfs_object
- end
-
- it 'responds with status 200 and error message' do
- response = router.try_call
- expect(response.first).to eq(200)
- response_body = ActiveSupport::JSON.decode(response.last.first)
-
- expect(response_body).to eq('objects' => [
- { 'oid' => sample_oid,
- 'size' => sample_size,
- 'error' => {
- 'code' => 404,
- 'message' => "Object does not exist on the server or you don't have permissions to access it",
- }
- }])
- end
- end
-
- context 'when downloading a lfs object that does not exist' do
- before do
- body = { 'operation' => 'download',
- 'objects' => [
- { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
- 'size' => 1575078
- }]
- }.to_json
- env['rack.input'] = StringIO.new(body)
- end
-
- it "responds with status 200 and error message" do
- response = router.try_call
- expect(response.first).to eq(200)
- response_body = ActiveSupport::JSON.decode(response.last.first)
-
- expect(response_body).to eq('objects' => [
- { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
- 'size' => 1575078,
- 'error' => {
- 'code' => 404,
- 'message' => "Object does not exist on the server or you don't have permissions to access it",
- }
- }])
- end
- end
-
- context 'when downloading one new and one existing lfs object' do
- before do
- body = { 'operation' => 'download',
- 'objects' => [
- { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
- 'size' => 1575078
- },
- { 'oid' => sample_oid,
- 'size' => sample_size
- }
- ]
- }.to_json
- env['rack.input'] = StringIO.new(body)
- project.lfs_objects << lfs_object
- end
-
- it "responds with status 200 with upload hypermedia link for the new object" do
- response = router.try_call
- expect(response.first).to eq(200)
- response_body = ActiveSupport::JSON.decode(response.last.first)
-
- expect(response_body).to eq('objects' => [
- { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
- 'size' => 1575078,
- 'error' => {
- 'code' => 404,
- 'message' => "Object does not exist on the server or you don't have permissions to access it",
- }
- },
- { 'oid' => sample_oid,
- 'size' => sample_size,
- 'actions' => {
- 'download' => {
- 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}",
- 'header' => { 'Authorization' => auth }
- }
- }
- }])
- end
- end
- end
-
- context 'when user is authenticated' do
- let(:auth) { authorize(user) }
-
- before do
- env["HTTP_AUTHORIZATION"] = auth
- project.team << [user, role]
- end
-
- it_behaves_like 'an authorized requests' do
- let(:role) { :reporter }
- let(:router) { lfs_router_auth }
- end
-
- context 'when user does is not member of the project' do
- let(:role) { :guest }
-
- it 'responds with 403' do
- expect(lfs_router_auth.try_call.first).to eq(403)
- end
- end
-
- context 'when user does not have download access' do
- let(:role) { :guest }
-
- it 'responds with 403' do
- expect(lfs_router_auth.try_call.first).to eq(403)
- end
- end
- end
-
- context 'when CI is authorized' do
- let(:auth) { 'gitlab-ci-token:password' }
-
- before do
- env["HTTP_AUTHORIZATION"] = auth
- end
-
- it_behaves_like 'an authorized requests' do
- let(:router) { lfs_router_ci_auth }
- end
- end
-
- context 'when user is not authenticated' do
- describe 'is accessing public project' do
- before do
- public_project.lfs_objects << lfs_object
- end
-
- it 'responds with status 200 and href to download' do
- response = lfs_router_public_noauth.try_call
- expect(response.first).to eq(200)
- response_body = ActiveSupport::JSON.decode(response.last.first)
-
- expect(response_body).to eq('objects' => [
- { 'oid' => sample_oid,
- 'size' => sample_size,
- 'actions' => {
- 'download' => {
- 'href' => "#{public_project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}",
- 'header' => {}
- }
- }
- }])
- end
- end
-
- describe 'is accessing non-public project' do
- before do
- project.lfs_objects << lfs_object
- end
-
- it 'responds with authorization required' do
- expect(lfs_router_noauth.try_call.first).to eq(401)
- end
- end
- end
- end
-
- describe 'upload' do
- before do
- body = { 'operation' => 'upload',
- 'objects' => [
- { 'oid' => sample_oid,
- 'size' => sample_size
- }]
- }.to_json
- env['rack.input'] = StringIO.new(body)
- end
-
- describe 'when request is authenticated' do
- describe 'when user has project push access' do
- before do
- @auth = authorize(user)
- env["HTTP_AUTHORIZATION"] = @auth
- project.team << [user, :developer]
- end
-
- context 'when pushing an lfs object that already exists' do
- before do
- public_project.lfs_objects << lfs_object
- end
-
- it "responds with status 200 and links the object to the project" do
- response_body = lfs_router_auth.try_call.last
- response = ActiveSupport::JSON.decode(response_body.first)
-
- expect(response['objects']).to be_kind_of(Array)
- expect(response['objects'].first['oid']).to eq(sample_oid)
- expect(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)).to include(public_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(response['objects'].first['actions']['upload']['header']).to eq('Authorization' => @auth)
- end
- end
-
- context 'when pushing a lfs object that does not exist' do
- before do
- body = { 'operation' => 'upload',
- 'objects' => [
- { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
- 'size' => 1575078
- }]
- }.to_json
- env['rack.input'] = StringIO.new(body)
- end
-
- it "responds with status 200 and upload hypermedia link" do
- response = lfs_router_auth.try_call
- expect(response.first).to eq(200)
-
- response_body = ActiveSupport::JSON.decode(response.last.first)
- expect(response_body['objects']).to be_kind_of(Array)
- expect(response_body['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897")
- expect(response_body['objects'].first['size']).to eq(1575078)
- expect(lfs_object.projects.pluck(:id)).not_to include(project.id)
- 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(response_body['objects'].first['actions']['upload']['header']).to eq('Authorization' => @auth)
- end
- end
-
- context 'when pushing one new and one existing lfs object' do
- before do
- body = { 'operation' => 'upload',
- 'objects' => [
- { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
- 'size' => 1575078
- },
- { 'oid' => sample_oid,
- 'size' => sample_size
- }
- ]
- }.to_json
- env['rack.input'] = StringIO.new(body)
- project.lfs_objects << lfs_object
- end
-
- it "responds with status 200 with upload hypermedia link for the new object" do
- response = lfs_router_auth.try_call
- expect(response.first).to eq(200)
-
- response_body = ActiveSupport::JSON.decode(response.last.first)
- expect(response_body['objects']).to be_kind_of(Array)
-
- expect(response_body['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897")
- expect(response_body['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(response_body['objects'].first['actions']['upload']['header']).to eq("Authorization" => @auth)
-
- expect(response_body['objects'].last['oid']).to eq(sample_oid)
- expect(response_body['objects'].last['size']).to eq(sample_size)
- expect(response_body['objects'].last).not_to have_key('actions')
- end
- end
- end
-
- context 'when user does not have push access' do
- it 'responds with 403' do
- expect(lfs_router_auth.try_call.first).to eq(403)
- end
- end
-
- context 'when CI is authorized' do
- it 'responds with 401' do
- expect(lfs_router_ci_auth.try_call.first).to eq(401)
- end
- end
- end
-
- context 'when user is not authenticated' do
- context 'when user has push access' do
- before do
- project.team << [user, :master]
- end
-
- it "responds with status 401" do
- expect(lfs_router_public_noauth.try_call.first).to eq(401)
- end
- end
-
- context 'when user does not have push access' do
- it "responds with status 401" do
- expect(lfs_router_public_noauth.try_call.first).to eq(401)
- end
- end
- end
-
- context 'when CI is authorized' do
- let(:auth) { 'gitlab-ci-token:password' }
-
- before do
- env["HTTP_AUTHORIZATION"] = auth
- end
-
- it "responds with status 403" do
- expect(lfs_router_public_ci_auth.try_call.first).to eq(401)
- end
- end
- end
-
- describe 'unsupported' do
- before do
- body = { 'operation' => 'other',
- 'objects' => [
- { 'oid' => sample_oid,
- 'size' => sample_size
- }]
- }.to_json
- env['rack.input'] = StringIO.new(body)
- end
-
- it 'responds with status 404' do
- expect(lfs_router_public_noauth.try_call.first).to eq(404)
- end
- end
- end
-
- describe 'when pushing a lfs object' do
- before do
- enable_lfs
- env['REQUEST_METHOD'] = 'PUT'
- end
-
- shared_examples 'unauthorized' do
- context 'and request is sent by gitlab-workhorse to authorize the request' do
- before do
- header_for_upload_authorize(router.project)
- end
-
- it 'responds with status 401' do
- expect(router.try_call.first).to eq(401)
- end
- end
-
- context 'and request is sent by gitlab-workhorse to finalize the upload' do
- before do
- headers_for_upload_finalize(router.project)
- end
-
- it 'responds with status 401' do
- expect(router.try_call.first).to eq(401)
- end
- end
-
- context 'and request is sent with a malformed headers' do
- before do
- env["PATH_INFO"] = "#{router.project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}"
- env["HTTP_X_GITLAB_LFS_TMP"] = "cat /etc/passwd"
- end
-
- it 'does not recognize it as a valid lfs command' do
- expect(router.try_call).to eq(nil)
- end
- end
- end
-
- shared_examples 'forbidden' do
- context 'and request is sent by gitlab-workhorse to authorize the request' do
- before do
- header_for_upload_authorize(router.project)
- end
-
- it 'responds with 403' do
- expect(router.try_call.first).to eq(403)
- end
- end
-
- context 'and request is sent by gitlab-workhorse to finalize the upload' do
- before do
- headers_for_upload_finalize(router.project)
- end
-
- it 'responds with 403' do
- expect(router.try_call.first).to eq(403)
- end
- end
- end
-
- describe 'to one project' do
- describe 'when user is authenticated' do
- describe 'when user has push access to the project' do
- before do
- project.team << [user, :developer]
- end
-
- context 'and request is sent by gitlab-workhorse to authorize the request' do
- before do
- header_for_upload_authorize(project)
- end
-
- it 'responds with status 200, location of lfs store and object details' do
- json_response = ActiveSupport::JSON.decode(lfs_router_auth.try_call.last.first)
-
- expect(lfs_router_auth.try_call.first).to eq(200)
- 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['LfsSize']).to eq(sample_size)
- end
- end
-
- context 'and request is sent by gitlab-workhorse to finalize the upload' do
- before do
- headers_for_upload_finalize(project)
- end
-
- it 'responds with status 200 and 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)
- end
- end
- end
-
- describe 'and user does not have push access' do
- let(:router) { lfs_router_auth }
-
- it_behaves_like 'forbidden'
- end
- end
-
- context 'when CI is authenticated' do
- let(:router) { lfs_router_ci_auth }
-
- it_behaves_like 'unauthorized'
- end
-
- context 'for unauthenticated' do
- let(:router) { new_lfs_router(project) }
-
- it_behaves_like 'unauthorized'
- end
- end
-
- describe 'to a forked project' do
- let(:forked_project) { fork_project(public_project, user) }
-
- describe 'when user is authenticated' do
- describe 'when user has push access to the project' do
- before do
- forked_project.team << [user_two, :developer]
- end
-
- context 'and request is sent by gitlab-workhorse to authorize the request' do
- before do
- header_for_upload_authorize(forked_project)
- end
-
- it 'responds with status 200, location of lfs store and object details' do
- json_response = ActiveSupport::JSON.decode(lfs_router_forked_auth.try_call.last.first)
-
- expect(lfs_router_forked_auth.try_call.first).to eq(200)
- 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['LfsSize']).to eq(sample_size)
- end
- end
-
- context 'and request is sent by gitlab-workhorse to finalize the upload' do
- before do
- headers_for_upload_finalize(forked_project)
- end
-
- it 'responds with status 200 and 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(public_project.id)
- end
- end
- end
-
- describe 'and user does not have push access' do
- let(:router) { lfs_router_forked_auth }
-
- it_behaves_like 'forbidden'
- end
- end
-
- context 'when CI is authenticated' do
- let(:router) { lfs_router_forked_ci_auth }
-
- it_behaves_like 'unauthorized'
- end
-
- context 'for unauthenticated' do
- let(:router) { lfs_router_forked_noauth }
-
- it_behaves_like 'unauthorized'
- end
-
- describe 'and second project not related to fork or a source project' do
- let(:second_project) { create(:project) }
- let(:lfs_router_second_project) { new_lfs_router(second_project, user: user) }
-
- before do
- public_project.lfs_objects << lfs_object
- headers_for_upload_finalize(second_project)
- end
-
- context 'when pushing the same lfs object to the second project' do
- before do
- second_project.team << [user, :master]
- end
-
- it 'responds with 200 and 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, public_project.id)
- end
- end
- end
- end
- end
-
- def enable_lfs
- allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
- end
-
- def authorize(user)
- ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password)
- end
-
- def new_lfs_router(project, user: nil, ci: false)
- Gitlab::Lfs::Router.new(project, user, ci, request)
- end
-
- def header_for_upload_authorize(project)
- env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}/authorize"
- end
-
- def headers_for_upload_finalize(project)
- env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}"
- env["HTTP_X_GITLAB_LFS_TMP"] = "#{sample_oid}6e561c9d4"
- end
-
- def fork_project(project, user, object = nil)
- allow(RepositoryForkWorker).to receive(:perform_async).and_return(true)
- Projects::ForkService.new(project, user, {}).execute
- end
-end
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 4e5481f9154..10db79bd15f 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -5,9 +5,12 @@ describe Ci::Pipeline, models: true do
let(:pipeline) { FactoryGirl.create :ci_pipeline, project: 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(:trigger_requests) }
it { is_expected.to have_many(:builds) }
+
it { is_expected.to validate_presence_of :sha }
it { is_expected.to validate_presence_of :status }
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index 05f22c7a9eb..ff6371ad685 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -177,10 +177,10 @@ describe CommitStatus, models: true do
describe '#stages' do
before do
- FactoryGirl.create :commit_status, pipeline: pipeline, stage: 'build', stage_idx: 0, status: 'success'
- FactoryGirl.create :commit_status, pipeline: pipeline, stage: 'build', stage_idx: 0, status: 'failed'
- FactoryGirl.create :commit_status, pipeline: pipeline, stage: 'deploy', stage_idx: 2, status: 'running'
- FactoryGirl.create :commit_status, pipeline: pipeline, stage: 'test', stage_idx: 1, status: 'success'
+ create :commit_status, pipeline: pipeline, stage: 'build', name: 'linux', stage_idx: 0, status: 'success'
+ create :commit_status, pipeline: pipeline, stage: 'build', name: 'mac', stage_idx: 0, status: 'failed'
+ create :commit_status, pipeline: pipeline, stage: 'deploy', name: 'staging', stage_idx: 2, status: 'running'
+ create :commit_status, pipeline: pipeline, stage: 'test', name: 'rspec', stage_idx: 1, status: 'success'
end
context 'stages list' do
@@ -192,7 +192,7 @@ describe CommitStatus, models: true do
end
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
is_expected.to eq({
@@ -201,6 +201,20 @@ describe CommitStatus, models: true do
'deploy' => 'running'
})
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
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index e842c58dd82..9dc34276f18 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -142,10 +142,10 @@ describe Project, models: true do
expect(project2).to be_valid
end
- it 'does not allow to introduce an empty URI' do
+ it 'allows an empty URI' do
project2 = build(:project, import_url: '')
- expect(project2).not_to be_valid
+ expect(project2).to be_valid
end
it 'does not produce import data on an empty URI' do
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index ff39f187759..fc74488ac0e 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -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(:todos).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
it 'does not include group memberships for which user is a requester' do
diff --git a/spec/requests/api/award_emoji_spec.rb b/spec/requests/api/award_emoji_spec.rb
index 72a6d45f47d..2b74dd4bbb0 100644
--- a/spec/requests/api/award_emoji_spec.rb
+++ b/spec/requests/api/award_emoji_spec.rb
@@ -135,6 +135,22 @@ describe API::API, api: true do
expect(response).to have_http_status(401)
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
@@ -147,6 +163,22 @@ describe API::API, api: true do
expect(response).to have_http_status(201)
expect(json_response['user']['username']).to eq(user.username)
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
describe 'DELETE /projects/:id/awardable/:awardable_id/award_emoji/:award_id' do
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index e567d36afa8..f6f85d6e95e 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -56,13 +56,21 @@ describe API::API, api: true do
context "git push with project.wiki" do
it 'responds with success' do
- project_wiki = create(:project, name: 'my.wiki', path: 'my.wiki')
- project_wiki.team << [user, :developer]
+ push(key, project.wiki)
- 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(json_response["status"]).to be_truthy
+ expect(json_response["repository_path"]).to eq(project.wiki.repository.path_to_repo)
end
end
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index 6adccb4ebae..12f2cfa6942 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -503,6 +503,20 @@ describe API::API, api: true do
])
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
it 'accepts the creation date to be set' do
creation_time = 2.weeks.ago
@@ -683,6 +697,17 @@ describe API::API, api: true do
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
it "rejects a non member from deleting an issue" do
delete api("/projects/#{project.id}/issues/#{issue.id}", non_member)
diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb
new file mode 100644
index 00000000000..93d2bc160cc
--- /dev/null
+++ b/spec/requests/lfs_http_spec.rb
@@ -0,0 +1,768 @@
+require 'spec_helper'
+
+describe Gitlab::Lfs::Router do
+ let(:user) { create(:user) }
+ let!(:lfs_object) { create(:lfs_object, :with_file) }
+
+ let(:headers) do
+ {
+ 'Authorization' => authorization,
+ 'X-Sendfile-Type' => sendfile
+ }.compact
+ end
+ let(:authorization) { }
+ let(:sendfile) { }
+
+ let(:sample_oid) { lfs_object.oid }
+ let(:sample_size) { lfs_object.size }
+
+ 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
+ allow(Gitlab.config.lfs).to receive(:enabled).and_return(false)
+ post_json "#{project.http_url_to_repo}/info/lfs/objects/batch", body, headers
+ end
+
+ it 'responds with 501' do
+ 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
+
+ describe 'deprecated API' do
+ let(:project) { create(:empty_project) }
+
+ before do
+ enable_lfs
+ end
+
+ shared_examples 'a deprecated' do
+ 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
+
+ describe 'when fetching lfs object' do
+ let(:project) { create(:empty_project) }
+ let(:update_permissions) { }
+
+ before do
+ enable_lfs
+ update_permissions
+ get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", nil, headers
+ end
+
+ context 'and request comes from gitlab-workhorse' do
+ context 'without user being authorized' do
+ it 'responds with status 401' do
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ context 'with required headers' do
+ shared_examples 'responds with a file' do
+ let(:sendfile) { 'X-Sendfile' }
+
+ it 'responds with status 200' do
+ expect(response).to have_http_status(200)
+ end
+
+ it 'responds with the file location' do
+ expect(response.headers['Content-Type']).to eq('application/octet-stream')
+ expect(response.headers['X-Sendfile']).to eq(lfs_object.file.path)
+ 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 403' do
+ expect(response).to have_http_status(403)
+ end
+ end
+
+ context 'and does have project access' do
+ let(:update_permissions) do
+ project.team << [user, :master]
+ project.lfs_objects << lfs_object
+ end
+
+ it_behaves_like 'responds with a file'
+ end
+ end
+
+ context 'when CI is authorized' do
+ let(:authorization) { authorize_ci_project }
+
+ let(:update_permissions) do
+ project.lfs_objects << lfs_object
+ end
+
+ it_behaves_like 'responds with a file'
+ end
+ end
+
+ context 'without required headers' do
+ let(:authorization) { authorize_user }
+
+ it 'responds with status 403' do
+ expect(response).to have_http_status(403)
+ end
+ end
+ end
+ end
+
+ describe 'when handling lfs batch request' do
+ let(:update_lfs_permissions) { }
+ let(:update_user_permissions) { }
+
+ before do
+ enable_lfs
+ update_lfs_permissions
+ update_user_permissions
+ post_json "#{project.http_url_to_repo}/info/lfs/objects/batch", body, headers
+ end
+
+ describe 'download' do
+ let(:project) { create(:empty_project) }
+ let(:body) do
+ { 'operation' => 'download',
+ 'objects' => [
+ { 'oid' => sample_oid,
+ 'size' => sample_size
+ }]
+ }
+ end
+
+ shared_examples 'an authorized requests' do
+ context 'when downloading an lfs object that is assigned to our project' do
+ let(:update_lfs_permissions) do
+ project.lfs_objects << lfs_object
+ end
+
+ it 'responds with status 200' do
+ expect(response).to have_http_status(200)
+ end
+
+ it 'with href to download' do
+ expect(json_response).to eq('objects' => [
+ { 'oid' => sample_oid,
+ 'size' => sample_size,
+ 'actions' => {
+ 'download' => {
+ 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}",
+ 'header' => { 'Authorization' => authorization }
+ }
+ }
+ }])
+ end
+ end
+
+ context 'when downloading an lfs object that is assigned to other project' do
+ let(:other_project) { create(:empty_project) }
+ let(:update_lfs_permissions) do
+ other_project.lfs_objects << lfs_object
+ end
+
+ it 'responds with status 200' do
+ expect(response).to have_http_status(200)
+ end
+
+ it 'with href to download' do
+ expect(json_response).to eq('objects' => [
+ { 'oid' => sample_oid,
+ 'size' => sample_size,
+ 'error' => {
+ 'code' => 404,
+ 'message' => "Object does not exist on the server or you don't have permissions to access it",
+ }
+ }])
+ end
+ end
+
+ context 'when downloading a lfs object that does not exist' do
+ let(:body) do
+ { 'operation' => 'download',
+ 'objects' => [
+ { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
+ 'size' => 1575078
+ }]
+ }
+ end
+
+ it 'responds with status 200' do
+ expect(response).to have_http_status(200)
+ end
+
+ it 'with an 404 for specific object' do
+ expect(json_response).to eq('objects' => [
+ { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
+ 'size' => 1575078,
+ 'error' => {
+ 'code' => 404,
+ 'message' => "Object does not exist on the server or you don't have permissions to access it",
+ }
+ }])
+ end
+ end
+
+ context 'when downloading one new and one existing lfs object' do
+ let(:body) do
+ { 'operation' => 'download',
+ 'objects' => [
+ { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
+ 'size' => 1575078
+ },
+ { 'oid' => sample_oid,
+ 'size' => sample_size
+ }
+ ]
+ }
+ end
+
+ let(:update_lfs_permissions) do
+ project.lfs_objects << lfs_object
+ end
+
+ it 'responds with status 200' do
+ expect(response).to have_http_status(200)
+ end
+
+ it 'responds with upload hypermedia link for the new object' do
+ expect(json_response).to eq('objects' => [
+ { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
+ 'size' => 1575078,
+ 'error' => {
+ 'code' => 404,
+ 'message' => "Object does not exist on the server or you don't have permissions to access it",
+ }
+ },
+ { 'oid' => sample_oid,
+ 'size' => sample_size,
+ 'actions' => {
+ 'download' => {
+ 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}",
+ 'header' => { 'Authorization' => authorization }
+ }
+ }
+ }])
+ end
+ end
+ end
+
+ context 'when user is authenticated' do
+ let(:authorization) { authorize_user }
+
+ let(:update_user_permissions) do
+ project.team << [user, role]
+ end
+
+ it_behaves_like 'an authorized requests' do
+ let(:role) { :reporter }
+ end
+
+ context 'when user does is not member of the project' do
+ let(:role) { :guest }
+
+ it 'responds with 403' do
+ expect(response).to have_http_status(403)
+ end
+ end
+
+ context 'when user does not have download access' do
+ let(:role) { :guest }
+
+ it 'responds with 403' do
+ expect(response).to have_http_status(403)
+ end
+ end
+ end
+
+ context 'when CI is authorized' do
+ let(:authorization) { authorize_ci_project }
+
+ it_behaves_like 'an authorized requests'
+ end
+
+ context 'when user is not authenticated' do
+ describe 'is accessing public project' do
+ let(:project) { create(:project, :public) }
+
+ let(:update_lfs_permissions) do
+ project.lfs_objects << lfs_object
+ end
+
+ it 'responds with status 200 and href to download' do
+ expect(response).to have_http_status(200)
+ end
+
+ it 'responds with status 200 and href to download' do
+ expect(json_response).to eq('objects' => [
+ { 'oid' => sample_oid,
+ 'size' => sample_size,
+ 'actions' => {
+ 'download' => {
+ 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}",
+ 'header' => {}
+ }
+ }
+ }])
+ end
+ end
+
+ describe 'is accessing non-public project' do
+ let(:update_lfs_permissions) do
+ project.lfs_objects << lfs_object
+ end
+
+ it 'responds with authorization required' do
+ expect(response).to have_http_status(401)
+ end
+ end
+ end
+ end
+
+ describe 'upload' do
+ let(:project) { create(:project, :public) }
+ let(:body) do
+ { 'operation' => 'upload',
+ 'objects' => [
+ { 'oid' => sample_oid,
+ 'size' => sample_size
+ }]
+ }
+ end
+
+ describe 'when request is authenticated' do
+ describe 'when user has project push access' do
+ let(:authorization) { authorize_user }
+
+ let(:update_user_permissions) do
+ project.team << [user, :developer]
+ end
+
+ context 'when pushing an lfs object that already exists' do
+ let(:other_project) { create(:empty_project) }
+ let(:update_lfs_permissions) do
+ other_project.lfs_objects << lfs_object
+ end
+
+ it 'responds with status 200' do
+ expect(response).to have_http_status(200)
+ end
+
+ it 'responds with links the object to the project' do
+ expect(json_response['objects']).to be_kind_of(Array)
+ 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)).to include(other_project.id)
+ expect(json_response['objects'].first['actions']['upload']['href']).to eq("#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}")
+ expect(json_response['objects'].first['actions']['upload']['header']).to eq('Authorization' => authorization)
+ end
+ end
+
+ context 'when pushing a lfs object that does not exist' do
+ let(:body) do
+ { 'operation' => 'upload',
+ 'objects' => [
+ { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
+ 'size' => 1575078
+ }]
+ }
+ end
+
+ it 'responds with status 200' do
+ expect(response).to have_http_status(200)
+ end
+
+ it 'responds with upload hypermedia link' do
+ expect(json_response['objects']).to be_kind_of(Array)
+ expect(json_response['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897")
+ expect(json_response['objects'].first['size']).to eq(1575078)
+ 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(json_response['objects'].first['actions']['upload']['header']).to eq('Authorization' => authorization)
+ end
+ end
+
+ context 'when pushing one new and one existing lfs object' do
+ let(:body) do
+ { 'operation' => 'upload',
+ 'objects' => [
+ { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
+ 'size' => 1575078
+ },
+ { 'oid' => sample_oid,
+ 'size' => sample_size
+ }
+ ]
+ }
+ end
+
+ let(:update_lfs_permissions) do
+ project.lfs_objects << lfs_object
+ end
+
+ it 'responds with status 200' do
+ expect(response).to have_http_status(200)
+ end
+
+ it 'responds with upload hypermedia link for the new object' do
+ expect(json_response['objects']).to be_kind_of(Array)
+
+ expect(json_response['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897")
+ expect(json_response['objects'].first['size']).to eq(1575078)
+ expect(json_response['objects'].first['actions']['upload']['href']).to eq("#{project.http_url_to_repo}/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078")
+ expect(json_response['objects'].first['actions']['upload']['header']).to eq("Authorization" => authorization)
+
+ expect(json_response['objects'].last['oid']).to eq(sample_oid)
+ expect(json_response['objects'].last['size']).to eq(sample_size)
+ expect(json_response['objects'].last).not_to have_key('actions')
+ end
+ end
+ end
+
+ context 'when user does not have push access' do
+ let(:authorization) { authorize_user }
+
+ it 'responds with 403' do
+ expect(response).to have_http_status(403)
+ end
+ end
+
+ context 'when CI is authorized' do
+ let(:authorization) { authorize_ci_project }
+
+ it 'responds with 401' do
+ expect(response).to have_http_status(401)
+ end
+ end
+ end
+
+ context 'when user is not authenticated' do
+ context 'when user has push access' do
+ let(:update_user_permissions) do
+ project.team << [user, :master]
+ end
+
+ it 'responds with status 401' do
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ context 'when user does not have push access' do
+ it 'responds with status 401' do
+ expect(response).to have_http_status(401)
+ end
+ end
+ end
+
+ context 'when CI is authorized' do
+ let(:authorization) { authorize_ci_project }
+
+ it 'responds with status 403' do
+ expect(response).to have_http_status(401)
+ end
+ end
+ end
+
+ describe 'unsupported' do
+ let(:project) { create(:empty_project) }
+ let(:body) do
+ { 'operation' => 'other',
+ 'objects' => [
+ { 'oid' => sample_oid,
+ 'size' => sample_size
+ }]
+ }
+ end
+
+ it 'responds with status 404' do
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ describe 'when pushing a lfs object' do
+ before do
+ enable_lfs
+ end
+
+ shared_examples 'unauthorized' do
+ context 'and request is sent by gitlab-workhorse to authorize the request' do
+ before do
+ put_authorize
+ end
+
+ it 'responds with status 401' do
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ context 'and request is sent by gitlab-workhorse to finalize the upload' do
+ before do
+ put_finalize
+ end
+
+ it 'responds with status 401' do
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ context 'and request is sent with a malformed headers' do
+ before do
+ put_finalize('cat /etc/passwd')
+ end
+
+ it 'does not recognize it as a valid lfs command' do
+ expect(response).to have_http_status(403)
+ end
+ end
+ end
+
+ shared_examples 'forbidden' do
+ context 'and request is sent by gitlab-workhorse to authorize the request' do
+ before do
+ put_authorize
+ end
+
+ it 'responds with 403' do
+ expect(response).to have_http_status(403)
+ end
+ end
+
+ context 'and request is sent by gitlab-workhorse to finalize the upload' do
+ before do
+ put_finalize
+ end
+
+ it 'responds with 403' do
+ expect(response).to have_http_status(403)
+ end
+ end
+ end
+
+ describe 'to one project' do
+ let(:project) { create(:empty_project) }
+
+ describe 'when user is authenticated' do
+ let(:authorization) { authorize_user }
+
+ describe 'when user has push access to the project' do
+ before do
+ project.team << [user, :developer]
+ end
+
+ context 'and request is sent by gitlab-workhorse to authorize the request' do
+ before do
+ put_authorize
+ end
+
+ it 'responds with status 200' do
+ expect(response).to have_http_status(200)
+ end
+
+ 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['LfsOid']).to eq(sample_oid)
+ expect(json_response['LfsSize']).to eq(sample_size)
+ end
+ end
+
+ context 'and request is sent by gitlab-workhorse to finalize the upload' do
+ before do
+ put_finalize
+ end
+
+ it 'responds with status 200' do
+ expect(response).to have_http_status(200)
+ end
+
+ it 'lfs object is linked to the project' do
+ expect(lfs_object.projects.pluck(:id)).to include(project.id)
+ end
+ end
+ end
+
+ describe 'and user does not have push access' do
+ it_behaves_like 'forbidden'
+ end
+ end
+
+ context 'when CI is authenticated' do
+ let(:authorization) { authorize_ci_project }
+
+ it_behaves_like 'unauthorized'
+ end
+
+ context 'for unauthenticated' do
+ it_behaves_like 'unauthorized'
+ end
+ end
+
+ describe 'to a forked project' do
+ 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
+ let(:authorization) { authorize_user }
+
+ describe 'when user has push access to the project' do
+ before do
+ project.team << [user, :developer]
+ end
+
+ context 'and request is sent by gitlab-workhorse to authorize the request' do
+ before do
+ put_authorize
+ end
+
+ it 'responds with status 200' do
+ expect(response).to have_http_status(200)
+ end
+
+ 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['LfsOid']).to eq(sample_oid)
+ expect(json_response['LfsSize']).to eq(sample_size)
+ end
+ end
+
+ context 'and request is sent by gitlab-workhorse to finalize the upload' do
+ before do
+ put_finalize
+ end
+
+ it 'responds with status 200' do
+ expect(response).to have_http_status(200)
+ end
+
+ it 'lfs object is linked to the source project' do
+ expect(lfs_object.projects.pluck(:id)).to include(upstream_project.id)
+ end
+ end
+ end
+
+ describe 'and user does not have push access' do
+ it_behaves_like 'forbidden'
+ end
+ end
+
+ context 'when CI is authenticated' do
+ let(:authorization) { authorize_ci_project }
+
+ it_behaves_like 'unauthorized'
+ end
+
+ context 'for unauthenticated' do
+ it_behaves_like 'unauthorized'
+ end
+
+ describe 'and second project not related to fork or a source project' do
+ let(:second_project) { create(:empty_project) }
+ let(:authorization) { authorize_user }
+
+ before do
+ second_project.team << [user, :master]
+ upstream_project.lfs_objects << lfs_object
+ end
+
+ context 'when pushing the same lfs object to the second project' do
+ before do
+ 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
+
+ it 'links the lfs object to the project' do
+ expect(lfs_object.projects.pluck(:id)).to include(second_project.id, upstream_project.id)
+ 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
+
+ def enable_lfs
+ allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
+ end
+
+ def authorize_ci_project
+ ActionController::HttpAuthentication::Basic.encode_credentials('gitlab-ci-token', project.runners_token)
+ end
+
+ def authorize_user
+ ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password)
+ end
+
+ def fork_project(project, user, object = nil)
+ allow(RepositoryForkWorker).to receive(:perform_async).and_return(true)
+ Projects::ForkService.new(project, user, {}).execute
+ end
+
+ def post_json(url, body = nil, headers = nil)
+ post(url, body.try(:to_json), (headers || {}).merge('Content-Type' => 'application/json'))
+ end
+
+ def json_response
+ @json_response ||= JSON.parse(response.body)
+ end
+end
diff --git a/spec/services/auth/container_registry_authentication_service_spec.rb b/spec/services/auth/container_registry_authentication_service_spec.rb
index 67777ad48bc..7cc71f706ce 100644
--- a/spec/services/auth/container_registry_authentication_service_spec.rb
+++ b/spec/services/auth/container_registry_authentication_service_spec.rb
@@ -87,51 +87,105 @@ describe Auth::ContainerRegistryAuthenticationService, services: true do
end
context 'user authorization' do
- let(:project) { create(:project) }
let(:current_user) { create(:user) }
- context 'allow to use scope-less authentication' do
- it_behaves_like 'a valid token'
- end
+ context 'for private project' do
+ let(:project) { create(:empty_project) }
- context 'allow developer to push images' do
- before { project.team << [current_user, :developer] }
+ context 'allow to use scope-less authentication' do
+ it_behaves_like 'a valid token'
+ end
- let(:current_params) do
- { scope: "repository:#{project.path_with_namespace}:push" }
+ context 'allow developer to push images' do
+ before { project.team << [current_user, :developer] }
+
+ let(:current_params) do
+ { scope: "repository:#{project.path_with_namespace}:push" }
+ end
+
+ it_behaves_like 'a pushable'
end
- it_behaves_like 'a pushable'
- end
+ context 'allow reporter to pull images' do
+ 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
- before { project.team << [current_user, :reporter] }
+ it_behaves_like 'a pullable'
+ end
- let(:current_params) do
- { scope: "repository:#{project.path_with_namespace}:pull" }
+ context 'return a least of privileges' do
+ 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
- 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
- context 'return a least of privileges' do
- before { project.team << [current_user, :reporter] }
+ context 'for public project' do
+ let(:project) { create(:empty_project, :public) }
- let(:current_params) do
- { scope: "repository:#{project.path_with_namespace}:push,pull" }
+ 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
- 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
- context 'disallow guest to pull or push images' do
- before { project.team << [current_user, :guest] }
+ context 'for internal project' do
+ let(:project) { create(:empty_project, :internal) }
- let(:current_params) do
- { scope: "repository:#{project.path_with_namespace}:pull,push" }
+ context 'for internal user' do
+ 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
- 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
diff --git a/spec/services/create_commit_builds_service_spec.rb b/spec/services/create_commit_builds_service_spec.rb
index 4d09bc5fb12..d4c5e584421 100644
--- a/spec/services/create_commit_builds_service_spec.rb
+++ b/spec/services/create_commit_builds_service_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe CreateCommitBuildsService, services: true do
let(:service) { CreateCommitBuildsService.new }
let(:project) { FactoryGirl.create(:empty_project) }
- let(:user) { nil }
+ let(:user) { create(:user) }
before do
stub_ci_pipeline_to_return_yaml_file
@@ -24,6 +24,7 @@ describe CreateCommitBuildsService, services: true do
it { expect(pipeline).to be_valid }
it { expect(pipeline).to be_persisted }
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) }
end
diff --git a/spec/services/create_deployment_service_spec.rb b/spec/services/create_deployment_service_spec.rb
index 654e441f3cd..8da2a2b3c1b 100644
--- a/spec/services/create_deployment_service_spec.rb
+++ b/spec/services/create_deployment_service_spec.rb
@@ -89,6 +89,12 @@ describe CreateDeploymentService, services: true do
expect_any_instance_of(described_class).to receive(:execute)
subject
end
+
+ it 'is set as deployable' do
+ subject
+
+ expect(Deployment.last.deployable).to eq(deployable)
+ end
end
context 'without environment specified' do
@@ -105,6 +111,8 @@ describe CreateDeploymentService, services: true do
context 'when build succeeds' do
it_behaves_like 'does create environment and deployment' do
+ let(:deployable) { build }
+
subject { build.success }
end
end
@@ -114,6 +122,14 @@ describe CreateDeploymentService, services: true do
subject { build.drop }
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