Merge branch 'sh-backport-10-3-4-security-fixes' into 'master'
Backport 10.3.4 security fixes into master See merge request gitlab-org/gitlab-ce!16509
This commit is contained in:
commit
f351cc28c2
|
@ -1,11 +1,15 @@
|
|||
<script>
|
||||
import actionBtn from './action_btn.vue';
|
||||
import { getTimeago } from '../../lib/utils/datetime_utility';
|
||||
import tooltip from '../../vue_shared/directives/tooltip';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
actionBtn,
|
||||
},
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
props: {
|
||||
deployKey: {
|
||||
type: Object,
|
||||
|
@ -32,6 +36,9 @@
|
|||
isEnabled(id) {
|
||||
return this.store.findEnabledKey(id) !== undefined;
|
||||
},
|
||||
tooltipTitle(project) {
|
||||
return project.can_push ? 'Write access allowed' : 'Read access only';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -52,21 +59,23 @@
|
|||
<div class="description">
|
||||
{{ deployKey.fingerprint }}
|
||||
</div>
|
||||
<div
|
||||
v-if="deployKey.can_push"
|
||||
class="write-access-allowed"
|
||||
>
|
||||
Write access allowed
|
||||
</div>
|
||||
</div>
|
||||
<div class="deploy-key-content prepend-left-default deploy-key-projects">
|
||||
<a
|
||||
v-for="(project, i) in deployKey.projects"
|
||||
class="label deploy-project-label"
|
||||
:href="project.full_path"
|
||||
v-for="(deployKeysProject, i) in deployKey.deploy_keys_projects"
|
||||
:key="i"
|
||||
class="label deploy-project-label"
|
||||
:href="deployKeysProject.project.full_path"
|
||||
:title="tooltipTitle(deployKeysProject)"
|
||||
v-tooltip
|
||||
>
|
||||
{{ project.full_name }}
|
||||
{{ deployKeysProject.project.full_name }}
|
||||
<i
|
||||
v-if="!deployKeysProject.can_push"
|
||||
aria-hidden="true"
|
||||
class="fa fa-lock"
|
||||
>
|
||||
</i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="deploy-key-content">
|
||||
|
|
|
@ -231,7 +231,7 @@ export default class LabelsSelect {
|
|||
selectedClass.push('label-item');
|
||||
$a.attr('data-label-id', label.id);
|
||||
}
|
||||
$a.addClass(selectedClass.join(' ')).html(colorEl + " " + label.title);
|
||||
$a.addClass(selectedClass.join(' ')).html(`${colorEl} ${_.escape(label.title)}`);
|
||||
// Return generated html
|
||||
return $li.html($a).prop('outerHTML');
|
||||
},
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
/* global katex */
|
||||
import marked from 'marked';
|
||||
import sanitize from 'sanitize-html';
|
||||
import Prompt from './prompt.vue';
|
||||
|
||||
const renderer = new marked.Renderer();
|
||||
|
@ -82,7 +83,12 @@
|
|||
},
|
||||
computed: {
|
||||
markdown() {
|
||||
return marked(this.cell.source.join('').replace(/\\/g, '\\\\'));
|
||||
return sanitize(marked(this.cell.source.join('').replace(/\\/g, '\\\\')), {
|
||||
allowedTags: false,
|
||||
allowedAttributes: {
|
||||
'*': ['class'],
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<script>
|
||||
import sanitize from 'sanitize-html';
|
||||
import Prompt from '../prompt.vue';
|
||||
|
||||
export default {
|
||||
|
@ -11,12 +12,24 @@
|
|||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
sanitizedOutput() {
|
||||
return sanitize(this.rawCode, {
|
||||
allowedTags: sanitize.defaults.allowedTags.concat([
|
||||
'img', 'svg',
|
||||
]),
|
||||
allowedAttributes: {
|
||||
img: ['src'],
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="output">
|
||||
<prompt />
|
||||
<div v-html="rawCode"></div>
|
||||
<div v-html="sanitizedOutput"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -50,10 +50,10 @@ class Admin::DeployKeysController < Admin::ApplicationController
|
|||
end
|
||||
|
||||
def create_params
|
||||
params.require(:deploy_key).permit(:key, :title, :can_push)
|
||||
params.require(:deploy_key).permit(:key, :title)
|
||||
end
|
||||
|
||||
def update_params
|
||||
params.require(:deploy_key).permit(:title, :can_push)
|
||||
params.require(:deploy_key).permit(:title)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -75,8 +75,6 @@ class Groups::MilestonesController < Groups::ApplicationController
|
|||
end
|
||||
|
||||
def milestones
|
||||
search_params = params.merge(group_ids: group.id)
|
||||
|
||||
milestones = MilestonesFinder.new(search_params).execute
|
||||
legacy_milestones = GroupMilestone.build_collection(group, group_projects, params)
|
||||
|
||||
|
@ -94,4 +92,8 @@ class Groups::MilestonesController < Groups::ApplicationController
|
|||
|
||||
render_404 unless @milestone
|
||||
end
|
||||
|
||||
def search_params
|
||||
params.permit(:state).merge(group_ids: group.id)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -112,6 +112,8 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
|||
|
||||
continue_login_process
|
||||
end
|
||||
rescue Gitlab::OAuth::SigninDisabledForProviderError
|
||||
handle_disabled_provider
|
||||
rescue Gitlab::OAuth::SignupDisabledError
|
||||
handle_signup_error
|
||||
end
|
||||
|
@ -168,6 +170,13 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
|||
redirect_to new_user_session_path
|
||||
end
|
||||
|
||||
def handle_disabled_provider
|
||||
label = Gitlab::OAuth::Provider.label_for(oauth['provider'])
|
||||
flash[:alert] = "Signing in using #{label} has been disabled"
|
||||
|
||||
redirect_to new_user_session_path
|
||||
end
|
||||
|
||||
def log_audit_event(user, options = {})
|
||||
AuditEventService.new(user, user, options)
|
||||
.for_authentication.security_event
|
||||
|
|
|
@ -24,7 +24,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
|
|||
def create
|
||||
@key = DeployKeys::CreateService.new(current_user, create_params).execute
|
||||
|
||||
unless @key.valid? && @project.deploy_keys << @key
|
||||
unless @key.valid?
|
||||
flash[:alert] = @key.errors.full_messages.join(', ').html_safe
|
||||
end
|
||||
|
||||
|
@ -71,11 +71,14 @@ class Projects::DeployKeysController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def create_params
|
||||
params.require(:deploy_key).permit(:key, :title, :can_push)
|
||||
create_params = params.require(:deploy_key)
|
||||
.permit(:key, :title, deploy_keys_projects_attributes: [:can_push])
|
||||
create_params.dig(:deploy_keys_projects_attributes, '0')&.merge!(project_id: @project.id)
|
||||
create_params
|
||||
end
|
||||
|
||||
def update_params
|
||||
params.require(:deploy_key).permit(:title, :can_push)
|
||||
params.require(:deploy_key).permit(:title, deploy_keys_projects_attributes: [:id, :can_push])
|
||||
end
|
||||
|
||||
def authorize_update_deploy_key!
|
||||
|
|
|
@ -92,12 +92,6 @@ class Projects::MilestonesController < Projects::ApplicationController
|
|||
|
||||
def milestones
|
||||
@milestones ||= begin
|
||||
if @project.group && can?(current_user, :read_group, @project.group)
|
||||
group = @project.group
|
||||
end
|
||||
|
||||
search_params = params.merge(project_ids: @project.id, group_ids: group&.id)
|
||||
|
||||
MilestonesFinder.new(search_params).execute
|
||||
end
|
||||
end
|
||||
|
@ -113,4 +107,12 @@ class Projects::MilestonesController < Projects::ApplicationController
|
|||
def milestone_params
|
||||
params.require(:milestone).permit(:title, :description, :start_date, :due_date, :state_event)
|
||||
end
|
||||
|
||||
def search_params
|
||||
if @project.group && can?(current_user, :read_group, @project.group)
|
||||
group = @project.group
|
||||
end
|
||||
|
||||
params.permit(:state).merge(project_ids: @project.id, group_ids: group&.id)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -46,11 +46,7 @@ class MilestonesFinder
|
|||
end
|
||||
|
||||
def order(items)
|
||||
if params.has_key?(:order)
|
||||
items.reorder(params[:order])
|
||||
else
|
||||
order_statement = Gitlab::Database.nulls_last_order('due_date', 'ASC')
|
||||
items.reorder(order_statement)
|
||||
end
|
||||
order_statement = Gitlab::Database.nulls_last_order('due_date', 'ASC')
|
||||
items.reorder(order_statement).order('title ASC')
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
class DeployKey < Key
|
||||
has_many :deploy_keys_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
|
||||
include IgnorableColumn
|
||||
|
||||
has_many :deploy_keys_projects, inverse_of: :deploy_key, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
|
||||
has_many :projects, through: :deploy_keys_projects
|
||||
|
||||
scope :in_projects, ->(projects) { joins(:deploy_keys_projects).where('deploy_keys_projects.project_id in (?)', projects) }
|
||||
scope :are_public, -> { where(public: true) }
|
||||
|
||||
ignore_column :can_push
|
||||
|
||||
accepts_nested_attributes_for :deploy_keys_projects
|
||||
|
||||
def private?
|
||||
!public?
|
||||
end
|
||||
|
@ -22,10 +28,18 @@ class DeployKey < Key
|
|||
end
|
||||
|
||||
def has_access_to?(project)
|
||||
projects.include?(project)
|
||||
deploy_keys_project_for(project).present?
|
||||
end
|
||||
|
||||
def can_push_to?(project)
|
||||
can_push? && has_access_to?(project)
|
||||
!!deploy_keys_project_for(project)&.can_push?
|
||||
end
|
||||
|
||||
def deploy_keys_project_for(project)
|
||||
deploy_keys_projects.find_by(project: project)
|
||||
end
|
||||
|
||||
def projects_with_write_access
|
||||
Project.preload(:route).where(id: deploy_keys_projects.with_write_access.select(:project_id))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
class DeployKeysProject < ActiveRecord::Base
|
||||
belongs_to :project
|
||||
belongs_to :deploy_key
|
||||
belongs_to :deploy_key, inverse_of: :deploy_keys_projects
|
||||
|
||||
validates :deploy_key_id, presence: true
|
||||
scope :without_project_deleted, -> { joins(:project).where(projects: { pending_delete: false }) }
|
||||
scope :in_project, ->(project) { where(project: project) }
|
||||
scope :with_write_access, -> { where(can_push: true) }
|
||||
|
||||
accepts_nested_attributes_for :deploy_key
|
||||
|
||||
validates :deploy_key, presence: true
|
||||
validates :deploy_key_id, uniqueness: { scope: [:project_id], message: "already exists in project" }
|
||||
validates :project_id, presence: true
|
||||
|
||||
|
|
|
@ -44,10 +44,10 @@ class GlobalMilestone
|
|||
def self.group_milestones_states_count(group)
|
||||
return STATE_COUNT_HASH unless group
|
||||
|
||||
params = { group_ids: [group.id], state: 'all', order: nil }
|
||||
params = { group_ids: [group.id], state: 'all' }
|
||||
|
||||
relation = MilestonesFinder.new(params).execute
|
||||
grouped_by_state = relation.group(:state).count
|
||||
grouped_by_state = relation.reorder(nil).group(:state).count
|
||||
|
||||
{
|
||||
opened: grouped_by_state['active'] || 0,
|
||||
|
@ -60,10 +60,10 @@ class GlobalMilestone
|
|||
def self.legacy_group_milestone_states_count(projects)
|
||||
return STATE_COUNT_HASH unless projects
|
||||
|
||||
params = { project_ids: projects.map(&:id), state: 'all', order: nil }
|
||||
params = { project_ids: projects.map(&:id), state: 'all' }
|
||||
|
||||
relation = MilestonesFinder.new(params).execute
|
||||
project_milestones_by_state_and_title = relation.group(:state, :title).count
|
||||
project_milestones_by_state_and_title = relation.reorder(nil).group(:state, :title).count
|
||||
|
||||
opened = count_by_state(project_milestones_by_state_and_title, 'active')
|
||||
closed = count_by_state(project_milestones_by_state_and_title, 'closed')
|
||||
|
|
|
@ -4,6 +4,7 @@ class WebHook < ActiveRecord::Base
|
|||
has_many :web_hook_logs, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
|
||||
|
||||
validates :url, presence: true, url: true
|
||||
validates :token, format: { without: /\n/ }
|
||||
|
||||
def execute(data, hook_name)
|
||||
WebHookService.new(self, data, hook_name).execute
|
||||
|
|
|
@ -118,6 +118,11 @@ class Service < ActiveRecord::Base
|
|||
nil
|
||||
end
|
||||
|
||||
def api_field_names
|
||||
fields.map { |field| field[:name] }
|
||||
.reject { |field_name| field_name =~ /(password|token|key)/ }
|
||||
end
|
||||
|
||||
def global_fields
|
||||
fields
|
||||
end
|
||||
|
|
|
@ -7,7 +7,7 @@ module Projects
|
|||
delegate :size, to: :available_public_keys, prefix: true
|
||||
|
||||
def new_key
|
||||
@key ||= DeployKey.new
|
||||
@key ||= DeployKey.new.tap { |dk| dk.deploy_keys_projects.build }
|
||||
end
|
||||
|
||||
def enabled_keys
|
||||
|
|
|
@ -3,19 +3,20 @@ class DeployKeyEntity < Grape::Entity
|
|||
expose :user_id
|
||||
expose :title
|
||||
expose :fingerprint
|
||||
expose :can_push
|
||||
expose :destroyed_when_orphaned?, as: :destroyed_when_orphaned
|
||||
expose :almost_orphaned?, as: :almost_orphaned
|
||||
expose :created_at
|
||||
expose :updated_at
|
||||
expose :projects, using: ProjectEntity do |deploy_key|
|
||||
deploy_key.projects.without_deleted.select { |project| options[:user].can?(:read_project, project) }
|
||||
expose :deploy_keys_projects, using: DeployKeysProjectEntity do |deploy_key|
|
||||
deploy_key.deploy_keys_projects
|
||||
.without_project_deleted
|
||||
.select { |deploy_key_project| Ability.allowed?(options[:user], :read_project, deploy_key_project.project) }
|
||||
end
|
||||
expose :can_edit
|
||||
|
||||
private
|
||||
|
||||
def can_edit
|
||||
options[:user].can?(:update_deploy_key, object)
|
||||
Ability.allowed?(options[:user], :update_deploy_key, object)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
class DeployKeysProjectEntity < Grape::Entity
|
||||
expose :can_push
|
||||
expose :project, using: ProjectEntity
|
||||
end
|
|
@ -1,15 +1,11 @@
|
|||
module MergeRequests
|
||||
class CreateService < MergeRequests::BaseService
|
||||
def execute
|
||||
# @project is used to determine whether the user can set the merge request's
|
||||
# assignee, milestone and labels. Whether they can depends on their
|
||||
# permissions on the target project.
|
||||
source_project = @project
|
||||
@project = Project.find(params[:target_project_id]) if params[:target_project_id]
|
||||
set_projects!
|
||||
|
||||
merge_request = MergeRequest.new
|
||||
merge_request.target_project = @project
|
||||
merge_request.source_project = source_project
|
||||
merge_request.source_project = @source_project
|
||||
merge_request.source_branch = params[:source_branch]
|
||||
merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch)
|
||||
|
||||
|
@ -58,5 +54,25 @@ module MergeRequests
|
|||
|
||||
pipelines.order(id: :desc).first
|
||||
end
|
||||
|
||||
def set_projects!
|
||||
# @project is used to determine whether the user can set the merge request's
|
||||
# assignee, milestone and labels. Whether they can depends on their
|
||||
# permissions on the target project.
|
||||
@source_project = @project
|
||||
@project = Project.find(params[:target_project_id]) if params[:target_project_id]
|
||||
|
||||
# make sure that source/target project ids are not in
|
||||
# params so it can't be overridden later when updating attributes
|
||||
# from params when applying quick actions
|
||||
params.delete(:source_project_id)
|
||||
params.delete(:target_project_id)
|
||||
|
||||
unless can?(current_user, :read_project, @source_project) &&
|
||||
can?(current_user, :read_project, @project)
|
||||
|
||||
raise Gitlab::Access::AccessDeniedError
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -26,7 +26,7 @@ module Projects
|
|||
end
|
||||
|
||||
def tmp_filename
|
||||
"#{SecureRandom.hex}_#{params[:path]}"
|
||||
SecureRandom.hex
|
||||
end
|
||||
|
||||
def file
|
||||
|
|
|
@ -113,7 +113,7 @@ class WebHookService
|
|||
'Content-Type' => 'application/json',
|
||||
'X-Gitlab-Event' => hook_name.singularize.titleize
|
||||
}.tap do |hash|
|
||||
hash['X-Gitlab-Token'] = hook.token if hook.token.present?
|
||||
hash['X-Gitlab-Token'] = Gitlab::Utils.remove_line_breaks(hook.token) if hook.token.present?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
%tr
|
||||
%th.col-sm-2 Title
|
||||
%th.col-sm-4 Fingerprint
|
||||
%th.col-sm-2 Write access allowed
|
||||
%th.col-sm-2 Projects with write access
|
||||
%th.col-sm-2 Added at
|
||||
%th.col-sm-2
|
||||
%tbody
|
||||
|
@ -23,10 +23,8 @@
|
|||
%td
|
||||
%code.key-fingerprint= deploy_key.fingerprint
|
||||
%td
|
||||
- if deploy_key.can_push?
|
||||
Yes
|
||||
- else
|
||||
No
|
||||
- deploy_key.projects_with_write_access.each do |project|
|
||||
= link_to project.full_name, admin_project_path(project), class: 'label deploy-project-label'
|
||||
%td
|
||||
%span.cgray
|
||||
added #{time_ago_with_tooltip(deploy_key.created_at)}
|
||||
|
|
|
@ -10,13 +10,15 @@
|
|||
%p.light.append-bottom-0
|
||||
Paste a machine public key here. Read more about how to generate it
|
||||
= link_to "here", help_page_path("ssh/README")
|
||||
.form-group
|
||||
.checkbox
|
||||
= f.label :can_push do
|
||||
= f.check_box :can_push
|
||||
%strong Write access allowed
|
||||
.form-group
|
||||
%p.light.append-bottom-0
|
||||
Allow this key to push to repository as well? (Default only allows pull access.)
|
||||
|
||||
= f.fields_for :deploy_keys_projects do |deploy_keys_project_form|
|
||||
.form-group
|
||||
.checkbox
|
||||
= deploy_keys_project_form.label :can_push do
|
||||
= deploy_keys_project_form.check_box :can_push
|
||||
%strong Write access allowed
|
||||
.form-group
|
||||
%p.light.append-bottom-0
|
||||
Allow this key to push to repository as well? (Default only allows pull access.)
|
||||
|
||||
= f.submit "Add key", class: "btn-create btn"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
- form = local_assigns.fetch(:form)
|
||||
- deploy_key = local_assigns.fetch(:deploy_key)
|
||||
- deploy_keys_project = deploy_key.deploy_keys_project_for(@project)
|
||||
|
||||
= form_errors(deploy_key)
|
||||
|
||||
|
@ -20,11 +21,13 @@
|
|||
.col-sm-10
|
||||
= form.text_field :fingerprint, class: 'form-control', readonly: 'readonly'
|
||||
|
||||
.form-group
|
||||
.control-label
|
||||
.col-sm-10
|
||||
= form.label :can_push do
|
||||
= form.check_box :can_push
|
||||
%strong Write access allowed
|
||||
%p.light.append-bottom-0
|
||||
Allow this key to push to repository as well? (Default only allows pull access.)
|
||||
- if deploy_keys_project.present?
|
||||
= form.fields_for :deploy_keys_projects, deploy_keys_project do |deploy_keys_project_form|
|
||||
.form-group
|
||||
.control-label
|
||||
.col-sm-10
|
||||
= deploy_keys_project_form.label :can_push do
|
||||
= deploy_keys_project_form.check_box :can_push
|
||||
%strong Write access allowed
|
||||
%p.light.append-bottom-0
|
||||
Allow this key to push to repository as well? (Default only allows pull access.)
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
class AddCanPushToDeployKeysProjects < ActiveRecord::Migration
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
# Set this constant to true if this migration requires downtime.
|
||||
DOWNTIME = false
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_column_with_default :deploy_keys_projects, :can_push, :boolean, default: false, allow_null: false
|
||||
end
|
||||
|
||||
def down
|
||||
remove_column :deploy_keys_projects, :can_push
|
||||
end
|
||||
end
|
|
@ -0,0 +1,44 @@
|
|||
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
|
||||
# for more information on how to write migrations for GitLab.
|
||||
|
||||
class PopulateCanPushFromDeployKeysProjects < ActiveRecord::Migration
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
# Set this constant to true if this migration requires downtime.
|
||||
DOWNTIME = false
|
||||
disable_ddl_transaction!
|
||||
|
||||
class DeploysKeyProject < ActiveRecord::Base
|
||||
include EachBatch
|
||||
|
||||
self.table_name = 'deploy_keys_projects'
|
||||
end
|
||||
|
||||
def up
|
||||
DeploysKeyProject.each_batch(of: 10_000) do |batch|
|
||||
start_id, end_id = batch.pluck('MIN(id), MAX(id)').first
|
||||
|
||||
execute <<-EOF
|
||||
UPDATE deploy_keys_projects
|
||||
SET can_push = keys.can_push
|
||||
FROM keys
|
||||
WHERE deploy_key_id = keys.id
|
||||
AND deploy_keys_projects.id BETWEEN #{start_id} AND #{end_id}
|
||||
EOF
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
DeploysKeyProject.each_batch(of: 10_000) do |batch|
|
||||
start_id, end_id = batch.pluck('MIN(id), MAX(id)').first
|
||||
|
||||
execute <<-EOF
|
||||
UPDATE keys
|
||||
SET can_push = deploy_keys_projects.can_push
|
||||
FROM deploy_keys_projects
|
||||
WHERE deploy_keys_projects.deploy_key_id = keys.id
|
||||
AND deploy_keys_projects.id BETWEEN #{start_id} AND #{end_id}
|
||||
EOF
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,43 @@
|
|||
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
|
||||
# for more information on how to write migrations for GitLab.
|
||||
|
||||
class PostPopulateCanPushFromDeployKeysProjects < ActiveRecord::Migration
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
disable_ddl_transaction!
|
||||
|
||||
class DeploysKeyProject < ActiveRecord::Base
|
||||
include EachBatch
|
||||
|
||||
self.table_name = 'deploy_keys_projects'
|
||||
end
|
||||
|
||||
def up
|
||||
DeploysKeyProject.each_batch(of: 10_000) do |batch|
|
||||
start_id, end_id = batch.pluck('MIN(id), MAX(id)').first
|
||||
|
||||
execute <<-EOF
|
||||
UPDATE deploy_keys_projects
|
||||
SET can_push = keys.can_push
|
||||
FROM keys
|
||||
WHERE deploy_key_id = keys.id
|
||||
AND deploy_keys_projects.id BETWEEN #{start_id} AND #{end_id}
|
||||
EOF
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
DeploysKeyProject.each_batch(of: 10_000) do |batch|
|
||||
start_id, end_id = batch.pluck('MIN(id), MAX(id)').first
|
||||
|
||||
execute <<-EOF
|
||||
UPDATE keys
|
||||
SET can_push = deploy_keys_projects.can_push
|
||||
FROM deploy_keys_projects
|
||||
WHERE deploy_keys_projects.deploy_key_id = keys.id
|
||||
AND deploy_keys_projects.id BETWEEN #{start_id} AND #{end_id}
|
||||
EOF
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,17 @@
|
|||
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
|
||||
# for more information on how to write migrations for GitLab.
|
||||
|
||||
class RemoveCanPushFromKeys < ActiveRecord::Migration
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
remove_column :keys, :can_push
|
||||
end
|
||||
|
||||
def down
|
||||
add_column_with_default :keys, :can_push, :boolean, default: false, allow_null: false
|
||||
end
|
||||
end
|
|
@ -626,6 +626,7 @@ ActiveRecord::Schema.define(version: 20180105212544) do
|
|||
t.integer "project_id", null: false
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.boolean "can_push", default: false, null: false
|
||||
end
|
||||
|
||||
add_index "deploy_keys_projects", ["project_id"], name: "index_deploy_keys_projects_on_project_id", using: :btree
|
||||
|
@ -896,7 +897,6 @@ ActiveRecord::Schema.define(version: 20180105212544) do
|
|||
t.string "type"
|
||||
t.string "fingerprint"
|
||||
t.boolean "public", default: false, null: false
|
||||
t.boolean "can_push", default: false, null: false
|
||||
t.datetime "last_used_at"
|
||||
end
|
||||
|
||||
|
|
|
@ -19,15 +19,13 @@ Example response:
|
|||
{
|
||||
"id": 1,
|
||||
"title": "Public key",
|
||||
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
|
||||
"can_push": false,
|
||||
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
|
||||
"created_at": "2013-10-02T10:12:29Z"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"title": "Another Public key",
|
||||
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
|
||||
"can_push": true,
|
||||
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
|
||||
"created_at": "2013-10-02T11:12:29Z"
|
||||
}
|
||||
]
|
||||
|
@ -57,15 +55,15 @@ Example response:
|
|||
"id": 1,
|
||||
"title": "Public key",
|
||||
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
|
||||
"can_push": false,
|
||||
"created_at": "2013-10-02T10:12:29Z"
|
||||
"created_at": "2013-10-02T10:12:29Z",
|
||||
"can_push": false
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"title": "Another Public key",
|
||||
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
|
||||
"can_push": false,
|
||||
"created_at": "2013-10-02T11:12:29Z"
|
||||
"created_at": "2013-10-02T11:12:29Z",
|
||||
"can_push": false
|
||||
}
|
||||
]
|
||||
```
|
||||
|
@ -96,8 +94,8 @@ Example response:
|
|||
"id": 1,
|
||||
"title": "Public key",
|
||||
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
|
||||
"can_push": false,
|
||||
"created_at": "2013-10-02T10:12:29Z"
|
||||
"created_at": "2013-10-02T10:12:29Z",
|
||||
"can_push": false
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -135,6 +133,36 @@ Example response:
|
|||
}
|
||||
```
|
||||
|
||||
## Update deploy key
|
||||
|
||||
Updates a deploy key for a project.
|
||||
|
||||
```
|
||||
PUT /projects/:id/deploy_keys/:key_id
|
||||
```
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
|
||||
| `title` | string | no | New deploy key's title |
|
||||
| `can_push` | boolean | no | Can deploy key push to the project's repository |
|
||||
|
||||
```bash
|
||||
curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "Content-Type: application/json" --data '{"title": "New deploy key", "can_push": true}' "https://gitlab.example.com/api/v4/projects/5/deploy_keys/11"
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 11,
|
||||
"title": "New deploy key",
|
||||
"key": "ssh-rsa AAAA...",
|
||||
"created_at": "2015-08-29T12:44:31.550Z",
|
||||
"can_push": true
|
||||
}
|
||||
```
|
||||
|
||||
## Delete deploy key
|
||||
|
||||
Removes a deploy key from the project. If the deploy key is used only for this project, it will be deleted from the system.
|
||||
|
|
|
@ -258,7 +258,7 @@ The `cache:key` variable can use any of the [predefined variables](../variables/
|
|||
The default key is **default** across the project, therefore everything is
|
||||
shared between each pipelines and jobs by default, starting from GitLab 9.0.
|
||||
|
||||
>**Note:** The `cache:key` variable cannot contain the `/` character.
|
||||
>**Note:** The `cache:key` variable cannot contain the `/` character, or the equivalent URI encoded `%2F`; a value made only of dots (`.`, `%2E`) is also forbidden.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
@ -4,6 +4,16 @@ module API
|
|||
|
||||
before { authenticate! }
|
||||
|
||||
helpers do
|
||||
def add_deploy_keys_project(project, attrs = {})
|
||||
project.deploy_keys_projects.create(attrs)
|
||||
end
|
||||
|
||||
def find_by_deploy_key(project, key_id)
|
||||
project.deploy_keys_projects.find_by!(deploy_key: key_id)
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Return all deploy keys'
|
||||
params do
|
||||
use :pagination
|
||||
|
@ -21,28 +31,31 @@ module API
|
|||
before { authorize_admin_project }
|
||||
|
||||
desc "Get a specific project's deploy keys" do
|
||||
success Entities::SSHKey
|
||||
success Entities::DeployKeysProject
|
||||
end
|
||||
params do
|
||||
use :pagination
|
||||
end
|
||||
get ":id/deploy_keys" do
|
||||
present paginate(user_project.deploy_keys), with: Entities::SSHKey
|
||||
keys = user_project.deploy_keys_projects.preload(:deploy_key)
|
||||
|
||||
present paginate(keys), with: Entities::DeployKeysProject
|
||||
end
|
||||
|
||||
desc 'Get single deploy key' do
|
||||
success Entities::SSHKey
|
||||
success Entities::DeployKeysProject
|
||||
end
|
||||
params do
|
||||
requires :key_id, type: Integer, desc: 'The ID of the deploy key'
|
||||
end
|
||||
get ":id/deploy_keys/:key_id" do
|
||||
key = user_project.deploy_keys.find params[:key_id]
|
||||
present key, with: Entities::SSHKey
|
||||
key = find_by_deploy_key(user_project, params[:key_id])
|
||||
|
||||
present key, with: Entities::DeployKeysProject
|
||||
end
|
||||
|
||||
desc 'Add new deploy key to currently authenticated user' do
|
||||
success Entities::SSHKey
|
||||
success Entities::DeployKeysProject
|
||||
end
|
||||
params do
|
||||
requires :key, type: String, desc: 'The new deploy key'
|
||||
|
@ -53,24 +66,31 @@ module API
|
|||
params[:key].strip!
|
||||
|
||||
# Check for an existing key joined to this project
|
||||
key = user_project.deploy_keys.find_by(key: params[:key])
|
||||
key = user_project.deploy_keys_projects
|
||||
.joins(:deploy_key)
|
||||
.find_by(keys: { key: params[:key] })
|
||||
|
||||
if key
|
||||
present key, with: Entities::SSHKey
|
||||
present key, with: Entities::DeployKeysProject
|
||||
break
|
||||
end
|
||||
|
||||
# Check for available deploy keys in other projects
|
||||
key = current_user.accessible_deploy_keys.find_by(key: params[:key])
|
||||
if key
|
||||
user_project.deploy_keys << key
|
||||
present key, with: Entities::SSHKey
|
||||
added_key = add_deploy_keys_project(user_project, deploy_key: key, can_push: !!params[:can_push])
|
||||
|
||||
present added_key, with: Entities::DeployKeysProject
|
||||
break
|
||||
end
|
||||
|
||||
# Create a new deploy key
|
||||
key = DeployKey.new(declared_params(include_missing: false))
|
||||
if key.valid? && user_project.deploy_keys << key
|
||||
present key, with: Entities::SSHKey
|
||||
key_attributes = { can_push: !!params[:can_push],
|
||||
deploy_key_attributes: declared_params.except(:can_push) }
|
||||
key = add_deploy_keys_project(user_project, key_attributes)
|
||||
|
||||
if key.valid?
|
||||
present key, with: Entities::DeployKeysProject
|
||||
else
|
||||
render_validation_error!(key)
|
||||
end
|
||||
|
@ -86,14 +106,21 @@ module API
|
|||
at_least_one_of :title, :can_push
|
||||
end
|
||||
put ":id/deploy_keys/:key_id" do
|
||||
key = DeployKey.find(params.delete(:key_id))
|
||||
deploy_keys_project = find_by_deploy_key(user_project, params[:key_id])
|
||||
|
||||
authorize!(:update_deploy_key, key)
|
||||
authorize!(:update_deploy_key, deploy_keys_project.deploy_key)
|
||||
|
||||
if key.update_attributes(declared_params(include_missing: false))
|
||||
present key, with: Entities::SSHKey
|
||||
can_push = params[:can_push].nil? ? deploy_keys_project.can_push : params[:can_push]
|
||||
title = params[:title] || deploy_keys_project.deploy_key.title
|
||||
|
||||
result = deploy_keys_project.update_attributes(can_push: can_push,
|
||||
deploy_key_attributes: { id: params[:key_id],
|
||||
title: title })
|
||||
|
||||
if result
|
||||
present deploy_keys_project, with: Entities::DeployKeysProject
|
||||
else
|
||||
render_validation_error!(key)
|
||||
render_validation_error!(deploy_keys_project)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -122,7 +149,7 @@ module API
|
|||
requires :key_id, type: Integer, desc: 'The ID of the deploy key'
|
||||
end
|
||||
delete ":id/deploy_keys/:key_id" do
|
||||
key = user_project.deploy_keys_projects.find_by(deploy_key_id: params[:key_id])
|
||||
key = user_project.deploy_keys.find(params[:key_id])
|
||||
not_found!('Deploy Key') unless key
|
||||
|
||||
destroy_conditionally!(key)
|
||||
|
|
|
@ -554,13 +554,18 @@ module API
|
|||
end
|
||||
|
||||
class SSHKey < Grape::Entity
|
||||
expose :id, :title, :key, :created_at, :can_push
|
||||
expose :id, :title, :key, :created_at
|
||||
end
|
||||
|
||||
class SSHKeyWithUser < SSHKey
|
||||
expose :user, using: Entities::UserPublic
|
||||
end
|
||||
|
||||
class DeployKeysProject < Grape::Entity
|
||||
expose :deploy_key, merge: true, using: Entities::SSHKey
|
||||
expose :can_push
|
||||
end
|
||||
|
||||
class GPGKey < Grape::Entity
|
||||
expose :id, :key, :created_at
|
||||
end
|
||||
|
@ -714,10 +719,7 @@ module API
|
|||
expose :job_events
|
||||
# Expose serialized properties
|
||||
expose :properties do |service, options|
|
||||
field_names = service.fields
|
||||
.select { |field| options[:include_passwords] || field[:type] != 'password' }
|
||||
.map { |field| field[:name] }
|
||||
service.properties.slice(*field_names)
|
||||
service.properties.slice(*service.api_field_names)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -785,7 +785,7 @@ module API
|
|||
service_params = declared_params(include_missing: false).merge(active: true)
|
||||
|
||||
if service.update_attributes(service_params)
|
||||
present service, with: Entities::ProjectService, include_passwords: current_user.admin?
|
||||
present service, with: Entities::ProjectService
|
||||
else
|
||||
render_api_error!('400 Bad Request', 400)
|
||||
end
|
||||
|
|
|
@ -3,6 +3,16 @@ module API
|
|||
class DeployKeys < Grape::API
|
||||
before { authenticate! }
|
||||
|
||||
helpers do
|
||||
def add_deploy_keys_project(project, attrs = {})
|
||||
project.deploy_keys_projects.create(attrs)
|
||||
end
|
||||
|
||||
def find_by_deploy_key(project, key_id)
|
||||
project.deploy_keys_projects.find_by!(deploy_key: key_id)
|
||||
end
|
||||
end
|
||||
|
||||
get "deploy_keys" do
|
||||
authenticated_as_admin!
|
||||
|
||||
|
@ -18,25 +28,28 @@ module API
|
|||
|
||||
%w(keys deploy_keys).each do |path|
|
||||
desc "Get a specific project's deploy keys" do
|
||||
success ::API::Entities::SSHKey
|
||||
success ::API::Entities::DeployKeysProject
|
||||
end
|
||||
get ":id/#{path}" do
|
||||
present user_project.deploy_keys, with: ::API::Entities::SSHKey
|
||||
keys = user_project.deploy_keys_projects.preload(:deploy_key)
|
||||
|
||||
present keys, with: ::API::Entities::DeployKeysProject
|
||||
end
|
||||
|
||||
desc 'Get single deploy key' do
|
||||
success ::API::Entities::SSHKey
|
||||
success ::API::Entities::DeployKeysProject
|
||||
end
|
||||
params do
|
||||
requires :key_id, type: Integer, desc: 'The ID of the deploy key'
|
||||
end
|
||||
get ":id/#{path}/:key_id" do
|
||||
key = user_project.deploy_keys.find params[:key_id]
|
||||
present key, with: ::API::Entities::SSHKey
|
||||
key = find_by_deploy_key(user_project, params[:key_id])
|
||||
|
||||
present key, with: ::API::Entities::DeployKeysProject
|
||||
end
|
||||
|
||||
desc 'Add new deploy key to currently authenticated user' do
|
||||
success ::API::Entities::SSHKey
|
||||
success ::API::Entities::DeployKeysProject
|
||||
end
|
||||
params do
|
||||
requires :key, type: String, desc: 'The new deploy key'
|
||||
|
@ -47,24 +60,31 @@ module API
|
|||
params[:key].strip!
|
||||
|
||||
# Check for an existing key joined to this project
|
||||
key = user_project.deploy_keys.find_by(key: params[:key])
|
||||
key = user_project.deploy_keys_projects
|
||||
.joins(:deploy_key)
|
||||
.find_by(keys: { key: params[:key] })
|
||||
|
||||
if key
|
||||
present key, with: ::API::Entities::SSHKey
|
||||
present key, with: ::API::Entities::DeployKeysProject
|
||||
break
|
||||
end
|
||||
|
||||
# Check for available deploy keys in other projects
|
||||
key = current_user.accessible_deploy_keys.find_by(key: params[:key])
|
||||
if key
|
||||
user_project.deploy_keys << key
|
||||
present key, with: ::API::Entities::SSHKey
|
||||
added_key = add_deploy_keys_project(user_project, deploy_key: key, can_push: !!params[:can_push])
|
||||
|
||||
present added_key, with: ::API::Entities::DeployKeysProject
|
||||
break
|
||||
end
|
||||
|
||||
# Create a new deploy key
|
||||
key = DeployKey.new(declared_params(include_missing: false))
|
||||
if key.valid? && user_project.deploy_keys << key
|
||||
present key, with: ::API::Entities::SSHKey
|
||||
key_attributes = { can_push: !!params[:can_push],
|
||||
deploy_key_attributes: declared_params.except(:can_push) }
|
||||
key = add_deploy_keys_project(user_project, key_attributes)
|
||||
|
||||
if key.valid?
|
||||
present key, with: ::API::Entities::DeployKeysProject
|
||||
else
|
||||
render_validation_error!(key)
|
||||
end
|
||||
|
|
|
@ -257,10 +257,7 @@ module API
|
|||
expose :job_events, as: :build_events
|
||||
# Expose serialized properties
|
||||
expose :properties do |service, options|
|
||||
field_names = service.fields
|
||||
.select { |field| options[:include_passwords] || field[:type] != 'password' }
|
||||
.map { |field| field[:name] }
|
||||
service.properties.slice(*field_names)
|
||||
service.properties.slice(*service.api_field_names)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -622,7 +622,7 @@ module API
|
|||
end
|
||||
get ":id/services/:service_slug" do
|
||||
service = user_project.find_or_initialize_service(params[:service_slug].underscore)
|
||||
present service, with: Entities::ProjectService, include_passwords: current_user.admin?
|
||||
present service, with: Entities::ProjectService
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -64,10 +64,24 @@ module Gitlab
|
|||
include LegacyValidationHelpers
|
||||
|
||||
def validate_each(record, attribute, value)
|
||||
unless validate_string(value)
|
||||
if validate_string(value)
|
||||
validate_path(record, attribute, value)
|
||||
else
|
||||
record.errors.add(attribute, 'should be a string or symbol')
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_path(record, attribute, value)
|
||||
path = CGI.unescape(value.to_s)
|
||||
|
||||
if path.include?('/')
|
||||
record.errors.add(attribute, 'cannot contain the "/" character')
|
||||
elsif path == '.' || path == '..'
|
||||
record.errors.add(attribute, 'cannot be "." or ".."')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class RegexpValidator < ActiveModel::EachValidator
|
||||
|
|
|
@ -17,12 +17,16 @@ module Gitlab
|
|||
def import
|
||||
mkdir_p(@shared.export_path)
|
||||
|
||||
remove_symlinks!
|
||||
|
||||
wait_for_archived_file do
|
||||
decompress_archive
|
||||
end
|
||||
rescue => e
|
||||
@shared.error(e)
|
||||
false
|
||||
ensure
|
||||
remove_symlinks!
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -43,7 +47,7 @@ module Gitlab
|
|||
|
||||
raise Projects::ImportService::Error.new("Unable to decompress #{@archive_file} into #{@shared.export_path}") unless result
|
||||
|
||||
remove_symlinks!
|
||||
result
|
||||
end
|
||||
|
||||
def remove_symlinks!
|
||||
|
|
|
@ -37,7 +37,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def archive_file
|
||||
@archive_file ||= File.join(@shared.export_path, '..', Gitlab::ImportExport.export_filename(project: @project))
|
||||
@archive_file ||= File.join(@shared.archive_path, Gitlab::ImportExport.export_filename(project: @project))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,7 +9,11 @@ module Gitlab
|
|||
end
|
||||
|
||||
def export_path
|
||||
@export_path ||= Gitlab::ImportExport.export_path(relative_path: opts[:relative_path])
|
||||
@export_path ||= Gitlab::ImportExport.export_path(relative_path: relative_path)
|
||||
end
|
||||
|
||||
def archive_path
|
||||
@archive_path ||= Gitlab::ImportExport.export_path(relative_path: relative_archive_path)
|
||||
end
|
||||
|
||||
def error(error)
|
||||
|
@ -21,6 +25,14 @@ module Gitlab
|
|||
|
||||
private
|
||||
|
||||
def relative_path
|
||||
File.join(opts[:relative_path], SecureRandom.hex)
|
||||
end
|
||||
|
||||
def relative_archive_path
|
||||
File.join(opts[:relative_path], '..')
|
||||
end
|
||||
|
||||
def error_out(message, caller)
|
||||
Rails.logger.error("Import/Export error raised on #{caller}: #{message}")
|
||||
end
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
module Gitlab
|
||||
module OAuth
|
||||
SignupDisabledError = Class.new(StandardError)
|
||||
SigninDisabledForProviderError = Class.new(StandardError)
|
||||
end
|
||||
end
|
|
@ -5,8 +5,6 @@
|
|||
#
|
||||
module Gitlab
|
||||
module OAuth
|
||||
SignupDisabledError = Class.new(StandardError)
|
||||
|
||||
class User
|
||||
attr_accessor :auth_hash, :gl_user
|
||||
|
||||
|
@ -29,7 +27,8 @@ module Gitlab
|
|||
end
|
||||
|
||||
def save(provider = 'OAuth')
|
||||
unauthorized_to_create unless gl_user
|
||||
raise SigninDisabledForProviderError if oauth_provider_disabled?
|
||||
raise SignupDisabledError unless gl_user
|
||||
|
||||
block_after_save = needs_blocking?
|
||||
|
||||
|
@ -226,8 +225,10 @@ module Gitlab
|
|||
Gitlab::AppLogger
|
||||
end
|
||||
|
||||
def unauthorized_to_create
|
||||
raise SignupDisabledError
|
||||
def oauth_provider_disabled?
|
||||
Gitlab::CurrentSettings.current_application_settings
|
||||
.disabled_oauth_sign_in_sources
|
||||
.include?(auth_hash.provider)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -67,7 +67,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def build_trace_section_regex
|
||||
@build_trace_section_regexp ||= /section_((?:start)|(?:end)):(\d+):([^\r]+)\r\033\[0K/.freeze
|
||||
@build_trace_section_regexp ||= /section_((?:start)|(?:end)):(\d+):([a-zA-Z0-9_.-]+)\r\033\[0K/.freeze
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -27,6 +27,10 @@ module Gitlab
|
|||
.gsub(/(\A-+|-+\z)/, '')
|
||||
end
|
||||
|
||||
def remove_line_breaks(str)
|
||||
str.gsub(/\r?\n/, '')
|
||||
end
|
||||
|
||||
def to_boolean(value)
|
||||
return value if [true, false].include?(value)
|
||||
return true if value =~ /^(true|t|yes|y|1|on)$/i
|
||||
|
|
|
@ -64,6 +64,7 @@
|
|||
"raven-js": "^3.14.0",
|
||||
"raw-loader": "^0.5.1",
|
||||
"react-dev-utils": "^0.5.2",
|
||||
"sanitize-html": "^1.16.1",
|
||||
"select2": "3.5.2-browserify",
|
||||
"sql.js": "^0.4.0",
|
||||
"svg4everybody": "2.1.9",
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Import::GitlabProjectsController do
|
||||
set(:namespace) { create(:namespace) }
|
||||
set(:user) { namespace.owner }
|
||||
let(:file) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
describe 'POST create' do
|
||||
context 'with an invalid path' do
|
||||
it 'redirects with an error' do
|
||||
post :create, namespace_id: namespace.id, path: '/test', file: file
|
||||
|
||||
expect(flash[:alert]).to start_with('Project could not be imported')
|
||||
expect(response).to have_gitlab_http_status(302)
|
||||
end
|
||||
|
||||
it 'redirects with an error when a relative path is used' do
|
||||
post :create, namespace_id: namespace.id, path: '../test', file: file
|
||||
|
||||
expect(flash[:alert]).to start_with('Project could not be imported')
|
||||
expect(response).to have_gitlab_http_status(302)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a valid path' do
|
||||
it 'redirects to the new project path' do
|
||||
post :create, namespace_id: namespace.id, path: 'test', file: file
|
||||
|
||||
expect(flash[:notice]).to include('is being imported')
|
||||
expect(response).to have_gitlab_http_status(302)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,75 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe OmniauthCallbacksController do
|
||||
include LoginHelpers
|
||||
|
||||
let(:user) { create(:omniauth_user, extern_uid: 'my-uid', provider: provider) }
|
||||
let(:provider) { :github }
|
||||
|
||||
before do
|
||||
mock_auth_hash(provider.to_s, 'my-uid', user.email)
|
||||
stub_omniauth_provider(provider, context: request)
|
||||
end
|
||||
|
||||
it 'allows sign in' do
|
||||
post provider
|
||||
|
||||
expect(request.env['warden']).to be_authenticated
|
||||
end
|
||||
|
||||
shared_context 'sign_up' do
|
||||
let(:user) { double(email: 'new@example.com') }
|
||||
|
||||
before do
|
||||
stub_omniauth_setting(block_auto_created_users: false)
|
||||
end
|
||||
end
|
||||
|
||||
context 'sign up' do
|
||||
include_context 'sign_up'
|
||||
|
||||
it 'is allowed' do
|
||||
post provider
|
||||
|
||||
expect(request.env['warden']).to be_authenticated
|
||||
end
|
||||
end
|
||||
|
||||
context 'when OAuth is disabled' do
|
||||
before do
|
||||
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
|
||||
settings = Gitlab::CurrentSettings.current_application_settings
|
||||
settings.update(disabled_oauth_sign_in_sources: [provider.to_s])
|
||||
end
|
||||
|
||||
it 'prevents login via POST' do
|
||||
post provider
|
||||
|
||||
expect(request.env['warden']).not_to be_authenticated
|
||||
end
|
||||
|
||||
it 'shows warning when attempting login' do
|
||||
post provider
|
||||
|
||||
expect(response).to redirect_to new_user_session_path
|
||||
expect(flash[:alert]).to eq('Signing in using GitHub has been disabled')
|
||||
end
|
||||
|
||||
it 'allows linking the disabled provider' do
|
||||
user.identities.destroy_all
|
||||
sign_in(user)
|
||||
|
||||
expect { post provider }.to change { user.reload.identities.count }.by(1)
|
||||
end
|
||||
|
||||
context 'sign up' do
|
||||
include_context 'sign_up'
|
||||
|
||||
it 'is prevented' do
|
||||
post provider
|
||||
|
||||
expect(request.env['warden']).not_to be_authenticated
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,5 +2,9 @@ FactoryBot.define do
|
|||
factory :deploy_keys_project do
|
||||
deploy_key
|
||||
project
|
||||
|
||||
trait :write_access do
|
||||
can_push true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,10 +15,6 @@ FactoryBot.define do
|
|||
factory :another_deploy_key, class: 'DeployKey'
|
||||
end
|
||||
|
||||
factory :write_access_key, class: 'DeployKey' do
|
||||
can_push true
|
||||
end
|
||||
|
||||
factory :rsa_key_2048 do
|
||||
key do
|
||||
<<~KEY.delete("\n")
|
||||
|
|
|
@ -17,6 +17,16 @@ RSpec.describe 'admin deploy keys' do
|
|||
end
|
||||
end
|
||||
|
||||
it 'shows all the projects the deploy key has write access' do
|
||||
write_key = create(:deploy_keys_project, :write_access, deploy_key: deploy_key)
|
||||
|
||||
visit admin_deploy_keys_path
|
||||
|
||||
page.within(find('.deploy-keys-list', match: :first)) do
|
||||
expect(page).to have_content(write_key.project.full_name)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'create a new deploy key' do
|
||||
let(:new_ssh_key) { attributes_for(:key)[:key] }
|
||||
|
||||
|
@ -28,14 +38,12 @@ RSpec.describe 'admin deploy keys' do
|
|||
it 'creates a new deploy key' do
|
||||
fill_in 'deploy_key_title', with: 'laptop'
|
||||
fill_in 'deploy_key_key', with: new_ssh_key
|
||||
check 'deploy_key_can_push'
|
||||
click_button 'Create'
|
||||
|
||||
expect(current_path).to eq admin_deploy_keys_path
|
||||
|
||||
page.within(find('.deploy-keys-list', match: :first)) do
|
||||
expect(page).to have_content('laptop')
|
||||
expect(page).to have_content('Yes')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -48,14 +56,12 @@ RSpec.describe 'admin deploy keys' do
|
|||
|
||||
it 'updates an existing deploy key' do
|
||||
fill_in 'deploy_key_title', with: 'new-title'
|
||||
check 'deploy_key_can_push'
|
||||
click_button 'Save changes'
|
||||
|
||||
expect(current_path).to eq admin_deploy_keys_path
|
||||
|
||||
page.within(find('.deploy-keys-list', match: :first)) do
|
||||
expect(page).to have_content('new-title')
|
||||
expect(page).to have_content('Yes')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -113,6 +113,7 @@ feature 'Cycle Analytics', :js do
|
|||
|
||||
context "as a guest" do
|
||||
before do
|
||||
project.add_developer(user)
|
||||
project.add_guest(guest)
|
||||
|
||||
allow_any_instance_of(Gitlab::ReferenceExtractor).to receive(:issues).and_return([issue])
|
||||
|
|
|
@ -8,6 +8,7 @@ feature 'Issue Sidebar' do
|
|||
let(:issue) { create(:issue, project: project) }
|
||||
let!(:user) { create(:user)}
|
||||
let!(:label) { create(:label, project: project, title: 'bug') }
|
||||
let!(:xss_label) { create(:label, project: project, title: '<script>alert("xss");</script>') }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
|
@ -99,6 +100,14 @@ feature 'Issue Sidebar' do
|
|||
restore_window_size
|
||||
open_issue_sidebar
|
||||
end
|
||||
|
||||
it 'escapes XSS when viewing issue labels' do
|
||||
page.within('.block.labels') do
|
||||
find('.edit-link').click
|
||||
|
||||
expect(page).to have_content '<script>alert("xss");</script>'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'editing issue labels', :js do
|
||||
|
|
|
@ -10,8 +10,7 @@ feature 'OAuth Login', :js, :allow_forgery_protection do
|
|||
|
||||
def stub_omniauth_config(provider)
|
||||
OmniAuth.config.add_mock(provider, OmniAuth::AuthHash.new(provider: provider.to_s, uid: "12345"))
|
||||
set_devise_mapping(context: Rails.application)
|
||||
Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[provider]
|
||||
stub_omniauth_provider(provider)
|
||||
end
|
||||
|
||||
providers = [:github, :twitter, :bitbucket, :gitlab, :google_oauth2,
|
||||
|
|
|
@ -32,7 +32,7 @@ feature 'Import/Export - project import integration test', :js do
|
|||
|
||||
expect(page).to have_content('Import an exported GitLab project')
|
||||
expect(URI.parse(current_url).query).to eq("namespace_id=#{namespace.id}&path=#{project_path}")
|
||||
expect(Gitlab::ImportExport).to receive(:import_upload_path).with(filename: /\A\h{32}_test-project-path\h*\z/).and_call_original
|
||||
expect(Gitlab::ImportExport).to receive(:import_upload_path).with(filename: /\A\h{32}\z/).and_call_original
|
||||
|
||||
attach_file('file', file)
|
||||
click_on 'Import project'
|
||||
|
|
|
@ -43,7 +43,7 @@ feature 'Repository settings' do
|
|||
|
||||
fill_in 'deploy_key_title', with: 'new_deploy_key'
|
||||
fill_in 'deploy_key_key', with: new_ssh_key
|
||||
check 'deploy_key_can_push'
|
||||
check 'deploy_key_deploy_keys_projects_attributes_0_can_push'
|
||||
click_button 'Add key'
|
||||
|
||||
expect(page).to have_content('new_deploy_key')
|
||||
|
@ -57,7 +57,7 @@ feature 'Repository settings' do
|
|||
find('li', text: private_deploy_key.title).click_link('Edit')
|
||||
|
||||
fill_in 'deploy_key_title', with: 'updated_deploy_key'
|
||||
check 'deploy_key_can_push'
|
||||
check 'deploy_key_deploy_keys_projects_attributes_0_can_push'
|
||||
click_button 'Save changes'
|
||||
|
||||
expect(page).to have_content('updated_deploy_key')
|
||||
|
@ -74,11 +74,9 @@ feature 'Repository settings' do
|
|||
find('li', text: private_deploy_key.title).click_link('Edit')
|
||||
|
||||
fill_in 'deploy_key_title', with: 'updated_deploy_key'
|
||||
check 'deploy_key_can_push'
|
||||
click_button 'Save changes'
|
||||
|
||||
expect(page).to have_content('updated_deploy_key')
|
||||
expect(page).to have_content('Write access allowed')
|
||||
end
|
||||
|
||||
scenario 'remove an existing deploy key' do
|
||||
|
|
|
@ -21,10 +21,19 @@ describe MilestonesFinder do
|
|||
expect(result).to contain_exactly(milestone_1, milestone_2)
|
||||
end
|
||||
|
||||
it 'returns milestones for groups and projects' do
|
||||
result = described_class.new(project_ids: [project_1.id, project_2.id], group_ids: group.id, state: 'all').execute
|
||||
context 'milestones for groups and project' do
|
||||
let(:result) do
|
||||
described_class.new(project_ids: [project_1.id, project_2.id], group_ids: group.id, state: 'all').execute
|
||||
end
|
||||
|
||||
expect(result).to contain_exactly(milestone_1, milestone_2, milestone_3, milestone_4)
|
||||
it 'returns milestones for groups and projects' do
|
||||
expect(result).to contain_exactly(milestone_1, milestone_2, milestone_3, milestone_4)
|
||||
end
|
||||
|
||||
it 'orders milestones by due date' do
|
||||
expect(result.first).to eq(milestone_1)
|
||||
expect(result.second).to eq(milestone_3)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with filters' do
|
||||
|
@ -61,30 +70,4 @@ describe MilestonesFinder do
|
|||
expect(result.to_a).to contain_exactly(milestone_1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with order' do
|
||||
let(:params) do
|
||||
{
|
||||
project_ids: [project_1.id, project_2.id],
|
||||
group_ids: group.id,
|
||||
state: 'all'
|
||||
}
|
||||
end
|
||||
|
||||
it "default orders by due date" do
|
||||
result = described_class.new(params).execute
|
||||
|
||||
expect(result.first).to eq(milestone_1)
|
||||
expect(result.second).to eq(milestone_3)
|
||||
end
|
||||
|
||||
it "orders by parameter" do
|
||||
result = described_class.new(params.merge(order: 'id DESC')).execute
|
||||
|
||||
expect(result.first).to eq(milestone_4)
|
||||
expect(result.second).to eq(milestone_3)
|
||||
expect(result.third).to eq(milestone_2)
|
||||
expect(result.fourth).to eq(milestone_1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -53,18 +53,24 @@ describe('Deploy keys key', () => {
|
|||
).toBe('Remove');
|
||||
});
|
||||
|
||||
it('shows write access text when key has write access', (done) => {
|
||||
vm.deployKey.can_push = true;
|
||||
it('shows write access title when key has write access', (done) => {
|
||||
vm.deployKey.deploy_keys_projects[0].can_push = true;
|
||||
|
||||
Vue.nextTick(() => {
|
||||
expect(
|
||||
vm.$el.querySelector('.write-access-allowed'),
|
||||
).not.toBeNull();
|
||||
|
||||
expect(
|
||||
vm.$el.querySelector('.write-access-allowed').textContent.trim(),
|
||||
vm.$el.querySelector('.deploy-project-label').getAttribute('data-original-title'),
|
||||
).toBe('Write access allowed');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('does not show write access title when key has write access', (done) => {
|
||||
vm.deployKey.deploy_keys_projects[0].can_push = false;
|
||||
|
||||
Vue.nextTick(() => {
|
||||
expect(
|
||||
vm.$el.querySelector('.deploy-project-label').getAttribute('data-original-title'),
|
||||
).toBe('Read access only');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -42,6 +42,18 @@ describe('Markdown component', () => {
|
|||
expect(vm.$el.querySelector('.markdown h1')).not.toBeNull();
|
||||
});
|
||||
|
||||
it('sanitizes output', (done) => {
|
||||
Object.assign(cell, {
|
||||
source: ['[XSS](data:text/html;base64,PHNjcmlwdD5hbGVydChkb2N1bWVudC5kb21haW4pPC9zY3JpcHQ+Cg==)\n'],
|
||||
});
|
||||
|
||||
Vue.nextTick(() => {
|
||||
expect(vm.$el.querySelector('a')).toBeNull();
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('katex', () => {
|
||||
beforeEach(() => {
|
||||
json = getJSONFixture('blob/notebook/math.json');
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
export default {
|
||||
'protocol-based JS injection: simple, no spaces': {
|
||||
input: '<a href="javascript:alert(\'XSS\');">foo</a>',
|
||||
output: '<a>foo</a>',
|
||||
},
|
||||
'protocol-based JS injection: simple, spaces before': {
|
||||
input: '<a href="javascript :alert(\'XSS\');">foo</a>',
|
||||
output: '<a>foo</a>',
|
||||
},
|
||||
'protocol-based JS injection: simple, spaces after': {
|
||||
input: '<a href="javascript: alert(\'XSS\');">foo</a>',
|
||||
output: '<a>foo</a>',
|
||||
},
|
||||
'protocol-based JS injection: simple, spaces before and after': {
|
||||
input: '<a href="javascript : alert(\'XSS\');">foo</a>',
|
||||
output: '<a>foo</a>',
|
||||
},
|
||||
'protocol-based JS injection: preceding colon': {
|
||||
input: '<a href=":javascript:alert(\'XSS\');">foo</a>',
|
||||
output: '<a>foo</a>',
|
||||
},
|
||||
'protocol-based JS injection: UTF-8 encoding': {
|
||||
input: '<a href="javascript:">foo</a>',
|
||||
output: '<a>foo</a>',
|
||||
},
|
||||
'protocol-based JS injection: long UTF-8 encoding': {
|
||||
input: '<a href="javascript:">foo</a>',
|
||||
output: '<a>foo</a>',
|
||||
},
|
||||
'protocol-based JS injection: long UTF-8 encoding without semicolons': {
|
||||
input: '<a href=javascript:alert('XSS')>foo</a>',
|
||||
output: '<a>foo</a>',
|
||||
},
|
||||
'protocol-based JS injection: hex encoding': {
|
||||
input: '<a href="javascript:">foo</a>',
|
||||
output: '<a>foo</a>',
|
||||
},
|
||||
'protocol-based JS injection: long hex encoding': {
|
||||
input: '<a href="javascript:">foo</a>',
|
||||
output: '<a>foo</a>',
|
||||
},
|
||||
'protocol-based JS injection: hex encoding without semicolons': {
|
||||
input: '<a href=javascript:alert('XSS')>foo</a>',
|
||||
output: '<a>foo</a>',
|
||||
},
|
||||
'protocol-based JS injection: null char': {
|
||||
input: '<a href=java\0script:alert("XSS")>foo</a>',
|
||||
output: '<a>foo</a>',
|
||||
},
|
||||
'protocol-based JS injection: invalid URL char': {
|
||||
input: '<img src=java\script:alert("XSS")>', // eslint-disable-line no-useless-escape
|
||||
output: '<img>',
|
||||
},
|
||||
'protocol-based JS injection: Unicode': {
|
||||
input: '<a href="\u0001java\u0003script:alert(\'XSS\')">foo</a>',
|
||||
output: '<a>foo</a>',
|
||||
},
|
||||
'protocol-based JS injection: spaces and entities': {
|
||||
input: '<a href="  javascript:alert(\'XSS\');">foo</a>',
|
||||
output: '<a>foo</a>',
|
||||
},
|
||||
'img on error': {
|
||||
input: '<img src="x" onerror="alert(document.domain)" />',
|
||||
output: '<img src="x">',
|
||||
},
|
||||
};
|
|
@ -0,0 +1,29 @@
|
|||
import Vue from 'vue';
|
||||
import htmlOutput from '~/notebook/cells/output/html.vue';
|
||||
import sanitizeTests from './html_sanitize_tests';
|
||||
|
||||
describe('html output cell', () => {
|
||||
function createComponent(rawCode) {
|
||||
const Component = Vue.extend(htmlOutput);
|
||||
|
||||
return new Component({
|
||||
propsData: {
|
||||
rawCode,
|
||||
},
|
||||
}).$mount();
|
||||
}
|
||||
|
||||
describe('sanitizes output', () => {
|
||||
Object.keys(sanitizeTests).forEach((key) => {
|
||||
it(key, () => {
|
||||
const test = sanitizeTests[key];
|
||||
const vm = createComponent(test.input);
|
||||
const outputEl = [...vm.$el.querySelectorAll('div')].pop();
|
||||
|
||||
expect(outputEl.innerHTML).toEqual(test.output);
|
||||
|
||||
vm.$destroy();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -136,8 +136,8 @@ describe Gitlab::Auth do
|
|||
|
||||
it 'grants deploy key write permissions' do
|
||||
project = create(:project)
|
||||
key = create(:deploy_key, can_push: true)
|
||||
create(:deploy_keys_project, deploy_key: key, project: project)
|
||||
key = create(:deploy_key)
|
||||
create(:deploy_keys_project, :write_access, deploy_key: key, project: project)
|
||||
token = Gitlab::LfsToken.new(key).token
|
||||
|
||||
expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: "lfs+deploy-key-#{key.id}")
|
||||
|
@ -146,7 +146,7 @@ describe Gitlab::Auth do
|
|||
|
||||
it 'does not grant deploy key write permissions' do
|
||||
project = create(:project)
|
||||
key = create(:deploy_key, can_push: true)
|
||||
key = create(:deploy_key)
|
||||
token = Gitlab::LfsToken.new(key).token
|
||||
|
||||
expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: "lfs+deploy-key-#{key.id}")
|
||||
|
|
|
@ -217,11 +217,58 @@ describe Gitlab::Ci::Ansi2html do
|
|||
"#{section_end[0...-5]}</div>"
|
||||
end
|
||||
|
||||
it "prints light red" do
|
||||
text = "#{section_start}\e[91mHello\e[0m\n#{section_end}"
|
||||
html = %{#{section_start_html}<span class="term-fg-l-red">Hello</span><br>#{section_end_html}}
|
||||
shared_examples 'forbidden char in section_name' do
|
||||
it 'ignores sections' do
|
||||
text = "#{section_start}Some text#{section_end}"
|
||||
html = text.gsub("\033[0K", '').gsub('<', '<')
|
||||
|
||||
expect(convert_html(text)).to eq(html)
|
||||
expect(convert_html(text)).to eq(html)
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'a legit section' do
|
||||
let(:text) { "#{section_start}Some text#{section_end}" }
|
||||
|
||||
it 'prints light red' do
|
||||
text = "#{section_start}\e[91mHello\e[0m\n#{section_end}"
|
||||
html = %{#{section_start_html}<span class="term-fg-l-red">Hello</span><br>#{section_end_html}}
|
||||
|
||||
expect(convert_html(text)).to eq(html)
|
||||
end
|
||||
|
||||
it 'begins with a section_start html marker' do
|
||||
expect(convert_html(text)).to start_with(section_start_html)
|
||||
end
|
||||
|
||||
it 'ends with a section_end html marker' do
|
||||
expect(convert_html(text)).to end_with(section_end_html)
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'a legit section'
|
||||
|
||||
context 'section name includes $' do
|
||||
let(:section_name) { 'my_$ection'}
|
||||
|
||||
it_behaves_like 'forbidden char in section_name'
|
||||
end
|
||||
|
||||
context 'section name includes <' do
|
||||
let(:section_name) { '<a_tag>'}
|
||||
|
||||
it_behaves_like 'forbidden char in section_name'
|
||||
end
|
||||
|
||||
context 'section name contains .-_' do
|
||||
let(:section_name) { 'a.Legit-SeCtIoN_namE' }
|
||||
|
||||
it_behaves_like 'a legit section'
|
||||
end
|
||||
|
||||
it 'do not allow XSS injections' do
|
||||
text = "#{section_start}section_end:1:2<script>alert('XSS Hack!');</script>#{section_end}"
|
||||
|
||||
expect(convert_html(text)).not_to include('<script>')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -4,6 +4,26 @@ describe Gitlab::Ci::Config::Entry::Key do
|
|||
let(:entry) { described_class.new(config) }
|
||||
|
||||
describe 'validations' do
|
||||
shared_examples 'key with slash' do
|
||||
it 'is invalid' do
|
||||
expect(entry).not_to be_valid
|
||||
end
|
||||
|
||||
it 'reports errors with config value' do
|
||||
expect(entry.errors).to include 'key config cannot contain the "/" character'
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'key with only dots' do
|
||||
it 'is invalid' do
|
||||
expect(entry).not_to be_valid
|
||||
end
|
||||
|
||||
it 'reports errors with config value' do
|
||||
expect(entry.errors).to include 'key config cannot be "." or ".."'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when entry config value is correct' do
|
||||
let(:config) { 'test' }
|
||||
|
||||
|
@ -30,6 +50,48 @@ describe Gitlab::Ci::Config::Entry::Key do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when entry value contains slash' do
|
||||
let(:config) { 'key/with/some/slashes' }
|
||||
|
||||
it_behaves_like 'key with slash'
|
||||
end
|
||||
|
||||
context 'when entry value contains URI encoded slash (%2F)' do
|
||||
let(:config) { 'key%2Fwith%2Fsome%2Fslashes' }
|
||||
|
||||
it_behaves_like 'key with slash'
|
||||
end
|
||||
|
||||
context 'when entry value is a dot' do
|
||||
let(:config) { '.' }
|
||||
|
||||
it_behaves_like 'key with only dots'
|
||||
end
|
||||
|
||||
context 'when entry value is two dots' do
|
||||
let(:config) { '..' }
|
||||
|
||||
it_behaves_like 'key with only dots'
|
||||
end
|
||||
|
||||
context 'when entry value is a URI encoded dot (%2E)' do
|
||||
let(:config) { '%2e' }
|
||||
|
||||
it_behaves_like 'key with only dots'
|
||||
end
|
||||
|
||||
context 'when entry value is two URI encoded dots (%2E)' do
|
||||
let(:config) { '%2E%2e' }
|
||||
|
||||
it_behaves_like 'key with only dots'
|
||||
end
|
||||
|
||||
context 'when entry value is one dot and one URI encoded dot' do
|
||||
let(:config) { '.%2e' }
|
||||
|
||||
it_behaves_like 'key with only dots'
|
||||
end
|
||||
end
|
||||
|
||||
describe '.default' do
|
||||
|
|
|
@ -51,12 +51,12 @@ describe Gitlab::GitAccess do
|
|||
context 'when the project exists' do
|
||||
context 'when actor exists' do
|
||||
context 'when actor is a DeployKey' do
|
||||
let(:deploy_key) { create(:deploy_key, user: user, can_push: true) }
|
||||
let(:deploy_key) { create(:deploy_key, user: user) }
|
||||
let(:actor) { deploy_key }
|
||||
|
||||
context 'when the DeployKey has access to the project' do
|
||||
before do
|
||||
deploy_key.projects << project
|
||||
deploy_key.deploy_keys_projects.create(project: project, can_push: true)
|
||||
end
|
||||
|
||||
it 'allows push and pull access' do
|
||||
|
@ -696,15 +696,13 @@ describe Gitlab::GitAccess do
|
|||
end
|
||||
|
||||
describe 'deploy key permissions' do
|
||||
let(:key) { create(:deploy_key, user: user, can_push: can_push) }
|
||||
let(:key) { create(:deploy_key, user: user) }
|
||||
let(:actor) { key }
|
||||
|
||||
context 'when deploy_key can push' do
|
||||
let(:can_push) { true }
|
||||
|
||||
context 'when project is authorized' do
|
||||
before do
|
||||
key.projects << project
|
||||
key.deploy_keys_projects.create(project: project, can_push: true)
|
||||
end
|
||||
|
||||
it { expect { push_access_check }.not_to raise_error }
|
||||
|
@ -732,11 +730,9 @@ describe Gitlab::GitAccess do
|
|||
end
|
||||
|
||||
context 'when deploy_key cannot push' do
|
||||
let(:can_push) { false }
|
||||
|
||||
context 'when project is authorized' do
|
||||
before do
|
||||
key.projects << project
|
||||
key.deploy_keys_projects.create(project: project, can_push: false)
|
||||
end
|
||||
|
||||
it { expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:deploy_key_upload]) }
|
||||
|
|
|
@ -12,30 +12,61 @@ describe Gitlab::ImportExport::FileImporter do
|
|||
stub_const('Gitlab::ImportExport::FileImporter::MAX_RETRIES', 0)
|
||||
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
|
||||
allow_any_instance_of(Gitlab::ImportExport::CommandLineUtil).to receive(:untar_zxf).and_return(true)
|
||||
|
||||
allow(SecureRandom).to receive(:hex).and_return('abcd')
|
||||
setup_files
|
||||
|
||||
described_class.import(archive_file: '', shared: shared)
|
||||
end
|
||||
|
||||
after do
|
||||
FileUtils.rm_rf(export_path)
|
||||
end
|
||||
|
||||
it 'removes symlinks in root folder' do
|
||||
expect(File.exist?(symlink_file)).to be false
|
||||
context 'normal run' do
|
||||
before do
|
||||
described_class.import(archive_file: '', shared: shared)
|
||||
end
|
||||
|
||||
it 'removes symlinks in root folder' do
|
||||
expect(File.exist?(symlink_file)).to be false
|
||||
end
|
||||
|
||||
it 'removes hidden symlinks in root folder' do
|
||||
expect(File.exist?(hidden_symlink_file)).to be false
|
||||
end
|
||||
|
||||
it 'removes symlinks in subfolders' do
|
||||
expect(File.exist?(subfolder_symlink_file)).to be false
|
||||
end
|
||||
|
||||
it 'does not remove a valid file' do
|
||||
expect(File.exist?(valid_file)).to be true
|
||||
end
|
||||
|
||||
it 'creates the file in the right subfolder' do
|
||||
expect(shared.export_path).to include('test/abcd')
|
||||
end
|
||||
end
|
||||
|
||||
it 'removes hidden symlinks in root folder' do
|
||||
expect(File.exist?(hidden_symlink_file)).to be false
|
||||
end
|
||||
context 'error' do
|
||||
before do
|
||||
allow_any_instance_of(described_class).to receive(:wait_for_archived_file).and_raise(StandardError)
|
||||
described_class.import(archive_file: '', shared: shared)
|
||||
end
|
||||
|
||||
it 'removes symlinks in subfolders' do
|
||||
expect(File.exist?(subfolder_symlink_file)).to be false
|
||||
end
|
||||
it 'removes symlinks in root folder' do
|
||||
expect(File.exist?(symlink_file)).to be false
|
||||
end
|
||||
|
||||
it 'does not remove a valid file' do
|
||||
expect(File.exist?(valid_file)).to be true
|
||||
it 'removes hidden symlinks in root folder' do
|
||||
expect(File.exist?(hidden_symlink_file)).to be false
|
||||
end
|
||||
|
||||
it 'removes symlinks in subfolders' do
|
||||
expect(File.exist?(subfolder_symlink_file)).to be false
|
||||
end
|
||||
|
||||
it 'does not remove a valid file' do
|
||||
expect(File.exist?(valid_file)).to be true
|
||||
end
|
||||
end
|
||||
|
||||
def setup_files
|
||||
|
|
|
@ -17,6 +17,22 @@ describe Gitlab::Utils do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.remove_line_breaks' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
where(:original, :expected) do
|
||||
"foo\nbar\nbaz" | "foobarbaz"
|
||||
"foo\r\nbar\r\nbaz" | "foobarbaz"
|
||||
"foobar" | "foobar"
|
||||
end
|
||||
|
||||
with_them do
|
||||
it "replace line breaks with an empty string" do
|
||||
expect(described_class.remove_line_breaks(original)).to eq(expected)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.to_boolean' do
|
||||
it 'accepts booleans' do
|
||||
expect(to_boolean(true)).to be(true)
|
||||
|
|
|
@ -8,7 +8,7 @@ describe DeployKeysProject do
|
|||
|
||||
describe "Validation" do
|
||||
it { is_expected.to validate_presence_of(:project_id) }
|
||||
it { is_expected.to validate_presence_of(:deploy_key_id) }
|
||||
it { is_expected.to validate_presence_of(:deploy_key) }
|
||||
end
|
||||
|
||||
describe "Destroying" do
|
||||
|
|
|
@ -29,6 +29,12 @@ describe WebHook do
|
|||
expect(hook.url).to eq('https://example.com')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'token' do
|
||||
it { is_expected.to allow_value("foobar").for(:token) }
|
||||
|
||||
it { is_expected.not_to allow_values("foo\nbar", "foo\r\nbar").for(:token) }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'execute' do
|
||||
|
|
|
@ -92,6 +92,10 @@ describe MicrosoftTeamsService do
|
|||
service.hook_data(merge_request, 'open')
|
||||
end
|
||||
|
||||
before do
|
||||
project.add_developer(user)
|
||||
end
|
||||
|
||||
it "calls Microsoft Teams API" do
|
||||
chat_service.execute(merge_sample_data)
|
||||
|
||||
|
|
|
@ -280,4 +280,38 @@ describe Service do
|
|||
expect(KubernetesService.find_by_template).to eq(kubernetes_service)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#api_field_names' do
|
||||
let(:fake_service) do
|
||||
Class.new(Service) do
|
||||
def fields
|
||||
[
|
||||
{ name: 'token' },
|
||||
{ name: 'api_token' },
|
||||
{ name: 'key' },
|
||||
{ name: 'api_key' },
|
||||
{ name: 'password' },
|
||||
{ name: 'password_field' },
|
||||
{ name: 'safe_field' }
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
let(:service) do
|
||||
fake_service.new(properties: [
|
||||
{ token: 'token-value' },
|
||||
{ api_token: 'api_token-value' },
|
||||
{ key: 'key-value' },
|
||||
{ api_key: 'api_key-value' },
|
||||
{ password: 'password-value' },
|
||||
{ password_field: 'password_field-value' },
|
||||
{ safe_field: 'safe_field-value' }
|
||||
])
|
||||
end
|
||||
|
||||
it 'filters out sensitive fields' do
|
||||
expect(service.api_field_names).to eq(['safe_field'])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -110,7 +110,7 @@ describe API::DeployKeys do
|
|||
end
|
||||
|
||||
it 'accepts can_push parameter' do
|
||||
key_attrs = attributes_for :write_access_key
|
||||
key_attrs = attributes_for(:another_key).merge(can_push: true)
|
||||
|
||||
post api("/projects/#{project.id}/deploy_keys", admin), key_attrs
|
||||
|
||||
|
@ -160,16 +160,6 @@ describe API::DeployKeys do
|
|||
expect(json_response['title']).to eq('new title')
|
||||
expect(json_response['can_push']).to eq(true)
|
||||
end
|
||||
|
||||
it 'updates a private ssh key from projects user has access with correct attributes' do
|
||||
create(:deploy_keys_project, project: project2, deploy_key: private_deploy_key)
|
||||
|
||||
put api("/projects/#{project.id}/deploy_keys/#{private_deploy_key.id}", admin), { title: 'new title', can_push: true }
|
||||
|
||||
expect(json_response['id']).to eq(private_deploy_key.id)
|
||||
expect(json_response['title']).to eq('new title')
|
||||
expect(json_response['can_push']).to eq(true)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'DELETE /projects/:id/deploy_keys/:key_id' do
|
||||
|
|
|
@ -754,16 +754,28 @@ describe API::MergeRequests do
|
|||
expect(response).to have_gitlab_http_status(400)
|
||||
end
|
||||
|
||||
context 'when target_branch is specified' do
|
||||
context 'when target_branch and target_project_id is specified' do
|
||||
let(:params) do
|
||||
{ title: 'Test merge_request',
|
||||
target_branch: 'master',
|
||||
source_branch: 'markdown',
|
||||
author: user2,
|
||||
target_project_id: unrelated_project.id }
|
||||
end
|
||||
|
||||
it 'returns 422 if targeting a different fork' do
|
||||
post api("/projects/#{forked_project.id}/merge_requests", user2),
|
||||
title: 'Test merge_request',
|
||||
target_branch: 'master',
|
||||
source_branch: 'markdown',
|
||||
author: user2,
|
||||
target_project_id: unrelated_project.id
|
||||
unrelated_project.add_developer(user2)
|
||||
|
||||
post api("/projects/#{forked_project.id}/merge_requests", user2), params
|
||||
|
||||
expect(response).to have_gitlab_http_status(422)
|
||||
end
|
||||
|
||||
it 'returns 403 if targeting a different fork which user can not access' do
|
||||
post api("/projects/#{forked_project.id}/merge_requests", user2), params
|
||||
|
||||
expect(response).to have_gitlab_http_status(403)
|
||||
end
|
||||
end
|
||||
|
||||
it "returns 201 when target_branch is specified and for the same project" do
|
||||
|
|
|
@ -83,14 +83,14 @@ describe API::Services do
|
|||
get api("/projects/#{project.id}/services/#{dashed_service}", admin)
|
||||
|
||||
expect(response).to have_gitlab_http_status(200)
|
||||
expect(json_response['properties'].keys.map(&:to_sym)).to match_array(service_attrs_list.map)
|
||||
expect(json_response['properties'].keys).to match_array(service_instance.api_field_names)
|
||||
end
|
||||
|
||||
it "returns properties of service #{service} other than passwords when authenticated as project owner" do
|
||||
get api("/projects/#{project.id}/services/#{dashed_service}", user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(200)
|
||||
expect(json_response['properties'].keys.map(&:to_sym)).to match_array(service_attrs_list_without_passwords)
|
||||
expect(json_response['properties'].keys).to match_array(service_instance.api_field_names)
|
||||
end
|
||||
|
||||
it "returns error when authenticated but not a project owner" do
|
||||
|
|
|
@ -107,7 +107,7 @@ describe API::V3::DeployKeys do
|
|||
end
|
||||
|
||||
it 'accepts can_push parameter' do
|
||||
key_attrs = attributes_for :write_access_key
|
||||
key_attrs = attributes_for(:another_key).merge(can_push: true)
|
||||
|
||||
post v3_api("/projects/#{project.id}/#{path}", admin), key_attrs
|
||||
|
||||
|
|
|
@ -371,16 +371,28 @@ describe API::MergeRequests do
|
|||
expect(response).to have_gitlab_http_status(400)
|
||||
end
|
||||
|
||||
context 'when target_branch is specified' do
|
||||
context 'when target_branch and target_project_id is specified' do
|
||||
let(:params) do
|
||||
{ title: 'Test merge_request',
|
||||
target_branch: 'master',
|
||||
source_branch: 'markdown',
|
||||
author: user2,
|
||||
target_project_id: unrelated_project.id }
|
||||
end
|
||||
|
||||
it 'returns 422 if targeting a different fork' do
|
||||
post v3_api("/projects/#{forked_project.id}/merge_requests", user2),
|
||||
title: 'Test merge_request',
|
||||
target_branch: 'master',
|
||||
source_branch: 'markdown',
|
||||
author: user2,
|
||||
target_project_id: unrelated_project.id
|
||||
unrelated_project.add_developer(user2)
|
||||
|
||||
post v3_api("/projects/#{forked_project.id}/merge_requests", user2), params
|
||||
|
||||
expect(response).to have_gitlab_http_status(422)
|
||||
end
|
||||
|
||||
it 'returns 403 if targeting a different fork which user can not access' do
|
||||
post v3_api("/projects/#{forked_project.id}/merge_requests", user2), params
|
||||
|
||||
expect(response).to have_gitlab_http_status(403)
|
||||
end
|
||||
end
|
||||
|
||||
it "returns 201 when target_branch is specified and for the same project" do
|
||||
|
|
|
@ -781,11 +781,11 @@ describe 'Git LFS API and storage' do
|
|||
end
|
||||
|
||||
context 'when deploy key has project push access' do
|
||||
let(:key) { create(:deploy_key, can_push: true) }
|
||||
let(:key) { create(:deploy_key) }
|
||||
let(:authorization) { authorize_deploy_key }
|
||||
|
||||
let(:update_user_permissions) do
|
||||
project.deploy_keys << key
|
||||
project.deploy_keys_projects.create(deploy_key: key, can_push: true)
|
||||
end
|
||||
|
||||
it_behaves_like 'pushes new LFS objects'
|
||||
|
|
|
@ -21,18 +21,21 @@ describe DeployKeyEntity do
|
|||
user_id: deploy_key.user_id,
|
||||
title: deploy_key.title,
|
||||
fingerprint: deploy_key.fingerprint,
|
||||
can_push: deploy_key.can_push,
|
||||
destroyed_when_orphaned: true,
|
||||
almost_orphaned: false,
|
||||
created_at: deploy_key.created_at,
|
||||
updated_at: deploy_key.updated_at,
|
||||
can_edit: false,
|
||||
projects: [
|
||||
deploy_keys_projects: [
|
||||
{
|
||||
id: project.id,
|
||||
name: project.name,
|
||||
full_path: project_path(project),
|
||||
full_name: project.full_name
|
||||
can_push: false,
|
||||
project:
|
||||
{
|
||||
id: project.id,
|
||||
name: project.name,
|
||||
full_path: project_path(project),
|
||||
full_name: project.full_name
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -263,5 +263,66 @@ describe MergeRequests::CreateService do
|
|||
expect(issue_ids).to match_array([first_issue.id, second_issue.id])
|
||||
end
|
||||
end
|
||||
|
||||
context 'when source and target projects are different' do
|
||||
let(:target_project) { create(:project) }
|
||||
|
||||
let(:opts) do
|
||||
{
|
||||
title: 'Awesome merge_request',
|
||||
source_branch: 'feature',
|
||||
target_branch: 'master',
|
||||
target_project_id: target_project.id
|
||||
}
|
||||
end
|
||||
|
||||
context 'when user can not access source project' do
|
||||
before do
|
||||
target_project.add_developer(assignee)
|
||||
target_project.add_master(user)
|
||||
end
|
||||
|
||||
it 'raises an error' do
|
||||
expect { described_class.new(project, user, opts).execute }
|
||||
.to raise_error Gitlab::Access::AccessDeniedError
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user can not access target project' do
|
||||
before do
|
||||
target_project.add_developer(assignee)
|
||||
target_project.add_master(user)
|
||||
end
|
||||
|
||||
it 'raises an error' do
|
||||
expect { described_class.new(project, user, opts).execute }
|
||||
.to raise_error Gitlab::Access::AccessDeniedError
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user sets source project id' do
|
||||
let(:another_project) { create(:project) }
|
||||
|
||||
let(:opts) do
|
||||
{
|
||||
title: 'Awesome merge_request',
|
||||
source_branch: 'feature',
|
||||
target_branch: 'master',
|
||||
source_project_id: another_project.id
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
project.add_developer(assignee)
|
||||
project.add_master(user)
|
||||
end
|
||||
|
||||
it 'ignores source_project_id' do
|
||||
merge_request = described_class.new(project, user, opts).execute
|
||||
|
||||
expect(merge_request.source_project_id).to eq(project.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -93,26 +93,27 @@ describe Projects::AutocompleteService do
|
|||
let(:user) { create(:user) }
|
||||
let(:group) { create(:group) }
|
||||
let(:project) { create(:project, group: group) }
|
||||
let!(:group_milestone) { create(:milestone, group: group) }
|
||||
let!(:project_milestone) { create(:milestone, project: project) }
|
||||
let!(:group_milestone1) { create(:milestone, group: group, due_date: '2017-01-01', title: 'Second Title') }
|
||||
let!(:group_milestone2) { create(:milestone, group: group, due_date: '2017-01-01', title: 'First Title') }
|
||||
let!(:project_milestone) { create(:milestone, project: project, due_date: '2016-01-01') }
|
||||
|
||||
let(:milestone_titles) { described_class.new(project, user).milestones.map(&:title) }
|
||||
|
||||
it 'includes project and group milestones' do
|
||||
expect(milestone_titles).to eq([group_milestone.title, project_milestone.title])
|
||||
it 'includes project and group milestones and sorts them correctly' do
|
||||
expect(milestone_titles).to eq([project_milestone.title, group_milestone2.title, group_milestone1.title])
|
||||
end
|
||||
|
||||
it 'does not include closed milestones' do
|
||||
group_milestone.close
|
||||
group_milestone1.close
|
||||
|
||||
expect(milestone_titles).to eq([project_milestone.title])
|
||||
expect(milestone_titles).to eq([project_milestone.title, group_milestone2.title])
|
||||
end
|
||||
|
||||
it 'does not include milestones from other projects in the group' do
|
||||
other_project = create(:project, group: group)
|
||||
project_milestone.update!(project: other_project)
|
||||
|
||||
expect(milestone_titles).to eq([group_milestone.title])
|
||||
expect(milestone_titles).to eq([group_milestone2.title, group_milestone1.title])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Projects::GitlabProjectsImportService do
|
||||
set(:namespace) { build(:namespace) }
|
||||
let(:file) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') }
|
||||
subject { described_class.new(namespace.owner, { namespace_id: namespace.id, path: path, file: file }) }
|
||||
|
||||
describe '#execute' do
|
||||
context 'with an invalid path' do
|
||||
let(:path) { '/invalid-path/' }
|
||||
|
||||
it 'returns an invalid project' do
|
||||
project = subject.execute
|
||||
|
||||
expect(project).not_to be_persisted
|
||||
expect(project).not_to be_valid
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a valid path' do
|
||||
let(:path) { 'test-path' }
|
||||
|
||||
it 'creates a project' do
|
||||
project = subject.execute
|
||||
|
||||
expect(project).to be_persisted
|
||||
expect(project).to be_valid
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,13 +2,16 @@ module DeviseHelpers
|
|||
# explicitly tells Devise which mapping to use
|
||||
# this is needed when we are testing a Devise controller bypassing the router
|
||||
def set_devise_mapping(context:)
|
||||
env =
|
||||
if context.respond_to?(:env_config)
|
||||
context.env_config
|
||||
elsif context.respond_to?(:env)
|
||||
context.env
|
||||
end
|
||||
env = env_from_context(context)
|
||||
|
||||
env['devise.mapping'] = Devise.mappings[:user] if env
|
||||
end
|
||||
|
||||
def env_from_context(context)
|
||||
if context.respond_to?(:env_config)
|
||||
context.env_config
|
||||
elsif context.respond_to?(:env)
|
||||
context.env
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -125,6 +125,13 @@ module LoginHelpers
|
|||
})
|
||||
end
|
||||
|
||||
def stub_omniauth_provider(provider, context: Rails.application)
|
||||
env = env_from_context(context)
|
||||
|
||||
set_devise_mapping(context: context)
|
||||
env['omniauth.auth'] = OmniAuth.config.mock_auth[provider]
|
||||
end
|
||||
|
||||
def stub_omniauth_saml_config(messages)
|
||||
set_devise_mapping(context: Rails.application)
|
||||
Rails.application.routes.disable_clear_and_finalize = true
|
||||
|
|
|
@ -3,13 +3,9 @@ Service.available_services_names.each do |service|
|
|||
let(:dashed_service) { service.dasherize }
|
||||
let(:service_method) { "#{service}_service".to_sym }
|
||||
let(:service_klass) { "#{service}_service".classify.constantize }
|
||||
let(:service_fields) { service_klass.new.fields }
|
||||
let(:service_instance) { service_klass.new }
|
||||
let(:service_fields) { service_instance.fields }
|
||||
let(:service_attrs_list) { service_fields.inject([]) {|arr, hash| arr << hash[:name].to_sym } }
|
||||
let(:service_attrs_list_without_passwords) do
|
||||
service_fields
|
||||
.select { |field| field[:type] != 'password' }
|
||||
.map { |field| field[:name].to_sym}
|
||||
end
|
||||
let(:service_attrs) do
|
||||
service_attrs_list.inject({}) do |hash, k|
|
||||
if k =~ /^(token*|.*_token|.*_key)/
|
||||
|
|
|
@ -57,6 +57,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do
|
|||
@issue = issue_service.execute
|
||||
@issues_sample_data = issue_service.hook_data(@issue, 'open')
|
||||
|
||||
project.add_developer(user)
|
||||
opts = {
|
||||
title: 'Awesome merge_request',
|
||||
description: 'please fix',
|
||||
|
|
49
yarn.lock
49
yarn.lock
|
@ -237,7 +237,7 @@ array-union@^1.0.1:
|
|||
dependencies:
|
||||
array-uniq "^1.0.1"
|
||||
|
||||
array-uniq@^1.0.1:
|
||||
array-uniq@^1.0.1, array-uniq@^1.0.2:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6"
|
||||
|
||||
|
@ -3198,7 +3198,7 @@ html-entities@1.2.0, html-entities@^1.2.0:
|
|||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.0.tgz#41948caf85ce82fed36e4e6a0ed371a6664379e2"
|
||||
|
||||
htmlparser2@^3.8.2:
|
||||
htmlparser2@^3.8.2, htmlparser2@^3.9.0:
|
||||
version "3.9.2"
|
||||
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.9.2.tgz#1bdf87acca0f3f9e53fa4fcceb0f4b4cbb00b338"
|
||||
dependencies:
|
||||
|
@ -4054,6 +4054,10 @@ lodash.capitalize@^4.0.0:
|
|||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz#f826c9b4e2a8511d84e3aca29db05e1a4f3b72a9"
|
||||
|
||||
lodash.clonedeep@^4.5.0:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
|
||||
|
||||
lodash.cond@^4.3.0:
|
||||
version "4.5.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.cond/-/lodash.cond-4.5.2.tgz#f471a1da486be60f6ab955d17115523dd1d255d5"
|
||||
|
@ -4069,6 +4073,10 @@ lodash.defaults@^3.1.2:
|
|||
lodash.assign "^3.0.0"
|
||||
lodash.restparam "^3.0.0"
|
||||
|
||||
lodash.escaperegexp@^4.1.2:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347"
|
||||
|
||||
lodash.get@^3.7.0:
|
||||
version "3.7.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-3.7.0.tgz#3ce68ae2c91683b281cc5394128303cbf75e691f"
|
||||
|
@ -4103,6 +4111,10 @@ lodash.memoize@^4.1.2:
|
|||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
|
||||
|
||||
lodash.mergewith@^4.6.0:
|
||||
version "4.6.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.0.tgz#150cf0a16791f5903b8891eab154609274bdea55"
|
||||
|
||||
lodash.restparam@^3.0.0:
|
||||
version "3.6.1"
|
||||
resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805"
|
||||
|
@ -5155,6 +5167,14 @@ postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0
|
|||
source-map "^0.5.6"
|
||||
supports-color "^3.2.3"
|
||||
|
||||
postcss@^6.0.14:
|
||||
version "6.0.15"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.15.tgz#f460cd6269fede0d1bf6defff0b934a9845d974d"
|
||||
dependencies:
|
||||
chalk "^2.3.0"
|
||||
source-map "^0.6.1"
|
||||
supports-color "^5.1.0"
|
||||
|
||||
postcss@^6.0.8:
|
||||
version "6.0.14"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.14.tgz#5534c72114739e75d0afcf017db853099f562885"
|
||||
|
@ -5669,6 +5689,18 @@ safe-buffer@^5.0.1, safe-buffer@~5.0.1:
|
|||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7"
|
||||
|
||||
sanitize-html@^1.16.1:
|
||||
version "1.16.3"
|
||||
resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-1.16.3.tgz#96c1b44a36ff7312e1c22a14b05274370ac8bd56"
|
||||
dependencies:
|
||||
htmlparser2 "^3.9.0"
|
||||
lodash.clonedeep "^4.5.0"
|
||||
lodash.escaperegexp "^4.1.2"
|
||||
lodash.mergewith "^4.6.0"
|
||||
postcss "^6.0.14"
|
||||
srcset "^1.0.0"
|
||||
xtend "^4.0.0"
|
||||
|
||||
sax@~1.2.1:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.2.tgz#fd8631a23bc7826bef5d871bdb87378c95647828"
|
||||
|
@ -5982,6 +6014,13 @@ sql.js@^0.4.0:
|
|||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/sql.js/-/sql.js-0.4.0.tgz#23be9635520eb0ff43a741e7e830397266e88445"
|
||||
|
||||
srcset@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/srcset/-/srcset-1.0.0.tgz#a5669de12b42f3b1d5e83ed03c71046fc48f41ef"
|
||||
dependencies:
|
||||
array-uniq "^1.0.2"
|
||||
number-is-nan "^1.0.0"
|
||||
|
||||
sshpk@^1.7.0:
|
||||
version "1.13.1"
|
||||
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3"
|
||||
|
@ -6126,6 +6165,12 @@ supports-color@^4.2.1:
|
|||
dependencies:
|
||||
has-flag "^2.0.0"
|
||||
|
||||
supports-color@^5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.1.0.tgz#058a021d1b619f7ddf3980d712ea3590ce7de3d5"
|
||||
dependencies:
|
||||
has-flag "^2.0.0"
|
||||
|
||||
svg4everybody@2.1.9:
|
||||
version "2.1.9"
|
||||
resolved "https://registry.yarnpkg.com/svg4everybody/-/svg4everybody-2.1.9.tgz#5bd9f6defc133859a044646d4743fabc28db7e2d"
|
||||
|
|
Loading…
Reference in New Issue