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