Commit f351cc28 authored by Oswaldo Ferreira's avatar Oswaldo Ferreira

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
parents 3b13159d d1eb3ff5
<script> <script>
import actionBtn from './action_btn.vue'; import actionBtn from './action_btn.vue';
import { getTimeago } from '../../lib/utils/datetime_utility'; import { getTimeago } from '../../lib/utils/datetime_utility';
import tooltip from '../../vue_shared/directives/tooltip';
export default { export default {
components: { components: {
actionBtn, actionBtn,
}, },
directives: {
tooltip,
},
props: { props: {
deployKey: { deployKey: {
type: Object, type: Object,
...@@ -32,6 +36,9 @@ ...@@ -32,6 +36,9 @@
isEnabled(id) { isEnabled(id) {
return this.store.findEnabledKey(id) !== undefined; return this.store.findEnabledKey(id) !== undefined;
}, },
tooltipTitle(project) {
return project.can_push ? 'Write access allowed' : 'Read access only';
},
}, },
}; };
</script> </script>
...@@ -52,21 +59,23 @@ ...@@ -52,21 +59,23 @@
<div class="description"> <div class="description">
{{ deployKey.fingerprint }} {{ deployKey.fingerprint }}
</div> </div>
<div
v-if="deployKey.can_push"
class="write-access-allowed"
>
Write access allowed
</div>
</div> </div>
<div class="deploy-key-content prepend-left-default deploy-key-projects"> <div class="deploy-key-content prepend-left-default deploy-key-projects">
<a <a
v-for="(project, i) in deployKey.projects" v-for="(deployKeysProject, i) in deployKey.deploy_keys_projects"
class="label deploy-project-label"
:href="project.full_path"
:key="i" :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> </a>
</div> </div>
<div class="deploy-key-content"> <div class="deploy-key-content">
......
...@@ -231,7 +231,7 @@ export default class LabelsSelect { ...@@ -231,7 +231,7 @@ export default class LabelsSelect {
selectedClass.push('label-item'); selectedClass.push('label-item');
$a.attr('data-label-id', label.id); $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 generated html
return $li.html($a).prop('outerHTML'); return $li.html($a).prop('outerHTML');
}, },
......
<script> <script>
/* global katex */ /* global katex */
import marked from 'marked'; import marked from 'marked';
import sanitize from 'sanitize-html';
import Prompt from './prompt.vue'; import Prompt from './prompt.vue';
const renderer = new marked.Renderer(); const renderer = new marked.Renderer();
...@@ -82,7 +83,12 @@ ...@@ -82,7 +83,12 @@
}, },
computed: { computed: {
markdown() { markdown() {
return marked(this.cell.source.join('').replace(/\\/g, '\\\\')); return sanitize(marked(this.cell.source.join('').replace(/\\/g, '\\\\')), {
allowedTags: false,
allowedAttributes: {
'*': ['class'],
},
});
}, },
}, },
}; };
......
<script> <script>
import sanitize from 'sanitize-html';
import Prompt from '../prompt.vue'; import Prompt from '../prompt.vue';
export default { export default {
...@@ -11,12 +12,24 @@ ...@@ -11,12 +12,24 @@
required: true, required: true,
}, },
}, },
computed: {
sanitizedOutput() {
return sanitize(this.rawCode, {
allowedTags: sanitize.defaults.allowedTags.concat([
'img', 'svg',
]),
allowedAttributes: {
img: ['src'],
},
});
},
},
}; };
</script> </script>
<template> <template>
<div class="output"> <div class="output">
<prompt /> <prompt />
<div v-html="rawCode"></div> <div v-html="sanitizedOutput"></div>
</div> </div>
</template> </template>
...@@ -50,10 +50,10 @@ class Admin::DeployKeysController < Admin::ApplicationController ...@@ -50,10 +50,10 @@ class Admin::DeployKeysController < Admin::ApplicationController
end end
def create_params def create_params
params.require(:deploy_key).permit(:key, :title, :can_push) params.require(:deploy_key).permit(:key, :title)
end end
def update_params def update_params
params.require(:deploy_key).permit(:title, :can_push) params.require(:deploy_key).permit(:title)
end end
end end
...@@ -75,8 +75,6 @@ class Groups::MilestonesController < Groups::ApplicationController ...@@ -75,8 +75,6 @@ class Groups::MilestonesController < Groups::ApplicationController
end end
def milestones def milestones
search_params = params.merge(group_ids: group.id)
milestones = MilestonesFinder.new(search_params).execute milestones = MilestonesFinder.new(search_params).execute
legacy_milestones = GroupMilestone.build_collection(group, group_projects, params) legacy_milestones = GroupMilestone.build_collection(group, group_projects, params)
...@@ -94,4 +92,8 @@ class Groups::MilestonesController < Groups::ApplicationController ...@@ -94,4 +92,8 @@ class Groups::MilestonesController < Groups::ApplicationController
render_404 unless @milestone render_404 unless @milestone
end end
def search_params
params.permit(:state).merge(group_ids: group.id)
end
end end
...@@ -112,6 +112,8 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -112,6 +112,8 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
continue_login_process continue_login_process
end end
rescue Gitlab::OAuth::SigninDisabledForProviderError
handle_disabled_provider
rescue Gitlab::OAuth::SignupDisabledError rescue Gitlab::OAuth::SignupDisabledError
handle_signup_error handle_signup_error
end end
...@@ -168,6 +170,13 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -168,6 +170,13 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
redirect_to new_user_session_path redirect_to new_user_session_path
end 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 = {}) def log_audit_event(user, options = {})
AuditEventService.new(user, user, options) AuditEventService.new(user, user, options)
.for_authentication.security_event .for_authentication.security_event
......
...@@ -24,7 +24,7 @@ class Projects::DeployKeysController < Projects::ApplicationController ...@@ -24,7 +24,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
def create def create
@key = DeployKeys::CreateService.new(current_user, create_params).execute @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 flash[:alert] = @key.errors.full_messages.join(', ').html_safe
end end
...@@ -71,11 +71,14 @@ class Projects::DeployKeysController < Projects::ApplicationController ...@@ -71,11 +71,14 @@ class Projects::DeployKeysController < Projects::ApplicationController
end end
def create_params 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 end
def update_params 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 end
def authorize_update_deploy_key! def authorize_update_deploy_key!
......
...@@ -92,12 +92,6 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -92,12 +92,6 @@ class Projects::MilestonesController < Projects::ApplicationController
def milestones def milestones
@milestones ||= begin @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 MilestonesFinder.new(search_params).execute
end end
end end
...@@ -113,4 +107,12 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -113,4 +107,12 @@ class Projects::MilestonesController < Projects::ApplicationController
def milestone_params def milestone_params
params.require(:milestone).permit(:title, :description, :start_date, :due_date, :state_event) params.require(:milestone).permit(:title, :description, :start_date, :due_date, :state_event)
end 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 end
...@@ -46,11 +46,7 @@ class MilestonesFinder ...@@ -46,11 +46,7 @@ class MilestonesFinder
end end
def order(items) def order(items)
if params.has_key?(:order)
items.reorder(params[:order])
else
order_statement = Gitlab::Database.nulls_last_order('due_date', 'ASC') order_statement = Gitlab::Database.nulls_last_order('due_date', 'ASC')
items.reorder(order_statement) items.reorder(order_statement).order('title ASC')
end
end end
end end
class DeployKey < Key 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 has_many :projects, through: :deploy_keys_projects
scope :in_projects, ->(projects) { joins(:deploy_keys_projects).where('deploy_keys_projects.project_id in (?)', projects) } scope :in_projects, ->(projects) { joins(:deploy_keys_projects).where('deploy_keys_projects.project_id in (?)', projects) }
scope :are_public, -> { where(public: true) } scope :are_public, -> { where(public: true) }
ignore_column :can_push
accepts_nested_attributes_for :deploy_keys_projects
def private? def private?
!public? !public?
end end
...@@ -22,10 +28,18 @@ class DeployKey < Key ...@@ -22,10 +28,18 @@ class DeployKey < Key
end end
def has_access_to?(project) def has_access_to?(project)
projects.include?(project) deploy_keys_project_for(project).present?
end end
def can_push_to?(project) 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
end end
class DeployKeysProject < ActiveRecord::Base class DeployKeysProject < ActiveRecord::Base
belongs_to :project 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 :deploy_key_id, uniqueness: { scope: [:project_id], message: "already exists in project" }
validates :project_id, presence: true validates :project_id, presence: true
......
...@@ -44,10 +44,10 @@ class GlobalMilestone ...@@ -44,10 +44,10 @@ class GlobalMilestone
def self.group_milestones_states_count(group) def self.group_milestones_states_count(group)
return STATE_COUNT_HASH unless 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 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, opened: grouped_by_state['active'] || 0,
...@@ -60,10 +60,10 @@ class GlobalMilestone ...@@ -60,10 +60,10 @@ class GlobalMilestone
def self.legacy_group_milestone_states_count(projects) def self.legacy_group_milestone_states_count(projects)
return STATE_COUNT_HASH unless 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 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') opened = count_by_state(project_milestones_by_state_and_title, 'active')
closed = count_by_state(project_milestones_by_state_and_title, 'closed') closed = count_by_state(project_milestones_by_state_and_title, 'closed')
......
...@@ -4,6 +4,7 @@ class WebHook < ActiveRecord::Base ...@@ -4,6 +4,7 @@ class WebHook < ActiveRecord::Base
has_many :web_hook_logs, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :web_hook_logs, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
validates :url, presence: true, url: true validates :url, presence: true, url: true
validates :token, format: { without: /\n/ }
def execute(data, hook_name) def execute(data, hook_name)
WebHookService.new(self, data, hook_name).execute WebHookService.new(self, data, hook_name).execute
......
...@@ -118,6 +118,11 @@ class Service < ActiveRecord::Base ...@@ -118,6 +118,11 @@ class Service < ActiveRecord::Base
nil nil
end end
def api_field_names
fields.map { |field| field[:name] }
.reject { |field_name| field_name =~ /(password|token|key)/ }
end
def global_fields def global_fields
fields fields
end end
......
...@@ -7,7 +7,7 @@ module Projects ...@@ -7,7 +7,7 @@ module Projects
delegate :size, to: :available_public_keys, prefix: true delegate :size, to: :available_public_keys, prefix: true
def new_key def new_key
@key ||= DeployKey.new @key ||= DeployKey.new.tap { |dk| dk.deploy_keys_projects.build }
end end
def enabled_keys def enabled_keys
......
...@@ -3,19 +3,20 @@ class DeployKeyEntity < Grape::Entity ...@@ -3,19 +3,20 @@ class DeployKeyEntity < Grape::Entity
expose :user_id expose :user_id
expose :title expose :title
expose :fingerprint expose :fingerprint
expose :can_push
expose :destroyed_when_orphaned?, as: :destroyed_when_orphaned expose :destroyed_when_orphaned?, as: :destroyed_when_orphaned
expose :almost_orphaned?, as: :almost_orphaned expose :almost_orphaned?, as: :almost_orphaned
expose :created_at expose :created_at
expose :updated_at expose :updated_at
expose :projects, using: ProjectEntity do |deploy_key| expose :deploy_keys_projects, using: DeployKeysProjectEntity do |deploy_key|
deploy_key.projects.without_deleted.select { |project| options[:user].can?(:read_project, project) } deploy_key.deploy_keys_projects
.without_project_deleted
.select { |deploy_key_project| Ability.allowed?(options[:user], :read_project, deploy_key_project.project) }
end end
expose :can_edit expose :can_edit
private private
def can_edit def can_edit
options[:user].can?(:update_deploy_key, object) Ability.allowed?(options[:user], :update_deploy_key, object)
end end
end end
class DeployKeysProjectEntity < Grape::Entity
expose :can_push
expose :project, using: ProjectEntity
end
module MergeRequests module MergeRequests
class CreateService < MergeRequests::BaseService class CreateService < MergeRequests::BaseService
def execute def execute
# @project is used to determine whether the user can set the merge request's set_projects!
# 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]
merge_request = MergeRequest.new merge_request = MergeRequest.new
merge_request.target_project = @project 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.source_branch = params[:source_branch]
merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch) merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch)
...@@ -58,5 +54,25 @@ module MergeRequests ...@@ -58,5 +54,25 @@ module MergeRequests
pipelines.order(id: :desc).first pipelines.order(id: :desc).first
end 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
end end
...@@ -26,7 +26,7 @@ module Projects ...@@ -26,7 +26,7 @@ module Projects
end end
def tmp_filename def tmp_filename
"#{SecureRandom.hex}_#{params[:path]}" SecureRandom.hex
end end
def file def file
......
...@@ -113,7 +113,7 @@ class WebHookService ...@@ -113,7 +113,7 @@ class WebHookService
'Content-Type' => 'application/json', 'Content-Type' => 'application/json',
'X-Gitlab-Event' => hook_name.singularize.titleize 'X-Gitlab-Event' => hook_name.singularize.titleize
}.tap do |hash| }.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 end
end end
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
%tr %tr
%th.col-sm-2 Title %th.col-sm-2 Title
%th.col-sm-4 Fingerprint %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 Added at
%th.col-sm-2 %th.col-sm-2
%tbody %tbody
...@@ -23,10 +23,8 @@ ...@@ -23,10 +23,8 @@
%td %td
%code.key-fingerprint= deploy_key.fingerprint %code.key-fingerprint= deploy_key.fingerprint
%td %td
- if deploy_key.can_push? - deploy_key.projects_with_write_access.each do |project|
Yes = link_to project.full_name, admin_project_path(project), class: 'label deploy-project-label'
- else
No
%td %td
%span.cgray %span.cgray
added #{time_ago_with_tooltip(deploy_key.created_at)} added #{time_ago_with_tooltip(deploy_key.created_at)}
......
...@@ -10,10 +10,12 @@ ...@@ -10,10 +10,12 @@
%p.light.append-bottom-0 %p.light.append-bottom-0
Paste a machine public key here. Read more about how to generate it Paste a machine public key here. Read more about how to generate it
= link_to "here", help_page_path("ssh/README") = link_to "here", help_page_path("ssh/README")
= f.fields_for :deploy_keys_projects do |deploy_keys_project_form|
.form-group .form-group
.checkbox .checkbox
= f.label :can_push do = deploy_keys_project_form.label :can_push do
= f.check_box :can_push = deploy_keys_project_form.check_box :can_push
%strong Write access allowed %strong Write access allowed
.form-group .form-group
%p.light.append-bottom-0 %p.light.append-bottom-0
......
- form = local_assigns.fetch(:form) - form = local_assigns.fetch(:form)
- deploy_key = local_assigns.fetch(:deploy_key) - deploy_key = local_assigns.fetch(:deploy_key)
- deploy_keys_project = deploy_key.deploy_keys_project_for(@project)
= form_errors(deploy_key) = form_errors(deploy_key)
...@@ -20,11 +21,13 @@ ...@@ -20,11 +21,13 @@
.col-sm-10 .col-sm-10
= form.text_field :fingerprint, class: 'form-control', readonly: 'readonly' = form.text_field :fingerprint, class: 'form-control', readonly: 'readonly'
.form-group - if deploy_keys_project.present?
= form.fields_for :deploy_keys_projects, deploy_keys_project do |deploy_keys_project_form|
.form-group
.control-label .control-label
.col-sm-10 .col-sm-10
= form.label :can_push do = deploy_keys_project_form.label :can_push do
= form.check_box :can_push = deploy_keys_project_form.check_box :can_push
%strong Write access allowed %strong Write access allowed
%p.light.append-bottom-0 %p.light.append-bottom-0
Allow this key to push to repository as well? (Default only allows pull access.) Allow this key to push to repository as well? (Default only allows pull access.)
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
# 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
# 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
# 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 ...@@ -626,6 +626,7 @@ ActiveRecord::Schema.define(version: 20180105212544) do
t.integer "project_id", null: false t.integer "project_id", null: false
t.datetime "created_at" t.datetime "created_at"
t.datetime "updated_at" t.datetime "updated_at"
t.boolean "can_push", default: false, null: false
end end
add_index "deploy_keys_projects", ["project_id"], name: "index_deploy_keys_projects_on_project_id", using: :btree 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 ...@@ -896,7 +897,6 @@ ActiveRecord::Schema.define(version: 20180105212544) do
t.string "type" t.string "type"
t.string "fingerprint" t.string "fingerprint"
t.boolean "public", default: false, null: false t.boolean "public", default: false, null: false
t.boolean "can_push", default: false, null: false
t.datetime "last_used_at" t.datetime "last_used_at"
end end
......
...@@ -20,14 +20,12 @@ Example response: ...@@ -20,14 +20,12 @@ Example response:
"id": 1, "id": 1,
"title": "Public key", "title": "Public key",
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=", "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"
}, },
{ {
"id": 3, "id": 3,
"title": "Another Public key", "title": "Another Public key",
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=", "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
"can_push": true,
"created_at": "2013-10-02T11:12:29Z" "created_at": "2013-10-02T11:12:29Z"
} }
] ]
...@@ -57,15 +55,15 @@ Example response: ...@@ -57,15 +55,15 @@ Example response:
"id": 1, "id": 1,
"title": "Public key", "title": "Public key",
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=", "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, "id": 3,
"title": "Another Public key", "title": "Another Public key",
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=", "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: ...@@ -96,8 +94,8 @@ Example response:
"id": 1, "id": 1,
"title": "Public key", "title": "Public key",
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=", "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: ...@@ -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 ## 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. 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/ ...@@ -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 The default key is **default** across the project, therefore everything is
shared between each pipelines and jobs by default, starting from GitLab 9.0. 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 ...@@ -4,6 +4,16 @@ module API
before { authenticate! } 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' desc 'Return all deploy keys'
params do params do
use :pagination use :pagination
...@@ -21,28 +31,31 @@ module API ...@@ -21,28 +31,31 @@ module API
before { authorize_admin_project } before { authorize_admin_project }
desc "Get a specific project's deploy keys" do desc "Get a specific project's deploy keys" do
success Entities::SSHKey success Entities::DeployKeysProject
end end
params do params do
use :pagination use :pagination
end end
get ":id/deploy_keys" do 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 end
desc 'Get single deploy key' do desc 'Get single deploy key' do
success Entities::SSHKey success Entities::DeployKeysProject
end end
params do params do
requires :key_id, type: Integer, desc: 'The ID of the deploy key' requires :key_id, type: Integer, desc: 'The ID of the deploy key'
end end
get ":id/deploy_keys/:key_id" do get ":id/deploy_keys/:key_id" do
key = user_project.deploy_keys.find params[:key_id] key = find_by_deploy_key(user_project, params[:key_id])
present key, with: Entities::SSHKey
present key, with: Entities::DeployKeysProject
end end
desc 'Add new deploy key to currently authenticated user' do desc 'Add new deploy key to currently authenticated user' do
success Entities::SSHKey success Entities::DeployKeysProject
end end
params do params do
requires :key, type: String, desc: 'The new deploy key' requires :key, type: String, desc: 'The new deploy key'
...@@ -53,24 +66,31 @@ module API ...@@ -53,24 +66,31 @@ module API
params[:key].strip! params[:key].strip!
# Check for an existing key joined to this project # 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 if key
present key, with: Entities::SSHKey present key, with: Entities::DeployKeysProject
break break
end end
# Check for available deploy keys in other projects # Check for available deploy keys in other projects
key = current_user.accessible_deploy_keys.find_by(key: params[:key]) key = current_user.accessible_deploy_keys.find_by(key: params[:key])
if key if key
user_project.deploy_keys << key added_key = add_deploy_keys_project(user_project, deploy_key: key, can_push: !!params[:can_push])
present key, with: Entities::SSHKey
present added_key, with: Entities::DeployKeysProject
break break
end end
# Create a new deploy key # Create a new deploy key
key = DeployKey.new(declared_params(include_missing: false)) key_attributes = { can_push: !!params[:can_push],
if key.valid? && user_project.deploy_keys << key deploy_key_attributes: declared_params.except(:can_push) }
present key, with: Entities::SSHKey key = add_deploy_keys_project(user_project, key_attributes)
if key.valid?
present key, with: Entities::DeployKeysProject
else else
render_validation_error!(key) render_validation_error!(key)
end end
...@@ -86,14 +106,21 @@ module API ...@@ -86,14 +106,21 @@ module API
at_least_one_of :title, :can_push at_least_one_of :title, :can_push
end end
put ":id/deploy_keys/:key_id" do 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)) can_push = params[:can_push].nil? ? deploy_keys_project.can_push : params[:can_push]
present key, with: Entities::SSHKey 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 else
render_validation_error!(key) render_validation_error!(deploy_keys_project)
end end
end end
...@@ -122,7 +149,7 @@ module API ...@@ -122,7 +149,7 @@ module API
requires :key_id, type: Integer, desc: 'The ID of the deploy key' requires :key_id, type: Integer, desc: 'The ID of the deploy key'
end end
delete ":id/deploy_keys/:key_id" do 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 not_found!('Deploy Key') unless key
destroy_conditionally!(key) destroy_conditionally!(key)
......
...@@ -554,13 +554,18 @@ module API ...@@ -554,13 +554,18 @@ module API
end end
class SSHKey < Grape::Entity class SSHKey < Grape::Entity
expose :id, :title, :key, :created_at, :can_push expose :id, :title, :key, :created_at
end end
class SSHKeyWithUser < SSHKey class SSHKeyWithUser < SSHKey
expose :user, using: Entities::UserPublic expose :user, using: Entities::UserPublic
end end
class DeployKeysProject < Grape::Entity
expose :deploy_key, merge: true, using: Entities::SSHKey
expose :can_push
end
class GPGKey < Grape::Entity class GPGKey < Grape::Entity
expose :id, :key, :created_at expose :id, :key, :created_at
end end
...@@ -714,10 +719,7 @@ module API ...@@ -714,10 +719,7 @@ module API
expose :job_events expose :job_events
# Expose serialized properties # Expose serialized properties
expose :properties do |service, options| expose :properties do |service, options|
field_names = service.fields service.properties.slice(*service.api_field_names)
.select { |field| options[:include_passwords] || field[:type] != 'password' }
.map { |field| field[:name] }
service.properties.slice(*field_names)
end end
end end
......
...@@ -785,7 +785,7 @@ module API ...@@ -785,7 +785,7 @@ module API
service_params = declared_params(include_missing: false).merge(active: true) service_params = declared_params(include_missing: false).merge(active: true)
if service.update_attributes(service_params) if service.update_attributes(service_params)
present service, with: Entities::ProjectService, include_passwords: current_user.admin? present service, with: Entities::ProjectService
else else
render_api_error!('400 Bad Request', 400) render_api_error!('400 Bad Request', 400)
end end
......
...@@ -3,6 +3,16 @@ module API ...@@ -3,6 +3,16 @@ module API
class DeployKeys < Grape::API class DeployKeys < Grape::API
before { authenticate! } 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 get "deploy_keys" do
authenticated_as_admin! authenticated_as_admin!
...@@ -18,25 +28,28 @@ module API ...@@ -18,25 +28,28 @@ module API
%w(keys deploy_keys).each do |path| %w(keys deploy_keys).each do |path|
desc "Get a specific project's deploy keys" do desc "Get a specific project's deploy keys" do
success ::API::Entities::SSHKey success ::API::Entities::DeployKeysProject
end end
get ":id/#{path}" do 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 end
desc 'Get single deploy key' do desc 'Get single deploy key' do
success ::API::Entities::SSHKey success ::API::Entities::DeployKeysProject
end end
params do params do
requires :key_id, type: Integer, desc: 'The ID of the deploy key' requires :key_id, type: Integer, desc: 'The ID of the deploy key'
end end
get ":id/#{path}/:key_id" do get ":id/#{path}/:key_id" do
key = user_project.deploy_keys.find params[:key_id] key = find_by_deploy_key(user_project, params[:key_id])
present key, with: ::API::Entities::SSHKey
present key, with: ::API::Entities::DeployKeysProject
end end
desc 'Add new deploy key to currently authenticated user' do desc 'Add new deploy key to currently authenticated user' do
success ::API::Entities::SSHKey success ::API::Entities::DeployKeysProject
end end
params do params do
requires :key, type: String, desc: 'The new deploy key' requires :key, type: String, desc: 'The new deploy key'
...@@ -47,24 +60,31 @@ module API ...@@ -47,24 +60,31 @@ module API
params[:key].strip! params[:key].strip!
# Check for an existing key joined to this project # 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 if key
present key, with: ::API::Entities::SSHKey present key, with: ::API::Entities::DeployKeysProject
break break
end end
# Check for available deploy keys in other projects # Check for available deploy keys in other projects
key = current_user.accessible_deploy_keys.find_by(key: params[:key]) key = current_user.accessible_deploy_keys.find_by(key: params[:key])
if key if key
user_project.deploy_keys << key added_key = add_deploy_keys_project(user_project, deploy_key: key, can_push: !!params[:can_push])
present key, with: ::API::Entities::SSHKey
present added_key, with: ::API::Entities::DeployKeysProject
break break
end end
# Create a new deploy key # Create a new deploy key
key = DeployKey.new(declared_params(include_missing: false)) key_attributes = { can_push: !!params[:can_push],
if key.valid? && user_project.deploy_keys << key deploy_key_attributes: declared_params.except(:can_push) }
present key, with: ::API::Entities::SSHKey key = add_deploy_keys_project(user_project, key_attributes)
if key.valid?
present key, with: ::API::Entities::DeployKeysProject
else else
render_validation_error!(key) render_validation_error!(key)
end end
......
...@@ -257,10 +257,7 @@ module API ...@@ -257,10 +257,7 @@ module API
expose :job_events, as: :build_events expose :job_events, as: :build_events
# Expose serialized properties # Expose serialized properties
expose :properties do |service, options| expose :properties do |service, options|
field_names = service.fields service.properties.slice(*service.api_field_names)
.select { |field| options[:include_passwords] || field[:type] != 'password' }
.map { |field| field[:name] }
service.properties.slice(*field_names)
end end
end end
......
...@@ -622,7 +622,7 @@ module API ...@@ -622,7 +622,7 @@ module API
end end
get ":id/services/:service_slug" do get ":id/services/:service_slug" do
service = user_project.find_or_initialize_service(params[:service_slug].underscore) 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
end end
......
...@@ -64,10 +64,24 @@ module Gitlab ...@@ -64,10 +64,24 @@ module Gitlab
include LegacyValidationHelpers include LegacyValidationHelpers
def validate_each(record, attribute, value) 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') record.errors.add(attribute, 'should be a string or symbol')
end end
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 end
class RegexpValidator < ActiveModel::EachValidator class RegexpValidator < ActiveModel::EachValidator
......
...@@ -17,12 +17,16 @@ module Gitlab ...@@ -17,12 +17,16 @@ module Gitlab
def import def import
mkdir_p(@shared.export_path) mkdir_p(@shared.export_path)
remove_symlinks!
wait_for_archived_file do wait_for_archived_file do
decompress_archive decompress_archive
end end
rescue => e rescue => e
@shared.error(e) @shared.error(e)
false false
ensure
remove_symlinks!
end end
private private
...@@ -43,7 +47,7 @@ module Gitlab ...@@ -43,7 +47,7 @@ module Gitlab
raise Projects::ImportService::Error.new("Unable to decompress #{@archive_file} into #{@shared.export_path}") unless result raise Projects::ImportService::Error.new("Unable to decompress #{@archive_file} into #{@shared.export_path}") unless result
remove_symlinks! result
end end
def remove_symlinks! def remove_symlinks!
......
...@@ -37,7 +37,7 @@ module Gitlab ...@@ -37,7 +37,7 @@ module Gitlab
end end
def archive_file 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 end
end end
......
...@@ -9,7 +9,11 @@ module Gitlab ...@@ -9,7 +9,11 @@ module Gitlab
end end
def export_path 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 end
def error(error) def error(error)
...@@ -21,6 +25,14 @@ module Gitlab ...@@ -21,6 +25,14 @@ module Gitlab
private 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) def error_out(message, caller)
Rails.logger.error("Import/Export error raised on #{caller}: #{message}") Rails.logger.error("Import/Export error raised on #{caller}: #{message}")
end end
......
module Gitlab
module OAuth
SignupDisabledError = Class.new(StandardError)
SigninDisabledForProviderError = Class.new(StandardError)
end
end
...@@ -5,8 +5,6 @@ ...@@ -5,8 +5,6 @@
# #
module Gitlab module Gitlab
module OAuth module OAuth
SignupDisabledError = Class.new(StandardError)
class User class User
attr_accessor :auth_hash, :gl_user attr_accessor :auth_hash, :gl_user
...@@ -29,7 +27,8 @@ module Gitlab ...@@ -29,7 +27,8 @@ module Gitlab
end end
def save(provider = 'OAuth') 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? block_after_save = needs_blocking?
...@@ -226,8 +225,10 @@ module Gitlab ...@@ -226,8 +225,10 @@ module Gitlab
Gitlab::AppLogger Gitlab::AppLogger
end end
def unauthorized_to_create def oauth_provider_disabled?
raise SignupDisabledError Gitlab::CurrentSettings.current_application_settings
.disabled_oauth_sign_in_sources
.include?(auth_hash.provider)
end end
end end
end end
......
...@@ -67,7 +67,7 @@ module Gitlab ...@@ -67,7 +67,7 @@ module Gitlab
end end
def build_trace_section_regex 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 end
end end
...@@ -27,6 +27,10 @@ module Gitlab ...@@ -27,6 +27,10 @@ module Gitlab
.gsub(/(\A-+|-+\z)/, '') .gsub(/(\A-+|-+\z)/, '')
end end
def remove_line_breaks(str)
str.gsub(/\r?\n/, '')
end
def to_boolean(value) def to_boolean(value)
return value if [true, false].include?(value) return value if [true, false].include?(value)
return true if value =~ /^(true|t|yes|y|1|on)$/i return true if value =~ /^(true|t|yes|y|1|on)$/i
......
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
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 ...@@ -2,5 +2,9 @@ FactoryBot.define do
factory :deploy_keys_project do factory :deploy_keys_project do
deploy_key deploy_key
project project
trait :write_access do
can_push true
end
end end
end end
...@@ -15,10 +15,6 @@ FactoryBot.define do ...@@ -15,10 +15,6 @@ FactoryBot.define do
factory :another_deploy_key, class: 'DeployKey' factory :another_deploy_key, class: 'DeployKey'
end end
factory :write_access_key, class: 'DeployKey' do
can_push true
end
factory :rsa_key_2048 do factory :rsa_key_2048 do
key do key do
<<~KEY.delete("\n") <<~KEY.delete("\n")
......
...@@ -17,6 +17,16 @@ RSpec.describe 'admin deploy keys' do ...@@ -17,6 +17,16 @@ RSpec.describe 'admin deploy keys' do
end end
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 describe 'create a new deploy key' do
let(:new_ssh_key) { attributes_for(:key)[:key] } let(:new_ssh_key) { attributes_for(:key)[:key] }
...@@ -28,14 +38,12 @@ RSpec.describe 'admin deploy keys' do ...@@ -28,14 +38,12 @@ RSpec.describe 'admin deploy keys' do
it 'creates a new deploy key' do it 'creates a new deploy key' do
fill_in 'deploy_key_title', with: 'laptop' fill_in 'deploy_key_title', with: 'laptop'
fill_in 'deploy_key_key', with: new_ssh_key fill_in 'deploy_key_key', with: new_ssh_key
check 'deploy_key_can_push'
click_button 'Create' click_button 'Create'
expect(current_path).to eq admin_deploy_keys_path expect(current_path).to eq admin_deploy_keys_path
page.within(find('.deploy-keys-list', match: :first)) do page.within(find('.deploy-keys-list', match: :first)) do
expect(page).to have_content('laptop') expect(page).to have_content('laptop')
expect(page).to have_content('Yes')
end end
end end
end end
...@@ -48,14 +56,12 @@ RSpec.describe 'admin deploy keys' do ...@@ -48,14 +56,12 @@ RSpec.describe 'admin deploy keys' do
it 'updates an existing deploy key' do it 'updates an existing deploy key' do
fill_in 'deploy_key_title', with: 'new-title' fill_in 'deploy_key_title', with: 'new-title'
check 'deploy_key_can_push'
click_button 'Save changes' click_button 'Save changes'
expect(current_path).to eq admin_deploy_keys_path expect(current_path).to eq admin_deploy_keys_path
page.within(find('.deploy-keys-list', match: :first)) do page.within(find('.deploy-keys-list', match: :first)) do
expect(page).to have_content('new-title') expect(page).to have_content('new-title')
expect(page).to have_content('Yes')
end end
end end
end end
......
...@@ -113,6 +113,7 @@ feature 'Cycle Analytics', :js do ...@@ -113,6 +113,7 @@ feature 'Cycle Analytics', :js do
context "as a guest" do context "as a guest" do
before do before do
project.add_developer(user)
project.add_guest(guest) project.add_guest(guest)
allow_any_instance_of(Gitlab::ReferenceExtractor).to receive(:issues).and_return([issue]) allow_any_instance_of(Gitlab::ReferenceExtractor).to receive(:issues).and_return([issue])
......
...@@ -8,6 +8,7 @@ feature 'Issue Sidebar' do ...@@ -8,6 +8,7 @@ feature 'Issue Sidebar' do
let(:issue) { create(:issue, project: project) } let(:issue) { create(:issue, project: project) }
let!(:user) { create(:user)} let!(:user) { create(:user)}
let!(:label) { create(:label, project: project, title: 'bug') } let!(:label) { create(:label, project: project, title: 'bug') }
let!(:xss_label) { create(:label, project: project, title: '&lt;script&gt;alert("xss");&lt;&#x2F;script&gt;') }
before do before do
sign_in(user) sign_in(user)
...@@ -99,6 +100,14 @@ feature 'Issue Sidebar' do ...@@ -99,6 +100,14 @@ feature 'Issue Sidebar' do
restore_window_size restore_window_size
open_issue_sidebar open_issue_sidebar
end 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 end
context 'editing issue labels', :js do context 'editing issue labels', :js do
......
...@@ -10,8 +10,7 @@ feature 'OAuth Login', :js, :allow_forgery_protection do ...@@ -10,8 +10,7 @@ feature 'OAuth Login', :js, :allow_forgery_protection do
def stub_omniauth_config(provider) def stub_omniauth_config(provider)
OmniAuth.config.add_mock(provider, OmniAuth::AuthHash.new(provider: provider.to_s, uid: "12345")) OmniAuth.config.add_mock(provider, OmniAuth::AuthHash.new(provider: provider.to_s, uid: "12345"))
set_devise_mapping(context: Rails.application) stub_omniauth_provider(provider)
Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[provider]
end end
providers = [:github, :twitter, :bitbucket, :gitlab, :google_oauth2, providers = [:github, :twitter, :bitbucket, :gitlab, :google_oauth2,
......
...@@ -32,7 +32,7 @@ feature 'Import/Export - project import integration test', :js do ...@@ -32,7 +32,7 @@ feature 'Import/Export - project import integration test', :js do
expect(page).to have_content('Import an exported GitLab project') 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(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) attach_file('file', file)
click_on 'Import project' click_on 'Import project'
......
...@@ -43,7 +43,7 @@ feature 'Repository settings' do ...@@ -43,7 +43,7 @@ feature 'Repository settings' do
fill_in 'deploy_key_title', with: 'new_deploy_key' fill_in 'deploy_key_title', with: 'new_deploy_key'
fill_in 'deploy_key_key', with: new_ssh_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' click_button 'Add key'
expect(page).to have_content('new_deploy_key') expect(page).to have_content('new_deploy_key')
...@@ -57,7 +57,7 @@ feature 'Repository settings' do ...@@ -57,7 +57,7 @@ feature 'Repository settings' do
find('li', text: private_deploy_key.title).click_link('Edit') find('li', text: private_deploy_key.title).click_link('Edit')
fill_in 'deploy_key_title', with: 'updated_deploy_key' 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' click_button 'Save changes'
expect(page).to have_content('updated_deploy_key') expect(page).to have_content('updated_deploy_key')
...@@ -74,11 +74,9 @@ feature 'Repository settings' do ...@@ -74,11 +74,9 @@ feature 'Repository settings' do
find('li', text: private_deploy_key.title).click_link('Edit') find('li', text: private_deploy_key.title).click_link('Edit')
fill_in 'deploy_key_title', with: 'updated_deploy_key' fill_in 'deploy_key_title', with: 'updated_deploy_key'
check 'deploy_key_can_push'
click_button 'Save changes' click_button 'Save changes'
expect(page).to have_content('updated_deploy_key') expect(page).to have_content('updated_deploy_key')
expect(page).to have_content('Write access allowed')
end end
scenario 'remove an existing deploy key' do scenario 'remove an existing deploy key' do
......
...@@ -21,12 +21,21 @@ describe MilestonesFinder do ...@@ -21,12 +21,21 @@ describe MilestonesFinder do
expect(result).to contain_exactly(milestone_1, milestone_2) expect(result).to contain_exactly(milestone_1, milestone_2)
end end
it 'returns milestones for groups and projects' do context 'milestones for groups and project' do
result = described_class.new(project_ids: [project_1.id, project_2.id], group_ids: group.id, state: 'all').execute let(:result) do
described_class.new(project_ids: [project_1.id, project_2.id], group_ids: group.id, state: 'all').execute
end
it 'returns milestones for groups and projects' do
expect(result).to contain_exactly(milestone_1, milestone_2, milestone_3, milestone_4) expect(result).to contain_exactly(milestone_1, milestone_2, milestone_3, milestone_4)
end 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 context 'with filters' do
let(:params) do let(:params) do
{ {
...@@ -61,30 +70,4 @@ describe MilestonesFinder do ...@@ -61,30 +70,4 @@ describe MilestonesFinder do
expect(result.to_a).to contain_exactly(milestone_1) expect(result.to_a).to contain_exactly(milestone_1)
end end
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 end
...@@ -53,18 +53,24 @@ describe('Deploy keys key', () => { ...@@ -53,18 +53,24 @@ describe('Deploy keys key', () => {
).toBe('Remove'); ).toBe('Remove');
}); });
it('shows write access text when key has write access', (done) => { it('shows write access title when key has write access', (done) => {
vm.deployKey.can_push = true; vm.deployKey.deploy_keys_projects[0].can_push = true;
Vue.nextTick(() => { Vue.nextTick(() => {
expect( expect(
vm.$el.querySelector('.write-access-allowed'), vm.$el.querySelector('.deploy-project-label').getAttribute('data-original-title'),
).not.toBeNull();
expect(
vm.$el.querySelector('.write-access-allowed').textContent.trim(),
).toBe('Write access allowed'); ).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(); done();
}); });
}); });
......
...@@ -42,6 +42,18 @@ describe('Markdown component', () => { ...@@ -42,6 +42,18 @@ describe('Markdown component', () => {
expect(vm.$el.querySelector('.markdown h1')).not.toBeNull(); 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', () => { describe('katex', () => {
beforeEach(() => { beforeEach(() => {
json = getJSONFixture('blob/notebook/math.json'); json = getJSONFixture('blob/notebook/math.json');
......
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&#58;">foo</a>',
output: '<a>foo</a>',
},
'protocol-based JS injection: long UTF-8 encoding': {
input: '<a href="javascript&#0058;">foo</a>',
output: '<a>foo</a>',
},
'protocol-based JS injection: long UTF-8 encoding without semicolons': {
input: '<a href=&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041>foo</a>',
output: '<a>foo</a>',
},
'protocol-based JS injection: hex encoding': {
input: '<a href="javascript&#x3A;">foo</a>',
output: '<a>foo</a>',
},
'protocol-based JS injection: long hex encoding': {
input: '<a href="javascript&#x003A;">foo</a>',
output: '<a>foo</a>',
},
'protocol-based JS injection: hex encoding without semicolons': {
input: '<a href=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29>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=" &#14; javascript:alert(\'XSS\');">foo</a>',
output: '<a>foo</a>',
},
'img on error': {
input: '<img src="x" onerror="alert(document.domain)" />',
output: '<img src="x">',
},
};
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 ...@@ -136,8 +136,8 @@ describe Gitlab::Auth do
it 'grants deploy key write permissions' do it 'grants deploy key write permissions' do
project = create(:project) project = create(:project)
key = create(:deploy_key, can_push: true) key = create(:deploy_key)
create(:deploy_keys_project, deploy_key: key, project: project) create(:deploy_keys_project, :write_access, deploy_key: key, project: project)
token = Gitlab::LfsToken.new(key).token token = Gitlab::LfsToken.new(key).token
expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: "lfs+deploy-key-#{key.id}") 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 ...@@ -146,7 +146,7 @@ describe Gitlab::Auth do
it 'does not grant deploy key write permissions' do it 'does not grant deploy key write permissions' do
project = create(:project) project = create(:project)
key = create(:deploy_key, can_push: true) key = create(:deploy_key)
token = Gitlab::LfsToken.new(key).token token = Gitlab::LfsToken.new(key).token
expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: "lfs+deploy-key-#{key.id}") expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: "lfs+deploy-key-#{key.id}")
......
...@@ -217,12 +217,59 @@ describe Gitlab::Ci::Ansi2html do ...@@ -217,12 +217,59 @@ describe Gitlab::Ci::Ansi2html do
"#{section_end[0...-5]}</div>" "#{section_end[0...-5]}</div>"
end end
it "prints light red" do 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('<', '&lt;')
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}" 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}} html = %{#{section_start_html}<span class="term-fg-l-red">Hello</span><br>#{section_end_html}}
expect(convert_html(text)).to eq(html) expect(convert_html(text)).to eq(html)
end 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 end
describe "truncates" do describe "truncates" do
......
...@@ -4,6 +4,26 @@ describe Gitlab::Ci::Config::Entry::Key do ...@@ -4,6 +4,26 @@ describe Gitlab::Ci::Config::Entry::Key do
let(:entry) { described_class.new(config) } let(:entry) { described_class.new(config) }
describe 'validations' do 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 context 'when entry config value is correct' do
let(:config) { 'test' } let(:config) { 'test' }
...@@ -30,6 +50,48 @@ describe Gitlab::Ci::Config::Entry::Key do ...@@ -30,6 +50,48 @@ describe Gitlab::Ci::Config::Entry::Key do
end end
end 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 end
describe '.default' do describe '.default' do
......
...@@ -51,12 +51,12 @@ describe Gitlab::GitAccess do ...@@ -51,12 +51,12 @@ describe Gitlab::GitAccess do
context 'when the project exists' do context 'when the project exists' do
context 'when actor exists' do context 'when actor exists' do
context 'when actor is a DeployKey' 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 } let(:actor) { deploy_key }
context 'when the DeployKey has access to the project' do context 'when the DeployKey has access to the project' do
before do before do
deploy_key.projects << project deploy_key.deploy_keys_projects.create(project: project, can_push: true)
end end
it 'allows push and pull access' do it 'allows push and pull access' do
...@@ -696,15 +696,13 @@ describe Gitlab::GitAccess do ...@@ -696,15 +696,13 @@ describe Gitlab::GitAccess do
end end
describe 'deploy key permissions' do 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 } let(:actor) { key }
context 'when deploy_key can push' do context 'when deploy_key can push' do
let(:can_push) { true }
context 'when project is authorized' do context 'when project is authorized' do
before do before do
key.projects << project key.deploy_keys_projects.create(project: project, can_push: true)
end end
it { expect { push_access_check }.not_to raise_error } it { expect { push_access_check }.not_to raise_error }
...@@ -732,11 +730,9 @@ describe Gitlab::GitAccess do ...@@ -732,11 +730,9 @@ describe Gitlab::GitAccess do
end end
context 'when deploy_key cannot push' do context 'when deploy_key cannot push' do
let(:can_push) { false }
context 'when project is authorized' do context 'when project is authorized' do
before do before do
key.projects << project key.deploy_keys_projects.create(project: project, can_push: false)
end end
it { expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:deploy_key_upload]) } it { expect { push_access_check }.to raise_unauthorized(described_class::ERROR_MESSAGES[:deploy_key_upload]) }
......
...@@ -12,16 +12,19 @@ describe Gitlab::ImportExport::FileImporter do ...@@ -12,16 +12,19 @@ describe Gitlab::ImportExport::FileImporter do
stub_const('Gitlab::ImportExport::FileImporter::MAX_RETRIES', 0) 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).to receive(:storage_path).and_return(export_path)
allow_any_instance_of(Gitlab::ImportExport::CommandLineUtil).to receive(:untar_zxf).and_return(true) allow_any_instance_of(Gitlab::ImportExport::CommandLineUtil).to receive(:untar_zxf).and_return(true)
allow(SecureRandom).to receive(:hex).and_return('abcd')
setup_files setup_files
described_class.import(archive_file: '', shared: shared)
end end
after do after do
FileUtils.rm_rf(export_path) FileUtils.rm_rf(export_path)
end end
context 'normal run' do
before do
described_class.import(archive_file: '', shared: shared)
end
it 'removes symlinks in root folder' do it 'removes symlinks in root folder' do
expect(File.exist?(symlink_file)).to be false expect(File.exist?(symlink_file)).to be false
end end
...@@ -38,6 +41,34 @@ describe Gitlab::ImportExport::FileImporter do ...@@ -38,6 +41,34 @@ describe Gitlab::ImportExport::FileImporter do
expect(File.exist?(valid_file)).to be true expect(File.exist?(valid_file)).to be true
end end
it 'creates the file in the right subfolder' do
expect(shared.export_path).to include('test/abcd')
end
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 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
end
def setup_files def setup_files
FileUtils.mkdir_p("#{shared.export_path}/subfolder/") FileUtils.mkdir_p("#{shared.export_path}/subfolder/")
FileUtils.touch(valid_file) FileUtils.touch(valid_file)
......
...@@ -17,6 +17,22 @@ describe Gitlab::Utils do ...@@ -17,6 +17,22 @@ describe Gitlab::Utils do
end end
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 describe '.to_boolean' do
it 'accepts booleans' do it 'accepts booleans' do
expect(to_boolean(true)).to be(true) expect(to_boolean(true)).to be(true)
......
...@@ -8,7 +8,7 @@ describe DeployKeysProject do ...@@ -8,7 +8,7 @@ describe DeployKeysProject do
describe "Validation" do describe "Validation" do
it { is_expected.to validate_presence_of(:project_id) } 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 end
describe "Destroying" do describe "Destroying" do
......
...@@ -29,6 +29,12 @@ describe WebHook do ...@@ -29,6 +29,12 @@ describe WebHook do
expect(hook.url).to eq('https://example.com') expect(hook.url).to eq('https://example.com')
end end
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 end
describe 'execute' do describe 'execute' do
......
...@@ -92,6 +92,10 @@ describe MicrosoftTeamsService do ...@@ -92,6 +92,10 @@ describe MicrosoftTeamsService do
service.hook_data(merge_request, 'open') service.hook_data(merge_request, 'open')
end end
before do
project.add_developer(user)
end
it "calls Microsoft Teams API" do it "calls Microsoft Teams API" do
chat_service.execute(merge_sample_data) chat_service.execute(merge_sample_data)
......
...@@ -280,4 +280,38 @@ describe Service do ...@@ -280,4 +280,38 @@ describe Service do
expect(KubernetesService.find_by_template).to eq(kubernetes_service) expect(KubernetesService.find_by_template).to eq(kubernetes_service)
end end
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 end
...@@ -110,7 +110,7 @@ describe API::DeployKeys do ...@@ -110,7 +110,7 @@ describe API::DeployKeys do
end end
it 'accepts can_push parameter' do 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 post api("/projects/#{project.id}/deploy_keys", admin), key_attrs
...@@ -160,16 +160,6 @@ describe API::DeployKeys do ...@@ -160,16 +160,6 @@ describe API::DeployKeys do
expect(json_response['title']).to eq('new title') expect(json_response['title']).to eq('new title')
expect(json_response['can_push']).to eq(true) expect(json_response['can_push']).to eq(true)
end 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 end
describe 'DELETE /projects/:id/deploy_keys/:key_id' do describe 'DELETE /projects/:id/deploy_keys/:key_id' do
......
...@@ -754,16 +754,28 @@ describe API::MergeRequests do ...@@ -754,16 +754,28 @@ describe API::MergeRequests do
expect(response).to have_gitlab_http_status(400) expect(response).to have_gitlab_http_status(400)
end end
context 'when target_branch is specified' do context 'when target_branch and target_project_id is specified' do
it 'returns 422 if targeting a different fork' do let(:params) do
post api("/projects/#{forked_project.id}/merge_requests", user2), { title: 'Test merge_request',
title: 'Test merge_request',
target_branch: 'master', target_branch: 'master',
source_branch: 'markdown', source_branch: 'markdown',
author: user2, author: user2,
target_project_id: unrelated_project.id target_project_id: unrelated_project.id }
end
it 'returns 422 if targeting a different fork' do
unrelated_project.add_developer(user2)
post api("/projects/#{forked_project.id}/merge_requests", user2), params
expect(response).to have_gitlab_http_status(422) expect(response).to have_gitlab_http_status(422)
end 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 end
it "returns 201 when target_branch is specified and for the same project" do it "returns 201 when target_branch is specified and for the same project" do
......
...@@ -83,14 +83,14 @@ describe API::Services do ...@@ -83,14 +83,14 @@ describe API::Services do
get api("/projects/#{project.id}/services/#{dashed_service}", admin) get api("/projects/#{project.id}/services/#{dashed_service}", admin)
expect(response).to have_gitlab_http_status(200) 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 end
it "returns properties of service #{service} other than passwords when authenticated as project owner" do it "returns properties of service #{service} other than passwords when authenticated as project owner" do
get api("/projects/#{project.id}/services/#{dashed_service}", user) get api("/projects/#{project.id}/services/#{dashed_service}", user)
expect(response).to have_gitlab_http_status(200) 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 end
it "returns error when authenticated but not a project owner" do it "returns error when authenticated but not a project owner" do
......
...@@ -107,7 +107,7 @@ describe API::V3::DeployKeys do ...@@ -107,7 +107,7 @@ describe API::V3::DeployKeys do
end end
it 'accepts can_push parameter' do 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 post v3_api("/projects/#{project.id}/#{path}", admin), key_attrs
......
...@@ -371,16 +371,28 @@ describe API::MergeRequests do ...@@ -371,16 +371,28 @@ describe API::MergeRequests do
expect(response).to have_gitlab_http_status(400) expect(response).to have_gitlab_http_status(400)
end end
context 'when target_branch is specified' do context 'when target_branch and target_project_id is specified' do
it 'returns 422 if targeting a different fork' do let(:params) do
post v3_api("/projects/#{forked_project.id}/merge_requests", user2), { title: 'Test merge_request',
title: 'Test merge_request',
target_branch: 'master', target_branch: 'master',
source_branch: 'markdown', source_branch: 'markdown',
author: user2, author: user2,
target_project_id: unrelated_project.id target_project_id: unrelated_project.id }
end
it 'returns 422 if targeting a different fork' do
unrelated_project.add_developer(user2)
post v3_api("/projects/#{forked_project.id}/merge_requests", user2), params
expect(response).to have_gitlab_http_status(422) expect(response).to have_gitlab_http_status(422)
end 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 end
it "returns 201 when target_branch is specified and for the same project" do 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 ...@@ -781,11 +781,11 @@ describe 'Git LFS API and storage' do
end end
context 'when deploy key has project push access' do 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(:authorization) { authorize_deploy_key }
let(:update_user_permissions) do let(:update_user_permissions) do
project.deploy_keys << key project.deploy_keys_projects.create(deploy_key: key, can_push: true)
end end
it_behaves_like 'pushes new LFS objects' it_behaves_like 'pushes new LFS objects'
......
...@@ -21,19 +21,22 @@ describe DeployKeyEntity do ...@@ -21,19 +21,22 @@ describe DeployKeyEntity do
user_id: deploy_key.user_id, user_id: deploy_key.user_id,
title: deploy_key.title, title: deploy_key.title,
fingerprint: deploy_key.fingerprint, fingerprint: deploy_key.fingerprint,
can_push: deploy_key.can_push,
destroyed_when_orphaned: true, destroyed_when_orphaned: true,
almost_orphaned: false, almost_orphaned: false,
created_at: deploy_key.created_at, created_at: deploy_key.created_at,
updated_at: deploy_key.updated_at, updated_at: deploy_key.updated_at,
can_edit: false, can_edit: false,
projects: [ deploy_keys_projects: [
{
can_push: false,
project:
{ {
id: project.id, id: project.id,
name: project.name, name: project.name,
full_path: project_path(project), full_path: project_path(project),
full_name: project.full_name full_name: project.full_name
} }
}
] ]
} }
end end
......
...@@ -263,5 +263,66 @@ describe MergeRequests::CreateService do ...@@ -263,5 +263,66 @@ describe MergeRequests::CreateService do
expect(issue_ids).to match_array([first_issue.id, second_issue.id]) expect(issue_ids).to match_array([first_issue.id, second_issue.id])
end end
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
end end
...@@ -93,26 +93,27 @@ describe Projects::AutocompleteService do ...@@ -93,26 +93,27 @@ describe Projects::AutocompleteService do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:group) { create(:group) } let(:group) { create(:group) }
let(:project) { create(:project, group: group) } let(:project) { create(:project, group: group) }
let!(:group_milestone) { create(:milestone, group: group) } let!(:group_milestone1) { create(:milestone, group: group, due_date: '2017-01-01', title: 'Second Title') }
let!(:project_milestone) { create(:milestone, project: project) } 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) } let(:milestone_titles) { described_class.new(project, user).milestones.map(&:title) }
it 'includes project and group milestones' do it 'includes project and group milestones and sorts them correctly' do
expect(milestone_titles).to eq([group_milestone.title, project_milestone.title]) expect(milestone_titles).to eq([project_milestone.title, group_milestone2.title, group_milestone1.title])
end end
it 'does not include closed milestones' do 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 end
it 'does not include milestones from other projects in the group' do it 'does not include milestones from other projects in the group' do
other_project = create(:project, group: group) other_project = create(:project, group: group)
project_milestone.update!(project: other_project) 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 end
end end
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 ...@@ -2,13 +2,16 @@ module DeviseHelpers
# explicitly tells Devise which mapping to use # explicitly tells Devise which mapping to use
# this is needed when we are testing a Devise controller bypassing the router # this is needed when we are testing a Devise controller bypassing the router
def set_devise_mapping(context:) def set_devise_mapping(context:)
env = 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) if context.respond_to?(:env_config)
context.env_config context.env_config
elsif context.respond_to?(:env) elsif context.respond_to?(:env)
context.env context.env
end end
env['devise.mapping'] = Devise.mappings[:user] if env
end end
end end
...@@ -125,6 +125,13 @@ module LoginHelpers ...@@ -125,6 +125,13 @@ module LoginHelpers
}) })
end 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) def stub_omniauth_saml_config(messages)
set_devise_mapping(context: Rails.application) set_devise_mapping(context: Rails.application)
Rails.application.routes.disable_clear_and_finalize = true Rails.application.routes.disable_clear_and_finalize = true
......
...@@ -3,13 +3,9 @@ Service.available_services_names.each do |service| ...@@ -3,13 +3,9 @@ Service.available_services_names.each do |service|
let(:dashed_service) { service.dasherize } let(:dashed_service) { service.dasherize }
let(:service_method) { "#{service}_service".to_sym } let(:service_method) { "#{service}_service".to_sym }
let(:service_klass) { "#{service}_service".classify.constantize } 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) { 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 let(:service_attrs) do
service_attrs_list.inject({}) do |hash, k| service_attrs_list.inject({}) do |hash, k|
if k =~ /^(token*|.*_token|.*_key)/ if k =~ /^(token*|.*_token|.*_key)/
......
...@@ -57,6 +57,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do ...@@ -57,6 +57,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do
@issue = issue_service.execute @issue = issue_service.execute
@issues_sample_data = issue_service.hook_data(@issue, 'open') @issues_sample_data = issue_service.hook_data(@issue, 'open')
project.add_developer(user)
opts = { opts = {
title: 'Awesome merge_request', title: 'Awesome merge_request',
description: 'please fix', description: 'please fix',
......
...@@ -237,7 +237,7 @@ array-union@^1.0.1: ...@@ -237,7 +237,7 @@ array-union@^1.0.1:
dependencies: dependencies:
array-uniq "^1.0.1" array-uniq "^1.0.1"
array-uniq@^1.0.1: array-uniq@^1.0.1, array-uniq@^1.0.2:
version "1.0.3" version "1.0.3"
resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" 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: ...@@ -3198,7 +3198,7 @@ html-entities@1.2.0, html-entities@^1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.0.tgz#41948caf85ce82fed36e4e6a0ed371a6664379e2" 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" version "3.9.2"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.9.2.tgz#1bdf87acca0f3f9e53fa4fcceb0f4b4cbb00b338" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.9.2.tgz#1bdf87acca0f3f9e53fa4fcceb0f4b4cbb00b338"
dependencies: dependencies:
...@@ -4054,6 +4054,10 @@ lodash.capitalize@^4.0.0: ...@@ -4054,6 +4054,10 @@ lodash.capitalize@^4.0.0:
version "4.2.1" version "4.2.1"
resolved "https://registry.yarnpkg.com/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz#f826c9b4e2a8511d84e3aca29db05e1a4f3b72a9" 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: lodash.cond@^4.3.0:
version "4.5.2" version "4.5.2"
resolved "https://registry.yarnpkg.com/lodash.cond/-/lodash.cond-4.5.2.tgz#f471a1da486be60f6ab955d17115523dd1d255d5" resolved "https://registry.yarnpkg.com/lodash.cond/-/lodash.cond-4.5.2.tgz#f471a1da486be60f6ab955d17115523dd1d255d5"
...@@ -4069,6 +4073,10 @@ lodash.defaults@^3.1.2: ...@@ -4069,6 +4073,10 @@ lodash.defaults@^3.1.2:
lodash.assign "^3.0.0" lodash.assign "^3.0.0"
lodash.restparam "^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: lodash.get@^3.7.0:
version "3.7.0" version "3.7.0"
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-3.7.0.tgz#3ce68ae2c91683b281cc5394128303cbf75e691f" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-3.7.0.tgz#3ce68ae2c91683b281cc5394128303cbf75e691f"
...@@ -4103,6 +4111,10 @@ lodash.memoize@^4.1.2: ...@@ -4103,6 +4111,10 @@ lodash.memoize@^4.1.2:
version "4.1.2" version "4.1.2"
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" 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: lodash.restparam@^3.0.0:
version "3.6.1" version "3.6.1"
resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" 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 ...@@ -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" source-map "^0.5.6"
supports-color "^3.2.3" 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: postcss@^6.0.8:
version "6.0.14" version "6.0.14"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.14.tgz#5534c72114739e75d0afcf017db853099f562885" 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: ...@@ -5669,6 +5689,18 @@ safe-buffer@^5.0.1, safe-buffer@~5.0.1:
version "5.0.1" version "5.0.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7" 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: sax@~1.2.1:
version "1.2.2" version "1.2.2"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.2.tgz#fd8631a23bc7826bef5d871bdb87378c95647828" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.2.tgz#fd8631a23bc7826bef5d871bdb87378c95647828"
...@@ -5982,6 +6014,13 @@ sql.js@^0.4.0: ...@@ -5982,6 +6014,13 @@ sql.js@^0.4.0:
version "0.4.0" version "0.4.0"
resolved "https://registry.yarnpkg.com/sql.js/-/sql.js-0.4.0.tgz#23be9635520eb0ff43a741e7e830397266e88445" 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: sshpk@^1.7.0:
version "1.13.1" version "1.13.1"
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3"
...@@ -6126,6 +6165,12 @@ supports-color@^4.2.1: ...@@ -6126,6 +6165,12 @@ supports-color@^4.2.1:
dependencies: dependencies:
has-flag "^2.0.0" 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: svg4everybody@2.1.9:
version "2.1.9" version "2.1.9"
resolved "https://registry.yarnpkg.com/svg4everybody/-/svg4everybody-2.1.9.tgz#5bd9f6defc133859a044646d4743fabc28db7e2d" resolved "https://registry.yarnpkg.com/svg4everybody/-/svg4everybody-2.1.9.tgz#5bd9f6defc133859a044646d4743fabc28db7e2d"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment