Commit 3ff4bf36 authored by GitLab Bot's avatar GitLab Bot

Merge remote-tracking branch 'upstream/master' into ce-to-ee-2018-10-01

# Conflicts:
#	app/models/merge_request.rb

[ci skip]
parents 25c8d7ce 0ef1060e
import Vue from 'vue'; import Vue from 'vue';
import sanitize from 'sanitize-html';
import issuableApp from './components/app.vue'; import issuableApp from './components/app.vue';
import '../vue_shared/vue_resource_interceptor'; import '../vue_shared/vue_resource_interceptor';
document.addEventListener('DOMContentLoaded', () => { export default function initIssueableApp() {
const initialDataEl = document.getElementById('js-issuable-app-initial-data'); 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({ return new Vue({
el: document.getElementById('js-issuable-app'), el: document.getElementById('js-issuable-app'),
...@@ -17,4 +18,4 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -17,4 +18,4 @@ document.addEventListener('DOMContentLoaded', () => {
}); });
}, },
}); });
}); }
...@@ -3,9 +3,10 @@ import Issue from '~/issue'; ...@@ -3,9 +3,10 @@ import Issue from '~/issue';
import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable'; import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable';
import ZenMode from '~/zen_mode'; import ZenMode from '~/zen_mode';
import '~/notes/index'; import '~/notes/index';
import '~/issue_show/index'; import initIssueableApp from '~/issue_show';
export default function () { export default function () {
initIssueableApp();
new Issue(); // eslint-disable-line no-new new Issue(); // eslint-disable-line no-new
new ShortcutsIssuable(); // eslint-disable-line no-new new ShortcutsIssuable(); // eslint-disable-line no-new
new ZenMode(); // eslint-disable-line no-new new ZenMode(); // eslint-disable-line no-new
......
<script> <script>
import { Link } from '@gitlab-org/gitlab-ui';
import Icon from '../../icon.vue'; import Icon from '../../icon.vue';
import { numberToHumanSize } from '../../../../lib/utils/number_utils'; import { numberToHumanSize } from '../../../../lib/utils/number_utils';
export default { export default {
components: { components: {
'gl-link': Link,
Icon, Icon,
}, },
props: { props: {
...@@ -37,7 +39,7 @@ export default { ...@@ -37,7 +39,7 @@ export default {
({{ fileSizeReadable }}) ({{ fileSizeReadable }})
</template> </template>
</p> </p>
<a <gl-link
:href="path" :href="path"
class="btn btn-default" class="btn btn-default"
rel="nofollow" rel="nofollow"
...@@ -49,7 +51,7 @@ export default { ...@@ -49,7 +51,7 @@ export default {
css-classes="float-left append-right-8" css-classes="float-left append-right-8"
/> />
{{ __('Download') }} {{ __('Download') }}
</a> </gl-link>
</div> </div>
</div> </div>
</template> </template>
...@@ -18,12 +18,14 @@ ...@@ -18,12 +18,14 @@
*/ */
import { Link } from '@gitlab-org/gitlab-ui';
import userAvatarImage from './user_avatar_image.vue'; import userAvatarImage from './user_avatar_image.vue';
import tooltip from '../../directives/tooltip'; import tooltip from '../../directives/tooltip';
export default { export default {
name: 'UserAvatarLink', name: 'UserAvatarLink',
components: { components: {
'gl-link': Link,
userAvatarImage, userAvatarImage,
}, },
directives: { directives: {
...@@ -83,7 +85,7 @@ export default { ...@@ -83,7 +85,7 @@ export default {
</script> </script>
<template> <template>
<a <gl-link
:href="linkHref" :href="linkHref"
class="user-avatar-link"> class="user-avatar-link">
<user-avatar-image <user-avatar-image
...@@ -99,5 +101,5 @@ export default { ...@@ -99,5 +101,5 @@ export default {
:title="tooltipText" :title="tooltipText"
:tooltip-placement="tooltipPlacement" :tooltip-placement="tooltipPlacement"
>{{ username }}</span> >{{ username }}</span>
</a> </gl-link>
</template> </template>
...@@ -59,7 +59,7 @@ ...@@ -59,7 +59,7 @@
} }
@include media-breakpoint-up(sm) { @include media-breakpoint-up(sm) {
.btn:first-of-type { .btn:nth-child(1) {
margin-left: auto; margin-left: auto;
} }
} }
......
...@@ -109,6 +109,15 @@ class ApplicationController < ActionController::Base ...@@ -109,6 +109,15 @@ class ApplicationController < ActionController::Base
request.env['rack.session.options'][:expire_after] = Settings.gitlab['unauthenticated_session_expire_delay'] request.env['rack.session.options'][:expire_after] = Settings.gitlab['unauthenticated_session_expire_delay']
end 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 protected
def append_info_to_payload(payload) def append_info_to_payload(payload)
......
...@@ -12,6 +12,7 @@ class EventsFinder ...@@ -12,6 +12,7 @@ class EventsFinder
# Arguments: # Arguments:
# source - which user or project to looks for events on # source - which user or project to looks for events on
# current_user - only return events for projects visible to this user # current_user - only return events for projects visible to this user
# WARNING: does not consider project feature visibility!
# params: # params:
# action: string # action: string
# target_type: string # target_type: string
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
# Get user activity feed for projects common for a user and a logged in user # Get user activity feed for projects common for a user and a logged in user
# #
# - current_user: The user viewing the events # - current_user: The user viewing the events
# WARNING: does not consider project feature visibility!
# - user: The user for which to load the events # - user: The user for which to load the events
# - params: # - params:
# - offset: The page of events to return # - offset: The page of events to return
......
...@@ -33,7 +33,8 @@ module BlobViewer ...@@ -33,7 +33,8 @@ module BlobViewer
end end
def homepage def homepage
json_data['homepage'] url = json_data['homepage']
url if Gitlab::UrlSanitizer.valid?(url)
end end
def npm_url def npm_url
......
...@@ -10,5 +10,9 @@ module Ci ...@@ -10,5 +10,9 @@ module Ci
alias_attribute :secret_value, :value alias_attribute :secret_value, :value
validates :key, uniqueness: { scope: :pipeline_id } validates :key, uniqueness: { scope: :pipeline_id }
def hook_attrs
{ key: key, value: value }
end
end end
end end
...@@ -154,6 +154,8 @@ class Event < ActiveRecord::Base ...@@ -154,6 +154,8 @@ class Event < ActiveRecord::Base
end end
end end
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
def visible_to_user?(user = nil) def visible_to_user?(user = nil)
if push? || commit_note? if push? || commit_note?
Ability.allowed?(user, :download_code, project) Ability.allowed?(user, :download_code, project)
...@@ -165,12 +167,18 @@ class Event < ActiveRecord::Base ...@@ -165,12 +167,18 @@ class Event < ActiveRecord::Base
Ability.allowed?(user, :read_issue, note? ? note_target : target) Ability.allowed?(user, :read_issue, note? ? note_target : target)
elsif merge_request? || merge_request_note? elsif merge_request? || merge_request_note?
Ability.allowed?(user, :read_merge_request, note? ? note_target : target) 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? elsif milestone?
Ability.allowed?(user, :read_project, project) Ability.allowed?(user, :read_milestone, project)
else else
false # No other event types are visible false # No other event types are visible
end end
end end
# rubocop:enable Metrics/PerceivedComplexity
# rubocop:enable Metrics/CyclomaticComplexity
def project_name def project_name
if project if project
...@@ -312,6 +320,10 @@ class Event < ActiveRecord::Base ...@@ -312,6 +320,10 @@ class Event < ActiveRecord::Base
note? && target && target.for_snippet? note? && target && target.for_snippet?
end end
def personal_snippet_note?
note? && target && target.for_personal_snippet?
end
def note_target def note_target
target.noteable target.noteable
end end
......
...@@ -3,6 +3,16 @@ ...@@ -3,6 +3,16 @@
class WebHook < ActiveRecord::Base class WebHook < ActiveRecord::Base
include Sortable 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 has_many :web_hook_logs, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
validates :url, presence: true, public_url: { allow_localhost: lambda(&:allow_local_requests?), validates :url, presence: true, public_url: { allow_localhost: lambda(&:allow_local_requests?),
...@@ -27,4 +37,38 @@ class WebHook < ActiveRecord::Base ...@@ -27,4 +37,38 @@ class WebHook < ActiveRecord::Base
def allow_local_requests? def allow_local_requests?
false false
end 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 end
...@@ -7,7 +7,10 @@ class MergeRequest < ActiveRecord::Base ...@@ -7,7 +7,10 @@ class MergeRequest < ActiveRecord::Base
include Noteable include Noteable
include Referable include Referable
include Presentable include Presentable
<<<<<<< HEAD
include Elastic::MergeRequestsSearch include Elastic::MergeRequestsSearch
=======
>>>>>>> upstream/master
include IgnorableColumn include IgnorableColumn
include TimeTrackable include TimeTrackable
include ManualInverseAssociation include ManualInverseAssociation
......
...@@ -14,8 +14,8 @@ module Clusters ...@@ -14,8 +14,8 @@ module Clusters
else else
check_timeout check_timeout
end end
rescue Kubeclient::HttpError => ke rescue Kubeclient::HttpError
app.make_errored!("Kubernetes error: #{ke.message}") unless app.errored? app.make_errored!("Kubernetes error") unless app.errored?
end end
private private
...@@ -27,7 +27,7 @@ module Clusters ...@@ -27,7 +27,7 @@ module Clusters
end end
def on_failed def on_failed
app.make_errored!(installation_errors || 'Installation silently failed') app.make_errored!('Installation failed')
ensure ensure
remove_installation_pod remove_installation_pod
end end
......
...@@ -12,10 +12,10 @@ module Clusters ...@@ -12,10 +12,10 @@ module Clusters
ClusterWaitForAppInstallationWorker.perform_in( ClusterWaitForAppInstallationWorker.perform_in(
ClusterWaitForAppInstallationWorker::INTERVAL, app.name, app.id) ClusterWaitForAppInstallationWorker::INTERVAL, app.name, app.id)
rescue Kubeclient::HttpError => ke rescue Kubeclient::HttpError
app.make_errored!("Kubernetes error: #{ke.message}") app.make_errored!("Kubernetes error.")
rescue StandardError => e rescue StandardError
app.make_errored!("Can't start installation process. #{e.message}") app.make_errored!("Can't start installation process.")
end end
end end
end end
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
.project-empty-note-panel .project-empty-note-panel
%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] } %div{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
.prepend-top-20 .prepend-top-20
%h4 %h4.append-bottom-20
= _('The repository for this project is empty') = _('The repository for this project is empty')
- if @project.can_current_user_push_code? - 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 ...@@ -3045,6 +3045,10 @@ ActiveRecord::Schema.define(version: 20180920043317) do
t.boolean "job_events", default: false, null: false t.boolean "job_events", default: false, null: false
t.boolean "confidential_note_events" t.boolean "confidential_note_events"
t.text "push_events_branch_filter" 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 end
add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree
......
...@@ -968,7 +968,13 @@ X-Gitlab-Event: Pipeline Hook ...@@ -968,7 +968,13 @@ X-Gitlab-Event: Pipeline Hook
], ],
"created_at": "2016-08-12 15:23:28 UTC", "created_at": "2016-08-12 15:23:28 UTC",
"finished_at": "2016-08-12 15:26:29 UTC", "finished_at": "2016-08-12 15:26:29 UTC",
"duration": 63 "duration": 63,
"variables": [
{
"key": "NESTOR_PROD_ENVIRONMENT",
"value": "us-west-1"
}
]
}, },
"user":{ "user":{
"name": "Administrator", "name": "Administrator",
......
# frozen_string_literal: true
module API module API
class AccessRequests < Grape::API class AccessRequests < Grape::API
include PaginationParams include PaginationParams
......
# frozen_string_literal: true
module API module API
class API < Grape::API class API < Grape::API
include APIGuard include APIGuard
......
# frozen_string_literal: true
# Guard API with OAuth 2.0 Access Token # Guard API with OAuth 2.0 Access Token
require 'rack/oauth2' require 'rack/oauth2'
......
# frozen_string_literal: true
module API module API
# External applications API # External applications API
class Applications < Grape::API class Applications < Grape::API
......
# frozen_string_literal: true
module API module API
class Avatar < Grape::API class Avatar < Grape::API
resource :avatar do resource :avatar do
......
# frozen_string_literal: true
module API module API
class AwardEmoji < Grape::API class AwardEmoji < Grape::API
include PaginationParams include PaginationParams
......
# frozen_string_literal: true
module API module API
class Badges < Grape::API class Badges < Grape::API
include PaginationParams include PaginationParams
......
# frozen_string_literal: true
module API module API
class Boards < Grape::API class Boards < Grape::API
include BoardsResponses include BoardsResponses
......
# frozen_string_literal: true
module API module API
module BoardsResponses module BoardsResponses
extend ActiveSupport::Concern extend ActiveSupport::Concern
......
# frozen_string_literal: true
require 'mime/types' require 'mime/types'
module API module API
......
# frozen_string_literal: true
module API module API
class BroadcastMessages < Grape::API class BroadcastMessages < Grape::API
include PaginationParams include PaginationParams
......
# frozen_string_literal: true
module API module API
class CircuitBreakers < Grape::API class CircuitBreakers < Grape::API
before { authenticated_as_admin! } before { authenticated_as_admin! }
......
# frozen_string_literal: true
require 'mime/types' require 'mime/types'
module API module API
......
# frozen_string_literal: true
require 'mime/types' require 'mime/types'
module API module API
......
# frozen_string_literal: true
module API module API
module CustomAttributesEndpoints module CustomAttributesEndpoints
extend ActiveSupport::Concern extend ActiveSupport::Concern
......
# frozen_string_literal: true
module API module API
class DeployKeys < Grape::API class DeployKeys < Grape::API
include PaginationParams include PaginationParams
......
# frozen_string_literal: true
module API module API
# Deployments RESTful API endpoints # Deployments RESTful API endpoints
class Deployments < Grape::API class Deployments < Grape::API
......
# frozen_string_literal: true
module API module API
class Discussions < Grape::API class Discussions < Grape::API
include PaginationParams include PaginationParams
......
# frozen_string_literal: true
module API module API
module Entities module Entities
class WikiPageBasic < Grape::Entity class WikiPageBasic < Grape::Entity
......
# frozen_string_literal: true
module API module API
# Environments RESTfull API endpoints # Environments RESTfull API endpoints
class Environments < Grape::API class Environments < Grape::API
......
# frozen_string_literal: true
module API module API
class Events < Grape::API class Events < Grape::API
include PaginationParams include PaginationParams
...@@ -16,12 +18,27 @@ module API ...@@ -16,12 +18,27 @@ module API
desc: 'Return events sorted in ascending and descending order' desc: 'Return events sorted in ascending and descending order'
end 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 # rubocop: disable CodeReuse/ActiveRecord
def present_events(events) def present_events(events, redact: true)
events = events.reorder(created_at: params[:sort]) events = events.reorder(created_at: params[:sort])
.with_associations .with_associations
present paginate(events), with: Entities::Event events = paginate(events)
events = redact_events(events) if redact
present events, with: Entities::Event
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
end end
...@@ -44,7 +61,8 @@ module API ...@@ -44,7 +61,8 @@ module API
events = EventsFinder.new(params.merge(source: current_user, current_user: current_user)).execute.preload(:author, :target) 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 end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
end end
......
# frozen_string_literal: true
module API module API
class Features < Grape::API class Features < Grape::API
before { authenticated_as_admin! } before { authenticated_as_admin! }
......
# frozen_string_literal: true
module API module API
class Files < Grape::API class Files < Grape::API
FILE_ENDPOINT_REQUIREMENTS = API::PROJECT_ENDPOINT_REQUIREMENTS.merge(file_path: API::NO_SLASH_URL_PART_REGEX) FILE_ENDPOINT_REQUIREMENTS = API::PROJECT_ENDPOINT_REQUIREMENTS.merge(file_path: API::NO_SLASH_URL_PART_REGEX)
......
# frozen_string_literal: true
module API module API
class GroupBoards < Grape::API class GroupBoards < Grape::API
include BoardsResponses include BoardsResponses
......
# frozen_string_literal: true
module API module API
class GroupMilestones < Grape::API class GroupMilestones < Grape::API
include MilestoneResponses include MilestoneResponses
......
# frozen_string_literal: true
module API module API
class GroupVariables < Grape::API class GroupVariables < Grape::API
include PaginationParams include PaginationParams
......
# frozen_string_literal: true
module API module API
class Groups < Grape::API class Groups < Grape::API
include PaginationParams include PaginationParams
......
# frozen_string_literal: true
module API module API
module Helpers module Helpers
prepend EE::API::Helpers prepend EE::API::Helpers
...@@ -394,9 +396,10 @@ module API ...@@ -394,9 +396,10 @@ module API
# lifted from https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb#L60 # lifted from https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb#L60
trace = exception.backtrace 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 << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
message << " " << trace.join("\n ") message << " " << trace.join("\n ")
message = message.join
API.logger.add Logger::FATAL, message API.logger.add Logger::FATAL, message
......
# frozen_string_literal: true
module API module API
module Helpers module Helpers
module BadgesHelpers module BadgesHelpers
......
# frozen_string_literal: true
module API module API
module Helpers module Helpers
module CommonHelpers module CommonHelpers
......
# frozen_string_literal: true
module API module API
module Helpers module Helpers
module CustomAttributes module CustomAttributes
......
# frozen_string_literal: true
module API module API
module Helpers module Helpers
module CustomValidators module CustomValidators
......
# frozen_string_literal: true
module API module API
module Helpers module Helpers
module HeadersHelpers module HeadersHelpers
......
# frozen_string_literal: true
module API module API
module Helpers module Helpers
module InternalHelpers module InternalHelpers
......
# frozen_string_literal: true
# rubocop:disable GitlabSecurity/PublicSend # rubocop:disable GitlabSecurity/PublicSend
module API module API
......
# frozen_string_literal: true
module API module API
module Helpers module Helpers
module NotesHelpers module NotesHelpers
......
# frozen_string_literal: true
module API module API
module Helpers module Helpers
module Pagination module Pagination
......
# frozen_string_literal: true
module API module API
module Helpers module Helpers
module ProjectSnapshotsHelpers module ProjectSnapshotsHelpers
......
# frozen_string_literal: true
module API module API
module Helpers module Helpers
module ProjectsHelpers module ProjectsHelpers
......
# frozen_string_literal: true
module API module API
module Helpers module Helpers
module RelatedResourcesHelpers module RelatedResourcesHelpers
......
# frozen_string_literal: true
module API module API
module Helpers module Helpers
module Runner module Runner
......
# frozen_string_literal: true
module API module API
# Internal access API # Internal access API
class Internal < Grape::API class Internal < Grape::API
......
# frozen_string_literal: true
module API module API
class Issues < Grape::API class Issues < Grape::API
include PaginationParams include PaginationParams
......
# frozen_string_literal: true
module API module API
class JobArtifacts < Grape::API class JobArtifacts < Grape::API
before { authenticate_non_get! } before { authenticate_non_get! }
......
# frozen_string_literal: true
module API module API
class Jobs < Grape::API class Jobs < Grape::API
include PaginationParams include PaginationParams
......
# frozen_string_literal: true
module API module API
# Keys API # Keys API
class Keys < Grape::API class Keys < Grape::API
......
# frozen_string_literal: true
module API module API
class Labels < Grape::API class Labels < Grape::API
include PaginationParams include PaginationParams
......
# frozen_string_literal: true
module API module API
class Lint < Grape::API class Lint < Grape::API
namespace :ci do namespace :ci do
......
# frozen_string_literal: true
module API module API
class Markdown < Grape::API class Markdown < Grape::API
params do params do
......
# frozen_string_literal: true
module API module API
class Members < Grape::API class Members < Grape::API
include PaginationParams include PaginationParams
......
# frozen_string_literal: true
module API module API
# MergeRequestDiff API # MergeRequestDiff API
class MergeRequestDiffs < Grape::API class MergeRequestDiffs < Grape::API
......
# frozen_string_literal: true
module API module API
class MergeRequests < Grape::API class MergeRequests < Grape::API
include PaginationParams include PaginationParams
......
# frozen_string_literal: true
module API module API
module MilestoneResponses module MilestoneResponses
extend ActiveSupport::Concern extend ActiveSupport::Concern
......
# frozen_string_literal: true
module API module API
class Namespaces < Grape::API class Namespaces < Grape::API
include PaginationParams include PaginationParams
......
# frozen_string_literal: true
module API module API
class Notes < Grape::API class Notes < Grape::API
include PaginationParams include PaginationParams
......
# frozen_string_literal: true
module API module API
# notification_settings API # notification_settings API
class NotificationSettings < Grape::API class NotificationSettings < Grape::API
......
# frozen_string_literal: true
module API module API
class PagesDomains < Grape::API class PagesDomains < Grape::API
include PaginationParams include PaginationParams
......
# frozen_string_literal: true
module API module API
# Concern for declare pagination params. # Concern for declare pagination params.
# #
......
# frozen_string_literal: true
module API module API
class PipelineSchedules < Grape::API class PipelineSchedules < Grape::API
include PaginationParams include PaginationParams
......
# frozen_string_literal: true
module API module API
class Pipelines < Grape::API class Pipelines < Grape::API
include PaginationParams include PaginationParams
......
# frozen_string_literal: true
module API module API
class ProjectExport < Grape::API class ProjectExport < Grape::API
before do before do
......
# frozen_string_literal: true
module API module API
class ProjectHooks < Grape::API class ProjectHooks < Grape::API
include PaginationParams include PaginationParams
......
# frozen_string_literal: true
module API module API
class ProjectImport < Grape::API class ProjectImport < Grape::API
include PaginationParams include PaginationParams
......
# frozen_string_literal: true
module API module API
class ProjectMilestones < Grape::API class ProjectMilestones < Grape::API
include PaginationParams include PaginationParams
......
# frozen_string_literal: true
module API module API
class ProjectSnapshots < Grape::API class ProjectSnapshots < Grape::API
helpers ::API::Helpers::ProjectSnapshotsHelpers helpers ::API::Helpers::ProjectSnapshotsHelpers
......
# frozen_string_literal: true
module API module API
class ProjectSnippets < Grape::API class ProjectSnippets < Grape::API
include PaginationParams include PaginationParams
......
# frozen_string_literal: true
require_dependency 'declarative_policy' require_dependency 'declarative_policy'
module API module API
......
# frozen_string_literal: true
module API module API
module ProjectsRelationBuilder module ProjectsRelationBuilder
extend ActiveSupport::Concern extend ActiveSupport::Concern
......
# frozen_string_literal: true
module API module API
class ProtectedBranches < Grape::API class ProtectedBranches < Grape::API
include PaginationParams include PaginationParams
......
# frozen_string_literal: true
module API module API
class ProtectedTags < Grape::API class ProtectedTags < Grape::API
include PaginationParams include PaginationParams
......
# frozen_string_literal: true
require 'mime/types' require 'mime/types'
module API module API
......
# frozen_string_literal: true
module API module API
class Runner < Grape::API class Runner < Grape::API
helpers ::API::Helpers::Runner helpers ::API::Helpers::Runner
......
# frozen_string_literal: true
module API module API
class Runners < Grape::API class Runners < Grape::API
include PaginationParams include PaginationParams
......
# frozen_string_literal: true
# Encapsulate a scope used for authorization, such as `api`, or `read_user` # Encapsulate a scope used for authorization, such as `api`, or `read_user`
module API module API
class Scope class Scope
......
# frozen_string_literal: true
module API module API
class Search < Grape::API class Search < Grape::API
include PaginationParams include PaginationParams
......
# frozen_string_literal: true
module API module API
class Settings < Grape::API class Settings < Grape::API
before { authenticated_as_admin! } before { authenticated_as_admin! }
......
# frozen_string_literal: true
require 'sidekiq/api' require 'sidekiq/api'
module API module API
......
# frozen_string_literal: true
module API module API
# Snippets API # Snippets API
class Snippets < Grape::API class Snippets < Grape::API
......
# frozen_string_literal: true
module API module API
class Subscriptions < Grape::API class Subscriptions < Grape::API
before { authenticate! } before { authenticate! }
......
# frozen_string_literal: true
module API module API
class SystemHooks < Grape::API class SystemHooks < Grape::API
include PaginationParams include PaginationParams
......
# frozen_string_literal: true
module API module API
class Tags < Grape::API class Tags < Grape::API
include PaginationParams include PaginationParams
......
# frozen_string_literal: true
module API module API
class Templates < Grape::API class Templates < Grape::API
include PaginationParams include PaginationParams
......
# frozen_string_literal: true
module API module API
module TimeTrackingEndpoints module TimeTrackingEndpoints
extend ActiveSupport::Concern extend ActiveSupport::Concern
......
# frozen_string_literal: true
module API module API
class Todos < Grape::API class Todos < Grape::API
include PaginationParams include PaginationParams
......
# frozen_string_literal: true
module API module API
class Triggers < Grape::API class Triggers < Grape::API
include PaginationParams include PaginationParams
......
# frozen_string_literal: true
module API module API
class Users < Grape::API class Users < Grape::API
include PaginationParams include PaginationParams
......
# frozen_string_literal: true
module API module API
class Variables < Grape::API class Variables < Grape::API
include PaginationParams include PaginationParams
......
# frozen_string_literal: true
module API module API
class Version < Grape::API class Version < Grape::API
before { authenticate! } before { authenticate! }
......
# frozen_string_literal: true
module API module API
class Wikis < Grape::API class Wikis < Grape::API
helpers do helpers do
......
# frozen_string_literal: true
require 'backup/files' require 'backup/files'
module Backup module Backup
......
# frozen_string_literal: true
require 'backup/files' require 'backup/files'
module Backup module Backup
......
# frozen_string_literal: true
require 'yaml' require 'yaml'
module Backup module Backup
......
# frozen_string_literal: true
require 'open3' require 'open3'
require_relative 'helper' require_relative 'helper'
......
# frozen_string_literal: true
module Backup module Backup
module Helper module Helper
def access_denied_error(path) def access_denied_error(path)
......
# frozen_string_literal: true
require 'backup/files' require 'backup/files'
module Backup module Backup
......
# frozen_string_literal: true
module Backup module Backup
class Manager class Manager
ARCHIVES_TO_BACKUP = %w[uploads builds artifacts pages lfs registry].freeze ARCHIVES_TO_BACKUP = %w[uploads builds artifacts pages lfs registry].freeze
......
# frozen_string_literal: true
require 'backup/files' require 'backup/files'
module Backup module Backup
......
# frozen_string_literal: true
require 'backup/files' require 'backup/files'
module Backup module Backup
......
# frozen_string_literal: true
require 'yaml' require 'yaml'
module Backup module Backup
......
# frozen_string_literal: true
require 'backup/files' require 'backup/files'
module Backup 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 ...@@ -26,7 +26,8 @@ module Gitlab
stages: pipeline.stages_names, stages: pipeline.stages_names,
created_at: pipeline.created_at, created_at: pipeline.created_at,
finished_at: pipeline.finished_at, finished_at: pipeline.finished_at,
duration: pipeline.duration duration: pipeline.duration,
variables: pipeline.variables.map(&:hook_attrs)
} }
end end
......
module Gitlab module Gitlab
class Highlight class Highlight
TIMEOUT_BACKGROUND = 30.seconds
TIMEOUT_FOREGROUND = 3.seconds
def self.highlight(blob_name, blob_content, repository: nil, plain: false) def self.highlight(blob_name, blob_content, repository: nil, plain: false)
new(blob_name, blob_content, repository: repository) new(blob_name, blob_content, repository: repository)
.highlight(blob_content, continue: false, plain: plain) .highlight(blob_content, continue: false, plain: plain)
...@@ -51,11 +54,20 @@ module Gitlab ...@@ -51,11 +54,20 @@ module Gitlab
end end
def highlight_rich(text, continue: true) 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 rescue
highlight_plain(text) highlight_plain(text)
end end
def timeout_time
Sidekiq.server? ? TIMEOUT_BACKGROUND : TIMEOUT_FOREGROUND
end
def link_dependencies(text, highlighted_text) def link_dependencies(text, highlighted_text)
Gitlab::DependencyLinker.link(blob_name, text, highlighted_text) Gitlab::DependencyLinker.link(blob_name, text, highlighted_text)
end end
......
...@@ -158,6 +158,12 @@ excluded_attributes: ...@@ -158,6 +158,12 @@ excluded_attributes:
- :reference - :reference
- :reference_html - :reference_html
- :epic_id - :epic_id
hooks:
- :token
- :encrypted_token
- :encrypted_token_iv
- :encrypted_url
- :encrypted_url_iv
methods: methods:
labels: labels:
......
...@@ -728,4 +728,80 @@ describe ApplicationController do ...@@ -728,4 +728,80 @@ describe ApplicationController do
end end
end 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 end
...@@ -18,6 +18,23 @@ describe 'Issue Detail', :js do ...@@ -18,6 +18,23 @@ describe 'Issue Detail', :js do
end end
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 context 'when edited by a user who is later deleted' do
before do before do
sign_in(user) 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 ...@@ -11,10 +11,6 @@ describe Backup::Manager do
allow(progress).to receive(:puts) allow(progress).to receive(:puts)
allow(progress).to receive(:print) 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 @old_progress = $progress # rubocop:disable Style/GlobalVars
$progress = progress # rubocop:disable Style/GlobalVars $progress = progress # rubocop:disable Style/GlobalVars
end end
......
...@@ -11,10 +11,6 @@ describe Backup::Repository do ...@@ -11,10 +11,6 @@ describe Backup::Repository do
allow(FileUtils).to receive(:mkdir_p).and_return(true) allow(FileUtils).to receive(:mkdir_p).and_return(true)
allow(FileUtils).to receive(:mv).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) allow_any_instance_of(described_class).to receive(:progress).and_return(progress)
end 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
...@@ -20,18 +20,35 @@ describe Gitlab::DataBuilder::Pipeline do ...@@ -20,18 +20,35 @@ describe Gitlab::DataBuilder::Pipeline do
let(:build_data) { data[:builds].first } let(:build_data) { data[:builds].first }
let(:project_data) { data[:project] } let(:project_data) { data[:project] }
it { expect(attributes).to be_a(Hash) } it 'has correct attributes' do
it { expect(attributes[:ref]).to eq(pipeline.ref) } expect(attributes).to be_a(Hash)
it { expect(attributes[:sha]).to eq(pipeline.sha) } expect(attributes[:ref]).to eq(pipeline.ref)
it { expect(attributes[:tag]).to eq(pipeline.tag) } expect(attributes[:sha]).to eq(pipeline.sha)
it { expect(attributes[:id]).to eq(pipeline.id) } expect(attributes[:tag]).to eq(pipeline.tag)
it { expect(attributes[:status]).to eq(pipeline.status) } expect(attributes[:id]).to eq(pipeline.id)
it { expect(attributes[:detailed_status]).to eq('passed') } expect(attributes[:status]).to eq(pipeline.status)
expect(attributes[:detailed_status]).to eq('passed')
it { expect(build_data).to be_a(Hash) } expect(build_data).to be_a(Hash)
it { expect(build_data[:id]).to eq(build.id) } expect(build_data[:id]).to eq(build.id)
it { expect(build_data[:status]).to eq(build.status) } expect(build_data[:status]).to eq(build.status)
expect(project_data).to eq(project.hook_attrs(backward: false))
it { expect(project_data).to eq(project.hook_attrs(backward: false)) } end
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
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
end end
...@@ -56,5 +56,22 @@ describe Gitlab::Highlight do ...@@ -56,5 +56,22 @@ describe Gitlab::Highlight do
described_class.highlight('file.name', 'Contents') described_class.highlight('file.name', 'Contents')
end 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
end end
...@@ -40,13 +40,14 @@ describe BlobViewer::PackageJson do ...@@ -40,13 +40,14 @@ describe BlobViewer::PackageJson do
end end
context 'when package.json has "private": true' do context 'when package.json has "private": true' do
let(:homepage) { 'http://example.com' }
let(:data) do let(:data) do
<<-SPEC.strip_heredoc <<-SPEC.strip_heredoc
{ {
"name": "module-name", "name": "module-name",
"version": "10.3.1", "version": "10.3.1",
"private": true, "private": true,
"homepage": "myawesomepackage.com" "homepage": #{homepage.to_json}
} }
SPEC SPEC
end end
...@@ -54,10 +55,22 @@ describe BlobViewer::PackageJson do ...@@ -54,10 +55,22 @@ describe BlobViewer::PackageJson do
subject { described_class.new(blob) } subject { described_class.new(blob) }
describe '#package_url' do describe '#package_url' do
it 'returns homepage if any' do context 'when the homepage has a valid URL' do
it 'returns homepage URL' do
expect(subject).to receive(:prepare!) expect(subject).to receive(:prepare!)
expect(subject.package_url).to eq('myawesomepackage.com') 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 be_nil
end
end end
end end
......
...@@ -5,4 +5,13 @@ describe Ci::PipelineVariable do ...@@ -5,4 +5,13 @@ describe Ci::PipelineVariable do
it { is_expected.to include_module(HasVariable) } it { is_expected.to include_module(HasVariable) }
it { is_expected.to validate_uniqueness_of(:key).scoped_to(:pipeline_id) } 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 end
...@@ -148,9 +148,14 @@ describe Event do ...@@ -148,9 +148,14 @@ describe Event do
let(:admin) { create(:admin) } let(:admin) { create(:admin) }
let(:issue) { create(:issue, project: project, author: author, assignees: [assignee]) } let(:issue) { create(:issue, project: project, author: author, assignees: [assignee]) }
let(:confidential_issue) { create(:issue, :confidential, 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_commit) { create(:note_on_commit, project: project) }
let(:note_on_issue) { create(:note_on_issue, noteable: issue, 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_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) } let(:event) { described_class.new(project: project, target: target, author_id: author.id) }
before do before do
...@@ -268,6 +273,125 @@ describe Event do ...@@ -268,6 +273,125 @@ describe Event do
end end
end 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 end
describe '.limit_recent' do describe '.limit_recent' do
......
...@@ -57,6 +57,12 @@ describe WebHook do ...@@ -57,6 +57,12 @@ describe WebHook do
end end
end end
describe 'encrypted attributes' do
subject { described_class.encrypted_attributes.keys }
it { is_expected.to contain_exactly(:token, :url) }
end
describe 'execute' do describe 'execute' do
let(:data) { { key: 'value' } } let(:data) { { key: 'value' } }
let(:hook_name) { 'project hook' } 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 ...@@ -82,7 +82,7 @@ describe Clusters::Applications::CheckInstallationProgressService do
service.execute service.execute
expect(application).to be_errored expect(application).to be_errored
expect(application.status_reason).to eq(errors) expect(application.status_reason).to eq("Installation failed")
end end
end end
......
...@@ -42,7 +42,7 @@ describe Clusters::Applications::InstallService do ...@@ -42,7 +42,7 @@ describe Clusters::Applications::InstallService do
service.execute service.execute
expect(application).to be_errored expect(application).to be_errored
expect(application.status_reason).to match(/kubernetes error:/i) expect(application.status_reason).to match('Kubernetes error.')
end end
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