New upstream version 11.8.2
This commit is contained in:
parent
0329642ba5
commit
5595a2eca7
129 changed files with 2040 additions and 687 deletions
42
CHANGELOG.md
42
CHANGELOG.md
|
@ -2,6 +2,48 @@
|
||||||
documentation](doc/development/changelog.md) for instructions on adding your own
|
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||||
entry.
|
entry.
|
||||||
|
|
||||||
|
## 11.8.2 (2019-03-13)
|
||||||
|
|
||||||
|
### Security (1 change)
|
||||||
|
|
||||||
|
- Fixed ability to see private groups by users not belonging to given group.
|
||||||
|
|
||||||
|
### Fixed (5 changes)
|
||||||
|
|
||||||
|
- Fix import_jid error on project import. !25239
|
||||||
|
- Properly handle multiple X-Forwarded-For addresses in runner IP. !25511
|
||||||
|
- Fix error when viewing group issue boards when user doesn't have explicit group permissions. !25524
|
||||||
|
- Fix method to mark a project repository as writable. !25546
|
||||||
|
- Allow project members to see private group if the project is in the group namespace.
|
||||||
|
|
||||||
|
|
||||||
|
## 11.8.1 (2019-02-28)
|
||||||
|
|
||||||
|
### Security (21 changes)
|
||||||
|
|
||||||
|
- Stop linking to unrecognized package sources. !55518
|
||||||
|
- Don't allow non-members to see private related MRs.
|
||||||
|
- Do not display impersonated sessions under active sessions and remove ability to revoke session.
|
||||||
|
- Display only information visible to current user on the Milestone page.
|
||||||
|
- Show only merge requests visible to user on milestone detail page.
|
||||||
|
- Disable issue boards API when issues are disabled.
|
||||||
|
- Don't show new issue link after move when a user does not have permissions.
|
||||||
|
- Fix git clone revealing private repo's presence.
|
||||||
|
- Fix blind SSRF in Prometheus integration by checking URL before querying.
|
||||||
|
- Check snippet attached file to be moved is within designated directory.
|
||||||
|
- Check if desired milestone for an issue is available.
|
||||||
|
- Fix arbitrary file read via diffs during import.
|
||||||
|
- Display the correct number of MRs a user has access to.
|
||||||
|
- Forbid creating discussions for users with restricted access.
|
||||||
|
- Do not disclose milestone titles for unauthorized users.
|
||||||
|
- Validate session key when authorizing with GCP to create a cluster.
|
||||||
|
- Block local URLs for Kubernetes integration.
|
||||||
|
- Limit mermaid rendering to 5K characters.
|
||||||
|
- Remove the possibility to share a project with a group that a user is not a member of.
|
||||||
|
- Fix leaking private repository information in API.
|
||||||
|
- Prevent releases links API to leak tag existance.
|
||||||
|
|
||||||
|
|
||||||
## 11.8.0 (2019-02-22)
|
## 11.8.0 (2019-02-22)
|
||||||
|
|
||||||
### Security (7 changes, 1 of them is from the community)
|
### Security (7 changes, 1 of them is from the community)
|
||||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
11.8.0
|
11.8.2
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import flash from '~/flash';
|
import flash from '~/flash';
|
||||||
|
import { sprintf, __ } from '../../locale';
|
||||||
|
|
||||||
// Renders diagrams and flowcharts from text using Mermaid in any element with the
|
// Renders diagrams and flowcharts from text using Mermaid in any element with the
|
||||||
// `js-render-mermaid` class.
|
// `js-render-mermaid` class.
|
||||||
|
@ -14,6 +15,9 @@ import flash from '~/flash';
|
||||||
// </pre>
|
// </pre>
|
||||||
//
|
//
|
||||||
|
|
||||||
|
// This is an arbitary number; Can be iterated upon when suitable.
|
||||||
|
const MAX_CHAR_LIMIT = 5000;
|
||||||
|
|
||||||
export default function renderMermaid($els) {
|
export default function renderMermaid($els) {
|
||||||
if (!$els.length) return;
|
if (!$els.length) return;
|
||||||
|
|
||||||
|
@ -34,6 +38,21 @@ export default function renderMermaid($els) {
|
||||||
$els.each((i, el) => {
|
$els.each((i, el) => {
|
||||||
const source = el.textContent;
|
const source = el.textContent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restrict the rendering to a certain amount of character to
|
||||||
|
* prevent mermaidjs from hanging up the entire thread and
|
||||||
|
* causing a DoS.
|
||||||
|
*/
|
||||||
|
if (source && source.length > MAX_CHAR_LIMIT) {
|
||||||
|
el.textContent = sprintf(
|
||||||
|
__(
|
||||||
|
'Cannot render the image. Maximum character count (%{charLimit}) has been exceeded.',
|
||||||
|
),
|
||||||
|
{ charLimit: MAX_CHAR_LIMIT },
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Remove any extra spans added by the backend syntax highlighting.
|
// Remove any extra spans added by the backend syntax highlighting.
|
||||||
Object.assign(el, { textContent: source });
|
Object.assign(el, { textContent: source });
|
||||||
|
|
||||||
|
|
|
@ -315,7 +315,7 @@ export default {
|
||||||
:endpoint="mr.testResultsPath"
|
:endpoint="mr.testResultsPath"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="mr-widget-section p-0">
|
<div class="mr-widget-section">
|
||||||
<component :is="componentName" :mr="mr" :service="service" />
|
<component :is="componentName" :mr="mr" :service="service" />
|
||||||
|
|
||||||
<section v-if="shouldRenderCollaborationStatus" class="mr-info-list mr-links">
|
<section v-if="shouldRenderCollaborationStatus" class="mr-info-list mr-links">
|
||||||
|
|
|
@ -82,7 +82,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.mr-widget-body,
|
.mr-widget-body,
|
||||||
.mr-widget-section,
|
|
||||||
.mr-widget-content,
|
.mr-widget-content,
|
||||||
.mr-widget-footer {
|
.mr-widget-footer {
|
||||||
padding: $gl-padding;
|
padding: $gl-padding;
|
||||||
|
|
|
@ -8,7 +8,7 @@ module MilestoneActions
|
||||||
format.html { redirect_to milestone_redirect_path }
|
format.html { redirect_to milestone_redirect_path }
|
||||||
format.json do
|
format.json do
|
||||||
render json: tabs_json("shared/milestones/_merge_requests_tab", {
|
render json: tabs_json("shared/milestones/_merge_requests_tab", {
|
||||||
merge_requests: @milestone.sorted_merge_requests, # rubocop:disable Gitlab/ModuleWithInstanceVariables
|
merge_requests: @milestone.sorted_merge_requests(current_user), # rubocop:disable Gitlab/ModuleWithInstanceVariables
|
||||||
show_project_name: true
|
show_project_name: true
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,6 +2,10 @@
|
||||||
|
|
||||||
module GoogleApi
|
module GoogleApi
|
||||||
class AuthorizationsController < ApplicationController
|
class AuthorizationsController < ApplicationController
|
||||||
|
include Gitlab::Utils::StrongMemoize
|
||||||
|
|
||||||
|
before_action :validate_session_key!
|
||||||
|
|
||||||
def callback
|
def callback
|
||||||
token, expires_at = GoogleApi::CloudPlatform::Client
|
token, expires_at = GoogleApi::CloudPlatform::Client
|
||||||
.new(nil, callback_google_api_auth_url)
|
.new(nil, callback_google_api_auth_url)
|
||||||
|
@ -11,21 +15,27 @@ module GoogleApi
|
||||||
session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at] =
|
session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at] =
|
||||||
expires_at.to_s
|
expires_at.to_s
|
||||||
|
|
||||||
state_redirect_uri = redirect_uri_from_session_key(params[:state])
|
redirect_to redirect_uri_from_session
|
||||||
|
|
||||||
if state_redirect_uri
|
|
||||||
redirect_to state_redirect_uri
|
|
||||||
else
|
|
||||||
redirect_to root_path
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def redirect_uri_from_session_key(state)
|
def validate_session_key!
|
||||||
key = GoogleApi::CloudPlatform::Client
|
access_denied! unless redirect_uri_from_session.present?
|
||||||
.session_key_for_redirect_uri(params[:state])
|
end
|
||||||
session[key] if key
|
|
||||||
|
def redirect_uri_from_session
|
||||||
|
strong_memoize(:redirect_uri_from_session) do
|
||||||
|
if params[:state].present?
|
||||||
|
session[session_key_for_redirect_uri(params[:state])]
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def session_key_for_redirect_uri(state)
|
||||||
|
GoogleApi::CloudPlatform::Client.session_key_for_redirect_uri(state)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,15 +2,6 @@
|
||||||
|
|
||||||
class Profiles::ActiveSessionsController < Profiles::ApplicationController
|
class Profiles::ActiveSessionsController < Profiles::ApplicationController
|
||||||
def index
|
def index
|
||||||
@sessions = ActiveSession.list(current_user)
|
@sessions = ActiveSession.list(current_user).reject(&:is_impersonated)
|
||||||
end
|
|
||||||
|
|
||||||
def destroy
|
|
||||||
ActiveSession.destroy(current_user, params[:id])
|
|
||||||
|
|
||||||
respond_to do |format|
|
|
||||||
format.html { redirect_to profile_active_sessions_url, status: :found }
|
|
||||||
format.js { head :ok }
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Projects::AutocompleteSourcesController < Projects::ApplicationController
|
class Projects::AutocompleteSourcesController < Projects::ApplicationController
|
||||||
|
before_action :authorize_read_milestone!, only: :milestones
|
||||||
|
|
||||||
def members
|
def members
|
||||||
render json: ::Projects::ParticipantsService.new(@project, current_user).execute(target)
|
render json: ::Projects::ParticipantsService.new(@project, current_user).execute(target)
|
||||||
end
|
end
|
||||||
|
|
|
@ -65,7 +65,11 @@ class Projects::CommitController < Projects::ApplicationController
|
||||||
# rubocop: enable CodeReuse/ActiveRecord
|
# rubocop: enable CodeReuse/ActiveRecord
|
||||||
|
|
||||||
def merge_requests
|
def merge_requests
|
||||||
@merge_requests = @commit.merge_requests.map do |mr|
|
@merge_requests = MergeRequestsFinder.new(
|
||||||
|
current_user,
|
||||||
|
project_id: @project.id,
|
||||||
|
commit_sha: @commit.sha
|
||||||
|
).execute.map do |mr|
|
||||||
{ iid: mr.iid, path: merge_request_path(mr), title: mr.title }
|
{ iid: mr.iid, path: merge_request_path(mr), title: mr.title }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -13,9 +13,10 @@ class Projects::GroupLinksController < Projects::ApplicationController
|
||||||
group = Group.find(params[:link_group_id]) if params[:link_group_id].present?
|
group = Group.find(params[:link_group_id]) if params[:link_group_id].present?
|
||||||
|
|
||||||
if group
|
if group
|
||||||
return render_404 unless can?(current_user, :read_group, group)
|
result = Projects::GroupLinks::CreateService.new(project, current_user, group_link_create_params).execute(group)
|
||||||
|
return render_404 if result[:http_status] == 404
|
||||||
|
|
||||||
Projects::GroupLinks::CreateService.new(project, current_user, group_link_create_params).execute(group)
|
flash[:alert] = result[:message] if result[:http_status] == 409
|
||||||
else
|
else
|
||||||
flash[:alert] = 'Please select a group.'
|
flash[:alert] = 'Please select a group.'
|
||||||
end
|
end
|
||||||
|
|
|
@ -37,13 +37,20 @@ class MergeRequestsFinder < IssuableFinder
|
||||||
end
|
end
|
||||||
|
|
||||||
def filter_items(_items)
|
def filter_items(_items)
|
||||||
items = by_source_branch(super)
|
items = by_commit(super)
|
||||||
|
items = by_source_branch(items)
|
||||||
items = by_wip(items)
|
items = by_wip(items)
|
||||||
by_target_branch(items)
|
by_target_branch(items)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def by_commit(items)
|
||||||
|
return items unless params[:commit_sha].presence
|
||||||
|
|
||||||
|
items.by_commit_sha(params[:commit_sha])
|
||||||
|
end
|
||||||
|
|
||||||
def source_branch
|
def source_branch
|
||||||
@source_branch ||= params[:source_branch].presence
|
@source_branch ||= params[:source_branch].presence
|
||||||
end
|
end
|
||||||
|
|
|
@ -16,7 +16,6 @@ module Types
|
||||||
|
|
||||||
field :description, GraphQL::STRING_TYPE, null: true
|
field :description, GraphQL::STRING_TYPE, null: true
|
||||||
|
|
||||||
field :default_branch, GraphQL::STRING_TYPE, null: true
|
|
||||||
field :tag_list, GraphQL::STRING_TYPE, null: true
|
field :tag_list, GraphQL::STRING_TYPE, null: true
|
||||||
|
|
||||||
field :ssh_url_to_repo, GraphQL::STRING_TYPE, null: true
|
field :ssh_url_to_repo, GraphQL::STRING_TYPE, null: true
|
||||||
|
@ -59,7 +58,6 @@ module Types
|
||||||
end
|
end
|
||||||
|
|
||||||
field :import_status, GraphQL::STRING_TYPE, null: true
|
field :import_status, GraphQL::STRING_TYPE, null: true
|
||||||
field :ci_config_path, GraphQL::STRING_TYPE, null: true
|
|
||||||
|
|
||||||
field :only_allow_merge_if_pipeline_succeeds, GraphQL::BOOLEAN_TYPE, null: true
|
field :only_allow_merge_if_pipeline_succeeds, GraphQL::BOOLEAN_TYPE, null: true
|
||||||
field :request_access_enabled, GraphQL::BOOLEAN_TYPE, null: true
|
field :request_access_enabled, GraphQL::BOOLEAN_TYPE, null: true
|
||||||
|
|
|
@ -74,6 +74,7 @@ module Emails
|
||||||
|
|
||||||
@new_issue = new_issue
|
@new_issue = new_issue
|
||||||
@new_project = new_issue.project
|
@new_project = new_issue.project
|
||||||
|
@can_access_project = recipient.can?(:read_project, @new_project)
|
||||||
mail_answer_thread(issue, issue_thread_options(updated_by_user.id, recipient.id, reason))
|
mail_answer_thread(issue, issue_thread_options(updated_by_user.id, recipient.id, reason))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,8 @@ class ActiveSession
|
||||||
|
|
||||||
attr_accessor :created_at, :updated_at,
|
attr_accessor :created_at, :updated_at,
|
||||||
:session_id, :ip_address,
|
:session_id, :ip_address,
|
||||||
:browser, :os, :device_name, :device_type
|
:browser, :os, :device_name, :device_type,
|
||||||
|
:is_impersonated
|
||||||
|
|
||||||
def current?(session)
|
def current?(session)
|
||||||
return false if session_id.nil? || session.id.nil?
|
return false if session_id.nil? || session.id.nil?
|
||||||
|
@ -31,7 +32,8 @@ class ActiveSession
|
||||||
device_type: client.device_type,
|
device_type: client.device_type,
|
||||||
created_at: user.current_sign_in_at || timestamp,
|
created_at: user.current_sign_in_at || timestamp,
|
||||||
updated_at: timestamp,
|
updated_at: timestamp,
|
||||||
session_id: session_id
|
session_id: session_id,
|
||||||
|
is_impersonated: request.session[:impersonator_id].present?
|
||||||
)
|
)
|
||||||
|
|
||||||
redis.pipelined do
|
redis.pipelined do
|
||||||
|
|
|
@ -41,7 +41,7 @@ module Clusters
|
||||||
validate :no_namespace, unless: :allow_user_defined_namespace?
|
validate :no_namespace, unless: :allow_user_defined_namespace?
|
||||||
|
|
||||||
# We expect to be `active?` only when enabled and cluster is created (the api_url is assigned)
|
# We expect to be `active?` only when enabled and cluster is created (the api_url is assigned)
|
||||||
validates :api_url, url: true, presence: true
|
validates :api_url, public_url: true, presence: true
|
||||||
validates :token, presence: true
|
validates :token, presence: true
|
||||||
|
|
||||||
validate :prevent_modification, on: :update
|
validate :prevent_modification, on: :update
|
||||||
|
|
|
@ -74,6 +74,7 @@ module Issuable
|
||||||
|
|
||||||
validates :author, presence: true
|
validates :author, presence: true
|
||||||
validates :title, presence: true, length: { maximum: 255 }
|
validates :title, presence: true, length: { maximum: 255 }
|
||||||
|
validate :milestone_is_valid
|
||||||
|
|
||||||
scope :authored, ->(user) { where(author_id: user) }
|
scope :authored, ->(user) { where(author_id: user) }
|
||||||
scope :recent, -> { reorder(id: :desc) }
|
scope :recent, -> { reorder(id: :desc) }
|
||||||
|
@ -117,6 +118,16 @@ module Issuable
|
||||||
def has_multiple_assignees?
|
def has_multiple_assignees?
|
||||||
assignees.count > 1
|
assignees.count > 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def milestone_available?
|
||||||
|
project_id == milestone&.project_id || project.ancestors_upto.compact.include?(milestone&.group)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def milestone_is_valid
|
||||||
|
errors.add(:milestone_id, message: "is invalid") if milestone_id.present? && !milestone_available?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class_methods do
|
class_methods do
|
||||||
|
|
|
@ -46,12 +46,31 @@ module Milestoneish
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def issue_participants_visible_by_user(user)
|
||||||
|
User.joins(:issue_assignees)
|
||||||
|
.where('issue_assignees.issue_id' => issues_visible_to_user(user).select(:id))
|
||||||
|
.distinct
|
||||||
|
end
|
||||||
|
|
||||||
|
def issue_labels_visible_by_user(user)
|
||||||
|
Label.joins(:label_links)
|
||||||
|
.where('label_links.target_id' => issues_visible_to_user(user).select(:id), 'label_links.target_type' => 'Issue')
|
||||||
|
.distinct
|
||||||
|
end
|
||||||
|
|
||||||
def sorted_issues(user)
|
def sorted_issues(user)
|
||||||
issues_visible_to_user(user).preload_associations.sort_by_attribute('label_priority')
|
issues_visible_to_user(user).preload_associations.sort_by_attribute('label_priority')
|
||||||
end
|
end
|
||||||
|
|
||||||
def sorted_merge_requests
|
def sorted_merge_requests(user)
|
||||||
merge_requests.sort_by_attribute('label_priority')
|
merge_requests_visible_to_user(user).sort_by_attribute('label_priority')
|
||||||
|
end
|
||||||
|
|
||||||
|
def merge_requests_visible_to_user(user)
|
||||||
|
memoize_per_user(user, :merge_requests_visible_to_user) do
|
||||||
|
MergeRequestsFinder.new(user, issues_finder_params)
|
||||||
|
.execute.where(milestone_id: milestoneish_id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def upcoming?
|
def upcoming?
|
||||||
|
|
|
@ -71,7 +71,7 @@ class MergeRequest < ActiveRecord::Base
|
||||||
|
|
||||||
serialize :merge_params, Hash # rubocop:disable Cop/ActiveRecordSerialize
|
serialize :merge_params, Hash # rubocop:disable Cop/ActiveRecordSerialize
|
||||||
|
|
||||||
after_create :ensure_merge_request_diff, unless: :importing?
|
after_create :ensure_merge_request_diff
|
||||||
after_update :clear_memoized_shas
|
after_update :clear_memoized_shas
|
||||||
after_update :reload_diff_if_branch_changed
|
after_update :reload_diff_if_branch_changed
|
||||||
after_save :ensure_metrics
|
after_save :ensure_metrics
|
||||||
|
@ -189,6 +189,9 @@ class MergeRequest < ActiveRecord::Base
|
||||||
|
|
||||||
after_save :keep_around_commit
|
after_save :keep_around_commit
|
||||||
|
|
||||||
|
alias_attribute :project, :target_project
|
||||||
|
alias_attribute :project_id, :target_project_id
|
||||||
|
|
||||||
def self.reference_prefix
|
def self.reference_prefix
|
||||||
'!'
|
'!'
|
||||||
end
|
end
|
||||||
|
@ -837,10 +840,6 @@ class MergeRequest < ActiveRecord::Base
|
||||||
target_project != source_project
|
target_project != source_project
|
||||||
end
|
end
|
||||||
|
|
||||||
def project
|
|
||||||
target_project
|
|
||||||
end
|
|
||||||
|
|
||||||
# If the merge request closes any issues, save this information in the
|
# If the merge request closes any issues, save this information in the
|
||||||
# `MergeRequestsClosingIssues` model. This is a performance optimization.
|
# `MergeRequestsClosingIssues` model. This is a performance optimization.
|
||||||
# Calculating this information for a number of merge requests requires
|
# Calculating this information for a number of merge requests requires
|
||||||
|
|
|
@ -25,6 +25,8 @@ class MergeRequestDiff < ActiveRecord::Base
|
||||||
|
|
||||||
has_many :merge_request_diff_commits, -> { order(:merge_request_diff_id, :relative_order) }
|
has_many :merge_request_diff_commits, -> { order(:merge_request_diff_id, :relative_order) }
|
||||||
|
|
||||||
|
validates :base_commit_sha, :head_commit_sha, :start_commit_sha, sha: true
|
||||||
|
|
||||||
state_machine :state, initial: :empty do
|
state_machine :state, initial: :empty do
|
||||||
event :clean do
|
event :clean do
|
||||||
transition any => :without_files
|
transition any => :without_files
|
||||||
|
|
|
@ -1830,7 +1830,7 @@ class Project < ActiveRecord::Base
|
||||||
# Set repository as writable again
|
# Set repository as writable again
|
||||||
def set_repository_writable!
|
def set_repository_writable!
|
||||||
with_lock do
|
with_lock do
|
||||||
update_column(repository_read_only, false)
|
update_column(:repository_read_only, false)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -76,7 +76,7 @@ class ProjectFeature < ActiveRecord::Base
|
||||||
# This feature might not be behind a feature flag at all, so default to true
|
# This feature might not be behind a feature flag at all, so default to true
|
||||||
return false unless ::Feature.enabled?(feature, user, default_enabled: true)
|
return false unless ::Feature.enabled?(feature, user, default_enabled: true)
|
||||||
|
|
||||||
get_permission(user, access_level(feature))
|
get_permission(user, feature)
|
||||||
end
|
end
|
||||||
|
|
||||||
def access_level(feature)
|
def access_level(feature)
|
||||||
|
@ -134,12 +134,12 @@ class ProjectFeature < ActiveRecord::Base
|
||||||
(FEATURES - %i(pages)).each {|f| validator.call("#{f}_access_level")}
|
(FEATURES - %i(pages)).each {|f| validator.call("#{f}_access_level")}
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_permission(user, level)
|
def get_permission(user, feature)
|
||||||
case level
|
case access_level(feature)
|
||||||
when DISABLED
|
when DISABLED
|
||||||
false
|
false
|
||||||
when PRIVATE
|
when PRIVATE
|
||||||
user && (project.team.member?(user) || user.full_private_access?)
|
team_access?(user, feature)
|
||||||
when ENABLED
|
when ENABLED
|
||||||
true
|
true
|
||||||
when PUBLIC
|
when PUBLIC
|
||||||
|
@ -148,4 +148,11 @@ class ProjectFeature < ActiveRecord::Base
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def team_access?(user, feature)
|
||||||
|
return unless user
|
||||||
|
return true if user.full_private_access?
|
||||||
|
|
||||||
|
project.team.member?(user, ProjectFeature.required_minimum_access_level(feature))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -71,7 +71,7 @@ class PrometheusService < MonitoringService
|
||||||
end
|
end
|
||||||
|
|
||||||
def prometheus_client
|
def prometheus_client
|
||||||
RestClient::Resource.new(api_url, max_redirects: 0) if api_url && manual_configuration? && active?
|
RestClient::Resource.new(api_url, max_redirects: 0) if should_return_client?
|
||||||
end
|
end
|
||||||
|
|
||||||
def prometheus_available?
|
def prometheus_available?
|
||||||
|
@ -83,6 +83,10 @@ class PrometheusService < MonitoringService
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def should_return_client?
|
||||||
|
api_url && manual_configuration? && active? && valid?
|
||||||
|
end
|
||||||
|
|
||||||
def synchronize_service_state
|
def synchronize_service_state
|
||||||
self.active = prometheus_available? || manual_configuration?
|
self.active = prometheus_available? || manual_configuration?
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ class GroupPolicy < BasePolicy
|
||||||
condition(:can_change_parent_share_with_group_lock) { can?(:change_share_with_group_lock, @subject.parent) }
|
condition(:can_change_parent_share_with_group_lock) { can?(:change_share_with_group_lock, @subject.parent) }
|
||||||
|
|
||||||
condition(:has_projects) do
|
condition(:has_projects) do
|
||||||
GroupProjectsFinder.new(group: @subject, current_user: @user, options: { include_subgroups: true }).execute.any?
|
GroupProjectsFinder.new(group: @subject, current_user: @user, options: { include_subgroups: true, only_owned: true }).execute.any?
|
||||||
end
|
end
|
||||||
|
|
||||||
condition(:has_clusters, scope: :subject) { clusterable_has_clusters? }
|
condition(:has_clusters, scope: :subject) { clusterable_has_clusters? }
|
||||||
|
@ -53,8 +53,9 @@ class GroupPolicy < BasePolicy
|
||||||
rule { admin }.enable :read_group
|
rule { admin }.enable :read_group
|
||||||
|
|
||||||
rule { has_projects }.policy do
|
rule { has_projects }.policy do
|
||||||
enable :read_group
|
enable :read_list
|
||||||
enable :read_label
|
enable :read_label
|
||||||
|
enable :read_group
|
||||||
end
|
end
|
||||||
|
|
||||||
rule { has_access }.enable :read_namespace
|
rule { has_access }.enable :read_namespace
|
||||||
|
|
|
@ -299,6 +299,8 @@ class ProjectPolicy < BasePolicy
|
||||||
|
|
||||||
rule { issues_disabled }.policy do
|
rule { issues_disabled }.policy do
|
||||||
prevent(*create_read_update_admin_destroy(:issue))
|
prevent(*create_read_update_admin_destroy(:issue))
|
||||||
|
prevent(*create_read_update_admin_destroy(:board))
|
||||||
|
prevent(*create_read_update_admin_destroy(:list))
|
||||||
end
|
end
|
||||||
|
|
||||||
rule { merge_requests_disabled | repository_disabled }.policy do
|
rule { merge_requests_disabled | repository_disabled }.policy do
|
||||||
|
@ -462,7 +464,7 @@ class ProjectPolicy < BasePolicy
|
||||||
when ProjectFeature::DISABLED
|
when ProjectFeature::DISABLED
|
||||||
false
|
false
|
||||||
when ProjectFeature::PRIVATE
|
when ProjectFeature::PRIVATE
|
||||||
guest? || admin?
|
admin? || team_access_level >= ProjectFeature.required_minimum_access_level(feature)
|
||||||
else
|
else
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
|
@ -387,4 +387,10 @@ class IssuableBaseService < BaseService
|
||||||
def parent
|
def parent
|
||||||
project
|
project
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# we need to check this because milestone from milestone_id param is displayed on "new" page
|
||||||
|
# where private project milestone could leak without this check
|
||||||
|
def ensure_milestone_available(issuable)
|
||||||
|
issuable.milestone_id = nil unless issuable.milestone_available?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,7 +6,9 @@ module Issues
|
||||||
|
|
||||||
def execute
|
def execute
|
||||||
filter_resolve_discussion_params
|
filter_resolve_discussion_params
|
||||||
@issue = project.issues.new(issue_params)
|
@issue = project.issues.new(issue_params).tap do |issue|
|
||||||
|
ensure_milestone_available(issue)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def issue_params_with_info_from_discussions
|
def issue_params_with_info_from_discussions
|
||||||
|
|
|
@ -19,6 +19,7 @@ module MergeRequests
|
||||||
merge_request.target_project = find_target_project
|
merge_request.target_project = find_target_project
|
||||||
merge_request.target_branch = find_target_branch
|
merge_request.target_branch = find_target_branch
|
||||||
merge_request.can_be_created = projects_and_branches_valid?
|
merge_request.can_be_created = projects_and_branches_valid?
|
||||||
|
ensure_milestone_available(merge_request)
|
||||||
|
|
||||||
# compare branches only if branches are valid, otherwise
|
# compare branches only if branches are valid, otherwise
|
||||||
# compare_branches may raise an error
|
# compare_branches may raise an error
|
||||||
|
|
|
@ -4,13 +4,19 @@ module Projects
|
||||||
module GroupLinks
|
module GroupLinks
|
||||||
class CreateService < BaseService
|
class CreateService < BaseService
|
||||||
def execute(group)
|
def execute(group)
|
||||||
return false unless group
|
return error('Not Found', 404) unless group && can?(current_user, :read_namespace, group)
|
||||||
|
|
||||||
project.project_group_links.create(
|
link = project.project_group_links.new(
|
||||||
group: group,
|
group: group,
|
||||||
group_access: params[:link_group_access],
|
group_access: params[:link_group_access],
|
||||||
expires_at: params[:expires_at]
|
expires_at: params[:expires_at]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if link.save
|
||||||
|
success(link: link)
|
||||||
|
else
|
||||||
|
error(link.errors.full_messages.to_sentence, 409)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -11,6 +11,8 @@ class FileMover
|
||||||
end
|
end
|
||||||
|
|
||||||
def execute
|
def execute
|
||||||
|
return unless valid?
|
||||||
|
|
||||||
move
|
move
|
||||||
|
|
||||||
if update_markdown
|
if update_markdown
|
||||||
|
@ -21,6 +23,12 @@ class FileMover
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def valid?
|
||||||
|
Pathname.new(temp_file_path).realpath.to_path.start_with?(
|
||||||
|
(Pathname(temp_file_uploader.root) + temp_file_uploader.base_dir).to_path
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
def move
|
def move
|
||||||
FileUtils.mkdir_p(File.dirname(file_path))
|
FileUtils.mkdir_p(File.dirname(file_path))
|
||||||
FileUtils.move(temp_file_path, file_path)
|
FileUtils.move(temp_file_path, file_path)
|
||||||
|
|
9
app/validators/sha_validator.rb
Normal file
9
app/validators/sha_validator.rb
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class ShaValidator < ActiveModel::EachValidator
|
||||||
|
def validate_each(record, attribute, value)
|
||||||
|
return if value.blank? || value.match(/\A\h{40}\z/)
|
||||||
|
|
||||||
|
record.errors.add(attribute, 'is not a valid SHA')
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,6 +1,9 @@
|
||||||
%p
|
%p
|
||||||
Issue was moved to another project.
|
Issue was moved to another project.
|
||||||
|
- if @can_access_project
|
||||||
%p
|
%p
|
||||||
New issue:
|
New issue:
|
||||||
= link_to project_issue_url(@new_project, @new_issue) do
|
= link_to project_issue_url(@new_project, @new_issue) do
|
||||||
= @new_issue.title
|
= @new_issue.title
|
||||||
|
- else
|
||||||
|
You don't have access to the project.
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
Issue was moved to another project.
|
Issue was moved to another project.
|
||||||
|
|
||||||
|
<% if @can_access_project %>
|
||||||
New issue location:
|
New issue location:
|
||||||
<%= project_issue_url(@new_project, @new_issue) %>
|
<%= project_issue_url(@new_project, @new_issue) %>
|
||||||
|
<% else %>
|
||||||
|
You don't have access to the project.
|
||||||
|
<% end %>
|
||||||
|
|
|
@ -23,9 +23,3 @@
|
||||||
%strong Signed in
|
%strong Signed in
|
||||||
on
|
on
|
||||||
= l(active_session.created_at, format: :short)
|
= l(active_session.created_at, format: :short)
|
||||||
|
|
||||||
- unless is_current_session
|
|
||||||
.float-right
|
|
||||||
= link_to profile_active_session_path(active_session.session_id), data: { confirm: 'Are you sure? The device will be signed out of GitLab.' }, method: :delete, class: "btn btn-danger prepend-left-10" do
|
|
||||||
%span.sr-only Revoke
|
|
||||||
Revoke
|
|
||||||
|
|
|
@ -3,9 +3,4 @@
|
||||||
This project manages its dependencies using
|
This project manages its dependencies using
|
||||||
%strong= viewer.manager_name
|
%strong= viewer.manager_name
|
||||||
|
|
||||||
- if viewer.package_name
|
|
||||||
and defines a #{viewer.package_type} named
|
|
||||||
%strong<
|
|
||||||
= link_to_if viewer.package_url.present?, viewer.package_name, viewer.package_url, target: '_blank', rel: 'noopener noreferrer'
|
|
||||||
|
|
||||||
= link_to 'Learn more', viewer.manager_url, target: '_blank', rel: 'noopener noreferrer'
|
= link_to 'Learn more', viewer.manager_url, target: '_blank', rel: 'noopener noreferrer'
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
= milestone_progress_bar(milestone)
|
= milestone_progress_bar(milestone)
|
||||||
= link_to pluralize(milestone.total_issues_count(current_user), 'Issue'), issues_path
|
= link_to pluralize(milestone.total_issues_count(current_user), 'Issue'), issues_path
|
||||||
·
|
·
|
||||||
= link_to pluralize(milestone.merge_requests.size, 'Merge Request'), merge_requests_path
|
= link_to pluralize(milestone.merge_requests_visible_to_user(current_user).size, 'Merge Request'), merge_requests_path
|
||||||
.float-lg-right.light #{milestone.percent_complete(current_user)}% complete
|
.float-lg-right.light #{milestone.percent_complete(current_user)}% complete
|
||||||
.col-sm-2
|
.col-sm-2
|
||||||
.milestone-actions.d-flex.justify-content-sm-start.justify-content-md-end
|
.milestone-actions.d-flex.justify-content-sm-start.justify-content-md-end
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
%li.nav-item
|
%li.nav-item
|
||||||
= link_to '#tab-merge-requests', class: 'nav-link', 'data-toggle' => 'tab', 'data-endpoint': milestone_merge_request_tab_path(milestone) do
|
= link_to '#tab-merge-requests', class: 'nav-link', 'data-toggle' => 'tab', 'data-endpoint': milestone_merge_request_tab_path(milestone) do
|
||||||
Merge Requests
|
Merge Requests
|
||||||
%span.badge.badge-pill= milestone.merge_requests.size
|
%span.badge.badge-pill= milestone.merge_requests_visible_to_user(current_user).size
|
||||||
- else
|
- else
|
||||||
%li.nav-item
|
%li.nav-item
|
||||||
= link_to '#tab-merge-requests', class: 'nav-link active', 'data-toggle' => 'tab', 'data-endpoint': milestone_merge_request_tab_path(milestone) do
|
= link_to '#tab-merge-requests', class: 'nav-link active', 'data-toggle' => 'tab', 'data-endpoint': milestone_merge_request_tab_path(milestone) do
|
||||||
|
@ -21,11 +21,11 @@
|
||||||
%li.nav-item
|
%li.nav-item
|
||||||
= link_to '#tab-participants', class: 'nav-link', 'data-toggle' => 'tab', 'data-endpoint': milestone_participants_tab_path(milestone) do
|
= link_to '#tab-participants', class: 'nav-link', 'data-toggle' => 'tab', 'data-endpoint': milestone_participants_tab_path(milestone) do
|
||||||
Participants
|
Participants
|
||||||
%span.badge.badge-pill= milestone.participants.count
|
%span.badge.badge-pill= milestone.issue_participants_visible_by_user(current_user).count
|
||||||
%li.nav-item
|
%li.nav-item
|
||||||
= link_to '#tab-labels', class: 'nav-link', 'data-toggle' => 'tab', 'data-endpoint': milestone_labels_tab_path(milestone) do
|
= link_to '#tab-labels', class: 'nav-link', 'data-toggle' => 'tab', 'data-endpoint': milestone_labels_tab_path(milestone) do
|
||||||
Labels
|
Labels
|
||||||
%span.badge.badge-pill= milestone.labels.count
|
%span.badge.badge-pill= milestone.issue_labels_visible_by_user(current_user).count
|
||||||
|
|
||||||
- issues = milestone.sorted_issues(current_user)
|
- issues = milestone.sorted_issues(current_user)
|
||||||
- show_project_name = local_assigns.fetch(:show_project_name, false)
|
- show_project_name = local_assigns.fetch(:show_project_name, false)
|
||||||
|
|
|
@ -40,7 +40,7 @@ scope(path: '*namespace_id/:project_id',
|
||||||
# /info/refs?service=git-receive-pack, but nothing else.
|
# /info/refs?service=git-receive-pack, but nothing else.
|
||||||
#
|
#
|
||||||
git_http_handshake = lambda do |request|
|
git_http_handshake = lambda do |request|
|
||||||
::Constraints::ProjectUrlConstrainer.new.matches?(request) &&
|
::Constraints::ProjectUrlConstrainer.new.matches?(request, existence_check: false) &&
|
||||||
(request.query_string.blank? ||
|
(request.query_string.blank? ||
|
||||||
request.query_string.match(/\Aservice=git-(upload|receive)-pack\z/))
|
request.query_string.match(/\Aservice=git-(upload|receive)-pack\z/))
|
||||||
end
|
end
|
||||||
|
|
|
@ -48,7 +48,7 @@ Learn how to install, configure, update, and maintain your GitLab instance.
|
||||||
- [Third party offers](../user/admin_area/settings/third_party_offers.md)
|
- [Third party offers](../user/admin_area/settings/third_party_offers.md)
|
||||||
- [Compliance](compliance.md): A collection of features from across the application that you may configure to help ensure that your GitLab instance and DevOps workflow meet compliance standards.
|
- [Compliance](compliance.md): A collection of features from across the application that you may configure to help ensure that your GitLab instance and DevOps workflow meet compliance standards.
|
||||||
- [Diff limits](../user/admin_area/diff_limits.md): Configure the diff rendering size limits of branch comparison pages.
|
- [Diff limits](../user/admin_area/diff_limits.md): Configure the diff rendering size limits of branch comparison pages.
|
||||||
- [Merge request diffs](merge_request_diffs.md): Configure the diffs shown on merge requests
|
- [Merge request diffs storage](merge_request_diffs.md): Configure merge requests diffs external storage.
|
||||||
- [Broadcast Messages](../user/admin_area/broadcast_messages.md): Send messages to GitLab users through the UI.
|
- [Broadcast Messages](../user/admin_area/broadcast_messages.md): Send messages to GitLab users through the UI.
|
||||||
|
|
||||||
#### Customizing GitLab's appearance
|
#### Customizing GitLab's appearance
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
# Merge request diffs administration
|
# Merge request diffs storage **[CORE ONLY]**
|
||||||
|
|
||||||
> **Notes:**
|
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/52568) in GitLab 11.8.
|
||||||
> - External merge request diffs introduced in GitLab 11.8
|
|
||||||
|
|
||||||
Merge request diffs are size-limited copies of diffs associated with merge
|
Merge request diffs are size-limited copies of diffs associated with merge
|
||||||
requests. When viewing a merge request, diffs are sourced from these copies
|
requests. When viewing a merge request, diffs are sourced from these copies
|
||||||
|
@ -16,9 +15,7 @@ large, in which case, switching to external storage is recommended.
|
||||||
Merge request diffs can be stored on disk, or in object storage. In general, it
|
Merge request diffs can be stored on disk, or in object storage. In general, it
|
||||||
is better to store the diffs in the database than on disk.
|
is better to store the diffs in the database than on disk.
|
||||||
|
|
||||||
To enable external storage of merge request diffs:
|
To enable external storage of merge request diffs, follow the instructions below.
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**In Omnibus installations:**
|
**In Omnibus installations:**
|
||||||
|
|
||||||
|
@ -30,16 +27,14 @@ To enable external storage of merge request diffs:
|
||||||
|
|
||||||
1. _The external diffs will be stored in in
|
1. _The external diffs will be stored in in
|
||||||
`/var/opt/gitlab/gitlab-rails/shared/external-diffs`._ To change the path,
|
`/var/opt/gitlab/gitlab-rails/shared/external-diffs`._ To change the path,
|
||||||
for example to `/mnt/storage/external-diffs`, edit `/etc/gitlab/gitlab.rb`
|
for example, to `/mnt/storage/external-diffs`, edit `/etc/gitlab/gitlab.rb`
|
||||||
and add the following line:
|
and add the following line:
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
gitlab_rails['external_diffs_storage_path'] = "/mnt/storage/external-diffs"
|
gitlab_rails['external_diffs_storage_path'] = "/mnt/storage/external-diffs"
|
||||||
```
|
```
|
||||||
|
|
||||||
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
|
1. Save the file and [reconfigure GitLab](restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**In installations from source:**
|
**In installations from source:**
|
||||||
|
|
||||||
|
@ -52,7 +47,7 @@ To enable external storage of merge request diffs:
|
||||||
```
|
```
|
||||||
|
|
||||||
1. _The external diffs will be stored in
|
1. _The external diffs will be stored in
|
||||||
`/home/git/gitlab/shared/external-diffs`._ To change the path, for example
|
`/home/git/gitlab/shared/external-diffs`._ To change the path, for example,
|
||||||
to `/mnt/storage/external-diffs`, edit `/home/git/gitlab/config/gitlab.yml`
|
to `/mnt/storage/external-diffs`, edit `/home/git/gitlab/config/gitlab.yml`
|
||||||
and add or amend the following lines:
|
and add or amend the following lines:
|
||||||
|
|
||||||
|
@ -62,18 +57,18 @@ To enable external storage of merge request diffs:
|
||||||
storage_path: /mnt/storage/external-diffs
|
storage_path: /mnt/storage/external-diffs
|
||||||
```
|
```
|
||||||
|
|
||||||
1. Save the file and [restart GitLab][] for the changes to take effect.
|
1. Save the file and [restart GitLab](restart_gitlab.md#installations-from-source) for the changes to take effect.
|
||||||
|
|
||||||
### Using object storage
|
### Using object storage
|
||||||
|
|
||||||
Instead of storing the external diffs on disk, we recommended you use an object
|
Instead of storing the external diffs on disk, we recommended the use of an object
|
||||||
store like AWS S3 instead. This configuration relies on valid AWS credentials to
|
store like AWS S3 instead. This configuration relies on valid AWS credentials to
|
||||||
be configured already.
|
be configured already.
|
||||||
|
|
||||||
### Object Storage Settings
|
### Object Storage Settings
|
||||||
|
|
||||||
For source installations, these settings are nested under `external_diffs:` and
|
For source installations, these settings are nested under `external_diffs:` and
|
||||||
then `object_store:`. On omnibus installs, they are prefixed by
|
then `object_store:`. On Omnibus installations, they are prefixed by
|
||||||
`external_diffs_object_store_`.
|
`external_diffs_object_store_`.
|
||||||
|
|
||||||
| Setting | Description | Default |
|
| Setting | Description | Default |
|
||||||
|
@ -118,7 +113,7 @@ The connection settings match those provided by [Fog](https://github.com/fog), a
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
NOTE: if you are using AWS IAM profiles, be sure to omit the
|
Note that, if you are using AWS IAM profiles, be sure to omit the
|
||||||
AWS access key and secret access key/value pairs. For example:
|
AWS access key and secret access key/value pairs. For example:
|
||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
|
@ -129,9 +124,7 @@ The connection settings match those provided by [Fog](https://github.com/fog), a
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
|
1. Save the file and [reconfigure GitLab](restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**In installations from source:**
|
**In installations from source:**
|
||||||
|
|
||||||
|
@ -151,4 +144,4 @@ The connection settings match those provided by [Fog](https://github.com/fog), a
|
||||||
region: eu-central-1
|
region: eu-central-1
|
||||||
```
|
```
|
||||||
|
|
||||||
1. Save the file and [restart GitLab][] for the changes to take effect.
|
1. Save the file and [restart GitLab](restart_gitlab.md#installations-from-source) for the changes to take effect.
|
||||||
|
|
|
@ -33,7 +33,7 @@ Please consider using a virtual machine to run GitLab.
|
||||||
|
|
||||||
## Ruby versions
|
## Ruby versions
|
||||||
|
|
||||||
GitLab requires Ruby (MRI) 2.3. Support for Ruby versions below 2.3 (2.1, 2.2) will stop with GitLab 8.13.
|
GitLab requires Ruby (MRI) 2.5. Support for Ruby versions below 2.5 (2.3, 2.4) will stop with GitLab 11.6.
|
||||||
|
|
||||||
You will have to use the standard MRI implementation of Ruby.
|
You will have to use the standard MRI implementation of Ruby.
|
||||||
We love [JRuby](https://www.jruby.org/) and [Rubinius](https://rubinius.com) but GitLab
|
We love [JRuby](https://www.jruby.org/) and [Rubinius](https://rubinius.com) but GitLab
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
> in GitLab 10.8.
|
> in GitLab 10.8.
|
||||||
|
|
||||||
GitLab lists all devices that have logged into your account. This allows you to
|
GitLab lists all devices that have logged into your account. This allows you to
|
||||||
review the sessions and revoke any of it that you don't recognize.
|
review the sessions.
|
||||||
|
|
||||||
## Listing all active sessions
|
## Listing all active sessions
|
||||||
|
|
||||||
|
@ -12,9 +12,3 @@ review the sessions and revoke any of it that you don't recognize.
|
||||||
1. Navigate to the **Active Sessions** tab.
|
1. Navigate to the **Active Sessions** tab.
|
||||||
|
|
||||||
![Active sessions list](img/active_sessions_list.png)
|
![Active sessions list](img/active_sessions_list.png)
|
||||||
|
|
||||||
## Revoking a session
|
|
||||||
|
|
||||||
1. Navigate to your [profile's](#profile-settings) **Settings > Active Sessions**.
|
|
||||||
1. Click on **Revoke** besides a session. The current session cannot be
|
|
||||||
revoked, as this would sign you out of GitLab.
|
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 19 KiB |
|
@ -318,10 +318,18 @@ module API
|
||||||
use :pagination
|
use :pagination
|
||||||
end
|
end
|
||||||
get ':id/repository/commits/:sha/merge_requests', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do
|
get ':id/repository/commits/:sha/merge_requests', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do
|
||||||
|
authorize! :read_merge_request, user_project
|
||||||
|
|
||||||
commit = user_project.commit(params[:sha])
|
commit = user_project.commit(params[:sha])
|
||||||
not_found! 'Commit' unless commit
|
not_found! 'Commit' unless commit
|
||||||
|
|
||||||
present paginate(commit.merge_requests), with: Entities::MergeRequestBasic
|
commit_merge_requests = MergeRequestsFinder.new(
|
||||||
|
current_user,
|
||||||
|
project_id: user_project.id,
|
||||||
|
commit_sha: commit.sha
|
||||||
|
).execute
|
||||||
|
|
||||||
|
present paginate(commit_merge_requests), with: Entities::MergeRequestBasic
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -156,7 +156,7 @@ module API
|
||||||
class BasicProjectDetails < ProjectIdentity
|
class BasicProjectDetails < ProjectIdentity
|
||||||
include ::API::ProjectsRelationBuilder
|
include ::API::ProjectsRelationBuilder
|
||||||
|
|
||||||
expose :default_branch
|
expose :default_branch, if: -> (project, options) { Ability.allowed?(options[:current_user], :download_code, project) }
|
||||||
# Avoids an N+1 query: https://github.com/mbleigh/acts-as-taggable-on/issues/91#issuecomment-168273770
|
# Avoids an N+1 query: https://github.com/mbleigh/acts-as-taggable-on/issues/91#issuecomment-168273770
|
||||||
expose :tag_list do |project|
|
expose :tag_list do |project|
|
||||||
# project.tags.order(:name).pluck(:name) is the most suitable option
|
# project.tags.order(:name).pluck(:name) is the most suitable option
|
||||||
|
@ -261,7 +261,7 @@ module API
|
||||||
expose :open_issues_count, if: lambda { |project, options| project.feature_available?(:issues, options[:current_user]) }
|
expose :open_issues_count, if: lambda { |project, options| project.feature_available?(:issues, options[:current_user]) }
|
||||||
expose :runners_token, if: lambda { |_project, options| options[:user_can_admin_project] }
|
expose :runners_token, if: lambda { |_project, options| options[:user_can_admin_project] }
|
||||||
expose :public_builds, as: :public_jobs
|
expose :public_builds, as: :public_jobs
|
||||||
expose :ci_config_path
|
expose :ci_config_path, if: -> (project, options) { Ability.allowed?(options[:current_user], :download_code, project) }
|
||||||
expose :shared_with_groups do |project, options|
|
expose :shared_with_groups do |project, options|
|
||||||
SharedGroup.represent(project.project_group_links, options)
|
SharedGroup.represent(project.project_group_links, options)
|
||||||
end
|
end
|
||||||
|
@ -270,8 +270,9 @@ module API
|
||||||
expose :only_allow_merge_if_all_discussions_are_resolved
|
expose :only_allow_merge_if_all_discussions_are_resolved
|
||||||
expose :printing_merge_request_link_enabled
|
expose :printing_merge_request_link_enabled
|
||||||
expose :merge_method
|
expose :merge_method
|
||||||
|
expose :statistics, using: 'API::Entities::ProjectStatistics', if: -> (project, options) {
|
||||||
expose :statistics, using: 'API::Entities::ProjectStatistics', if: :statistics
|
options[:statistics] && Ability.allowed?(options[:current_user], :download_code, project)
|
||||||
|
}
|
||||||
|
|
||||||
# rubocop: disable CodeReuse/ActiveRecord
|
# rubocop: disable CodeReuse/ActiveRecord
|
||||||
def self.preload_relation(projects_relation, options = {})
|
def self.preload_relation(projects_relation, options = {})
|
||||||
|
|
|
@ -22,7 +22,7 @@ module API
|
||||||
get ':id/environments' do
|
get ':id/environments' do
|
||||||
authorize! :read_environment, user_project
|
authorize! :read_environment, user_project
|
||||||
|
|
||||||
present paginate(user_project.environments), with: Entities::Environment
|
present paginate(user_project.environments), with: Entities::Environment, current_user: current_user
|
||||||
end
|
end
|
||||||
|
|
||||||
desc 'Creates a new environment' do
|
desc 'Creates a new environment' do
|
||||||
|
@ -40,7 +40,7 @@ module API
|
||||||
environment = user_project.environments.create(declared_params)
|
environment = user_project.environments.create(declared_params)
|
||||||
|
|
||||||
if environment.persisted?
|
if environment.persisted?
|
||||||
present environment, with: Entities::Environment
|
present environment, with: Entities::Environment, current_user: current_user
|
||||||
else
|
else
|
||||||
render_validation_error!(environment)
|
render_validation_error!(environment)
|
||||||
end
|
end
|
||||||
|
@ -63,7 +63,7 @@ module API
|
||||||
|
|
||||||
update_params = declared_params(include_missing: false).extract!(:name, :external_url)
|
update_params = declared_params(include_missing: false).extract!(:name, :external_url)
|
||||||
if environment.update(update_params)
|
if environment.update(update_params)
|
||||||
present environment, with: Entities::Environment
|
present environment, with: Entities::Environment, current_user: current_user
|
||||||
else
|
else
|
||||||
render_validation_error!(environment)
|
render_validation_error!(environment)
|
||||||
end
|
end
|
||||||
|
@ -99,7 +99,7 @@ module API
|
||||||
environment.stop_with_action!(current_user)
|
environment.stop_with_action!(current_user)
|
||||||
|
|
||||||
status 200
|
status 200
|
||||||
present environment, with: Entities::Environment
|
present environment, with: Entities::Environment, current_user: current_user
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -70,14 +70,7 @@ module API
|
||||||
def find_noteable(parent, noteables_str, noteable_id)
|
def find_noteable(parent, noteables_str, noteable_id)
|
||||||
noteable = public_send("find_#{parent}_#{noteables_str.singularize}", noteable_id) # rubocop:disable GitlabSecurity/PublicSend
|
noteable = public_send("find_#{parent}_#{noteables_str.singularize}", noteable_id) # rubocop:disable GitlabSecurity/PublicSend
|
||||||
|
|
||||||
readable =
|
readable = can?(current_user, noteable_read_ability_name(noteable), noteable)
|
||||||
if noteable.is_a?(Commit)
|
|
||||||
# for commits there is not :read_commit policy, check if user
|
|
||||||
# has :read_note permission on the commit's project
|
|
||||||
can?(current_user, :read_note, user_project)
|
|
||||||
else
|
|
||||||
can?(current_user, noteable_read_ability_name(noteable), noteable)
|
|
||||||
end
|
|
||||||
|
|
||||||
return not_found!(noteables_str) unless readable
|
return not_found!(noteables_str) unless readable
|
||||||
|
|
||||||
|
@ -89,12 +82,11 @@ module API
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_note(noteable, opts)
|
def create_note(noteable, opts)
|
||||||
policy_object = noteable.is_a?(Commit) ? user_project : noteable
|
authorize!(:create_note, noteable)
|
||||||
authorize!(:create_note, policy_object)
|
|
||||||
|
|
||||||
parent = noteable_parent(noteable)
|
parent = noteable_parent(noteable)
|
||||||
|
|
||||||
opts.delete(:created_at) unless current_user.can?(:set_note_created_at, policy_object)
|
opts.delete(:created_at) unless current_user.can?(:set_note_created_at, noteable)
|
||||||
|
|
||||||
opts[:updated_at] = opts[:created_at] if opts[:created_at]
|
opts[:updated_at] = opts[:created_at] if opts[:created_at]
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ module API
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_runner_ip
|
def get_runner_ip
|
||||||
{ ip_address: request.env["HTTP_X_FORWARDED_FOR"] || request.ip }
|
{ ip_address: env["action_dispatch.remote_ip"].to_s || request.ip }
|
||||||
end
|
end
|
||||||
|
|
||||||
def current_runner
|
def current_runner
|
||||||
|
|
|
@ -184,7 +184,8 @@ module API
|
||||||
|
|
||||||
if project.saved?
|
if project.saved?
|
||||||
present project, with: Entities::Project,
|
present project, with: Entities::Project,
|
||||||
user_can_admin_project: can?(current_user, :admin_project, project)
|
user_can_admin_project: can?(current_user, :admin_project, project),
|
||||||
|
current_user: current_user
|
||||||
else
|
else
|
||||||
if project.errors[:limit_reached].present?
|
if project.errors[:limit_reached].present?
|
||||||
error!(project.errors[:limit_reached], 403)
|
error!(project.errors[:limit_reached], 403)
|
||||||
|
@ -217,7 +218,8 @@ module API
|
||||||
|
|
||||||
if project.saved?
|
if project.saved?
|
||||||
present project, with: Entities::Project,
|
present project, with: Entities::Project,
|
||||||
user_can_admin_project: can?(current_user, :admin_project, project)
|
user_can_admin_project: can?(current_user, :admin_project, project),
|
||||||
|
current_user: current_user
|
||||||
else
|
else
|
||||||
render_validation_error!(project)
|
render_validation_error!(project)
|
||||||
end
|
end
|
||||||
|
@ -279,7 +281,8 @@ module API
|
||||||
conflict!(forked_project.errors.messages)
|
conflict!(forked_project.errors.messages)
|
||||||
else
|
else
|
||||||
present forked_project, with: Entities::Project,
|
present forked_project, with: Entities::Project,
|
||||||
user_can_admin_project: can?(current_user, :admin_project, forked_project)
|
user_can_admin_project: can?(current_user, :admin_project, forked_project),
|
||||||
|
current_user: current_user
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -328,7 +331,8 @@ module API
|
||||||
|
|
||||||
if result[:status] == :success
|
if result[:status] == :success
|
||||||
present user_project, with: Entities::Project,
|
present user_project, with: Entities::Project,
|
||||||
user_can_admin_project: can?(current_user, :admin_project, user_project)
|
user_can_admin_project: can?(current_user, :admin_project, user_project),
|
||||||
|
current_user: current_user
|
||||||
else
|
else
|
||||||
render_validation_error!(user_project)
|
render_validation_error!(user_project)
|
||||||
end
|
end
|
||||||
|
@ -342,7 +346,7 @@ module API
|
||||||
|
|
||||||
::Projects::UpdateService.new(user_project, current_user, archived: true).execute
|
::Projects::UpdateService.new(user_project, current_user, archived: true).execute
|
||||||
|
|
||||||
present user_project, with: Entities::Project
|
present user_project, with: Entities::Project, current_user: current_user
|
||||||
end
|
end
|
||||||
|
|
||||||
desc 'Unarchive a project' do
|
desc 'Unarchive a project' do
|
||||||
|
@ -353,7 +357,7 @@ module API
|
||||||
|
|
||||||
::Projects::UpdateService.new(@project, current_user, archived: false).execute
|
::Projects::UpdateService.new(@project, current_user, archived: false).execute
|
||||||
|
|
||||||
present user_project, with: Entities::Project
|
present user_project, with: Entities::Project, current_user: current_user
|
||||||
end
|
end
|
||||||
|
|
||||||
desc 'Star a project' do
|
desc 'Star a project' do
|
||||||
|
@ -366,7 +370,7 @@ module API
|
||||||
current_user.toggle_star(user_project)
|
current_user.toggle_star(user_project)
|
||||||
user_project.reload
|
user_project.reload
|
||||||
|
|
||||||
present user_project, with: Entities::Project
|
present user_project, with: Entities::Project, current_user: current_user
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -378,7 +382,7 @@ module API
|
||||||
current_user.toggle_star(user_project)
|
current_user.toggle_star(user_project)
|
||||||
user_project.reload
|
user_project.reload
|
||||||
|
|
||||||
present user_project, with: Entities::Project
|
present user_project, with: Entities::Project, current_user: current_user
|
||||||
else
|
else
|
||||||
not_modified!
|
not_modified!
|
||||||
end
|
end
|
||||||
|
@ -414,7 +418,7 @@ module API
|
||||||
result = ::Projects::ForkService.new(fork_from_project, current_user).execute(user_project)
|
result = ::Projects::ForkService.new(fork_from_project, current_user).execute(user_project)
|
||||||
|
|
||||||
if result
|
if result
|
||||||
present user_project.reload, with: Entities::Project
|
present user_project.reload, with: Entities::Project, current_user: current_user
|
||||||
else
|
else
|
||||||
render_api_error!("Project already forked", 409) if user_project.forked?
|
render_api_error!("Project already forked", 409) if user_project.forked?
|
||||||
end
|
end
|
||||||
|
@ -436,27 +440,24 @@ module API
|
||||||
end
|
end
|
||||||
params do
|
params do
|
||||||
requires :group_id, type: Integer, desc: 'The ID of a group'
|
requires :group_id, type: Integer, desc: 'The ID of a group'
|
||||||
requires :group_access, type: Integer, values: Gitlab::Access.values, desc: 'The group access level'
|
requires :group_access, type: Integer, values: Gitlab::Access.values, as: :link_group_access, desc: 'The group access level'
|
||||||
optional :expires_at, type: Date, desc: 'Share expiration date'
|
optional :expires_at, type: Date, desc: 'Share expiration date'
|
||||||
end
|
end
|
||||||
post ":id/share" do
|
post ":id/share" do
|
||||||
authorize! :admin_project, user_project
|
authorize! :admin_project, user_project
|
||||||
group = Group.find_by_id(params[:group_id])
|
group = Group.find_by_id(params[:group_id])
|
||||||
|
|
||||||
unless group && can?(current_user, :read_group, group)
|
|
||||||
not_found!('Group')
|
|
||||||
end
|
|
||||||
|
|
||||||
unless user_project.allowed_to_share_with_group?
|
unless user_project.allowed_to_share_with_group?
|
||||||
break render_api_error!("The project sharing with group is disabled", 400)
|
break render_api_error!("The project sharing with group is disabled", 400)
|
||||||
end
|
end
|
||||||
|
|
||||||
link = user_project.project_group_links.new(declared_params(include_missing: false))
|
result = ::Projects::GroupLinks::CreateService.new(user_project, current_user, declared_params(include_missing: false))
|
||||||
|
.execute(group)
|
||||||
|
|
||||||
if link.save
|
if result[:status] == :success
|
||||||
present link, with: Entities::ProjectGroupLink
|
present result[:link], with: Entities::ProjectGroupLink
|
||||||
else
|
else
|
||||||
render_api_error!(link.errors.full_messages.first, 409)
|
render_api_error!(result[:message], result[:http_status])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -520,7 +521,7 @@ module API
|
||||||
result = ::Projects::TransferService.new(user_project, current_user).execute(namespace)
|
result = ::Projects::TransferService.new(user_project, current_user).execute(namespace)
|
||||||
|
|
||||||
if result
|
if result
|
||||||
present user_project, with: Entities::Project
|
present user_project, with: Entities::Project, current_user: current_user
|
||||||
else
|
else
|
||||||
render_api_error!("Failed to transfer project #{user_project.errors.messages}", 400)
|
render_api_error!("Failed to transfer project #{user_project.errors.messages}", 400)
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,6 +8,8 @@ module API
|
||||||
RELEASE_ENDPOINT_REQUIREMETS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS
|
RELEASE_ENDPOINT_REQUIREMETS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS
|
||||||
.merge(tag_name: API::NO_SLASH_URL_PART_REGEX)
|
.merge(tag_name: API::NO_SLASH_URL_PART_REGEX)
|
||||||
|
|
||||||
|
before { authorize! :read_release, user_project }
|
||||||
|
|
||||||
params do
|
params do
|
||||||
requires :id, type: String, desc: 'The ID of a project'
|
requires :id, type: String, desc: 'The ID of a project'
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,12 +2,13 @@
|
||||||
|
|
||||||
module Constraints
|
module Constraints
|
||||||
class ProjectUrlConstrainer
|
class ProjectUrlConstrainer
|
||||||
def matches?(request)
|
def matches?(request, existence_check: true)
|
||||||
namespace_path = request.params[:namespace_id]
|
namespace_path = request.params[:namespace_id]
|
||||||
project_path = request.params[:project_id] || request.params[:id]
|
project_path = request.params[:project_id] || request.params[:id]
|
||||||
full_path = [namespace_path, project_path].join('/')
|
full_path = [namespace_path, project_path].join('/')
|
||||||
|
|
||||||
return false unless ProjectPathValidator.valid_path?(full_path)
|
return false unless ProjectPathValidator.valid_path?(full_path)
|
||||||
|
return true unless existence_check
|
||||||
|
|
||||||
# We intentionally allow SELECT(*) here so result of this query can be used
|
# We intentionally allow SELECT(*) here so result of this query can be used
|
||||||
# as cache for further Project.find_by_full_path calls within request
|
# as cache for further Project.find_by_full_path calls within request
|
||||||
|
|
|
@ -4,6 +4,7 @@ module Gitlab
|
||||||
module DependencyLinker
|
module DependencyLinker
|
||||||
class BaseLinker
|
class BaseLinker
|
||||||
URL_REGEX = %r{https?://[^'" ]+}.freeze
|
URL_REGEX = %r{https?://[^'" ]+}.freeze
|
||||||
|
GIT_INVALID_URL_REGEX = /^git\+#{URL_REGEX}/.freeze
|
||||||
REPO_REGEX = %r{[^/'" ]+/[^/'" ]+}.freeze
|
REPO_REGEX = %r{[^/'" ]+/[^/'" ]+}.freeze
|
||||||
|
|
||||||
class_attribute :file_type
|
class_attribute :file_type
|
||||||
|
@ -29,8 +30,25 @@ module Gitlab
|
||||||
highlighted_lines.join.html_safe
|
highlighted_lines.join.html_safe
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def external_url(name, external_ref)
|
||||||
|
return if external_ref =~ GIT_INVALID_URL_REGEX
|
||||||
|
|
||||||
|
case external_ref
|
||||||
|
when /\A#{URL_REGEX}\z/
|
||||||
|
external_ref
|
||||||
|
when /\A#{REPO_REGEX}\z/
|
||||||
|
github_url(external_ref)
|
||||||
|
else
|
||||||
|
package_url(name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def package_url(_name)
|
||||||
|
raise NotImplementedError
|
||||||
|
end
|
||||||
|
|
||||||
def link_dependencies
|
def link_dependencies
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,8 +8,8 @@ module Gitlab
|
||||||
private
|
private
|
||||||
|
|
||||||
def link_packages
|
def link_packages
|
||||||
link_packages_at_key("require", &method(:package_url))
|
link_packages_at_key("require")
|
||||||
link_packages_at_key("require-dev", &method(:package_url))
|
link_packages_at_key("require-dev")
|
||||||
end
|
end
|
||||||
|
|
||||||
def package_url(name)
|
def package_url(name)
|
||||||
|
|
|
@ -3,8 +3,14 @@
|
||||||
module Gitlab
|
module Gitlab
|
||||||
module DependencyLinker
|
module DependencyLinker
|
||||||
class GemfileLinker < MethodLinker
|
class GemfileLinker < MethodLinker
|
||||||
|
class_attribute :package_keyword
|
||||||
|
|
||||||
|
self.package_keyword = :gem
|
||||||
self.file_type = :gemfile
|
self.file_type = :gemfile
|
||||||
|
|
||||||
|
GITHUB_REGEX = /(github:|:github\s*=>)\s*['"](?<name>[^'"]+)['"]/.freeze
|
||||||
|
GIT_REGEX = /(git:|:git\s*=>)\s*['"](?<name>#{URL_REGEX})['"]/.freeze
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def link_dependencies
|
def link_dependencies
|
||||||
|
@ -14,20 +20,34 @@ module Gitlab
|
||||||
|
|
||||||
def link_urls
|
def link_urls
|
||||||
# Link `github: "user/repo"` to https://github.com/user/repo
|
# Link `github: "user/repo"` to https://github.com/user/repo
|
||||||
link_regex(/(github:|:github\s*=>)\s*['"](?<name>[^'"]+)['"]/, &method(:github_url))
|
link_regex(GITHUB_REGEX, &method(:github_url))
|
||||||
|
|
||||||
# Link `git: "https://gitlab.example.com/user/repo"` to https://gitlab.example.com/user/repo
|
# Link `git: "https://gitlab.example.com/user/repo"` to https://gitlab.example.com/user/repo
|
||||||
link_regex(/(git:|:git\s*=>)\s*['"](?<name>#{URL_REGEX})['"]/, &:itself)
|
link_regex(GIT_REGEX, &:itself)
|
||||||
|
|
||||||
# Link `source "https://rubygems.org"` to https://rubygems.org
|
# Link `source "https://rubygems.org"` to https://rubygems.org
|
||||||
link_method_call('source', URL_REGEX, &:itself)
|
link_method_call('source', URL_REGEX, &:itself)
|
||||||
end
|
end
|
||||||
|
|
||||||
def link_packages
|
def link_packages
|
||||||
# Link `gem "package_name"` to https://rubygems.org/gems/package_name
|
packages = parse_packages
|
||||||
link_method_call('gem') do |name|
|
|
||||||
|
return if packages.blank?
|
||||||
|
|
||||||
|
packages.each do |package|
|
||||||
|
link_method_call('gem', package.name) do
|
||||||
|
external_url(package.name, package.external_ref)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def package_url(name)
|
||||||
"https://rubygems.org/gems/#{name}"
|
"https://rubygems.org/gems/#{name}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def parse_packages
|
||||||
|
parser = Gitlab::DependencyLinker::Parser::Gemfile.new(plain_text)
|
||||||
|
parser.parse(keyword: self.class.package_keyword)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -11,7 +11,7 @@ module Gitlab
|
||||||
link_method_call('homepage', URL_REGEX, &:itself)
|
link_method_call('homepage', URL_REGEX, &:itself)
|
||||||
link_method_call('license', &method(:license_url))
|
link_method_call('license', &method(:license_url))
|
||||||
|
|
||||||
link_method_call(%w[name add_dependency add_runtime_dependency add_development_dependency]) do |name|
|
link_method_call(%w[add_dependency add_runtime_dependency add_development_dependency]) do |name|
|
||||||
"https://rubygems.org/gems/#{name}"
|
"https://rubygems.org/gems/#{name}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -23,18 +23,22 @@ module Gitlab
|
||||||
# link_method_call('name')
|
# link_method_call('name')
|
||||||
# # Will link `package` in `self.name = "package"`
|
# # Will link `package` in `self.name = "package"`
|
||||||
def link_method_call(method_name, value = nil, &url_proc)
|
def link_method_call(method_name, value = nil, &url_proc)
|
||||||
|
regex = method_call_regex(method_name, value)
|
||||||
|
|
||||||
|
link_regex(regex, &url_proc)
|
||||||
|
end
|
||||||
|
|
||||||
|
def method_call_regex(method_name, value = nil)
|
||||||
method_name = regexp_for_value(method_name)
|
method_name = regexp_for_value(method_name)
|
||||||
value = regexp_for_value(value)
|
value = regexp_for_value(value)
|
||||||
|
|
||||||
regex = %r{
|
%r{
|
||||||
#{method_name} # Method name
|
#{method_name} # Method name
|
||||||
\s* # Whitespace
|
\s* # Whitespace
|
||||||
[(=]? # Opening brace or equals sign
|
[(=]? # Opening brace or equals sign
|
||||||
\s* # Whitespace
|
\s* # Whitespace
|
||||||
['"](?<name>#{value})['"] # Package name in quotes
|
['"](?<name>#{value})['"] # Package name in quotes
|
||||||
}x
|
}x
|
||||||
|
|
||||||
link_regex(regex, &url_proc)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
19
lib/gitlab/dependency_linker/package.rb
Normal file
19
lib/gitlab/dependency_linker/package.rb
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Gitlab
|
||||||
|
module DependencyLinker
|
||||||
|
class Package
|
||||||
|
attr_reader :name, :git_ref, :github_ref
|
||||||
|
|
||||||
|
def initialize(name, git_ref, github_ref)
|
||||||
|
@name = name
|
||||||
|
@git_ref = git_ref
|
||||||
|
@github_ref = github_ref
|
||||||
|
end
|
||||||
|
|
||||||
|
def external_ref
|
||||||
|
@git_ref || @github_ref
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -8,7 +8,6 @@ module Gitlab
|
||||||
private
|
private
|
||||||
|
|
||||||
def link_dependencies
|
def link_dependencies
|
||||||
link_json('name', json["name"], &method(:package_url))
|
|
||||||
link_json('license', &method(:license_url))
|
link_json('license', &method(:license_url))
|
||||||
link_json(%w[homepage url], URL_REGEX, &:itself)
|
link_json(%w[homepage url], URL_REGEX, &:itself)
|
||||||
|
|
||||||
|
@ -16,25 +15,19 @@ module Gitlab
|
||||||
end
|
end
|
||||||
|
|
||||||
def link_packages
|
def link_packages
|
||||||
link_packages_at_key("dependencies", &method(:package_url))
|
link_packages_at_key("dependencies")
|
||||||
link_packages_at_key("devDependencies", &method(:package_url))
|
link_packages_at_key("devDependencies")
|
||||||
end
|
end
|
||||||
|
|
||||||
def link_packages_at_key(key, &url_proc)
|
def link_packages_at_key(key)
|
||||||
dependencies = json[key]
|
dependencies = json[key]
|
||||||
return unless dependencies
|
return unless dependencies
|
||||||
|
|
||||||
dependencies.each do |name, version|
|
dependencies.each do |name, version|
|
||||||
link_json(name, version, link: :key, &url_proc)
|
external_url = external_url(name, version)
|
||||||
|
|
||||||
link_json(name) do |value|
|
link_json(name, version, link: :key) { external_url }
|
||||||
case value
|
link_json(name) { external_url }
|
||||||
when /\A#{URL_REGEX}\z/
|
|
||||||
value
|
|
||||||
when /\A#{REPO_REGEX}\z/
|
|
||||||
github_url(value)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
40
lib/gitlab/dependency_linker/parser/gemfile.rb
Normal file
40
lib/gitlab/dependency_linker/parser/gemfile.rb
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Gitlab
|
||||||
|
module DependencyLinker
|
||||||
|
module Parser
|
||||||
|
class Gemfile < MethodLinker
|
||||||
|
GIT_REGEX = Gitlab::DependencyLinker::GemfileLinker::GIT_REGEX
|
||||||
|
GITHUB_REGEX = Gitlab::DependencyLinker::GemfileLinker::GITHUB_REGEX
|
||||||
|
|
||||||
|
def initialize(plain_text)
|
||||||
|
@plain_text = plain_text
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns a list of Gitlab::DependencyLinker::Package
|
||||||
|
#
|
||||||
|
# keyword - The package definition keyword, e.g. `:gem` for
|
||||||
|
# Gemfile parsing, `:pod` for Podfile.
|
||||||
|
def parse(keyword:)
|
||||||
|
plain_lines.each_with_object([]) do |line, packages|
|
||||||
|
name = fetch(line, method_call_regex(keyword))
|
||||||
|
|
||||||
|
next unless name
|
||||||
|
|
||||||
|
git_ref = fetch(line, GIT_REGEX)
|
||||||
|
github_ref = fetch(line, GITHUB_REGEX)
|
||||||
|
|
||||||
|
packages << Gitlab::DependencyLinker::Package.new(name, git_ref, github_ref)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def fetch(line, regex, group: :name)
|
||||||
|
match = line.match(regex)
|
||||||
|
match[group] if match
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -5,12 +5,21 @@ module Gitlab
|
||||||
class PodfileLinker < GemfileLinker
|
class PodfileLinker < GemfileLinker
|
||||||
include Cocoapods
|
include Cocoapods
|
||||||
|
|
||||||
|
self.package_keyword = :pod
|
||||||
self.file_type = :podfile
|
self.file_type = :podfile
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def link_packages
|
def link_packages
|
||||||
link_method_call('pod', &method(:package_url))
|
packages = parse_packages
|
||||||
|
|
||||||
|
return unless packages
|
||||||
|
|
||||||
|
packages.each do |package|
|
||||||
|
link_method_call('pod', package.name) do
|
||||||
|
external_url(package.name, package.external_ref)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -19,7 +19,7 @@ module Gitlab
|
||||||
link_method_call('license', &method(:license_url))
|
link_method_call('license', &method(:license_url))
|
||||||
link_regex(/license\s*=\s*\{\s*(type:|:type\s*=>)\s*#{STRING_REGEX}/, &method(:license_url))
|
link_regex(/license\s*=\s*\{\s*(type:|:type\s*=>)\s*#{STRING_REGEX}/, &method(:license_url))
|
||||||
|
|
||||||
link_method_call(%w[name dependency], &method(:package_url))
|
link_method_call('dependency', &method(:package_url))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -20,6 +20,17 @@ module Gitlab
|
||||||
create_target_branch unless branch_exists?(@merge_request.target_branch)
|
create_target_branch unless branch_exists?(@merge_request.target_branch)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# The merge_request_diff associated with the current @merge_request might
|
||||||
|
# be invalid. Than means, when the @merge_request object is saved, the
|
||||||
|
# @merge_request.merge_request_diff won't. This can leave the merge request
|
||||||
|
# in an invalid state, because a merge request must have an associated
|
||||||
|
# merge request diff.
|
||||||
|
# In this change, if the associated merge request diff is invalid, we set
|
||||||
|
# it to nil. This change, in association with the after callback
|
||||||
|
# :ensure_merge_request_diff in the MergeRequest class, makes that
|
||||||
|
# when the merge request is going to be created and it doesn't have
|
||||||
|
# one, a default one will be generated.
|
||||||
|
@merge_request.merge_request_diff = nil unless @merge_request.merge_request_diff&.valid?
|
||||||
@merge_request
|
@merge_request
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,7 @@ module Gitlab
|
||||||
def log_base_data
|
def log_base_data
|
||||||
{
|
{
|
||||||
importer: 'Import/Export',
|
importer: 'Import/Export',
|
||||||
import_jid: @project&.import_state&.import_jid,
|
import_jid: @project&.import_state&.jid,
|
||||||
project_id: @project&.id,
|
project_id: @project&.id,
|
||||||
project_path: @project&.full_path
|
project_path: @project&.full_path
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,6 +82,8 @@ module Gitlab
|
||||||
def initialize(api_prefix, **kubeclient_options)
|
def initialize(api_prefix, **kubeclient_options)
|
||||||
@api_prefix = api_prefix
|
@api_prefix = api_prefix
|
||||||
@kubeclient_options = kubeclient_options.merge(http_max_redirects: 0)
|
@kubeclient_options = kubeclient_options.merge(http_max_redirects: 0)
|
||||||
|
|
||||||
|
validate_url!
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_or_update_cluster_role_binding(resource)
|
def create_or_update_cluster_role_binding(resource)
|
||||||
|
@ -118,6 +120,12 @@ module Gitlab
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def validate_url!
|
||||||
|
return if Gitlab::CurrentSettings.allow_local_requests_from_hooks_and_services?
|
||||||
|
|
||||||
|
Gitlab::UrlBlocker.validate!(api_prefix, allow_local_network: false)
|
||||||
|
end
|
||||||
|
|
||||||
def cluster_role_binding_exists?(resource)
|
def cluster_role_binding_exists?(resource)
|
||||||
get_cluster_role_binding(resource.metadata.name)
|
get_cluster_role_binding(resource.metadata.name)
|
||||||
rescue ::Kubeclient::ResourceNotFoundError
|
rescue ::Kubeclient::ResourceNotFoundError
|
||||||
|
|
|
@ -1293,6 +1293,9 @@ msgstr ""
|
||||||
msgid "Cannot modify managed Kubernetes cluster"
|
msgid "Cannot modify managed Kubernetes cluster"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Certificate"
|
msgid "Certificate"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -8,3 +8,4 @@ gem 'rspec', '~> 3.7'
|
||||||
gem 'selenium-webdriver', '~> 3.12'
|
gem 'selenium-webdriver', '~> 3.12'
|
||||||
gem 'airborne', '~> 0.2.13'
|
gem 'airborne', '~> 0.2.13'
|
||||||
gem 'nokogiri', '~> 1.10.1'
|
gem 'nokogiri', '~> 1.10.1'
|
||||||
|
gem 'rspec-retry', '~> 0.6.1'
|
||||||
|
|
|
@ -76,6 +76,8 @@ GEM
|
||||||
rspec-mocks (3.7.0)
|
rspec-mocks (3.7.0)
|
||||||
diff-lcs (>= 1.2.0, < 2.0)
|
diff-lcs (>= 1.2.0, < 2.0)
|
||||||
rspec-support (~> 3.7.0)
|
rspec-support (~> 3.7.0)
|
||||||
|
rspec-retry (0.6.1)
|
||||||
|
rspec-core (> 3.3)
|
||||||
rspec-support (3.7.0)
|
rspec-support (3.7.0)
|
||||||
rubyzip (1.2.2)
|
rubyzip (1.2.2)
|
||||||
selenium-webdriver (3.141.0)
|
selenium-webdriver (3.141.0)
|
||||||
|
@ -101,6 +103,7 @@ DEPENDENCIES
|
||||||
pry-byebug (~> 3.5.1)
|
pry-byebug (~> 3.5.1)
|
||||||
rake (~> 12.3.0)
|
rake (~> 12.3.0)
|
||||||
rspec (~> 3.7)
|
rspec (~> 3.7)
|
||||||
|
rspec-retry (~> 0.6.1)
|
||||||
selenium-webdriver (~> 3.12)
|
selenium-webdriver (~> 3.12)
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
module QA
|
module QA
|
||||||
context 'Create' do
|
# https://gitlab.com/gitlab-org/quality/staging/issues/40
|
||||||
|
context 'Create', :quarantine do
|
||||||
describe 'Push mirror a repository over HTTP' do
|
describe 'Push mirror a repository over HTTP' do
|
||||||
it 'configures and syncs a (push) mirrored repository' do
|
it 'configures and syncs a (push) mirrored repository' do
|
||||||
Runtime::Browser.visit(:gitlab, Page::Main::Login)
|
Runtime::Browser.visit(:gitlab, Page::Main::Login)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
require_relative '../qa'
|
require_relative '../qa'
|
||||||
|
require 'rspec/retry'
|
||||||
|
|
||||||
Dir[::File.join(__dir__, 'support', '**', '*.rb')].each { |f| require f }
|
Dir[::File.join(__dir__, 'support', '**', '*.rb')].each { |f| require f }
|
||||||
|
|
||||||
|
@ -43,6 +44,17 @@ RSpec.configure do |config|
|
||||||
config.profile_examples = 10
|
config.profile_examples = 10
|
||||||
config.order = :random
|
config.order = :random
|
||||||
Kernel.srand config.seed
|
Kernel.srand config.seed
|
||||||
|
|
||||||
|
# show retry status in spec process
|
||||||
|
config.verbose_retry = true
|
||||||
|
|
||||||
|
# show exception that triggers a retry if verbose_retry is set to true
|
||||||
|
config.display_try_failure_messages = true
|
||||||
|
|
||||||
|
config.around do |example|
|
||||||
|
retry_times = example.metadata.keys.include?(:quarantine) ? 1 : 3
|
||||||
|
example.run_with_retry retry: retry_times
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Skip tests in quarantine unless we explicitly focus on them.
|
# Skip tests in quarantine unless we explicitly focus on them.
|
||||||
|
|
|
@ -28,6 +28,22 @@ describe 'rspec config tests' do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
let(:group_2) do
|
||||||
|
RSpec.describe do
|
||||||
|
before(:all) do
|
||||||
|
@expectations = [1, 2, 3]
|
||||||
|
end
|
||||||
|
|
||||||
|
example 'not in quarantine' do
|
||||||
|
expect(@expectations.shift).to be(3)
|
||||||
|
end
|
||||||
|
|
||||||
|
example 'in quarantine', :quarantine do
|
||||||
|
expect(@expectations.shift).to be(3)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'with no tags focussed' do
|
context 'with no tags focussed' do
|
||||||
before do
|
before do
|
||||||
group.run
|
group.run
|
||||||
|
@ -301,4 +317,39 @@ describe 'rspec config tests' do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'rspec retry' do
|
||||||
|
context 'in an untagged context' do
|
||||||
|
before do
|
||||||
|
group_2.run
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should run example :retry times' do
|
||||||
|
examples = group_2.descendant_filtered_examples
|
||||||
|
ex = examples.find { |e| e.description == 'not in quarantine' }
|
||||||
|
expect(ex.execution_result.status).to eq(:passed)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with :quarantine focussed' do
|
||||||
|
before do
|
||||||
|
RSpec.configure do |config|
|
||||||
|
config.inclusion_filter = :quarantine
|
||||||
|
end
|
||||||
|
group_2.run
|
||||||
|
end
|
||||||
|
|
||||||
|
after do
|
||||||
|
RSpec.configure do |config|
|
||||||
|
config.inclusion_filter.clear
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should run example once only' do
|
||||||
|
examples = group_2.descendant_filtered_examples
|
||||||
|
ex = examples.find { |e| e.description == 'in quarantine' }
|
||||||
|
expect(ex.execution_result.status).to eq(:failed)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -13,7 +13,7 @@ describe Dashboard::MilestonesController do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
let(:issue) { create(:issue, project: project, milestone: project_milestone) }
|
let(:issue) { create(:issue, project: project, milestone: project_milestone) }
|
||||||
let(:group_issue) { create(:issue, milestone: group_milestone) }
|
let(:group_issue) { create(:issue, milestone: group_milestone, project: create(:project, group: group)) }
|
||||||
|
|
||||||
let!(:label) { create(:label, project: project, title: 'Issue Label', issues: [issue]) }
|
let!(:label) { create(:label, project: project, title: 'Issue Label', issues: [issue]) }
|
||||||
let!(:group_label) { create(:group_label, group: group, title: 'Group Issue Label', issues: [group_issue]) }
|
let!(:group_label) { create(:group_label, group: group, title: 'Group Issue Label', issues: [group_issue]) }
|
||||||
|
|
|
@ -6,7 +6,7 @@ describe GoogleApi::AuthorizationsController do
|
||||||
let(:token) { 'token' }
|
let(:token) { 'token' }
|
||||||
let(:expires_at) { 1.hour.since.strftime('%s') }
|
let(:expires_at) { 1.hour.since.strftime('%s') }
|
||||||
|
|
||||||
subject { get :callback, params: { code: 'xxx', state: @state } }
|
subject { get :callback, params: { code: 'xxx', state: state } }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
sign_in(user)
|
sign_in(user)
|
||||||
|
@ -15,6 +15,26 @@ describe GoogleApi::AuthorizationsController do
|
||||||
.to receive(:get_token).and_return([token, expires_at])
|
.to receive(:get_token).and_return([token, expires_at])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
shared_examples_for 'access denied' do
|
||||||
|
it 'returns a 404' do
|
||||||
|
subject
|
||||||
|
|
||||||
|
expect(session[GoogleApi::CloudPlatform::Client.session_key_for_token]).to be_nil
|
||||||
|
expect(response).to have_http_status(:not_found)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'session key is present' do
|
||||||
|
let(:session_key) { 'session-key' }
|
||||||
|
let(:redirect_uri) { 'example.com' }
|
||||||
|
|
||||||
|
before do
|
||||||
|
session[GoogleApi::CloudPlatform::Client.session_key_for_redirect_uri(session_key)] = redirect_uri
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'session key matches state param' do
|
||||||
|
let(:state) { session_key }
|
||||||
|
|
||||||
it 'sets token and expires_at in session' do
|
it 'sets token and expires_at in session' do
|
||||||
subject
|
subject
|
||||||
|
|
||||||
|
@ -24,26 +44,28 @@ describe GoogleApi::AuthorizationsController do
|
||||||
.to eq(expires_at)
|
.to eq(expires_at)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when redirect uri key is stored in state' do
|
|
||||||
set(:project) { create(:project) }
|
|
||||||
let(:redirect_uri) { project_clusters_url(project).to_s }
|
|
||||||
|
|
||||||
before do
|
|
||||||
@state = GoogleApi::CloudPlatform::Client
|
|
||||||
.new_session_key_for_redirect_uri do |key|
|
|
||||||
session[key] = redirect_uri
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'redirects to the URL stored in state param' do
|
it 'redirects to the URL stored in state param' do
|
||||||
expect(subject).to redirect_to(redirect_uri)
|
expect(subject).to redirect_to(redirect_uri)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when redirection url is not stored in state' do
|
context 'session key does not match state param' do
|
||||||
it 'redirects to root_path' do
|
let(:state) { 'bad-key' }
|
||||||
expect(subject).to redirect_to(root_path)
|
|
||||||
end
|
it_behaves_like 'access denied'
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'state param is blank' do
|
||||||
|
let(:state) { '' }
|
||||||
|
|
||||||
|
it_behaves_like 'access denied'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'state param is present, but session key is blank' do
|
||||||
|
let(:state) { 'session-key' }
|
||||||
|
|
||||||
|
it_behaves_like 'access denied'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,6 +6,8 @@ describe Groups::SharedProjectsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
def share_project(project)
|
def share_project(project)
|
||||||
|
group.add_developer(user)
|
||||||
|
|
||||||
Projects::GroupLinks::CreateService.new(
|
Projects::GroupLinks::CreateService.new(
|
||||||
project,
|
project,
|
||||||
user,
|
user,
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Projects::AutocompleteSourcesController do
|
||||||
|
describe 'GET milestones' do
|
||||||
|
let(:user) { create(:user) }
|
||||||
|
let(:group) { create(:group, :public) }
|
||||||
|
let(:project) { create(:project, :public, namespace: group) }
|
||||||
|
let!(:project_milestone) { create(:milestone, project: project) }
|
||||||
|
let!(:group_milestone) { create(:milestone, group: group) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
sign_in(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'lists milestones' do
|
||||||
|
group.add_owner(user)
|
||||||
|
|
||||||
|
get :milestones, format: :json, params: { namespace_id: group.path, project_id: project.path }
|
||||||
|
|
||||||
|
milestone_titles = json_response.map { |milestone| milestone["title"] }
|
||||||
|
expect(milestone_titles).to match_array([project_milestone.title, group_milestone.title])
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when user cannot read project issues and merge requests' do
|
||||||
|
it 'renders 404' do
|
||||||
|
project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE)
|
||||||
|
project.project_feature.update!(merge_requests_access_level: ProjectFeature::PRIVATE)
|
||||||
|
|
||||||
|
get :milestones, format: :json, params: { namespace_id: group.path, project_id: project.path }
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(404)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -65,8 +65,24 @@ describe Projects::GroupLinksController do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when user does not have access to the public group' do
|
||||||
|
let(:group) { create(:group, :public) }
|
||||||
|
|
||||||
|
include_context 'link project to group'
|
||||||
|
|
||||||
|
it 'renders 404' do
|
||||||
|
expect(response.status).to eq 404
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not share project with that group' do
|
||||||
|
expect(group.shared_projects).not_to include project
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'when project group id equal link group id' do
|
context 'when project group id equal link group id' do
|
||||||
before do
|
before do
|
||||||
|
group2.add_developer(user)
|
||||||
|
|
||||||
post(:create, params: {
|
post(:create, params: {
|
||||||
namespace_id: project.namespace,
|
namespace_id: project.namespace,
|
||||||
project_id: project,
|
project_id: project,
|
||||||
|
@ -102,5 +118,26 @@ describe Projects::GroupLinksController do
|
||||||
expect(flash[:alert]).to eq('Please select a group.')
|
expect(flash[:alert]).to eq('Please select a group.')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when link is not persisted in the database' do
|
||||||
|
before do
|
||||||
|
allow(::Projects::GroupLinks::CreateService).to receive_message_chain(:new, :execute)
|
||||||
|
.and_return({ status: :error, http_status: 409, message: 'error' })
|
||||||
|
|
||||||
|
post(:create, params: {
|
||||||
|
namespace_id: project.namespace,
|
||||||
|
project_id: project,
|
||||||
|
link_group_id: group.id,
|
||||||
|
link_group_access: ProjectGroupLink.default_access
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'redirects to project group links page' do
|
||||||
|
expect(response).to redirect_to(
|
||||||
|
project_project_members_path(project)
|
||||||
|
)
|
||||||
|
expect(flash[:alert]).to eq('error')
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -205,6 +205,8 @@ describe SnippetsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when the snippet description contains a file' do
|
context 'when the snippet description contains a file' do
|
||||||
|
include FileMoverHelpers
|
||||||
|
|
||||||
let(:picture_file) { '/-/system/temp/secret56/picture.jpg' }
|
let(:picture_file) { '/-/system/temp/secret56/picture.jpg' }
|
||||||
let(:text_file) { '/-/system/temp/secret78/text.txt' }
|
let(:text_file) { '/-/system/temp/secret78/text.txt' }
|
||||||
let(:description) do
|
let(:description) do
|
||||||
|
@ -215,6 +217,8 @@ describe SnippetsController do
|
||||||
before do
|
before do
|
||||||
allow(FileUtils).to receive(:mkdir_p)
|
allow(FileUtils).to receive(:mkdir_p)
|
||||||
allow(FileUtils).to receive(:move)
|
allow(FileUtils).to receive(:move)
|
||||||
|
stub_file_mover(text_file)
|
||||||
|
stub_file_mover(picture_file)
|
||||||
end
|
end
|
||||||
|
|
||||||
subject { create_snippet({ description: description }, { files: [picture_file, text_file] }) }
|
subject { create_snippet({ description: description }, { files: [picture_file, text_file] }) }
|
||||||
|
|
|
@ -233,8 +233,8 @@ describe 'Issues' do
|
||||||
created_at: Time.now - (index * 60))
|
created_at: Time.now - (index * 60))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
let(:newer_due_milestone) { create(:milestone, due_date: '2013-12-11') }
|
let(:newer_due_milestone) { create(:milestone, project: project, due_date: '2013-12-11') }
|
||||||
let(:later_due_milestone) { create(:milestone, due_date: '2013-12-12') }
|
let(:later_due_milestone) { create(:milestone, project: project, due_date: '2013-12-12') }
|
||||||
|
|
||||||
it 'sorts by newest' do
|
it 'sorts by newest' do
|
||||||
visit project_issues_path(project, sort: sort_value_created_date)
|
visit project_issues_path(project, sort: sort_value_created_date)
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
describe 'Merge request > User sees versions', :js do
|
describe 'Merge request > User sees versions', :js do
|
||||||
let(:merge_request) { create(:merge_request, importing: true) }
|
let(:merge_request) do
|
||||||
|
create(:merge_request).tap do |mr|
|
||||||
|
mr.merge_request_diff.destroy
|
||||||
|
end
|
||||||
|
end
|
||||||
let(:project) { merge_request.source_project }
|
let(:project) { merge_request.source_project }
|
||||||
let(:user) { project.creator }
|
let(:user) { project.creator }
|
||||||
let!(:merge_request_diff1) { merge_request.merge_request_diffs.create(head_commit_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') }
|
let!(:merge_request_diff1) { merge_request.merge_request_diffs.create(head_commit_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') }
|
||||||
|
|
|
@ -13,7 +13,7 @@ describe 'Merge requests > User lists merge requests' do
|
||||||
source_project: project,
|
source_project: project,
|
||||||
source_branch: 'fix',
|
source_branch: 'fix',
|
||||||
assignee: user,
|
assignee: user,
|
||||||
milestone: create(:milestone, due_date: '2013-12-11'),
|
milestone: create(:milestone, project: project, due_date: '2013-12-11'),
|
||||||
created_at: 1.minute.ago,
|
created_at: 1.minute.ago,
|
||||||
updated_at: 1.minute.ago)
|
updated_at: 1.minute.ago)
|
||||||
create(:merge_request,
|
create(:merge_request,
|
||||||
|
@ -21,7 +21,7 @@ describe 'Merge requests > User lists merge requests' do
|
||||||
source_project: project,
|
source_project: project,
|
||||||
source_branch: 'markdown',
|
source_branch: 'markdown',
|
||||||
assignee: user,
|
assignee: user,
|
||||||
milestone: create(:milestone, due_date: '2013-12-12'),
|
milestone: create(:milestone, project: project, due_date: '2013-12-12'),
|
||||||
created_at: 2.minutes.ago,
|
created_at: 2.minutes.ago,
|
||||||
updated_at: 2.minutes.ago)
|
updated_at: 2.minutes.ago)
|
||||||
create(:merge_request,
|
create(:merge_request,
|
||||||
|
|
|
@ -7,6 +7,8 @@ describe 'Profile > Active Sessions', :clean_gitlab_redis_shared_state do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
let(:admin) { create(:admin) }
|
||||||
|
|
||||||
around do |example|
|
around do |example|
|
||||||
Timecop.freeze(Time.zone.parse('2018-03-12 09:06')) do
|
Timecop.freeze(Time.zone.parse('2018-03-12 09:06')) do
|
||||||
example.run
|
example.run
|
||||||
|
@ -16,6 +18,7 @@ describe 'Profile > Active Sessions', :clean_gitlab_redis_shared_state do
|
||||||
it 'User sees their active sessions' do
|
it 'User sees their active sessions' do
|
||||||
Capybara::Session.new(:session1)
|
Capybara::Session.new(:session1)
|
||||||
Capybara::Session.new(:session2)
|
Capybara::Session.new(:session2)
|
||||||
|
Capybara::Session.new(:session3)
|
||||||
|
|
||||||
# note: headers can only be set on the non-js (aka. rack-test) driver
|
# note: headers can only be set on the non-js (aka. rack-test) driver
|
||||||
using_session :session1 do
|
using_session :session1 do
|
||||||
|
@ -37,9 +40,27 @@ describe 'Profile > Active Sessions', :clean_gitlab_redis_shared_state do
|
||||||
gitlab_sign_in(user)
|
gitlab_sign_in(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# set an admin session impersonating the user
|
||||||
|
using_session :session3 do
|
||||||
|
Capybara.page.driver.header(
|
||||||
|
'User-Agent',
|
||||||
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36'
|
||||||
|
)
|
||||||
|
|
||||||
|
gitlab_sign_in(admin)
|
||||||
|
|
||||||
|
visit admin_user_path(user)
|
||||||
|
|
||||||
|
click_link 'Impersonate'
|
||||||
|
end
|
||||||
|
|
||||||
using_session :session1 do
|
using_session :session1 do
|
||||||
visit profile_active_sessions_path
|
visit profile_active_sessions_path
|
||||||
|
|
||||||
|
expect(page).to(
|
||||||
|
have_selector('ul.list-group li.list-group-item', { text: 'Signed in on',
|
||||||
|
count: 2 }))
|
||||||
|
|
||||||
expect(page).to have_content(
|
expect(page).to have_content(
|
||||||
'127.0.0.1 ' \
|
'127.0.0.1 ' \
|
||||||
'This is your current session ' \
|
'This is your current session ' \
|
||||||
|
@ -57,33 +78,8 @@ describe 'Profile > Active Sessions', :clean_gitlab_redis_shared_state do
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(page).to have_selector '[title="Smartphone"]', count: 1
|
expect(page).to have_selector '[title="Smartphone"]', count: 1
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'User can revoke a session', :js, :redis_session_store do
|
expect(page).not_to have_content('Chrome on Windows')
|
||||||
Capybara::Session.new(:session1)
|
|
||||||
Capybara::Session.new(:session2)
|
|
||||||
|
|
||||||
# set an additional session in another browser
|
|
||||||
using_session :session2 do
|
|
||||||
gitlab_sign_in(user)
|
|
||||||
end
|
|
||||||
|
|
||||||
using_session :session1 do
|
|
||||||
gitlab_sign_in(user)
|
|
||||||
visit profile_active_sessions_path
|
|
||||||
|
|
||||||
expect(page).to have_link('Revoke', count: 1)
|
|
||||||
|
|
||||||
accept_confirm { click_on 'Revoke' }
|
|
||||||
|
|
||||||
expect(page).not_to have_link('Revoke')
|
|
||||||
end
|
|
||||||
|
|
||||||
using_session :session2 do
|
|
||||||
visit profile_active_sessions_path
|
|
||||||
|
|
||||||
expect(page).to have_content('You need to sign in or sign up before continuing.')
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -548,10 +548,7 @@ describe 'File blob', :js do
|
||||||
it 'displays an auxiliary viewer' do
|
it 'displays an auxiliary viewer' do
|
||||||
aggregate_failures do
|
aggregate_failures do
|
||||||
# shows names of dependency manager and package
|
# shows names of dependency manager and package
|
||||||
expect(page).to have_content('This project manages its dependencies using RubyGems and defines a gem named activerecord.')
|
expect(page).to have_content('This project manages its dependencies using RubyGems.')
|
||||||
|
|
||||||
# shows a link to the gem
|
|
||||||
expect(page).to have_link('activerecord', href: 'https://rubygems.org/gems/activerecord')
|
|
||||||
|
|
||||||
# shows a learn more link
|
# shows a learn more link
|
||||||
expect(page).to have_link('Learn more', href: 'https://rubygems.org/')
|
expect(page).to have_link('Learn more', href: 'https://rubygems.org/')
|
||||||
|
|
|
@ -27,6 +27,7 @@ describe 'Project > Members > Invite group', :js do
|
||||||
|
|
||||||
before do
|
before do
|
||||||
project.add_maintainer(maintainer)
|
project.add_maintainer(maintainer)
|
||||||
|
group_to_share_with.add_guest(maintainer)
|
||||||
sign_in(maintainer)
|
sign_in(maintainer)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -112,6 +113,7 @@ describe 'Project > Members > Invite group', :js do
|
||||||
|
|
||||||
before do
|
before do
|
||||||
project.add_maintainer(maintainer)
|
project.add_maintainer(maintainer)
|
||||||
|
group.add_guest(maintainer)
|
||||||
sign_in(maintainer)
|
sign_in(maintainer)
|
||||||
|
|
||||||
visit project_settings_members_path(project)
|
visit project_settings_members_path(project)
|
||||||
|
|
|
@ -10,6 +10,7 @@ describe 'Projects > Settings > User manages group links' do
|
||||||
|
|
||||||
before do
|
before do
|
||||||
project.add_maintainer(user)
|
project.add_maintainer(user)
|
||||||
|
group_market.add_guest(user)
|
||||||
sign_in(user)
|
sign_in(user)
|
||||||
|
|
||||||
share_link = project.project_group_links.new(group_access: Gitlab::Access::MAINTAINER)
|
share_link = project.project_group_links.new(group_access: Gitlab::Access::MAINTAINER)
|
||||||
|
|
|
@ -93,4 +93,29 @@ describe 'Private Group access' do
|
||||||
it { is_expected.to be_denied_for(:visitor) }
|
it { is_expected.to be_denied_for(:visitor) }
|
||||||
it { is_expected.to be_denied_for(:external) }
|
it { is_expected.to be_denied_for(:external) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'GET /groups/:path for shared projects' do
|
||||||
|
let(:project) { create(:project, :public) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
Projects::GroupLinks::CreateService.new(
|
||||||
|
project,
|
||||||
|
create(:user),
|
||||||
|
link_group_access: ProjectGroupLink::DEVELOPER
|
||||||
|
).execute(group)
|
||||||
|
end
|
||||||
|
|
||||||
|
subject { group_path(group) }
|
||||||
|
|
||||||
|
it { is_expected.to be_allowed_for(:admin) }
|
||||||
|
it { is_expected.to be_allowed_for(:owner).of(group) }
|
||||||
|
it { is_expected.to be_allowed_for(:maintainer).of(group) }
|
||||||
|
it { is_expected.to be_allowed_for(:developer).of(group) }
|
||||||
|
it { is_expected.to be_allowed_for(:reporter).of(group) }
|
||||||
|
it { is_expected.to be_allowed_for(:guest).of(group) }
|
||||||
|
it { is_expected.to be_denied_for(project_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
|
||||||
|
|
|
@ -13,6 +13,7 @@ describe MergeRequestsFinder do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "multiple projects with merge requests" do
|
||||||
let(:user) { create :user }
|
let(:user) { create :user }
|
||||||
let(:user2) { create :user }
|
let(:user2) { create :user }
|
||||||
|
|
||||||
|
@ -31,7 +32,7 @@ describe MergeRequestsFinder do
|
||||||
p
|
p
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
let(:project4) { create_project_without_n_plus_1(group: subgroup) }
|
let(:project4) { create_project_without_n_plus_1(:repository, group: subgroup) }
|
||||||
let(:project5) { create_project_without_n_plus_1(group: subgroup) }
|
let(:project5) { create_project_without_n_plus_1(group: subgroup) }
|
||||||
let(:project6) { create_project_without_n_plus_1(group: subgroup) }
|
let(:project6) { create_project_without_n_plus_1(group: subgroup) }
|
||||||
|
|
||||||
|
@ -55,7 +56,7 @@ describe MergeRequestsFinder do
|
||||||
project6.add_developer(user)
|
project6.add_developer(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#execute" do
|
describe '#execute' do
|
||||||
it 'filters by scope' do
|
it 'filters by scope' do
|
||||||
params = { scope: 'authored', state: 'opened' }
|
params = { scope: 'authored', state: 'opened' }
|
||||||
merge_requests = described_class.new(user, params).execute
|
merge_requests = described_class.new(user, params).execute
|
||||||
|
@ -68,6 +69,15 @@ describe MergeRequestsFinder do
|
||||||
expect(merge_requests.size).to eq(2)
|
expect(merge_requests.size).to eq(2)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'filters by commit sha' do
|
||||||
|
merge_requests = described_class.new(
|
||||||
|
user,
|
||||||
|
commit_sha: merge_request5.merge_request_diff.last_commit_sha
|
||||||
|
).execute
|
||||||
|
|
||||||
|
expect(merge_requests).to contain_exactly(merge_request5)
|
||||||
|
end
|
||||||
|
|
||||||
context 'filtering by group' do
|
context 'filtering by group' do
|
||||||
it 'includes all merge requests when user has access' do
|
it 'includes all merge requests when user has access' do
|
||||||
params = { group_id: group.id }
|
params = { group_id: group.id }
|
||||||
|
@ -285,3 +295,127 @@ describe MergeRequestsFinder do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when projects require different access levels for merge requests' do
|
||||||
|
let(:user) { create(:user) }
|
||||||
|
|
||||||
|
let(:public_project) { create(:project, :public) }
|
||||||
|
let(:internal) { create(:project, :internal) }
|
||||||
|
let(:private_project) { create(:project, :private) }
|
||||||
|
let(:public_with_private_repo) { create(:project, :public, :repository, :repository_private) }
|
||||||
|
let(:internal_with_private_repo) { create(:project, :internal, :repository, :repository_private) }
|
||||||
|
|
||||||
|
let(:merge_requests) { described_class.new(user, {}).execute }
|
||||||
|
|
||||||
|
let!(:mr_public) { create(:merge_request, source_project: public_project) }
|
||||||
|
let!(:mr_private) { create(:merge_request, source_project: private_project) }
|
||||||
|
let!(:mr_internal) { create(:merge_request, source_project: internal) }
|
||||||
|
let!(:mr_private_repo_access) { create(:merge_request, source_project: public_with_private_repo) }
|
||||||
|
let!(:mr_internal_private_repo_access) { create(:merge_request, source_project: internal_with_private_repo) }
|
||||||
|
|
||||||
|
context 'with admin user' do
|
||||||
|
let(:user) { create(:user, :admin) }
|
||||||
|
|
||||||
|
it 'returns all merge requests' do
|
||||||
|
expect(merge_requests).to eq(
|
||||||
|
[mr_internal_private_repo_access, mr_private_repo_access, mr_internal, mr_private, mr_public]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when project restricts merge requests' do
|
||||||
|
let(:non_member) { create(:user) }
|
||||||
|
let(:project) { create(:project, :repository, :public, :merge_requests_private) }
|
||||||
|
let!(:merge_request) { create(:merge_request, source_project: project) }
|
||||||
|
|
||||||
|
it "returns nothing to to non members" do
|
||||||
|
merge_requests = described_class.new(
|
||||||
|
non_member,
|
||||||
|
project_id: project.id
|
||||||
|
).execute
|
||||||
|
|
||||||
|
expect(merge_requests).to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with external user' do
|
||||||
|
let(:user) { create(:user, :external) }
|
||||||
|
|
||||||
|
it 'returns only public merge requests' do
|
||||||
|
expect(merge_requests).to eq([mr_public])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with authenticated user' do
|
||||||
|
it 'returns public and internal merge requests' do
|
||||||
|
expect(merge_requests).to eq([mr_internal, mr_public])
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'being added to the private project' do
|
||||||
|
context 'as a guest' do
|
||||||
|
before do
|
||||||
|
private_project.add_guest(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not return merge requests from the private project' do
|
||||||
|
expect(merge_requests).to eq([mr_internal, mr_public])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'as a developer' do
|
||||||
|
before do
|
||||||
|
private_project.add_developer(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns merge requests from the private project' do
|
||||||
|
expect(merge_requests).to eq([mr_internal, mr_private, mr_public])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'being added to the public project with private repo access' do
|
||||||
|
context 'as a guest' do
|
||||||
|
before do
|
||||||
|
public_with_private_repo.add_guest(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns merge requests from the project' do
|
||||||
|
expect(merge_requests).to eq([mr_internal, mr_public])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'as a reporter' do
|
||||||
|
before do
|
||||||
|
public_with_private_repo.add_reporter(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns merge requests from the project' do
|
||||||
|
expect(merge_requests).to eq([mr_private_repo_access, mr_internal, mr_public])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'being added to the internal project with private repo access' do
|
||||||
|
context 'as a guest' do
|
||||||
|
before do
|
||||||
|
internal_with_private_repo.add_guest(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns merge requests from the project' do
|
||||||
|
expect(merge_requests).to eq([mr_internal, mr_public])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'as a reporter' do
|
||||||
|
before do
|
||||||
|
internal_with_private_repo.add_reporter(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns merge requests from the project' do
|
||||||
|
expect(merge_requests).to eq([mr_internal_private_repo_access, mr_internal, mr_public])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
|
@ -16,6 +16,10 @@ describe Constraints::ProjectUrlConstrainer do
|
||||||
let(:request) { build_request('foo', 'bar') }
|
let(:request) { build_request('foo', 'bar') }
|
||||||
|
|
||||||
it { expect(subject.matches?(request)).to be_falsey }
|
it { expect(subject.matches?(request)).to be_falsey }
|
||||||
|
|
||||||
|
context 'existence_check is false' do
|
||||||
|
it { expect(subject.matches?(request, existence_check: false)).to be_truthy }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "project id ending with .git" do
|
context "project id ending with .git" do
|
||||||
|
|
|
@ -50,8 +50,8 @@ describe Gitlab::DependencyLinker::ComposerJsonLinker do
|
||||||
%{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
|
%{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'links the module name' do
|
it 'does not link the module name' do
|
||||||
expect(subject).to include(link('laravel/laravel', 'https://packagist.org/packages/laravel/laravel'))
|
expect(subject).not_to include(link('laravel/laravel', 'https://packagist.org/packages/laravel/laravel'))
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'links the homepage' do
|
it 'links the homepage' do
|
||||||
|
|
|
@ -41,13 +41,16 @@ describe Gitlab::DependencyLinker::GemfileLinker do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'links dependencies' do
|
it 'links dependencies' do
|
||||||
expect(subject).to include(link('rails', 'https://rubygems.org/gems/rails'))
|
|
||||||
expect(subject).to include(link('rails-deprecated_sanitizer', 'https://rubygems.org/gems/rails-deprecated_sanitizer'))
|
expect(subject).to include(link('rails-deprecated_sanitizer', 'https://rubygems.org/gems/rails-deprecated_sanitizer'))
|
||||||
expect(subject).to include(link('responders', 'https://rubygems.org/gems/responders'))
|
|
||||||
expect(subject).to include(link('sprockets', 'https://rubygems.org/gems/sprockets'))
|
|
||||||
expect(subject).to include(link('default_value_for', 'https://rubygems.org/gems/default_value_for'))
|
expect(subject).to include(link('default_value_for', 'https://rubygems.org/gems/default_value_for'))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'links to external dependencies' do
|
||||||
|
expect(subject).to include(link('rails', 'https://github.com/rails/rails'))
|
||||||
|
expect(subject).to include(link('responders', 'https://github.com/rails/responders'))
|
||||||
|
expect(subject).to include(link('sprockets', 'https://gitlab.example.com/gems/sprockets'))
|
||||||
|
end
|
||||||
|
|
||||||
it 'links GitHub repos' do
|
it 'links GitHub repos' do
|
||||||
expect(subject).to include(link('rails/rails', 'https://github.com/rails/rails'))
|
expect(subject).to include(link('rails/rails', 'https://github.com/rails/rails'))
|
||||||
expect(subject).to include(link('rails/responders', 'https://github.com/rails/responders'))
|
expect(subject).to include(link('rails/responders', 'https://github.com/rails/responders'))
|
||||||
|
|
|
@ -43,8 +43,8 @@ describe Gitlab::DependencyLinker::GemspecLinker do
|
||||||
%{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
|
%{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'links the gem name' do
|
it 'does not link the gem name' do
|
||||||
expect(subject).to include(link('gitlab_git', 'https://rubygems.org/gems/gitlab_git'))
|
expect(subject).not_to include(link('gitlab_git', 'https://rubygems.org/gems/gitlab_git'))
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'links the license' do
|
it 'links the license' do
|
||||||
|
|
|
@ -33,7 +33,8 @@ describe Gitlab::DependencyLinker::PackageJsonLinker do
|
||||||
"express": "4.2.x",
|
"express": "4.2.x",
|
||||||
"bigpipe": "bigpipe/pagelet",
|
"bigpipe": "bigpipe/pagelet",
|
||||||
"plates": "https://github.com/flatiron/plates/tarball/master",
|
"plates": "https://github.com/flatiron/plates/tarball/master",
|
||||||
"karma": "^1.4.1"
|
"karma": "^1.4.1",
|
||||||
|
"random": "git+https://EdOverflow@github.com/example/example.git"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"vows": "^0.7.0",
|
"vows": "^0.7.0",
|
||||||
|
@ -51,8 +52,8 @@ describe Gitlab::DependencyLinker::PackageJsonLinker do
|
||||||
%{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
|
%{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'links the module name' do
|
it 'does not link the module name' do
|
||||||
expect(subject).to include(link('module-name', 'https://npmjs.com/package/module-name'))
|
expect(subject).not_to include(link('module-name', 'https://npmjs.com/package/module-name'))
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'links the homepage' do
|
it 'links the homepage' do
|
||||||
|
@ -71,14 +72,21 @@ describe Gitlab::DependencyLinker::PackageJsonLinker do
|
||||||
expect(subject).to include(link('primus', 'https://npmjs.com/package/primus'))
|
expect(subject).to include(link('primus', 'https://npmjs.com/package/primus'))
|
||||||
expect(subject).to include(link('async', 'https://npmjs.com/package/async'))
|
expect(subject).to include(link('async', 'https://npmjs.com/package/async'))
|
||||||
expect(subject).to include(link('express', 'https://npmjs.com/package/express'))
|
expect(subject).to include(link('express', 'https://npmjs.com/package/express'))
|
||||||
expect(subject).to include(link('bigpipe', 'https://npmjs.com/package/bigpipe'))
|
|
||||||
expect(subject).to include(link('plates', 'https://npmjs.com/package/plates'))
|
|
||||||
expect(subject).to include(link('karma', 'https://npmjs.com/package/karma'))
|
expect(subject).to include(link('karma', 'https://npmjs.com/package/karma'))
|
||||||
expect(subject).to include(link('vows', 'https://npmjs.com/package/vows'))
|
expect(subject).to include(link('vows', 'https://npmjs.com/package/vows'))
|
||||||
expect(subject).to include(link('assume', 'https://npmjs.com/package/assume'))
|
expect(subject).to include(link('assume', 'https://npmjs.com/package/assume'))
|
||||||
expect(subject).to include(link('pre-commit', 'https://npmjs.com/package/pre-commit'))
|
expect(subject).to include(link('pre-commit', 'https://npmjs.com/package/pre-commit'))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'links dependencies to URL detected on value' do
|
||||||
|
expect(subject).to include(link('bigpipe', 'https://github.com/bigpipe/pagelet'))
|
||||||
|
expect(subject).to include(link('plates', 'https://github.com/flatiron/plates/tarball/master'))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not link to NPM when invalid git URL' do
|
||||||
|
expect(subject).not_to include(link('random', 'https://npmjs.com/package/random'))
|
||||||
|
end
|
||||||
|
|
||||||
it 'links GitHub repos' do
|
it 'links GitHub repos' do
|
||||||
expect(subject).to include(link('bigpipe/pagelet', 'https://github.com/bigpipe/pagelet'))
|
expect(subject).to include(link('bigpipe/pagelet', 'https://github.com/bigpipe/pagelet'))
|
||||||
end
|
end
|
||||||
|
|
42
spec/lib/gitlab/dependency_linker/parser/gemfile_spec.rb
Normal file
42
spec/lib/gitlab/dependency_linker/parser/gemfile_spec.rb
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
describe Gitlab::DependencyLinker::Parser::Gemfile do
|
||||||
|
describe '#parse' do
|
||||||
|
let(:file_content) do
|
||||||
|
<<-CONTENT.strip_heredoc
|
||||||
|
source 'https://rubygems.org'
|
||||||
|
|
||||||
|
gem "rails", '4.2.6', github: "rails/rails"
|
||||||
|
gem 'rails-deprecated_sanitizer', '~> 1.0.3'
|
||||||
|
gem 'responders', '~> 2.0', :github => 'rails/responders'
|
||||||
|
gem 'sprockets', '~> 3.6.0', git: 'https://gitlab.example.com/gems/sprockets'
|
||||||
|
gem 'default_value_for', '~> 3.0.0'
|
||||||
|
CONTENT
|
||||||
|
end
|
||||||
|
|
||||||
|
subject { described_class.new(file_content).parse(keyword: 'gem') }
|
||||||
|
|
||||||
|
def fetch_package(name)
|
||||||
|
subject.find { |package| package.name == name }
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns parsed packages' do
|
||||||
|
expect(subject.size).to eq(5)
|
||||||
|
expect(subject).to all(be_a(Gitlab::DependencyLinker::Package))
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'packages respond to name and external_ref accordingly' do
|
||||||
|
expect(fetch_package('rails')).to have_attributes(name: 'rails',
|
||||||
|
github_ref: 'rails/rails',
|
||||||
|
git_ref: nil)
|
||||||
|
|
||||||
|
expect(fetch_package('sprockets')).to have_attributes(name: 'sprockets',
|
||||||
|
github_ref: nil,
|
||||||
|
git_ref: 'https://gitlab.example.com/gems/sprockets')
|
||||||
|
|
||||||
|
expect(fetch_package('default_value_for')).to have_attributes(name: 'default_value_for',
|
||||||
|
github_ref: nil,
|
||||||
|
git_ref: nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -43,7 +43,10 @@ describe Gitlab::DependencyLinker::PodfileLinker do
|
||||||
|
|
||||||
it 'links packages' do
|
it 'links packages' do
|
||||||
expect(subject).to include(link('AFNetworking', 'https://cocoapods.org/pods/AFNetworking'))
|
expect(subject).to include(link('AFNetworking', 'https://cocoapods.org/pods/AFNetworking'))
|
||||||
expect(subject).to include(link('Interstellar/Core', 'https://cocoapods.org/pods/Interstellar'))
|
end
|
||||||
|
|
||||||
|
it 'links external packages' do
|
||||||
|
expect(subject).to include(link('Interstellar/Core', 'https://github.com/ashfurrow/Interstellar.git'))
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'links Git repos' do
|
it 'links Git repos' do
|
||||||
|
|
|
@ -42,8 +42,8 @@ describe Gitlab::DependencyLinker::PodspecLinker do
|
||||||
%{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
|
%{<a href="#{url}" rel="nofollow noreferrer noopener" target="_blank">#{name}</a>}
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'links the gem name' do
|
it 'does not link the pod name' do
|
||||||
expect(subject).to include(link('Reachability', 'https://cocoapods.org/pods/Reachability'))
|
expect(subject).not_to include(link('Reachability', 'https://cocoapods.org/pods/Reachability'))
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'links the license' do
|
it 'links the license' do
|
||||||
|
|
|
@ -41,4 +41,20 @@ describe Gitlab::ImportExport::MergeRequestParser do
|
||||||
|
|
||||||
expect(parsed_merge_request).to eq(merge_request)
|
expect(parsed_merge_request).to eq(merge_request)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when the merge request has diffs' do
|
||||||
|
let(:merge_request) do
|
||||||
|
build(:merge_request, source_project: forked_project, target_project: project)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the diff is invalid' do
|
||||||
|
let(:merge_request_diff) { build(:merge_request_diff, merge_request: merge_request, base_commit_sha: 'foobar') }
|
||||||
|
|
||||||
|
it 'sets the diff to nil' do
|
||||||
|
expect(merge_request_diff).to be_invalid
|
||||||
|
expect(merge_request_diff.merge_request).to eq merge_request
|
||||||
|
expect(parsed_merge_request.merge_request_diff).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -14,6 +14,16 @@ describe Gitlab::ImportExport::Shared do
|
||||||
expect(subject.errors).to eq(['Error importing into [FILTERED] Permission denied @ unlink_internal - [FILTERED]'])
|
expect(subject.errors).to eq(['Error importing into [FILTERED] Permission denied @ unlink_internal - [FILTERED]'])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'updates the import JID' do
|
||||||
|
import_state = create(:import_state, project: project, jid: 'jid-test')
|
||||||
|
|
||||||
|
expect_next_instance_of(Gitlab::Import::Logger) do |logger|
|
||||||
|
expect(logger).to receive(:error).with(hash_including(import_jid: import_state.jid))
|
||||||
|
end
|
||||||
|
|
||||||
|
subject.error(error)
|
||||||
|
end
|
||||||
|
|
||||||
it 'calls the error logger with the full message' do
|
it 'calls the error logger with the full message' do
|
||||||
expect(subject).to receive(:log_error).with(hash_including(message: error.message))
|
expect(subject).to receive(:log_error).with(hash_including(message: error.message))
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,36 @@ describe Gitlab::Kubernetes::KubeClient do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#initialize' do
|
||||||
|
shared_examples 'local address' do
|
||||||
|
it 'blocks local addresses' do
|
||||||
|
expect { client }.to raise_error(Gitlab::UrlBlocker::BlockedUrlError)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when local requests are allowed' do
|
||||||
|
before do
|
||||||
|
stub_application_setting(allow_local_requests_from_hooks_and_services: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'allows local addresses' do
|
||||||
|
expect { client }.not_to raise_error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'localhost address' do
|
||||||
|
let(:api_url) { 'http://localhost:22' }
|
||||||
|
|
||||||
|
it_behaves_like 'local address'
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'private network address' do
|
||||||
|
let(:api_url) { 'http://192.168.1.2:3003' }
|
||||||
|
|
||||||
|
it_behaves_like 'local address'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe '#core_client' do
|
describe '#core_client' do
|
||||||
subject { client.core_client }
|
subject { client.core_client }
|
||||||
|
|
||||||
|
|
|
@ -194,6 +194,11 @@ describe Notify do
|
||||||
let(:new_issue) { create(:issue) }
|
let(:new_issue) { create(:issue) }
|
||||||
subject { described_class.issue_moved_email(recipient, issue, new_issue, current_user) }
|
subject { described_class.issue_moved_email(recipient, issue, new_issue, current_user) }
|
||||||
|
|
||||||
|
context 'when a user has permissions to access the new issue' do
|
||||||
|
before do
|
||||||
|
new_issue.project.add_developer(recipient)
|
||||||
|
end
|
||||||
|
|
||||||
it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do
|
it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do
|
||||||
let(:model) { issue }
|
let(:model) { issue }
|
||||||
end
|
end
|
||||||
|
@ -213,6 +218,31 @@ describe Notify do
|
||||||
is_expected.to have_body_text(project_issue_path(project, issue))
|
is_expected.to have_body_text(project_issue_path(project, issue))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'contains the issue title' do
|
||||||
|
is_expected.to have_body_text new_issue.title
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when a user does not permissions to access the new issue' do
|
||||||
|
it 'has the correct subject and body' do
|
||||||
|
new_issue_url = project_issue_path(new_issue.project, new_issue)
|
||||||
|
|
||||||
|
aggregate_failures do
|
||||||
|
is_expected.to have_referable_subject(issue, reply: true)
|
||||||
|
is_expected.not_to have_body_text(new_issue_url)
|
||||||
|
is_expected.to have_body_text(project_issue_path(project, issue))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not contain the issue title' do
|
||||||
|
is_expected.not_to have_body_text new_issue.title
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'contains information about missing permissions' do
|
||||||
|
is_expected.to have_body_text "You don't have access to the project."
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,10 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_shared_state do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:session) { double(:session, id: '6919a6f1bb119dd7396fadc38fd18d0d') }
|
let(:session) do
|
||||||
|
double(:session, { id: '6919a6f1bb119dd7396fadc38fd18d0d',
|
||||||
|
'[]': {} })
|
||||||
|
end
|
||||||
|
|
||||||
let(:request) do
|
let(:request) do
|
||||||
double(:request, {
|
double(:request, {
|
||||||
|
|
|
@ -98,6 +98,22 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
|
||||||
|
|
||||||
it { expect(kubernetes.save).to be_truthy }
|
it { expect(kubernetes.save).to be_truthy }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when api_url is localhost' do
|
||||||
|
let(:api_url) { 'http://localhost:22' }
|
||||||
|
|
||||||
|
it { expect(kubernetes.save).to be_falsey }
|
||||||
|
|
||||||
|
context 'Application settings allows local requests' do
|
||||||
|
before do
|
||||||
|
allow(ApplicationSetting)
|
||||||
|
.to receive(:current)
|
||||||
|
.and_return(ApplicationSetting.build_from_defaults(allow_local_requests_from_hooks_and_services: true))
|
||||||
|
end
|
||||||
|
|
||||||
|
it { expect(kubernetes.save).to be_truthy }
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when validates token' do
|
context 'when validates token' do
|
||||||
|
|
|
@ -32,6 +32,7 @@ describe Issuable do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "Validation" do
|
describe "Validation" do
|
||||||
|
context 'general validations' do
|
||||||
subject { build(:issue) }
|
subject { build(:issue) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
@ -45,6 +46,44 @@ describe Issuable do
|
||||||
it { is_expected.to validate_length_of(:title).is_at_most(255) }
|
it { is_expected.to validate_length_of(:title).is_at_most(255) }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'milestone' do
|
||||||
|
let(:project) { create(:project) }
|
||||||
|
let(:milestone_id) { create(:milestone, project: project).id }
|
||||||
|
let(:params) do
|
||||||
|
{
|
||||||
|
title: 'something',
|
||||||
|
project: project,
|
||||||
|
author: build(:user),
|
||||||
|
milestone_id: milestone_id
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
subject { issuable_class.new(params) }
|
||||||
|
|
||||||
|
context 'with correct params' do
|
||||||
|
it { is_expected.to be_valid }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with empty string milestone' do
|
||||||
|
let(:milestone_id) { '' }
|
||||||
|
|
||||||
|
it { is_expected.to be_valid }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with nil milestone id' do
|
||||||
|
let(:milestone_id) { nil }
|
||||||
|
|
||||||
|
it { is_expected.to be_valid }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with a milestone id from another project' do
|
||||||
|
let(:milestone_id) { create(:milestone).id }
|
||||||
|
|
||||||
|
it { is_expected.to be_invalid }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "Scope" do
|
describe "Scope" do
|
||||||
subject { build(:issue) }
|
subject { build(:issue) }
|
||||||
|
|
||||||
|
@ -66,6 +105,48 @@ describe Issuable do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#milestone_available?' do
|
||||||
|
let(:group) { create(:group) }
|
||||||
|
let(:project) { create(:project, group: group) }
|
||||||
|
let(:issue) { create(:issue, project: project) }
|
||||||
|
|
||||||
|
def build_issuable(milestone_id)
|
||||||
|
issuable_class.new(project: project, milestone_id: milestone_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns true with a milestone from the issue project' do
|
||||||
|
milestone = create(:milestone, project: project)
|
||||||
|
|
||||||
|
expect(build_issuable(milestone.id).milestone_available?).to be_truthy
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns true with a milestone from the issue project group' do
|
||||||
|
milestone = create(:milestone, group: group)
|
||||||
|
|
||||||
|
expect(build_issuable(milestone.id).milestone_available?).to be_truthy
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns true with a milestone from the the parent of the issue project group', :nested_groups do
|
||||||
|
parent = create(:group)
|
||||||
|
group.update(parent: parent)
|
||||||
|
milestone = create(:milestone, group: parent)
|
||||||
|
|
||||||
|
expect(build_issuable(milestone.id).milestone_available?).to be_truthy
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns false with a milestone from another project' do
|
||||||
|
milestone = create(:milestone)
|
||||||
|
|
||||||
|
expect(build_issuable(milestone.id).milestone_available?).to be_falsey
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns false with a milestone from another group' do
|
||||||
|
milestone = create(:milestone, group: create(:group))
|
||||||
|
|
||||||
|
expect(build_issuable(milestone.id).milestone_available?).to be_falsey
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe ".search" do
|
describe ".search" do
|
||||||
let!(:searchable_issue) { create(:issue, title: "Searchable awesome issue") }
|
let!(:searchable_issue) { create(:issue, title: "Searchable awesome issue") }
|
||||||
let!(:searchable_issue2) { create(:issue, title: 'Aw') }
|
let!(:searchable_issue2) { create(:issue, title: 'Aw') }
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue