Commit 9afa4a5f authored by Douwe Maan's avatar Douwe Maan

Merge branch 'ce-to-ee-2018-10-01' into 'master'

CE upstream - 2018-10-01 21:21 UTC

Closes #2702 and #2697

See merge request gitlab-org/gitlab-ee!7743
parents f367bcc6 39a4258a
import Vue from 'vue';
import sanitize from 'sanitize-html';
import issuableApp from './components/app.vue';
import '../vue_shared/vue_resource_interceptor';
document.addEventListener('DOMContentLoaded', () => {
export default function initIssueableApp() {
const initialDataEl = document.getElementById('js-issuable-app-initial-data');
const props = JSON.parse(initialDataEl.innerHTML.replace(/"/g, '"'));
const props = JSON.parse(sanitize(initialDataEl.textContent).replace(/"/g, '"'));
return new Vue({
el: document.getElementById('js-issuable-app'),
......@@ -17,4 +18,4 @@ document.addEventListener('DOMContentLoaded', () => {
});
},
});
});
}
......@@ -3,9 +3,10 @@ import Issue from '~/issue';
import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable';
import ZenMode from '~/zen_mode';
import '~/notes/index';
import '~/issue_show/index';
import initIssueableApp from '~/issue_show';
export default function () {
initIssueableApp();
new Issue(); // eslint-disable-line no-new
new ShortcutsIssuable(); // eslint-disable-line no-new
new ZenMode(); // eslint-disable-line no-new
......
<script>
import { Link } from '@gitlab-org/gitlab-ui';
import Icon from '../../icon.vue';
import { numberToHumanSize } from '../../../../lib/utils/number_utils';
export default {
components: {
'gl-link': Link,
Icon,
},
props: {
......@@ -37,7 +39,7 @@ export default {
({{ fileSizeReadable }})
</template>
</p>
<a
<gl-link
:href="path"
class="btn btn-default"
rel="nofollow"
......@@ -49,7 +51,7 @@ export default {
css-classes="float-left append-right-8"
/>
{{ __('Download') }}
</a>
</gl-link>
</div>
</div>
</template>
......@@ -18,12 +18,14 @@
*/
import { Link } from '@gitlab-org/gitlab-ui';
import userAvatarImage from './user_avatar_image.vue';
import tooltip from '../../directives/tooltip';
export default {
name: 'UserAvatarLink',
components: {
'gl-link': Link,
userAvatarImage,
},
directives: {
......@@ -83,7 +85,7 @@ export default {
</script>
<template>
<a
<gl-link
:href="linkHref"
class="user-avatar-link">
<user-avatar-image
......@@ -99,5 +101,5 @@ export default {
:title="tooltipText"
:tooltip-placement="tooltipPlacement"
>{{ username }}</span>
</a>
</gl-link>
</template>
......@@ -59,7 +59,7 @@
}
@include media-breakpoint-up(sm) {
.btn:first-of-type {
.btn:nth-child(1) {
margin-left: auto;
}
}
......
......@@ -109,6 +109,15 @@ class ApplicationController < ActionController::Base
request.env['rack.session.options'][:expire_after] = Settings.gitlab['unauthenticated_session_expire_delay']
end
def render(*args)
super.tap do
# Set a header for custom error pages to prevent them from being intercepted by gitlab-workhorse
if response.content_type == 'text/html' && (400..599).cover?(response.status)
response.headers['X-GitLab-Custom-Error'] = '1'
end
end
end
protected
def append_info_to_payload(payload)
......
......@@ -12,6 +12,7 @@ class EventsFinder
# Arguments:
# source - which user or project to looks for events on
# current_user - only return events for projects visible to this user
# WARNING: does not consider project feature visibility!
# params:
# action: string
# target_type: string
......
......@@ -3,6 +3,7 @@
# Get user activity feed for projects common for a user and a logged in user
#
# - current_user: The user viewing the events
# WARNING: does not consider project feature visibility!
# - user: The user for which to load the events
# - params:
# - offset: The page of events to return
......
......@@ -33,7 +33,8 @@ module BlobViewer
end
def homepage
json_data['homepage']
url = json_data['homepage']
url if Gitlab::UrlSanitizer.valid?(url)
end
def npm_url
......
......@@ -10,5 +10,9 @@ module Ci
alias_attribute :secret_value, :value
validates :key, uniqueness: { scope: :pipeline_id }
def hook_attrs
{ key: key, value: value }
end
end
end
......@@ -154,6 +154,8 @@ class Event < ActiveRecord::Base
end
end
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
def visible_to_user?(user = nil)
if push? || commit_note?
Ability.allowed?(user, :download_code, project)
......@@ -165,12 +167,18 @@ class Event < ActiveRecord::Base
Ability.allowed?(user, :read_issue, note? ? note_target : target)
elsif merge_request? || merge_request_note?
Ability.allowed?(user, :read_merge_request, note? ? note_target : target)
elsif personal_snippet_note?
Ability.allowed?(user, :read_personal_snippet, note_target)
elsif project_snippet_note?
Ability.allowed?(user, :read_project_snippet, note_target)
elsif milestone?
Ability.allowed?(user, :read_project, project)
Ability.allowed?(user, :read_milestone, project)
else
false # No other event types are visible
end
end
# rubocop:enable Metrics/PerceivedComplexity
# rubocop:enable Metrics/CyclomaticComplexity
def project_name
if project
......@@ -312,6 +320,10 @@ class Event < ActiveRecord::Base
note? && target && target.for_snippet?
end
def personal_snippet_note?
note? && target && target.for_personal_snippet?
end
def note_target
target.noteable
end
......
......@@ -3,6 +3,16 @@
class WebHook < ActiveRecord::Base
include Sortable
attr_encrypted :token,
mode: :per_attribute_iv,
algorithm: 'aes-256-gcm',
key: Settings.attr_encrypted_db_key_base_truncated
attr_encrypted :url,
mode: :per_attribute_iv,
algorithm: 'aes-256-gcm',
key: Settings.attr_encrypted_db_key_base_truncated
has_many :web_hook_logs, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
validates :url, presence: true, public_url: { allow_localhost: lambda(&:allow_local_requests?),
......@@ -27,4 +37,38 @@ class WebHook < ActiveRecord::Base
def allow_local_requests?
false
end
# In 11.4, the web_hooks table has both `token` and `encrypted_token` fields.
# Ensure that the encrypted version always takes precedence if present.
alias_method :attr_encrypted_token, :token
def token
attr_encrypted_token.presence || read_attribute(:token)
end
# In 11.4, the web_hooks table has both `token` and `encrypted_token` fields.
# Pending a background migration to encrypt all fields, we should just clear
# the unencrypted value whenever the new value is set.
alias_method :'attr_encrypted_token=', :'token='
def token=(value)
self.attr_encrypted_token = value
write_attribute(:token, nil)
end
# In 11.4, the web_hooks table has both `url` and `encrypted_url` fields.
# Ensure that the encrypted version always takes precedence if present.
alias_method :attr_encrypted_url, :url
def url
attr_encrypted_url.presence || read_attribute(:url)
end
# In 11.4, the web_hooks table has both `url` and `encrypted_url` fields.
# Pending a background migration to encrypt all fields, we should just clear
# the unencrypted value whenever the new value is set.
alias_method :'attr_encrypted_url=', :'url='
def url=(value)
self.attr_encrypted_url = value
write_attribute(:url, nil)
end
end
......@@ -7,7 +7,6 @@ class MergeRequest < ActiveRecord::Base
include Noteable
include Referable
include Presentable
include Elastic::MergeRequestsSearch
include IgnorableColumn
include TimeTrackable
include ManualInverseAssociation
......
......@@ -14,8 +14,8 @@ module Clusters
else
check_timeout
end
rescue Kubeclient::HttpError => ke
app.make_errored!("Kubernetes error: #{ke.message}") unless app.errored?
rescue Kubeclient::HttpError
app.make_errored!("Kubernetes error") unless app.errored?
end
private
......@@ -27,7 +27,7 @@ module Clusters
end
def on_failed
app.make_errored!(installation_errors || 'Installation silently failed')
app.make_errored!('Installation failed')
ensure
remove_installation_pod
end
......
......@@ -12,10 +12,10 @@ module Clusters
ClusterWaitForAppInstallationWorker.perform_in(
ClusterWaitForAppInstallationWorker::INTERVAL, app.name, app.id)
rescue Kubeclient::HttpError => ke
app.make_errored!("Kubernetes error: #{ke.message}")
rescue StandardError => e
app.make_errored!("Can't start installation process. #{e.message}")
rescue Kubeclient::HttpError
app.make_errored!("Kubernetes error.")
rescue StandardError
app.make_errored!("Can't start installation process.")
end
end
end
......
......@@ -9,7 +9,7 @@
.project-empty-note-panel
%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
.prepend-top-20
%h4
%h4.append-bottom-20
= _('The repository for this project is empty')
- if @project.can_current_user_push_code?
......
---
title: Set a header for custom error pages to prevent them from being intercepted
by gitlab-workhorse
merge_request: 21870
author: David Piegza
type: fixed
---
title: Fixes modal button alignment
merge_request: 22024
author: Jacopo Beschi @jacopo-beschi
type: fixed
---
title: Encrypt webhook tokens and URLs in the database
merge_request: 21645
author:
type: security
---
title: Add link component to DownloadViewer component
merge_request: 21987
author: George Tsiolis
type: other
---
title: Add link component to UserAvatarLink component
merge_request: 21986
author: George Tsiolis
type: other
---
title: Redact confidential events in the API
merge_request:
author:
type: security
---
title: Improve empty project placeholder for non-members and members without write access
merge_request: 21977
author: George Tsiolis
type: other
---
title: Enable frozen string in lib/api and lib/backup
merge_request:
author: gfyoung
type: performance
---
title: pipeline webhook event now contain pipeline variables
merge_request: 18171
author: Pierre Tardy
type: added
---
title: Set timeout for syntax highlighting
merge_request:
author:
type: security
---
title: Sanitize JSON data properly to fix XSS on Issue details page
merge_request:
author:
type: security
---
title: Fix xss vulnerability sourced from package.json
merge_request:
author:
type: security
# frozen_string_literal: true
class AddAttrEncryptedColumnsToWebHook < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
add_column :web_hooks, :encrypted_token, :string
add_column :web_hooks, :encrypted_token_iv, :string
add_column :web_hooks, :encrypted_url, :string
add_column :web_hooks, :encrypted_url_iv, :string
end
end
# frozen_string_literal: true
class EncryptWebHooksColumns < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
BATCH_SIZE = 10000
RANGE_SIZE = 100
MIGRATION = 'EncryptColumns'
COLUMNS = [:token, :url]
WebHook = ::Gitlab::BackgroundMigration::Models::EncryptColumns::WebHook
disable_ddl_transaction!
def up
WebHook.each_batch(of: BATCH_SIZE) do |relation, index|
delay = index * 2.minutes
relation.each_batch(of: RANGE_SIZE) do |relation|
range = relation.pluck('MIN(id)', 'MAX(id)').first
args = [WebHook, COLUMNS, *range]
BackgroundMigrationWorker.perform_in(delay, MIGRATION, args)
end
end
end
def down
# noop
end
end
......@@ -3045,6 +3045,10 @@ ActiveRecord::Schema.define(version: 20180920043317) do
t.boolean "job_events", default: false, null: false
t.boolean "confidential_note_events"
t.text "push_events_branch_filter"
t.string "encrypted_token"
t.string "encrypted_token_iv"
t.string "encrypted_url"
t.string "encrypted_url_iv"
end
add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree
......
......@@ -968,7 +968,13 @@ X-Gitlab-Event: Pipeline Hook
],
"created_at": "2016-08-12 15:23:28 UTC",
"finished_at": "2016-08-12 15:26:29 UTC",
"duration": 63
"duration": 63,
"variables": [
{
"key": "NESTOR_PROD_ENVIRONMENT",
"value": "us-west-1"
}
]
},
"user":{
"name": "Administrator",
......
# frozen_string_literal: true
module API
class AccessRequests < Grape::API
include PaginationParams
......
# frozen_string_literal: true
module API
class API < Grape::API
include APIGuard
......
# frozen_string_literal: true
# Guard API with OAuth 2.0 Access Token
require 'rack/oauth2'
......
# frozen_string_literal: true
module API
# External applications API
class Applications < Grape::API
......
# frozen_string_literal: true
module API
class Avatar < Grape::API
resource :avatar do
......
# frozen_string_literal: true
module API
class AwardEmoji < Grape::API
include PaginationParams
......
# frozen_string_literal: true
module API
class Badges < Grape::API
include PaginationParams
......
# frozen_string_literal: true
module API
class Boards < Grape::API
include BoardsResponses
......
# frozen_string_literal: true
module API
module BoardsResponses
extend ActiveSupport::Concern
......
# frozen_string_literal: true
require 'mime/types'
module API
......
# frozen_string_literal: true
module API
class BroadcastMessages < Grape::API
include PaginationParams
......
# frozen_string_literal: true
module API
class CircuitBreakers < Grape::API
before { authenticated_as_admin! }
......
# frozen_string_literal: true
require 'mime/types'
module API
......
# frozen_string_literal: true
require 'mime/types'
module API
......
# frozen_string_literal: true
module API
module CustomAttributesEndpoints
extend ActiveSupport::Concern
......
# frozen_string_literal: true
module API
class DeployKeys < Grape::API
include PaginationParams
......
# frozen_string_literal: true
module API
# Deployments RESTful API endpoints
class Deployments < Grape::API
......
# frozen_string_literal: true
module API
class Discussions < Grape::API
include PaginationParams
......
# frozen_string_literal: true
module API
module Entities
class WikiPageBasic < Grape::Entity
......
# frozen_string_literal: true
module API
# Environments RESTfull API endpoints
class Environments < Grape::API
......
# frozen_string_literal: true
module API
class Events < Grape::API
include PaginationParams
......@@ -16,12 +18,27 @@ module API
desc: 'Return events sorted in ascending and descending order'
end
RedactedEvent = OpenStruct.new(target_title: 'Confidential event').freeze
def redact_events(events)
events.map do |event|
if event.visible_to_user?(current_user)
event
else
RedactedEvent
end
end
end
# rubocop: disable CodeReuse/ActiveRecord
def present_events(events)
def present_events(events, redact: true)
events = events.reorder(created_at: params[:sort])
.with_associations
present paginate(events), with: Entities::Event
events = paginate(events)
events = redact_events(events) if redact
present events, with: Entities::Event
end
# rubocop: enable CodeReuse/ActiveRecord
end
......@@ -44,7 +61,8 @@ module API
events = EventsFinder.new(params.merge(source: current_user, current_user: current_user)).execute.preload(:author, :target)
present_events(events)
# Since we're viewing our own events, redaction is unnecessary
present_events(events, redact: false)
end
# rubocop: enable CodeReuse/ActiveRecord
end
......
# frozen_string_literal: true
module API
class Features < Grape::API
before { authenticated_as_admin! }
......
# frozen_string_literal: true
module API
class Files < Grape::API
FILE_ENDPOINT_REQUIREMENTS = API::PROJECT_ENDPOINT_REQUIREMENTS.merge(file_path: API::NO_SLASH_URL_PART_REGEX)
......
# frozen_string_literal: true
module API
class GroupBoards < Grape::API
include BoardsResponses
......
# frozen_string_literal: true
module API
class GroupMilestones < Grape::API
include MilestoneResponses
......
# frozen_string_literal: true
module API
class GroupVariables < Grape::API
include PaginationParams
......
# frozen_string_literal: true
module API
class Groups < Grape::API
include PaginationParams
......
# frozen_string_literal: true
module API
module Helpers
prepend EE::API::Helpers
......@@ -394,9 +396,10 @@ module API
# lifted from https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb#L60
trace = exception.backtrace
message = "\n#{exception.class} (#{exception.message}):\n"
message = ["\n#{exception.class} (#{exception.message}):\n"]
message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
message << " " << trace.join("\n ")
message = message.join
API.logger.add Logger::FATAL, message
......
# frozen_string_literal: true
module API
module Helpers
module BadgesHelpers
......
# frozen_string_literal: true
module API
module Helpers
module CommonHelpers
......
# frozen_string_literal: true
module API
module Helpers
module CustomAttributes
......
# frozen_string_literal: true
module API
module Helpers
module CustomValidators
......
# frozen_string_literal: true
module API
module Helpers
module HeadersHelpers
......
# frozen_string_literal: true
module API
module Helpers
module InternalHelpers
......
# frozen_string_literal: true
# rubocop:disable GitlabSecurity/PublicSend
module API
......
# frozen_string_literal: true
module API
module Helpers
module NotesHelpers
......
# frozen_string_literal: true
module API
module Helpers
module Pagination
......
# frozen_string_literal: true
module API
module Helpers
module ProjectSnapshotsHelpers
......
# frozen_string_literal: true
module API
module Helpers
module ProjectsHelpers
......
# frozen_string_literal: true
module API
module Helpers
module RelatedResourcesHelpers
......
# frozen_string_literal: true
module API
module Helpers
module Runner
......
# frozen_string_literal: true
module API
# Internal access API
class Internal < Grape::API
......
# frozen_string_literal: true
module API
class Issues < Grape::API
include PaginationParams
......
# frozen_string_literal: true
module API
class JobArtifacts < Grape::API
before { authenticate_non_get! }
......
# frozen_string_literal: true
module API
class Jobs < Grape::API
include PaginationParams
......
# frozen_string_literal: true
module API
# Keys API
class Keys < Grape::API
......
# frozen_string_literal: true
module API
class Labels < Grape::API
include PaginationParams
......
# frozen_string_literal: true
module API
class Lint < Grape::API
namespace :ci do
......
# frozen_string_literal: true
module API
class Markdown < Grape::API
params do
......
# frozen_string_literal: true
module API
class Members < Grape::API
include PaginationParams
......
# frozen_string_literal: true
module API
# MergeRequestDiff API
class MergeRequestDiffs < Grape::API
......
# frozen_string_literal: true
module API
class MergeRequests < Grape::API
include PaginationParams
......
# frozen_string_literal: true
module API
module MilestoneResponses
extend ActiveSupport::Concern
......
# frozen_string_literal: true
module API
class Namespaces < Grape::API
include PaginationParams
......
# frozen_string_literal: true
module API
class Notes < Grape::API
include PaginationParams
......
# frozen_string_literal: true
module API
# notification_settings API
class NotificationSettings < Grape::API
......
# frozen_string_literal: true
module API
class PagesDomains < Grape::API
include PaginationParams
......
# frozen_string_literal: true
module API
# Concern for declare pagination params.
#
......
# frozen_string_literal: true
module API
class PipelineSchedules < Grape::API
include PaginationParams
......
# frozen_string_literal: true
module API
class Pipelines < Grape::API
include PaginationParams
......
# frozen_string_literal: true
module API
class ProjectExport < Grape::API
before do
......
# frozen_string_literal: true
module API
class ProjectHooks < Grape::API
include PaginationParams
......
# frozen_string_literal: true
module API
class ProjectImport < Grape::API
include PaginationParams
......
# frozen_string_literal: true
module API
class ProjectMilestones < Grape::API
include PaginationParams
......
# frozen_string_literal: true
module API
class ProjectSnapshots < Grape::API
helpers ::API::Helpers::ProjectSnapshotsHelpers
......
# frozen_string_literal: true
module API
class ProjectSnippets < Grape::API
include PaginationParams
......
# frozen_string_literal: true
require_dependency 'declarative_policy'
module API
......
# frozen_string_literal: true
module API
module ProjectsRelationBuilder
extend ActiveSupport::Concern
......
# frozen_string_literal: true
module API
class ProtectedBranches < Grape::API
include PaginationParams
......
# frozen_string_literal: true
module API
class ProtectedTags < Grape::API
include PaginationParams
......
# frozen_string_literal: true
require 'mime/types'
module API
......
# frozen_string_literal: true
module API
class Runner < Grape::API
helpers ::API::Helpers::Runner
......
# frozen_string_literal: true
module API
class Runners < Grape::API
include PaginationParams
......
# frozen_string_literal: true
# Encapsulate a scope used for authorization, such as `api`, or `read_user`
module API
class Scope
......
# frozen_string_literal: true
module API
class Search < Grape::API
include PaginationParams
......
# frozen_string_literal: true
module API
class Settings < Grape::API
before { authenticated_as_admin! }
......
# frozen_string_literal: true
require 'sidekiq/api'
module API
......
# frozen_string_literal: true
module API
# Snippets API
class Snippets < Grape::API
......
# frozen_string_literal: true
module API
class Subscriptions < Grape::API
before { authenticate! }
......
# frozen_string_literal: true
module API
class SystemHooks < Grape::API
include PaginationParams
......
# frozen_string_literal: true
module API
class Tags < Grape::API
include PaginationParams
......
# frozen_string_literal: true
module API
class Templates < Grape::API
include PaginationParams
......
# frozen_string_literal: true
module API
module TimeTrackingEndpoints
extend ActiveSupport::Concern
......
# frozen_string_literal: true
module API
class Todos < Grape::API
include PaginationParams
......
# frozen_string_literal: true
module API
class Triggers < Grape::API
include PaginationParams
......
# frozen_string_literal: true
module API
class Users < Grape::API
include PaginationParams
......
# frozen_string_literal: true
module API
class Variables < Grape::API
include PaginationParams
......
# frozen_string_literal: true
module API
class Version < Grape::API
before { authenticate! }
......
# frozen_string_literal: true
module API
class Wikis < Grape::API
helpers do
......
# frozen_string_literal: true
require 'backup/files'
module Backup
......
# frozen_string_literal: true
require 'backup/files'
module Backup
......
# frozen_string_literal: true
require 'yaml'
module Backup
......
# frozen_string_literal: true
require 'open3'
require_relative 'helper'
......
# frozen_string_literal: true
module Backup
module Helper
def access_denied_error(path)
......
# frozen_string_literal: true
require 'backup/files'
module Backup
......
# frozen_string_literal: true
module Backup
class Manager
ARCHIVES_TO_BACKUP = %w[uploads builds artifacts pages lfs registry].freeze
......
# frozen_string_literal: true
require 'backup/files'
module Backup
......
# frozen_string_literal: true
require 'backup/files'
module Backup
......
# frozen_string_literal: true
require 'yaml'
module Backup
......
# frozen_string_literal: true
require 'backup/files'
module Backup
......
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
# EncryptColumn migrates data from an unencrypted column - `foo`, say - to
# an encrypted column - `encrypted_foo`, say.
#
# For this background migration to work, the table that is migrated _has_ to
# have an `id` column as the primary key. Additionally, the encrypted column
# should be managed by attr_encrypted, and map to an attribute with the same
# name as the unencrypted column (i.e., the unencrypted column should be
# shadowed).
#
# To avoid depending on a particular version of the model in app/, add a
# model to `lib/gitlab/background_migration/models/encrypt_columns` and use
# it in the migration that enqueues the jobs, so code can be shared.
class EncryptColumns
def perform(model, attributes, from, to)
model = model.constantize if model.is_a?(String)
attributes = expand_attributes(model, Array(attributes).map(&:to_sym))
model.transaction do
# Use SELECT ... FOR UPDATE to prevent the value being changed while
# we are encrypting it
relation = model.where(id: from..to).lock
relation.each do |instance|
encrypt!(instance, attributes)
end
end
end
private
# Build a hash of { attribute => encrypted column name }
def expand_attributes(klass, attributes)
expanded = attributes.flat_map do |attribute|
attr_config = klass.encrypted_attributes[attribute]
crypt_column_name = attr_config&.fetch(:attribute)
raise "Couldn't determine encrypted column for #{klass}##{attribute}" if
crypt_column_name.nil?
[attribute, crypt_column_name]
end
Hash[*expanded]
end
# Generate ciphertext for each column and update the database
def encrypt!(instance, attributes)
to_clear = attributes
.map { |plain, crypt| apply_attribute!(instance, plain, crypt) }
.compact
.flat_map { |plain| [plain, nil] }
to_clear = Hash[*to_clear]
if instance.changed?
instance.save!
instance.update_columns(to_clear)
end
end
def apply_attribute!(instance, plain_column, crypt_column)
plaintext = instance[plain_column]
ciphertext = instance[crypt_column]
# No need to do anything if the plaintext is nil, or an encrypted
# value already exists
return nil unless plaintext.present? && !ciphertext.present?
# attr_encrypted will calculate and set the expected value for us
instance.public_send("#{plain_column}=", plaintext) # rubocop:disable GitlabSecurity/PublicSend
plain_column
end
end
end
end
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
module Models
module EncryptColumns
# This model is shared between synchronous and background migrations to
# encrypt the `token` and `url` columns
class WebHook < ActiveRecord::Base
include ::EachBatch
self.table_name = 'web_hooks'
self.inheritance_column = :_type_disabled
attr_encrypted :token,
mode: :per_attribute_iv,
algorithm: 'aes-256-gcm',
key: Settings.attr_encrypted_db_key_base_truncated
attr_encrypted :url,
mode: :per_attribute_iv,
algorithm: 'aes-256-gcm',
key: Settings.attr_encrypted_db_key_base_truncated
end
end
end
end
end
......@@ -26,7 +26,8 @@ module Gitlab
stages: pipeline.stages_names,
created_at: pipeline.created_at,
finished_at: pipeline.finished_at,
duration: pipeline.duration
duration: pipeline.duration,
variables: pipeline.variables.map(&:hook_attrs)
}
end
......
module Gitlab
class Highlight
TIMEOUT_BACKGROUND = 30.seconds
TIMEOUT_FOREGROUND = 3.seconds
def self.highlight(blob_name, blob_content, repository: nil, plain: false)
new(blob_name, blob_content, repository: repository)
.highlight(blob_content, continue: false, plain: plain)
......@@ -51,11 +54,20 @@ module Gitlab
end
def highlight_rich(text, continue: true)
@formatter.format(lexer.lex(text, continue: continue), tag: lexer.tag).html_safe
tag = lexer.tag
tokens = lexer.lex(text, continue: continue)
Timeout.timeout(timeout_time) { @formatter.format(tokens, tag: tag).html_safe }
rescue Timeout::Error => e
Gitlab::Sentry.track_exception(e)
highlight_plain(text)
rescue
highlight_plain(text)
end
def timeout_time
Sidekiq.server? ? TIMEOUT_BACKGROUND : TIMEOUT_FOREGROUND
end
def link_dependencies(text, highlighted_text)
Gitlab::DependencyLinker.link(blob_name, text, highlighted_text)
end
......
......@@ -158,6 +158,12 @@ excluded_attributes:
- :reference
- :reference_html
- :epic_id
hooks:
- :token
- :encrypted_token
- :encrypted_token_iv
- :encrypted_url
- :encrypted_url_iv
methods:
labels:
......
......@@ -728,4 +728,80 @@ describe ApplicationController do
end
end
end
context 'X-GitLab-Custom-Error header' do
before do
sign_in user
end
context 'given a 422 error page' do
controller do
def index
render 'errors/omniauth_error', layout: 'errors', status: 422
end
end
it 'sets a custom header' do
get :index
expect(response.headers['X-GitLab-Custom-Error']).to eq '1'
end
end
context 'given a 500 error page' do
controller do
def index
render 'errors/omniauth_error', layout: 'errors', status: 500
end
end
it 'sets a custom header' do
get :index
expect(response.headers['X-GitLab-Custom-Error']).to eq '1'
end
end
context 'given a 200 success page' do
controller do
def index
render 'errors/omniauth_error', layout: 'errors', status: 200
end
end
it 'does not set a custom header' do
get :index
expect(response.headers['X-GitLab-Custom-Error']).to be_nil
end
end
context 'given a json response' do
controller do
def index
render json: {}, status: :unprocessable_entity
end
end
it 'does not set a custom header' do
get :index, format: :json
expect(response.headers['X-GitLab-Custom-Error']).to be_nil
end
end
context 'given a json response for an html request' do
controller do
def index
render json: {}, status: :unprocessable_entity
end
end
it 'does not set a custom header' do
get :index
expect(response.headers['X-GitLab-Custom-Error']).to be_nil
end
end
end
end
......@@ -18,6 +18,23 @@ describe 'Issue Detail', :js do
end
end
context 'when issue description has xss snippet' do
before do
issue.update!(description: '![xss" onload=alert(1);//](a)')
sign_in(user)
visit project_issue_path(project, issue)
wait_for_requests
end
it 'should encode the description to prevent xss issues' do
page.within('.issuable-details .detail-page-description') do
expect(page).to have_selector('img', count: 1)
expect(find('img')['onerror']).to be_nil
expect(find('img')['src']).to end_with('/a')
end
end
end
context 'when edited by a user who is later deleted' do
before do
sign_in(user)
......
import initIssueableApp from '~/issue_show';
describe('Issue show index', () => {
describe('initIssueableApp', () => {
it('should initialize app with no potential XSS attack', () => {
const d = document.createElement('div');
d.id = 'js-issuable-app-initial-data';
d.innerHTML = JSON.stringify({
initialDescriptionHtml: '&lt;img src=x onerror=alert(1)&gt;',
});
document.body.appendChild(d);
const alertSpy = spyOn(window, 'alert');
initIssueableApp();
expect(alertSpy).not.toHaveBeenCalled();
});
});
});
......@@ -11,10 +11,6 @@ describe Backup::Manager do
allow(progress).to receive(:puts)
allow(progress).to receive(:print)
allow_any_instance_of(String).to receive(:color) do |string, _color|
string
end
@old_progress = $progress # rubocop:disable Style/GlobalVars
$progress = progress # rubocop:disable Style/GlobalVars
end
......
......@@ -11,10 +11,6 @@ describe Backup::Repository do
allow(FileUtils).to receive(:mkdir_p).and_return(true)
allow(FileUtils).to receive(:mv).and_return(true)
allow_any_instance_of(String).to receive(:color) do |string, _color|
string
end
allow_any_instance_of(described_class).to receive(:progress).and_return(progress)
end
......
require 'spec_helper'
describe Gitlab::BackgroundMigration::EncryptColumns, :migration, schema: 20180910115836 do
let(:model) { Gitlab::BackgroundMigration::Models::EncryptColumns::WebHook }
let(:web_hooks) { table(:web_hooks) }
let(:plaintext_attrs) do
{
'encrypted_token' => nil,
'encrypted_url' => nil,
'token' => 'secret',
'url' => 'http://example.com?access_token=secret'
}
end
let(:encrypted_attrs) do
{
'encrypted_token' => be_present,
'encrypted_url' => be_present,
'token' => nil,
'url' => nil
}
end
describe '#perform' do
it 'encrypts columns for the specified range' do
hooks = web_hooks.create([plaintext_attrs] * 5).sort_by(&:id)
# Encrypt all but the first and last rows
subject.perform(model, [:token, :url], hooks[1].id, hooks[3].id)
hooks = web_hooks.where(id: hooks.map(&:id)).order(:id)
aggregate_failures do
expect(hooks[0]).to have_attributes(plaintext_attrs)
expect(hooks[1]).to have_attributes(encrypted_attrs)
expect(hooks[2]).to have_attributes(encrypted_attrs)
expect(hooks[3]).to have_attributes(encrypted_attrs)
expect(hooks[4]).to have_attributes(plaintext_attrs)
end
end
it 'acquires an exclusive lock for the update' do
relation = double('relation', each: nil)
expect(model).to receive(:where) { relation }
expect(relation).to receive(:lock) { relation }
subject.perform(model, [:token, :url], 1, 1)
end
it 'skips already-encrypted columns' do
values = {
'encrypted_token' => 'known encrypted token',
'encrypted_url' => 'known encrypted url',
'token' => 'token',
'url' => 'url'
}
hook = web_hooks.create(values)
subject.perform(model, [:token, :url], hook.id, hook.id)
hook.reload
expect(hook).to have_attributes(values)
end
end
end
......@@ -6,10 +6,10 @@ describe Gitlab::DataBuilder::Pipeline do
let(:pipeline) do
create(:ci_pipeline,
project: project,
status: 'success',
sha: project.commit.sha,
ref: project.default_branch)
project: project,
status: 'success',
sha: project.commit.sha,
ref: project.default_branch)
end
let!(:build) { create(:ci_build, pipeline: pipeline) }
......@@ -20,18 +20,35 @@ describe Gitlab::DataBuilder::Pipeline do
let(:build_data) { data[:builds].first }
let(:project_data) { data[:project] }
it { expect(attributes).to be_a(Hash) }
it { expect(attributes[:ref]).to eq(pipeline.ref) }
it { expect(attributes[:sha]).to eq(pipeline.sha) }
it { expect(attributes[:tag]).to eq(pipeline.tag) }
it { expect(attributes[:id]).to eq(pipeline.id) }
it { expect(attributes[:status]).to eq(pipeline.status) }
it { expect(attributes[:detailed_status]).to eq('passed') }
it 'has correct attributes' do
expect(attributes).to be_a(Hash)
expect(attributes[:ref]).to eq(pipeline.ref)
expect(attributes[:sha]).to eq(pipeline.sha)
expect(attributes[:tag]).to eq(pipeline.tag)
expect(attributes[:id]).to eq(pipeline.id)
expect(attributes[:status]).to eq(pipeline.status)
expect(attributes[:detailed_status]).to eq('passed')
expect(build_data).to be_a(Hash)
expect(build_data[:id]).to eq(build.id)
expect(build_data[:status]).to eq(build.status)
expect(project_data).to eq(project.hook_attrs(backward: false))
end
it { expect(build_data).to be_a(Hash) }
it { expect(build_data[:id]).to eq(build.id) }
it { expect(build_data[:status]).to eq(build.status) }
context 'pipeline without variables' do
it 'has empty variables hash' do
expect(attributes[:variables]).to be_a(Array)
expect(attributes[:variables]).to be_empty()
end
end
it { expect(project_data).to eq(project.hook_attrs(backward: false)) }
context 'pipeline with variables' do
let(:build) { create(:ci_build, pipeline: pipeline) }
let(:data) { described_class.build(pipeline) }
let(:attributes) { data[:object_attributes] }
let!(:pipeline_variable) { create(:ci_pipeline_variable, pipeline: pipeline, key: 'TRIGGER_KEY_1', value: 'TRIGGER_VALUE_1') }
it { expect(attributes[:variables]).to be_a(Array) }
it { expect(attributes[:variables]).to contain_exactly({ key: 'TRIGGER_KEY_1', value: 'TRIGGER_VALUE_1' }) }
end
end
end
......@@ -56,5 +56,22 @@ describe Gitlab::Highlight do
described_class.highlight('file.name', 'Contents')
end
context 'timeout' do
subject { described_class.new('file.name', 'Contents') }
it 'utilizes timeout for web' do
expect(Timeout).to receive(:timeout).with(described_class::TIMEOUT_FOREGROUND).and_call_original
subject.highlight("Content")
end
it 'utilizes longer timeout for sidekiq' do
allow(Sidekiq).to receive(:server?).and_return(true)
expect(Timeout).to receive(:timeout).with(described_class::TIMEOUT_BACKGROUND).and_call_original
subject.highlight("Content")
end
end
end
end
......@@ -40,13 +40,14 @@ describe BlobViewer::PackageJson do
end
context 'when package.json has "private": true' do
let(:homepage) { 'http://example.com' }
let(:data) do
<<-SPEC.strip_heredoc
{
"name": "module-name",
"version": "10.3.1",
"private": true,
"homepage": "myawesomepackage.com"
"homepage": #{homepage.to_json}
}
SPEC
end
......@@ -54,10 +55,22 @@ describe BlobViewer::PackageJson do
subject { described_class.new(blob) }
describe '#package_url' do
it 'returns homepage if any' do
expect(subject).to receive(:prepare!)
context 'when the homepage has a valid URL' do
it 'returns homepage URL' do
expect(subject).to receive(:prepare!)
expect(subject.package_url).to eq(homepage)
end
end
context 'when the homepage has an invalid URL' do
let(:homepage) { 'javascript:alert()' }
it 'returns nil' do
expect(subject).to receive(:prepare!)
expect(subject.package_url).to eq('myawesomepackage.com')
expect(subject.package_url).to be_nil
end
end
end
......
......@@ -5,4 +5,13 @@ describe Ci::PipelineVariable do
it { is_expected.to include_module(HasVariable) }
it { is_expected.to validate_uniqueness_of(:key).scoped_to(:pipeline_id) }
describe '#hook_attrs' do
let(:variable) { create(:ci_pipeline_variable, key: 'foo', value: 'bar') }
subject { variable.hook_attrs }
it { is_expected.to be_a(Hash) }
it { is_expected.to eq({ key: 'foo', value: 'bar' }) }
end
end
......@@ -148,9 +148,14 @@ describe Event do
let(:admin) { create(:admin) }
let(:issue) { create(:issue, project: project, author: author, assignees: [assignee]) }
let(:confidential_issue) { create(:issue, :confidential, project: project, author: author, assignees: [assignee]) }
let(:project_snippet) { create(:project_snippet, :public, project: project, author: author) }
let(:personal_snippet) { create(:personal_snippet, :public, author: author) }
let(:note_on_commit) { create(:note_on_commit, project: project) }
let(:note_on_issue) { create(:note_on_issue, noteable: issue, project: project) }
let(:note_on_confidential_issue) { create(:note_on_issue, noteable: confidential_issue, project: project) }
let(:note_on_project_snippet) { create(:note_on_project_snippet, author: author, noteable: project_snippet, project: project) }
let(:note_on_personal_snippet) { create(:note_on_personal_snippet, author: author, noteable: personal_snippet, project: nil) }
let(:milestone_on_project) { create(:milestone, project: project) }
let(:event) { described_class.new(project: project, target: target, author_id: author.id) }
before do
......@@ -268,6 +273,125 @@ describe Event do
end
end
end
context 'milestone event' do
let(:target) { milestone_on_project }
it do
expect(event.visible_to_user?(nil)).to be_truthy
expect(event.visible_to_user?(non_member)).to be_truthy
expect(event.visible_to_user?(member)).to be_truthy
expect(event.visible_to_user?(guest)).to be_truthy
expect(event.visible_to_user?(admin)).to be_truthy
end
context 'on public project with private issue tracker and merge requests' do
let(:project) { create(:project, :public, :issues_private, :merge_requests_private) }
it do
expect(event.visible_to_user?(nil)).to be_falsy
expect(event.visible_to_user?(non_member)).to be_falsy
expect(event.visible_to_user?(member)).to be_truthy
expect(event.visible_to_user?(guest)).to be_truthy
expect(event.visible_to_user?(admin)).to be_truthy
end
end
context 'on private project' do
let(:project) { create(:project, :private) }
it do
expect(event.visible_to_user?(nil)).to be_falsy
expect(event.visible_to_user?(non_member)).to be_falsy
expect(event.visible_to_user?(member)).to be_truthy
expect(event.visible_to_user?(guest)).to be_truthy
expect(event.visible_to_user?(admin)).to be_truthy
end
end
end
context 'project snippet note event' do
let(:target) { note_on_project_snippet }
it do
expect(event.visible_to_user?(nil)).to be_truthy
expect(event.visible_to_user?(non_member)).to be_truthy
expect(event.visible_to_user?(author)).to be_truthy
expect(event.visible_to_user?(member)).to be_truthy
expect(event.visible_to_user?(guest)).to be_truthy
expect(event.visible_to_user?(admin)).to be_truthy
end
context 'on public project with private snippets' do
let(:project) { create(:project, :public, :snippets_private) }
it do
expect(event.visible_to_user?(nil)).to be_falsy
expect(event.visible_to_user?(non_member)).to be_falsy
# Normally, we'd expect the author of a comment to be able to view it.
# However, this doesn't seem to be the case for comments on snippets.
expect(event.visible_to_user?(author)).to be_falsy
expect(event.visible_to_user?(member)).to be_truthy
expect(event.visible_to_user?(guest)).to be_truthy
expect(event.visible_to_user?(admin)).to be_truthy
end
end
context 'on private project' do
let(:project) { create(:project, :private) }
it do
expect(event.visible_to_user?(nil)).to be_falsy
expect(event.visible_to_user?(non_member)).to be_falsy
# Normally, we'd expect the author of a comment to be able to view it.
# However, this doesn't seem to be the case for comments on snippets.
expect(event.visible_to_user?(author)).to be_falsy
expect(event.visible_to_user?(member)).to be_truthy
expect(event.visible_to_user?(guest)).to be_truthy
expect(event.visible_to_user?(admin)).to be_truthy
end
end
end
context 'personal snippet note event' do
let(:target) { note_on_personal_snippet }
it do
expect(event.visible_to_user?(nil)).to be_truthy
expect(event.visible_to_user?(non_member)).to be_truthy
expect(event.visible_to_user?(author)).to be_truthy
expect(event.visible_to_user?(admin)).to be_truthy
end
context 'on internal snippet' do
let(:personal_snippet) { create(:personal_snippet, :internal, author: author) }
it do
expect(event.visible_to_user?(nil)).to be_falsy
expect(event.visible_to_user?(non_member)).to be_truthy
expect(event.visible_to_user?(author)).to be_truthy
expect(event.visible_to_user?(admin)).to be_truthy
end
end
context 'on private snippet' do
let(:personal_snippet) { create(:personal_snippet, :private, author: author) }
it do
expect(event.visible_to_user?(nil)).to be_falsy
expect(event.visible_to_user?(non_member)).to be_falsy
expect(event.visible_to_user?(author)).to be_truthy
# It is very unexpected that a private personal snippet is not visible
# to an instance administrator. This should be fixed in the future.
expect(event.visible_to_user?(admin)).to be_falsy
end
end
end
end
describe '.limit_recent' do
......
......@@ -57,6 +57,12 @@ describe WebHook do
end
end
describe 'encrypted attributes' do
subject { described_class.encrypted_attributes.keys }
it { is_expected.to contain_exactly(:token, :url) }
end
describe 'execute' do
let(:data) { { key: 'value' } }
let(:hook_name) { 'project hook' }
......
require 'spec_helper'
describe 'Redacted events in API::Events' do
shared_examples 'private events are redacted' do
it 'redacts events the user does not have access to' do
expect_any_instance_of(Event).to receive(:visible_to_user?).and_call_original
get api(path), user
expect(response).to have_gitlab_http_status(200)
expect(json_response).to contain_exactly(
'project_id' => nil,
'action_name' => nil,
'target_id' => nil,
'target_iid' => nil,
'target_type' => nil,
'author_id' => nil,
'target_title' => 'Confidential event',
'created_at' => nil,
'author_username' => nil
)
end
end
describe '/users/:id/events' do
let(:project) { create(:project, :public) }
let(:path) { "/users/#{project.owner.id}/events" }
let(:issue) { create(:issue, :confidential, project: project) }
before do
EventCreateService.new.open_issue(issue, issue.author)
end
context 'unauthenticated user views another user with private events' do
let(:user) { nil }
include_examples 'private events are redacted'
end
context 'authenticated user without access views another user with private events' do
let(:user) { create(:user) }
include_examples 'private events are redacted'
end
end
describe '/projects/:id/events' do
let(:project) { create(:project, :public) }
let(:path) { "/projects/#{project.id}/events" }
let(:issue) { create(:issue, :confidential, project: project) }
before do
EventCreateService.new.open_issue(issue, issue.author)
end
context 'unauthenticated user views public project' do
let(:user) { nil }
include_examples 'private events are redacted'
end
context 'authenticated user without access views public project' do
let(:user) { create(:user) }
include_examples 'private events are redacted'
end
end
end
......@@ -82,7 +82,7 @@ describe Clusters::Applications::CheckInstallationProgressService do
service.execute
expect(application).to be_errored
expect(application.status_reason).to eq(errors)
expect(application.status_reason).to eq("Installation failed")
end
end
......
......@@ -42,7 +42,7 @@ describe Clusters::Applications::InstallService do
service.execute
expect(application).to be_errored
expect(application.status_reason).to match(/kubernetes error:/i)
expect(application.status_reason).to match('Kubernetes error.')
end
end
......
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