Update upstream source from tag 'upstream/14.5.3+ds1'

Update to upstream version '14.5.3+ds1'
with Debian dir 61802afe6d
This commit is contained in:
Pirate Praveen 2022-01-12 13:01:40 +05:30
commit 5b1839d50c
81 changed files with 1198 additions and 868 deletions

View file

@ -2,6 +2,10 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 14.5.3 (2022-01-11)
No changes.
## 14.5.2 (2021-12-03) ## 14.5.2 (2021-12-03)
No changes. No changes.

View file

@ -1 +1 @@
14.5.2 14.5.3

View file

@ -1 +1 @@
14.5.2 14.5.3

View file

@ -64,10 +64,12 @@ class GlEmoji extends HTMLElement {
this.classList.add('emoji-icon'); this.classList.add('emoji-icon');
this.classList.add(fallbackSpriteClass); this.classList.add(fallbackSpriteClass);
} else if (hasImageFallback) { } else if (hasImageFallback) {
this.innerHTML = emojiImageTag(name, fallbackSrc); this.innerHTML = '';
this.appendChild(emojiImageTag(name, fallbackSrc));
} else { } else {
const src = emojiFallbackImageSrc(name); const src = emojiFallbackImageSrc(name);
this.innerHTML = emojiImageTag(name, src); this.innerHTML = '';
this.appendChild(emojiImageTag(name, src));
} }
} }
}); });

View file

@ -211,7 +211,17 @@ export function emojiFallbackImageSrc(inputName) {
} }
export function emojiImageTag(name, src) { export function emojiImageTag(name, src) {
return `<img class="emoji" title=":${name}:" alt=":${name}:" src="${src}" width="20" height="20" align="absmiddle" />`; const img = document.createElement('img');
img.className = 'emoji';
img.setAttribute('title', `:${name}:`);
img.setAttribute('alt', `:${name}:`);
img.setAttribute('src', src);
img.setAttribute('width', '20');
img.setAttribute('height', '20');
img.setAttribute('align', 'absmiddle');
return img;
} }
export function glEmojiTag(inputName, options) { export function glEmojiTag(inputName, options) {

View file

@ -20,7 +20,7 @@ module SessionlessAuthentication
end end
def sessionless_sign_in(user) def sessionless_sign_in(user)
if user && can?(user, :log_in) if can?(user, :log_in) && !user.password_expired_if_applicable?
# Notice we are passing store false, so the user is not # Notice we are passing store false, so the user is not
# actually stored in the session and a token is needed # actually stored in the session and a token is needed
# for every request. If you want the token to work as a # for every request. If you want the token to work as a

View file

@ -28,9 +28,15 @@ class Import::GithubController < Import::BaseController
end end
def callback def callback
auth_state = session[auth_state_key]
session[auth_state_key] = nil
if auth_state.blank? || !ActiveSupport::SecurityUtils.secure_compare(auth_state, params[:state])
provider_unauthorized
else
session[access_token_key] = get_token(params[:code]) session[access_token_key] = get_token(params[:code])
redirect_to status_import_url redirect_to status_import_url
end end
end
def personal_access_token def personal_access_token
session[access_token_key] = params[:personal_access_token]&.strip session[access_token_key] = params[:personal_access_token]&.strip
@ -154,13 +160,16 @@ class Import::GithubController < Import::BaseController
end end
def authorize_url def authorize_url
state = SecureRandom.base64(64)
session[auth_state_key] = state
if Feature.enabled?(:remove_legacy_github_client) if Feature.enabled?(:remove_legacy_github_client)
oauth_client.auth_code.authorize_url( oauth_client.auth_code.authorize_url(
redirect_uri: callback_import_url, redirect_uri: callback_import_url,
scope: 'repo, user, user:email' scope: 'repo, user, user:email',
state: state
) )
else else
client.authorize_url(callback_import_url) client.authorize_url(callback_import_url, state)
end end
end end
@ -219,6 +228,10 @@ class Import::GithubController < Import::BaseController
alert: _('Missing OAuth configuration for GitHub.') alert: _('Missing OAuth configuration for GitHub.')
end end
def auth_state_key
:"#{provider_name}_auth_state_key"
end
def access_token_key def access_token_key
:"#{provider_name}_access_token" :"#{provider_name}_access_token"
end end

View file

@ -36,7 +36,7 @@ module Resolvers
def unconditional_includes def unconditional_includes
[ [
{ {
project: [:project_feature] project: [:project_feature, :group]
}, },
:author :author
] ]

View file

@ -6,6 +6,13 @@ module Integrations
def notify(message, opts) def notify(message, opts)
# See https://gitlab.com/gitlab-org/slack-notifier/#custom-http-client # See https://gitlab.com/gitlab-org/slack-notifier/#custom-http-client
#
# TODO: By default both Markdown and HTML links are converted into Slack "mrkdwn" syntax,
# but it seems we only need to support Markdown and could disable HTML.
#
# See:
# - https://gitlab.com/gitlab-org/slack-notifier#middleware
# - https://gitlab.com/gitlab-org/gitlab/-/issues/347048
notifier = ::Slack::Messenger.new(webhook, opts.merge(http_client: HTTPClient)) notifier = ::Slack::Messenger.new(webhook, opts.merge(http_client: HTTPClient))
notifier.ping( notifier.ping(
message.pretext, message.pretext,

View file

@ -23,7 +23,7 @@ module Integrations
def attachments def attachments
[{ [{
title: title, title: strip_markup(title),
title_link: alert_url, title_link: alert_url,
color: attachment_color, color: attachment_color,
fields: attachment_fields fields: attachment_fields
@ -31,7 +31,7 @@ module Integrations
end end
def message def message
"Alert firing in #{project_name}" "Alert firing in #{strip_markup(project_name)}"
end end
private private

View file

@ -5,6 +5,10 @@ module Integrations
class BaseMessage class BaseMessage
RELATIVE_LINK_REGEX = %r{!\[[^\]]*\]\((/uploads/[^\)]*)\)}.freeze RELATIVE_LINK_REGEX = %r{!\[[^\]]*\]\((/uploads/[^\)]*)\)}.freeze
# Markup characters which are used for links in HTML, Markdown,
# and Slack "mrkdwn" syntax (`<http://example.com|Label>`).
UNSAFE_MARKUP_CHARACTERS = '<>[]|'
attr_reader :markdown attr_reader :markdown
attr_reader :user_full_name attr_reader :user_full_name
attr_reader :user_name attr_reader :user_name
@ -65,12 +69,26 @@ module Integrations
string.gsub(RELATIVE_LINK_REGEX, "#{project_url}\\1") string.gsub(RELATIVE_LINK_REGEX, "#{project_url}\\1")
end end
# Remove unsafe markup from user input, which can be used to hijack links in our own markup,
# or insert new ones.
#
# This currently removes Markdown and Slack "mrkdwn" links (keeping the link label),
# and all HTML markup (keeping the text nodes).
# We can't just escape the markup characters, because each chat app handles this differently.
#
# See:
# - https://api.slack.com/reference/surfaces/formatting#escaping
# - https://gitlab.com/gitlab-org/slack-notifier#escaping
def strip_markup(string)
string&.delete(UNSAFE_MARKUP_CHARACTERS)
end
def attachment_color def attachment_color
'#345' '#345'
end end
def link(text, url) def link(text, url)
"[#{text}](#{url})" "[#{strip_markup(text)}](#{url})"
end end
def pretty_duration(seconds) def pretty_duration(seconds)

View file

@ -27,7 +27,7 @@ module Integrations
def attachments def attachments
[{ [{
text: "#{project_link} with job #{deployment_link} by #{user_link}\n#{commit_link}: #{commit_title}", text: "#{project_link} with job #{deployment_link} by #{user_link}\n#{commit_link}: #{strip_markup(commit_title)}",
color: color color: color
}] }]
end end
@ -40,9 +40,9 @@ module Integrations
def message def message
if running? if running?
"Starting deploy to #{environment}" "Starting deploy to #{strip_markup(environment)}"
else else
"Deploy to #{environment} #{humanized_status}" "Deploy to #{strip_markup(environment)} #{humanized_status}"
end end
end end

View file

@ -32,7 +32,7 @@ module Integrations
def activity def activity
{ {
title: "Issue #{state} by #{user_combined_name}", title: "Issue #{state} by #{strip_markup(user_combined_name)}",
subtitle: "in #{project_link}", subtitle: "in #{project_link}",
text: issue_link, text: issue_link,
image: user_avatar image: user_avatar
@ -42,7 +42,7 @@ module Integrations
private private
def message def message
"[#{project_link}] Issue #{issue_link} #{state} by #{user_combined_name}" "[#{project_link}] Issue #{issue_link} #{state} by #{strip_markup(user_combined_name)}"
end end
def opened_issue? def opened_issue?
@ -67,7 +67,7 @@ module Integrations
end end
def issue_title def issue_title
"#{Issue.reference_prefix}#{issue_iid} #{title}" "#{Issue.reference_prefix}#{issue_iid} #{strip_markup(title)}"
end end
end end
end end

View file

@ -29,7 +29,7 @@ module Integrations
def activity def activity
{ {
title: "Merge request #{state_or_action_text} by #{user_combined_name}", title: "Merge request #{state_or_action_text} by #{strip_markup(user_combined_name)}",
subtitle: "in #{project_link}", subtitle: "in #{project_link}",
text: merge_request_link, text: merge_request_link,
image: user_avatar image: user_avatar
@ -39,7 +39,7 @@ module Integrations
private private
def format_title(title) def format_title(title)
'*' + title.lines.first.chomp + '*' '*' + strip_markup(title.lines.first.chomp) + '*'
end end
def message def message
@ -51,7 +51,7 @@ module Integrations
end end
def merge_request_message def merge_request_message
"#{user_combined_name} #{state_or_action_text} merge request #{merge_request_link} in #{project_link}" "#{strip_markup(user_combined_name)} #{state_or_action_text} merge request #{merge_request_link} in #{project_link}"
end end
def merge_request_link def merge_request_link
@ -59,7 +59,7 @@ module Integrations
end end
def merge_request_title def merge_request_title
"#{MergeRequest.reference_prefix}#{merge_request_iid} #{title}" "#{MergeRequest.reference_prefix}#{merge_request_iid} #{strip_markup(title)}"
end end
def merge_request_url def merge_request_url

View file

@ -35,9 +35,9 @@ module Integrations
def activity def activity
{ {
title: "#{user_combined_name} #{link('commented on ' + target, note_url)}", title: "#{strip_markup(user_combined_name)} #{link('commented on ' + target, note_url)}",
subtitle: "in #{project_link}", subtitle: "in #{project_link}",
text: formatted_title, text: strip_markup(formatted_title),
image: user_avatar image: user_avatar
} }
end end
@ -45,7 +45,7 @@ module Integrations
private private
def message def message
"#{user_combined_name} #{link('commented on ' + target, note_url)} in #{project_link}: *#{formatted_title}*" "#{strip_markup(user_combined_name)} #{link('commented on ' + target, note_url)} in #{project_link}: *#{strip_markup(formatted_title)}*"
end end
def format_title(title) def format_title(title)

View file

@ -56,7 +56,7 @@ module Integrations
[{ [{
fallback: format(message), fallback: format(message),
color: attachment_color, color: attachment_color,
author_name: user_combined_name, author_name: strip_markup(user_combined_name),
author_icon: user_avatar, author_icon: user_avatar,
author_link: author_url, author_link: author_url,
title: s_("ChatMessage|Pipeline #%{pipeline_id} %{humanized_status} in %{duration}") % title: s_("ChatMessage|Pipeline #%{pipeline_id} %{humanized_status} in %{duration}") %
@ -80,7 +80,7 @@ module Integrations
pipeline_link: pipeline_link, pipeline_link: pipeline_link,
ref_type: ref_type, ref_type: ref_type,
ref_link: ref_link, ref_link: ref_link,
user_combined_name: user_combined_name, user_combined_name: strip_markup(user_combined_name),
humanized_status: humanized_status humanized_status: humanized_status
}, },
subtitle: s_("ChatMessage|in %{project_link}") % { project_link: project_link }, subtitle: s_("ChatMessage|in %{project_link}") % { project_link: project_link },
@ -154,7 +154,7 @@ module Integrations
pipeline_link: pipeline_link, pipeline_link: pipeline_link,
ref_type: ref_type, ref_type: ref_type,
ref_link: ref_link, ref_link: ref_link,
user_combined_name: user_combined_name, user_combined_name: strip_markup(user_combined_name),
humanized_status: humanized_status, humanized_status: humanized_status,
duration: pretty_duration(duration) duration: pretty_duration(duration)
} }
@ -189,7 +189,7 @@ module Integrations
end end
def ref_link def ref_link
"[#{ref}](#{ref_url})" link(ref, ref_url)
end end
def project_url def project_url
@ -197,7 +197,7 @@ module Integrations
end end
def project_link def project_link
"[#{project.name}](#{project_url})" link(project.name, project_url)
end end
def pipeline_failed_jobs_url def pipeline_failed_jobs_url
@ -213,7 +213,7 @@ module Integrations
end end
def pipeline_link def pipeline_link
"[##{pipeline_id}](#{pipeline_url})" link("##{pipeline_id}", pipeline_url)
end end
def job_url(job) def job_url(job)
@ -221,7 +221,7 @@ module Integrations
end end
def job_link(job) def job_link(job)
"[#{job[:name]}](#{job_url(job)})" link(job[:name], job_url(job))
end end
def failed_jobs_links def failed_jobs_links
@ -242,7 +242,7 @@ module Integrations
def stage_link(stage) def stage_link(stage)
# All stages link to the pipeline page # All stages link to the pipeline page
"[#{stage}](#{pipeline_url})" link(stage, pipeline_url)
end end
def failed_stages_links def failed_stages_links
@ -254,7 +254,7 @@ module Integrations
end end
def commit_link def commit_link
"[#{commit.title}](#{commit_url})" link(commit.title, commit_url)
end end
def author_url def author_url

View file

@ -39,7 +39,7 @@ module Integrations
def humanized_action(short: false) def humanized_action(short: false)
action, ref_link, target_link = compose_action_details action, ref_link, target_link = compose_action_details
text = [user_combined_name, action, ref_type, ref_link] text = [strip_markup(user_combined_name), action, ref_type, ref_link]
text << target_link unless short text << target_link unless short
text.join(' ') text.join(' ')
end end
@ -67,7 +67,7 @@ module Integrations
url = commit[:url] url = commit[:url]
"[#{id}](#{url}): #{title} - #{author}" "#{link(id, url)}: #{strip_markup(title)} - #{strip_markup(author)}"
end end
def new_branch? def new_branch?
@ -91,15 +91,15 @@ module Integrations
end end
def ref_link def ref_link
"[#{ref}](#{ref_url})" link(ref, ref_url)
end end
def project_link def project_link
"[#{project_name}](#{project_url})" link(project_name, project_url)
end end
def compare_link def compare_link
"[Compare changes](#{compare_url})" link('Compare changes', compare_url)
end end
def compose_action_details def compose_action_details

View file

@ -36,9 +36,9 @@ module Integrations
def activity def activity
{ {
title: "#{user_combined_name} #{action} #{wiki_page_link}", title: "#{strip_markup(user_combined_name)} #{action} #{wiki_page_link}",
subtitle: "in #{project_link}", subtitle: "in #{project_link}",
text: title, text: strip_markup(title),
image: user_avatar image: user_avatar
} }
end end
@ -46,7 +46,7 @@ module Integrations
private private
def message def message
"#{user_combined_name} #{action} #{wiki_page_link} (#{diff_link}) in #{project_link}: *#{title}*" "#{strip_markup(user_combined_name)} #{action} #{wiki_page_link} (#{diff_link}) in #{project_link}: *#{strip_markup(title)}*"
end end
def description_message def description_message

View file

@ -72,10 +72,24 @@ module Packages
end end
end end
# TODO (technical debt): Extract the package size calculation to its own component and unit test it separately.
def calculated_package_file_size
strong_memoize(:calculated_package_file_size) do
# This calculation is based on:
# 1. 4 chars in a Base64 encoded string are 3 bytes in the original string. Meaning 1 char is 0.75 bytes.
# 2. The encoded string may have 1 or 2 extra '=' chars used for padding. Each padding char means 1 byte less in the original string.
# Reference:
# - https://blog.aaronlenoir.com/2017/11/10/get-original-length-from-base-64-string/
# - https://en.wikipedia.org/wiki/Base64#Decoding_Base64_with_padding
encoded_data = attachment['data']
((encoded_data.length * 0.75 ) - encoded_data[-2..].count('=')).to_i
end
end
def file_params def file_params
{ {
file: CarrierWaveStringFile.new(Base64.decode64(attachment['data'])), file: CarrierWaveStringFile.new(Base64.decode64(attachment['data'])),
size: attachment['length'], size: calculated_package_file_size,
file_sha1: version_data[:dist][:shasum], file_sha1: version_data[:dist][:shasum],
file_name: package_file_name, file_name: package_file_name,
build: params[:build] build: params[:build]
@ -88,7 +102,7 @@ module Packages
end end
def file_size_exceeded? def file_size_exceeded?
project.actual_limits.exceeded?(:npm_max_file_size, attachment['length'].to_i) project.actual_limits.exceeded?(:npm_max_file_size, calculated_package_file_size)
end end
end end
end end

View file

@ -291,6 +291,15 @@
:weight: 1 :weight: 1
:idempotent: true :idempotent: true
:tags: [] :tags: []
- :name: cronjob:dependency_proxy_cleanup_dependency_proxy
:worker_name: DependencyProxy::CleanupDependencyProxyWorker
:feature_category: :dependency_proxy
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent: true
:tags: []
- :name: cronjob:dependency_proxy_image_ttl_group_policy - :name: cronjob:dependency_proxy_image_ttl_group_policy
:worker_name: DependencyProxy::ImageTtlGroupPolicyWorker :worker_name: DependencyProxy::ImageTtlGroupPolicyWorker
:feature_category: :dependency_proxy :feature_category: :dependency_proxy

View file

@ -0,0 +1,17 @@
# frozen_string_literal: true
module DependencyProxy
module Expireable
extend ActiveSupport::Concern
UPDATE_BATCH_SIZE = 100
private
def expire_artifacts(collection)
collection.each_batch(of: UPDATE_BATCH_SIZE) do |batch|
batch.update_all(status: :expired)
end
end
end
end

View file

@ -0,0 +1,28 @@
# frozen_string_literal: true
module DependencyProxy
class CleanupDependencyProxyWorker
include ApplicationWorker
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
data_consistency :always
idempotent!
feature_category :dependency_proxy
def perform
enqueue_blob_cleanup_job if DependencyProxy::Blob.expired.any?
enqueue_manifest_cleanup_job if DependencyProxy::Manifest.expired.any?
end
private
def enqueue_blob_cleanup_job
DependencyProxy::CleanupBlobWorker.perform_with_capacity
end
def enqueue_manifest_cleanup_job
DependencyProxy::CleanupManifestWorker.perform_with_capacity
end
end
end

View file

@ -4,20 +4,19 @@ module DependencyProxy
class ImageTtlGroupPolicyWorker # rubocop:disable Scalability/IdempotentWorker class ImageTtlGroupPolicyWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker include ApplicationWorker
include CronjobQueue # rubocop:disable Scalability/CronWorkerContext include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
include DependencyProxy::Expireable
data_consistency :always data_consistency :always
feature_category :dependency_proxy feature_category :dependency_proxy
UPDATE_BATCH_SIZE = 100
def perform def perform
DependencyProxy::ImageTtlGroupPolicy.enabled.each do |policy| DependencyProxy::ImageTtlGroupPolicy.enabled.each do |policy|
qualified_blobs = policy.group.dependency_proxy_blobs.active.read_before(policy.ttl) qualified_blobs = policy.group.dependency_proxy_blobs.active.read_before(policy.ttl)
qualified_manifests = policy.group.dependency_proxy_manifests.active.read_before(policy.ttl) qualified_manifests = policy.group.dependency_proxy_manifests.active.read_before(policy.ttl)
enqueue_blob_cleanup_job if expire_artifacts(qualified_blobs, DependencyProxy::Blob) expire_artifacts(qualified_blobs)
enqueue_manifest_cleanup_job if expire_artifacts(qualified_manifests, DependencyProxy::Manifest) expire_artifacts(qualified_manifests)
end end
log_counts log_counts
@ -25,25 +24,6 @@ module DependencyProxy
private private
def expire_artifacts(artifacts, model)
rows_updated = false
artifacts.each_batch(of: UPDATE_BATCH_SIZE) do |batch|
rows = batch.update_all(status: :expired)
rows_updated ||= rows > 0
end
rows_updated
end
def enqueue_blob_cleanup_job
DependencyProxy::CleanupBlobWorker.perform_with_capacity
end
def enqueue_manifest_cleanup_job
DependencyProxy::CleanupManifestWorker.perform_with_capacity
end
def log_counts def log_counts
use_replica_if_available do use_replica_if_available do
expired_blob_count = DependencyProxy::Blob.expired.count expired_blob_count = DependencyProxy::Blob.expired.count

View file

@ -2,6 +2,7 @@
class PurgeDependencyProxyCacheWorker class PurgeDependencyProxyCacheWorker
include ApplicationWorker include ApplicationWorker
include DependencyProxy::Expireable
data_consistency :always data_consistency :always
@ -18,8 +19,8 @@ class PurgeDependencyProxyCacheWorker
return unless valid? return unless valid?
@group.dependency_proxy_blobs.destroy_all # rubocop:disable Cop/DestroyAll expire_artifacts(@group.dependency_proxy_blobs)
@group.dependency_proxy_manifests.destroy_all # rubocop:disable Cop/DestroyAll expire_artifacts(@group.dependency_proxy_manifests)
end end
private private

View file

@ -534,6 +534,9 @@ Settings.cron_jobs['container_expiration_policy_worker']['job_class'] = 'Contain
Settings.cron_jobs['image_ttl_group_policy_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['image_ttl_group_policy_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['image_ttl_group_policy_worker']['cron'] ||= '40 0 * * *' Settings.cron_jobs['image_ttl_group_policy_worker']['cron'] ||= '40 0 * * *'
Settings.cron_jobs['image_ttl_group_policy_worker']['job_class'] = 'DependencyProxy::ImageTtlGroupPolicyWorker' Settings.cron_jobs['image_ttl_group_policy_worker']['job_class'] = 'DependencyProxy::ImageTtlGroupPolicyWorker'
Settings.cron_jobs['cleanup_dependency_proxy_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['cleanup_dependency_proxy_worker']['cron'] ||= '20 3,15 * * *'
Settings.cron_jobs['cleanup_dependency_proxy_worker']['job_class'] = 'DependencyProxy::CleanupDependencyProxyWorker'
Settings.cron_jobs['x509_issuer_crl_check_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['x509_issuer_crl_check_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['x509_issuer_crl_check_worker']['cron'] ||= '30 1 * * *' Settings.cron_jobs['x509_issuer_crl_check_worker']['cron'] ||= '30 1 * * *'
Settings.cron_jobs['x509_issuer_crl_check_worker']['job_class'] = 'X509IssuerCrlCheckWorker' Settings.cron_jobs['x509_issuer_crl_check_worker']['job_class'] = 'X509IssuerCrlCheckWorker'

View file

@ -11,7 +11,8 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/11631) in GitLab 12.10. > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/11631) in GitLab 12.10.
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/273655) from GitLab Premium to GitLab Free in 13.6. > - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/273655) from GitLab Premium to GitLab Free in 13.6.
Deletes the cached manifests and blobs for a group. This endpoint requires the [Owner role](../user/permissions.md) Schedules for deletion the cached manifests and blobs for a group. This endpoint requires the
[Owner role](../user/permissions.md)
for the group. for the group.
```plaintext ```plaintext

View file

@ -6,15 +6,6 @@ module API
feature_category :dependency_proxy feature_category :dependency_proxy
helpers do
def obtain_new_purge_cache_lease
Gitlab::ExclusiveLease
.new("dependency_proxy:delete_group_blobs:#{user_group.id}",
timeout: 1.hour)
.try_obtain
end
end
after_validation do after_validation do
authorize! :admin_group, user_group authorize! :admin_group, user_group
end end
@ -29,9 +20,6 @@ module API
delete ':id/dependency_proxy/cache' do delete ':id/dependency_proxy/cache' do
not_found! unless user_group.dependency_proxy_feature_available? not_found! unless user_group.dependency_proxy_feature_available?
message = 'This request has already been made. It may take some time to purge the cache. You can run this at most once an hour for a given group'
render_api_error!(message, 409) unless obtain_new_purge_cache_lease
# rubocop:disable CodeReuse/Worker # rubocop:disable CodeReuse/Worker
PurgeDependencyProxyCacheWorker.perform_async(current_user.id, user_group.id) PurgeDependencyProxyCacheWorker.perform_async(current_user.id, user_group.id)
# rubocop:enable CodeReuse/Worker # rubocop:enable CodeReuse/Worker

View file

@ -613,6 +613,7 @@ module API
source_project = Project.find_by_id(params[:project_id]) source_project = Project.find_by_id(params[:project_id])
not_found!('Project') unless source_project && can?(current_user, :read_project, source_project) not_found!('Project') unless source_project && can?(current_user, :read_project, source_project)
forbidden!('Project') unless source_project && can?(current_user, :admin_project_member, source_project)
result = ::Members::ImportProjectTeamService.new(current_user, params).execute result = ::Members::ImportProjectTeamService.new(current_user, params).execute

View file

@ -48,10 +48,11 @@ module Gitlab
) )
end end
def authorize_url(redirect_uri) def authorize_url(redirect_uri, state = nil)
client.auth_code.authorize_url({ client.auth_code.authorize_url({
redirect_uri: redirect_uri, redirect_uri: redirect_uri,
scope: "repo, user, user:email" scope: "repo, user, user:email",
state: state
}) })
end end

View file

@ -252,13 +252,13 @@ module Gitlab
def internal_web?(uri) def internal_web?(uri)
uri.scheme == config.gitlab.protocol && uri.scheme == config.gitlab.protocol &&
uri.hostname == config.gitlab.host && uri.hostname == config.gitlab.host &&
(uri.port.blank? || uri.port == config.gitlab.port) get_port(uri) == config.gitlab.port
end end
def internal_shell?(uri) def internal_shell?(uri)
uri.scheme == 'ssh' && uri.scheme == 'ssh' &&
uri.hostname == config.gitlab_shell.ssh_host && uri.hostname == config.gitlab_shell.ssh_host &&
(uri.port.blank? || uri.port == config.gitlab_shell.ssh_port) get_port(uri) == config.gitlab_shell.ssh_port
end end
def domain_allowed?(uri) def domain_allowed?(uri)

View file

@ -8,10 +8,6 @@ RSpec.describe Dashboard::ProjectsController, :aggregate_failures do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
describe '#index' do describe '#index' do
context 'user not logged in' do
it_behaves_like 'authenticates sessionless user', :index, :atom
end
context 'user logged in' do context 'user logged in' do
let_it_be(:project) { create(:project, name: 'Project 1') } let_it_be(:project) { create(:project, name: 'Project 1') }
let_it_be(:project2) { create(:project, name: 'Project Two') } let_it_be(:project2) { create(:project, name: 'Project Two') }

View file

@ -72,9 +72,6 @@ RSpec.describe DashboardController do
end end
end end
it_behaves_like 'authenticates sessionless user', :issues, :atom, author_id: User.first
it_behaves_like 'authenticates sessionless user', :issues_calendar, :ics
describe "#check_filters_presence!" do describe "#check_filters_presence!" do
let(:user) { create(:user) } let(:user) { create(:user) }

View file

@ -1219,26 +1219,6 @@ RSpec.describe GroupsController, factory_default: :keep do
end end
end end
context 'token authentication' do
it_behaves_like 'authenticates sessionless user', :show, :atom, public: true do
before do
default_params.merge!(id: group)
end
end
it_behaves_like 'authenticates sessionless user', :issues, :atom, public: true do
before do
default_params.merge!(id: group, author_id: user.id)
end
end
it_behaves_like 'authenticates sessionless user', :issues_calendar, :ics, public: true do
before do
default_params.merge!(id: group)
end
end
end
describe 'external authorization' do describe 'external authorization' do
before do before do
group.add_owner(user) group.add_owner(user)

View file

@ -6,6 +6,7 @@ RSpec.describe Import::GithubController do
include ImportSpecHelper include ImportSpecHelper
let(:provider) { :github } let(:provider) { :github }
let(:new_import_url) { public_send("new_import_#{provider}_url") }
include_context 'a GitHub-ish import controller' include_context 'a GitHub-ish import controller'
@ -50,14 +51,38 @@ RSpec.describe Import::GithubController do
stub_omniauth_provider('github') stub_omniauth_provider('github')
end end
it "updates access token" do context "when auth state param is missing from session" do
it "reports an error" do
get :callback
expect(controller).to redirect_to(new_import_url)
expect(flash[:alert]).to eq('Access denied to your GitHub account.')
end
end
context "when auth state param is present in session" do
let(:valid_auth_state) { "secret-state" }
before do
session[:github_auth_state_key] = valid_auth_state
end
it "updates access token if state param is valid" do
token = "asdasd12345" token = "asdasd12345"
get :callback get :callback, params: { state: valid_auth_state }
expect(session[:github_access_token]).to eq(token) expect(session[:github_access_token]).to eq(token)
expect(controller).to redirect_to(status_import_github_url) expect(controller).to redirect_to(status_import_github_url)
end end
it "reports an error if state param is invalid" do
get :callback, params: { state: "different-state" }
expect(controller).to redirect_to(new_import_url)
expect(flash[:alert]).to eq('Access denied to your GitHub account.')
end
end
end end
describe "POST personal_access_token" do describe "POST personal_access_token" do
@ -71,8 +96,6 @@ RSpec.describe Import::GithubController do
end end
context 'when OAuth config is missing' do context 'when OAuth config is missing' do
let(:new_import_url) { public_send("new_import_#{provider}_url") }
before do before do
allow(controller).to receive(:oauth_config).and_return(nil) allow(controller).to receive(:oauth_config).and_return(nil)
end end
@ -108,6 +131,16 @@ RSpec.describe Import::GithubController do
get :status get :status
end end
it 'gets authorization url using legacy client' do
allow(controller).to receive(:logged_in_with_provider?).and_return(true)
expect(controller).to receive(:go_to_provider_for_permissions).and_call_original
expect_next_instance_of(Gitlab::LegacyGithubImport::Client) do |client|
expect(client).to receive(:authorize_url).and_call_original
end
get :new
end
end end
context 'when feature remove_legacy_github_client is enabled' do context 'when feature remove_legacy_github_client is enabled' do
@ -130,6 +163,16 @@ RSpec.describe Import::GithubController do
get :status get :status
end end
it 'gets authorization url using oauth client' do
allow(controller).to receive(:logged_in_with_provider?).and_return(true)
expect(controller).to receive(:go_to_provider_for_permissions).and_call_original
expect_next_instance_of(OAuth2::Client) do |client|
expect(client.auth_code).to receive(:authorize_url).and_call_original
end
get :new
end
context 'pagination' do context 'pagination' do
context 'when no page is specified' do context 'when no page is specified' do
it 'requests first page' do it 'requests first page' do

View file

@ -162,27 +162,4 @@ RSpec.describe Projects::CommitsController do
end end
end end
end end
context 'token authentication' do
context 'public project' do
it_behaves_like 'authenticates sessionless user', :show, :atom, { public: true, ignore_incrementing: true } do
before do
public_project = create(:project, :repository, :public)
default_params.merge!(namespace_id: public_project.namespace, project_id: public_project, id: "master.atom")
end
end
end
context 'private project' do
it_behaves_like 'authenticates sessionless user', :show, :atom, { public: false, ignore_incrementing: true } do
before do
private_project = create(:project, :repository, :private)
private_project.add_maintainer(user)
default_params.merge!(namespace_id: private_project.namespace, project_id: private_project, id: "master.atom")
end
end
end
end
end end

View file

@ -1965,40 +1965,4 @@ RSpec.describe Projects::IssuesController do
end end
end end
end end
context 'private project with token authentication' do
let_it_be(:private_project) { create(:project, :private) }
it_behaves_like 'authenticates sessionless user', :index, :atom, ignore_incrementing: true do
before do
default_params.merge!(project_id: private_project, namespace_id: private_project.namespace)
private_project.add_maintainer(user)
end
end
it_behaves_like 'authenticates sessionless user', :calendar, :ics, ignore_incrementing: true do
before do
default_params.merge!(project_id: private_project, namespace_id: private_project.namespace)
private_project.add_maintainer(user)
end
end
end
context 'public project with token authentication' do
let_it_be(:public_project) { create(:project, :public) }
it_behaves_like 'authenticates sessionless user', :index, :atom, public: true do
before do
default_params.merge!(project_id: public_project, namespace_id: public_project.namespace)
end
end
it_behaves_like 'authenticates sessionless user', :calendar, :ics, public: true do
before do
default_params.merge!(project_id: public_project, namespace_id: public_project.namespace)
end
end
end
end end

View file

@ -193,6 +193,8 @@ RSpec.describe Projects::RawController do
let_it_be(:user) { create(:user, static_object_token: 'very-secure-token') } let_it_be(:user) { create(:user, static_object_token: 'very-secure-token') }
let_it_be(:file_path) { 'master/README.md' } let_it_be(:file_path) { 'master/README.md' }
let(:token) { user.static_object_token }
before do before do
project.add_developer(user) project.add_developer(user)
end end
@ -207,17 +209,46 @@ RSpec.describe Projects::RawController do
end end
context 'when a token param is present' do context 'when a token param is present' do
subject(:execute_raw_request_with_token_in_params) do
execute_raw_requests(requests: 1, project: project, file_path: file_path, token: token)
end
context 'when token is correct' do context 'when token is correct' do
it 'calls the action normally' do it 'calls the action normally' do
execute_raw_requests(requests: 1, project: project, file_path: file_path, token: user.static_object_token) execute_raw_request_with_token_in_params
expect(response).to have_gitlab_http_status(:ok)
end
context 'when user with expired password' do
let_it_be(:user) { create(:user, password_expires_at: 2.minutes.ago) }
it 'redirects to sign in page' do
execute_raw_request_with_token_in_params
expect(response).to have_gitlab_http_status(:found)
expect(response.location).to end_with('/users/sign_in')
end
end
context 'when password expiration is not applicable' do
context 'when ldap user' do
let_it_be(:user) { create(:omniauth_user, provider: 'ldap', password_expires_at: 2.minutes.ago) }
it 'calls the action normally' do
execute_raw_request_with_token_in_params
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
end end
end end
end
end
context 'when token is incorrect' do context 'when token is incorrect' do
let(:token) { 'foobar' }
it 'redirects to sign in page' do it 'redirects to sign in page' do
execute_raw_requests(requests: 1, project: project, file_path: file_path, token: 'foobar') execute_raw_request_with_token_in_params
expect(response).to have_gitlab_http_status(:found) expect(response).to have_gitlab_http_status(:found)
expect(response.location).to end_with('/users/sign_in') expect(response.location).to end_with('/users/sign_in')
@ -226,19 +257,47 @@ RSpec.describe Projects::RawController do
end end
context 'when a token header is present' do context 'when a token header is present' do
subject(:execute_raw_request_with_token_in_headers) do
request.headers['X-Gitlab-Static-Object-Token'] = token
execute_raw_requests(requests: 1, project: project, file_path: file_path)
end
context 'when token is correct' do context 'when token is correct' do
it 'calls the action normally' do it 'calls the action normally' do
request.headers['X-Gitlab-Static-Object-Token'] = user.static_object_token execute_raw_request_with_token_in_headers
execute_raw_requests(requests: 1, project: project, file_path: file_path)
expect(response).to have_gitlab_http_status(:ok)
end
context 'when user with expired password' do
let_it_be(:user) { create(:user, password_expires_at: 2.minutes.ago) }
it 'redirects to sign in page' do
execute_raw_request_with_token_in_headers
expect(response).to have_gitlab_http_status(:found)
expect(response.location).to end_with('/users/sign_in')
end
end
context 'when password expiration is not applicable' do
context 'when ldap user' do
let_it_be(:user) { create(:omniauth_user, provider: 'ldap', password_expires_at: 2.minutes.ago) }
it 'calls the action normally' do
execute_raw_request_with_token_in_headers
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
end end
end end
end
end
context 'when token is incorrect' do context 'when token is incorrect' do
let(:token) { 'foobar' }
it 'redirects to sign in page' do it 'redirects to sign in page' do
request.headers['X-Gitlab-Static-Object-Token'] = 'foobar' execute_raw_request_with_token_in_headers
execute_raw_requests(requests: 1, project: project, file_path: file_path)
expect(response).to have_gitlab_http_status(:found) expect(response).to have_gitlab_http_status(:found)
expect(response.location).to end_with('/users/sign_in') expect(response.location).to end_with('/users/sign_in')

View file

@ -178,6 +178,29 @@ RSpec.describe Projects::RepositoriesController do
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
end end
context 'when user with expired password' do
let_it_be(:user) { create(:user, password_expires_at: 2.minutes.ago) }
it 'redirects to sign in page' do
get :archive, params: { namespace_id: project.namespace, project_id: project, id: 'master', token: user.static_object_token }, format: 'zip'
expect(response).to have_gitlab_http_status(:found)
expect(response.location).to end_with('/users/sign_in')
end
end
context 'when password expiration is not applicable' do
context 'when ldap user' do
let_it_be(:user) { create(:omniauth_user, provider: 'ldap', password_expires_at: 2.minutes.ago) }
it 'calls the action normally' do
get :archive, params: { namespace_id: project.namespace, project_id: project, id: 'master', token: user.static_object_token }, format: 'zip'
expect(response).to have_gitlab_http_status(:ok)
end
end
end
end end
context 'when token is incorrect' do context 'when token is incorrect' do
@ -197,6 +220,31 @@ RSpec.describe Projects::RepositoriesController do
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
end end
context 'when user with expired password' do
let_it_be(:user) { create(:user, password_expires_at: 2.minutes.ago) }
it 'redirects to sign in page' do
request.headers['X-Gitlab-Static-Object-Token'] = user.static_object_token
get :archive, params: { namespace_id: project.namespace, project_id: project, id: 'master' }, format: 'zip'
expect(response).to have_gitlab_http_status(:found)
expect(response.location).to end_with('/users/sign_in')
end
end
context 'when password expiration is not applicable' do
context 'when ldap user' do
let_it_be(:user) { create(:omniauth_user, provider: 'ldap', password_expires_at: 2.minutes.ago) }
it 'calls the action normally' do
request.headers['X-Gitlab-Static-Object-Token'] = user.static_object_token
get :archive, params: { namespace_id: project.namespace, project_id: project, id: 'master' }, format: 'zip'
expect(response).to have_gitlab_http_status(:ok)
end
end
end
end end
context 'when token is incorrect' do context 'when token is incorrect' do

View file

@ -117,28 +117,6 @@ RSpec.describe Projects::TagsController do
end end
end end
context 'private project with token authentication' do
let(:private_project) { create(:project, :repository, :private) }
it_behaves_like 'authenticates sessionless user', :index, :atom, ignore_incrementing: true do
before do
default_params.merge!(project_id: private_project, namespace_id: private_project.namespace)
private_project.add_maintainer(user)
end
end
end
context 'public project with token authentication' do
let(:public_project) { create(:project, :repository, :public) }
it_behaves_like 'authenticates sessionless user', :index, :atom, public: true do
before do
default_params.merge!(project_id: public_project, namespace_id: public_project.namespace)
end
end
end
describe 'POST #create' do describe 'POST #create' do
before do before do
project.add_developer(user) project.add_developer(user)

View file

@ -1555,28 +1555,6 @@ RSpec.describe ProjectsController do
end end
end end
context 'private project with token authentication' do
let_it_be(:private_project) { create(:project, :private) }
it_behaves_like 'authenticates sessionless user', :show, :atom, ignore_incrementing: true do
before do
default_params.merge!(id: private_project, namespace_id: private_project.namespace)
private_project.add_maintainer(user)
end
end
end
context 'public project with token authentication' do
let_it_be(:public_project) { create(:project, :public) }
it_behaves_like 'authenticates sessionless user', :show, :atom, public: true do
before do
default_params.merge!(id: public_project, namespace_id: public_project.namespace)
end
end
end
context 'GET show.atom' do context 'GET show.atom' do
let_it_be(:public_project) { create(:project, :public) } let_it_be(:public_project) { create(:project, :public) }
let_it_be(:event) { create(:event, :commented, project: public_project, target: create(:note, project: public_project)) } let_it_be(:event) { create(:event, :commented, project: public_project, target: create(:note, project: public_project)) }

View file

@ -258,18 +258,6 @@ ZDXgrA==
certificate_source { :gitlab_provided } certificate_source { :gitlab_provided }
end end
# This contains:
# webdioxide.com
# Let's Encrypt R3
# ISRG Root X1 (issued by DST Root CA X3)
#
# DST Root CA X3 expired on 2021-09-30, but ISRG Root X1 should be trusted on most systems.
trait :letsencrypt_expired_x3_root do
certificate do
File.read(Rails.root.join('spec/fixtures/ssl', 'letsencrypt_expired_x3.pem'))
end
end
trait :explicit_ecdsa do trait :explicit_ecdsa do
certificate do certificate do
'-----BEGIN CERTIFICATE----- '-----BEGIN CERTIFICATE-----

View file

@ -31,7 +31,6 @@ RSpec.describe 'factories' do
[:pages_domain, :with_trusted_chain], [:pages_domain, :with_trusted_chain],
[:pages_domain, :with_trusted_expired_chain], [:pages_domain, :with_trusted_expired_chain],
[:pages_domain, :explicit_ecdsa], [:pages_domain, :explicit_ecdsa],
[:pages_domain, :letsencrypt_expired_x3_root],
[:project_member, :blocked], [:project_member, :blocked],
[:remote_mirror, :ssh], [:remote_mirror, :ssh],
[:user_preference, :only_comments], [:user_preference, :only_comments],

View file

@ -1,23 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j
ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL
MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3
LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug
RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm
+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW
PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM
xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB
Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3
hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg
EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF
MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA
FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec
nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z
eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF
hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2
Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe
vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
+OkuE6N36B9K
-----END CERTIFICATE-----

View file

@ -1,100 +1,86 @@
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIItjCCB56gAwIBAgIQCu5Ga1hR41iahM0SWhyeNjANBgkqhkiG9w0BAQsFADB1 MIIGYzCCBUugAwIBAgIQAaQHyOeT/PBR4ioLKYneZDANBgkqhkiG9w0BAQsFADBY
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 MQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTEuMCwGA1UE
d3cuZGlnaWNlcnQuY29tMTQwMgYDVQQDEytEaWdpQ2VydCBTSEEyIEV4dGVuZGVk AxMlR2xvYmFsU2lnbiBBdGxhcyBSMyBEViBUTFMgQ0EgSDIgMjAyMTAeFw0yMTEw
IFZhbGlkYXRpb24gU2VydmVyIENBMB4XDTE5MTIwNDAwMDAwMFoXDTIxMTIwODEy MTgxODUwMDRaFw0yMjExMTkxODUwMDNaMBsxGTAXBgNVBAMMEGFib3V0LmdpdGxh
MDAwMFowgb0xHTAbBgNVBA8MFFByaXZhdGUgT3JnYW5pemF0aW9uMRMwEQYLKwYB Yi5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDWSo0eziN/0lq5
BAGCNzwCAQMTAlVTMRUwEwYLKwYBBAGCNzwCAQITBFV0YWgxFTATBgNVBAUTDDUy dIcS7ZceJw2odzZeT0tRkcKEW8iagNul6JetrFlk6h5lxoLEu35+MK6/fWHNmt7u
OTk1MzctMDE0MjELMAkGA1UEBhMCVVMxDTALBgNVBAgTBFV0YWgxDTALBgNVBAcT eQk7HS0uRipskAzeGrL1Hvk8EjIcHXXTxpRu7JqWOu7ZSXwNxW5cqn7L9/N2gYwt
BExlaGkxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMRUwEwYDVQQDEwxkaWdpY2Vy Jg/sfkv9AFQiNOdKrarKfbcBstxmra6rQbh5ggLG5UBT23N4ZrA3XnzvEx3+GjtO
dC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAeRYb/RLbljGZ u/a5izbk7FQP3gyXKyfm/SQRpNsytYa9jJqu5Hmyzfap5KaueOJbtJEOk8dR/HWR
IB//DrEdyKYMQqqaJwBlrr3t2paAWNuDJizvVkTMIzdJesI1pA58Myenxp5Dp8GJ i/gmAUevq62MNxorYbz8YU/P1468tS7iORkD31Tc2QWCMQSPya5qGaCGnz7dVgWy
u/VhBf//v/HAZHUE4xwu104Fg6A1BwUEKgVKERf+7kTt17Lf9fcMIjMyL+FeyPXb E1xTPbBXAgMBAAGjggNkMIIDYDAbBgNVHREEFDASghBhYm91dC5naXRsYWIuY29t
DOFbH+ej/nYaneFLch2j2xWZg1+Thk0qBlGE8WWAK+fvbEuM0SOeH9RkYFCNGPRS MA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw
KsLn0GvaCnnD4LfNDyMqYop0IpaqXoREEnkRv1MVSOw+hBj497wnnO+/GZegfzwU HQYDVR0OBBYEFJFVruwpjWeUfGJXl3m5grAjhAwPMFcGA1UdIARQME4wCAYGZ4EM
iS60h+PjlDfmdCP18qOS7tRd0qnfU3N3S+PYEd3R63LMcIfbgXNEEWBNKpiH9+8f AQIBMEIGCisGAQQBoDIKAQMwNDAyBggrBgEFBQcCARYmaHR0cHM6Ly93d3cuZ2xv
eXq6bXKPAgMBAAGjggT3MIIE8zAfBgNVHSMEGDAWgBQ901Cl1qCt7vNKYApl0yHU YmFsc2lnbi5jb20vcmVwb3NpdG9yeS8wDAYDVR0TAQH/BAIwADCBngYIKwYBBQUH
+PjWDzAdBgNVHQ4EFgQUTx0XO7HqD5DOhwlm2p+70uYPBmgwggGjBgNVHREEggGa AQEEgZEwgY4wQAYIKwYBBQUHMAGGNGh0dHA6Ly9vY3NwLmdsb2JhbHNpZ24uY29t
MIIBloIMZGlnaWNlcnQuY29tggl0aGF3dGUuZGWCC2ZyZWVzc2wuY29tggxyYXBp L2NhL2dzYXRsYXNyM2R2dGxzY2FoMjIwMjEwSgYIKwYBBQUHMAKGPmh0dHA6Ly9z
ZHNzbC5jb22CDGdlb3RydXN0LmNvbYIJdGhhd3RlLmZyggp0aGF3dGUuY29tghB3 ZWN1cmUuZ2xvYmFsc2lnbi5jb20vY2FjZXJ0L2dzYXRsYXNyM2R2dGxzY2FoMjIw
d3cucmFwaWRzc2wuY29tghB3d3cuZ2VvdHJ1c3QuY29tgg13d3cudGhhd3RlLmZy MjEuY3J0MB8GA1UdIwQYMBaAFCo0uar6vzyI8Ufy0hJ4vsXlqrBpMEgGA1UdHwRB
gg13d3cudGhhd3RlLmRlgg53d3cudGhhd3RlLmNvbYIQd3d3LmRpZ2ljZXJ0LmNv MD8wPaA7oDmGN2h0dHA6Ly9jcmwuZ2xvYmFsc2lnbi5jb20vY2EvZ3NhdGxhc3Iz
bYIYa2ItaW50ZXJuYWwuZGlnaWNlcnQuY29tghprbm93bGVkZ2ViYXNlLmRpZ2lj ZHZ0bHNjYWgyMjAyMS5jcmwwggF+BgorBgEEAdZ5AgQCBIIBbgSCAWoBaAB3AG9T
ZXJ0LmNvbYIWa25vd2xlZGdlLmRpZ2ljZXJ0LmNvbYIPa2guZGlnaWNlcnQuY29t dqwx8DEZ2JkApFEV/3cVHBHZAsEAKQaNsgiaN9kTAAABfJS9R5YAAAQDAEgwRgIh
ghlrbm93bGVkZ2VodWIuZGlnaWNlcnQuY29tghh3ZWJzZWN1cml0eS5kaWdpY2Vy AOOZmc41vB2ICwkwEB5Bmpm/X8UHfjbxwrCXEdeRmO+qAiEAg/JugZIrG2PeV4bA
dC5jb22CFGNvbnRlbnQuZGlnaWNlcnQuY29tgg93d3cuZnJlZXNzbC5jb22CHHd3 Gm6rry7HUfB954bQJ4p0PeQVmwsAdABGpVXrdfqRIDC1oolp9PN9ESxBdL79SbiF
dy53ZWJzZWN1cml0eS5kaWdpY2VydC5jb20wDgYDVR0PAQH/BAQDAgWgMB0GA1Ud q/L8cP5tRwAAAXyUvUeOAAAEAwBFMEMCHyRAiTz2fZ8DuQF6hrVP+IMTCPBtjB3D
JQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjB1BgNVHR8EbjBsMDSgMqAwhi5odHRw m4naI8tC/foCIDXFCRIYjRb00CFI6piLYGihRy+GYF5nMQhQ9uE6hltzAHcAUaOw
Oi8vY3JsMy5kaWdpY2VydC5jb20vc2hhMi1ldi1zZXJ2ZXItZzIuY3JsMDSgMqAw 9f0BeZxWbbg3eI8MpHrMGyfL956IQpoN/tSLBeUAAAF8lL1ICgAABAMASDBGAiEA
hi5odHRwOi8vY3JsNC5kaWdpY2VydC5jb20vc2hhMi1ldi1zZXJ2ZXItZzIuY3Js 5d/bXb9TPZWhwSH8GGji/LDFL6OJnZtOV94sBaDiFgMCIQCtl00oCRMFFnqsvBo6
MEsGA1UdIAREMEIwNwYJYIZIAYb9bAIBMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8v SRtnDqJkEHYBS12I4LyC+D1onjANBgkqhkiG9w0BAQsFAAOCAQEAE5xcno79J+Ec
d3d3LmRpZ2ljZXJ0LmNvbS9DUFMwBwYFZ4EMAQEwgYgGCCsGAQUFBwEBBHwwejAk DIPJKnJCugKiM7yKjCjCp/63osCbRC+jUwRyXBIe/oTdY3geKwDOQAvyEeJPSWP1
BggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFIGCCsGAQUFBzAC LbNp0l3yHbYXfsYl/NMTrJpjrJrrRO5BxG/d3IPwXIlcZrrdDSoGfGYIF9N23iqB
hkZodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRTSEEyRXh0ZW5k in15L7B+PodTl8/mSQZTjbLoecPvl+AOcLyStcWCKYQUlQb3x4UV3R4Z1ukwGbBC
ZWRWYWxpZGF0aW9uU2VydmVyQ0EuY3J0MAwGA1UdEwEB/wQCMAAwggF8BgorBgEE cDbTR2XOSJzA9ECJcxKnWjQRQUc54pdG3pt13Wu2dVapX5sWZpV05rga3bBDjCqw
AdZ5AgQCBIIBbASCAWgBZgB1AKS5CZC0GFgUh7sTosxncAo8NZgE+RvfuON3zQ7I DcfKuYbOChm2i6CQ578lAntPTIS02EkGFHrmYxrIAvlhGksHpJNJtRoff1KkQKni
DdwQAAABbtLkOs4AAAQDAEYwRAIgQ7gh393PInhYfPOhg/lF9yZNRdvjBeufFoG8 r8emWp7D2Q==
VnBuPNMCIBP8YGC83ig5ttw3ipSRjH0bKj4Ak5O4rynoql9Dy8x3AHYAVhQGmi/X
wuzT9eG9RLI+x0Z2ubyZEVzA75SYVdaJ0N0AAAFu0uQ7VgAABAMARzBFAiEAhzE7
1c48wn3s/30IB4WgxfpLburH0Ku8cchv8QeqcgACIBrWpUlDD18AOfkPCOcB2kWU
vRXsdptVm3jPeU5TtDSoAHUAu9nfvB+KcbWTlCOXqpJ7RzhXlQqrUugakJZkNo4e
0YUAAAFu0uQ60gAABAMARjBEAiBBpH5m7ntGKFTOFgSLcFXRDg66xJqerMy0gOHj
4TIBYAIgfFABPNy6P61hjiOWwjq73lvoEdAyh18GeFHIp0BgsWEwDQYJKoZIhvcN
AQELBQADggEBAInaSEqteyQA1zUKiXVqgffhHKZsUq9UnMows6X+UoFPoby9xqm6
IaY/77zaFZYwXJlP/SvrlbgTLHAdir3y38uhAlfPX4iRuwggOpFFF5hqDckzCm91
ocGnoG6sUY5mOqKu2vIcZkUQDe+K5gOxI6ME/4YwzWCIcTmBPQ6NQmqiFLPoQty1
gdbGCcLQNFCuNq4n5OK2NmBjcbtyT4gglat7C4+KV8RkEubZ+MkXzyDkpEXjjzsK
7iuNB0hRgyyhGzHrlZ/l0OLoT0Cb4I5PzzRSseFEyPKCC1WSF7aE9rFfUqhpqSAT
7NV7SEijYyFFtuZfz9RGglcqnRlAfgTy+tU=
-----END CERTIFICATE----- -----END CERTIFICATE-----
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIEtjCCA56gAwIBAgIQDHmpRLCMEZUgkmFf4msdgzANBgkqhkiG9w0BAQsFADBs MIIExTCCA62gAwIBAgIQeimFGrf0XWZ5UGZBtv/XHTANBgkqhkiG9w0BAQsFADBM
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 MSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xv
d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j YmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjAeFw0yMTA2MTYxMjAwMDBaFw0y
ZSBFViBSb290IENBMB4XDTEzMTAyMjEyMDAwMFoXDTI4MTAyMjEyMDAwMFowdTEL NDA2MTYwMDAwMDBaMFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWdu
MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 IG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFIzIERWIFRMUyBDQSBI
LmRpZ2ljZXJ0LmNvbTE0MDIGA1UEAxMrRGlnaUNlcnQgU0hBMiBFeHRlbmRlZCBW MiAyMDIxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1JTAQMj+QUYF
YWxpZGF0aW9uIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC 3d9X5eOWFOphbB6GpHE3J0uvUXcQwxnd8Jz26aQCE1ZYxJFEc2WmsxuVeVXU+rZj
ggEBANdTpARR+JmmFkhLZyeqk0nQOe0MsLAAh/FnKIaFjI5j2ryxQDji0/XspQUY 7+MYD7Mg72bhuiwUdwRGRN4a2N122LfIQlTFlHu/fwcNqYX/fe3phvZt9upnH4oJ
uD0+xZkXMuwYjPrxDKZkIYXLBxA0sFKIKx9om9KxjxKws9LniB8f7zh3VFNfgHk/ aLBbay+t+HPPC4em74x2WKaIl31ZXzgzllLomnlLISLOKiQe1rEHp4yy3/yE2a4G
LhqqqB5LKw2rt2O5Nbd9FLxZS99RStKh4gzikIKHaq7q12TWmFXo/a8aUGxUvBHy 1l/lprA49dcyM/oylm9Bbkum2F4C+EOjHgTAoDVJrJpdWvPj0CU+HkmftujfFp4S
/Urynbt/DvTVvo4WiRJV2MBxNO723C3sxIclho3YIeSwTQyJ3DkmF93215SF2AQh 55LECSr2TfJt7xjgR3eLUx12nlpoauWEzZ0/i6OIDPfbmqcksw4ani/YO07LbRM6
cJ1vb/9cuhnhRctWVyh+HA1BV6q3uCe7seT6Ku8hI3UarS2bhjWMnHe1c63YlC3k cY9VZzkAvwIDAQABo4IBlTCCAZEwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQG
8wyd7sFOYn4XwHGeLN7x+RAoGTMCAwEAAaOCAUkwggFFMBIGA1UdEwEB/wQIMAYB CCsGAQUFBwMBBggrBgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQW
Af8CAQAwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEF BBQqNLmq+r88iPFH8tISeL7F5aqwaTAfBgNVHSMEGDAWgBSP8Et/qC5FJK5NUPpj
BQcDAjA0BggrBgEFBQcBAQQoMCYwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRp move4t0bvDB7BggrBgEFBQcBAQRvMG0wLgYIKwYBBQUHMAGGImh0dHA6Ly9vY3Nw
Z2ljZXJ0LmNvbTBLBgNVHR8ERDBCMECgPqA8hjpodHRwOi8vY3JsNC5kaWdpY2Vy Mi5nbG9iYWxzaWduLmNvbS9yb290cjMwOwYIKwYBBQUHMAKGL2h0dHA6Ly9zZWN1
dC5jb20vRGlnaUNlcnRIaWdoQXNzdXJhbmNlRVZSb290Q0EuY3JsMD0GA1UdIAQ2 cmUuZ2xvYmFsc2lnbi5jb20vY2FjZXJ0L3Jvb3QtcjMuY3J0MDYGA1UdHwQvMC0w
MDQwMgYEVR0gADAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5j K6ApoCeGJWh0dHA6Ly9jcmwuZ2xvYmFsc2lnbi5jb20vcm9vdC1yMy5jcmwwVwYD
b20vQ1BTMB0GA1UdDgQWBBQ901Cl1qCt7vNKYApl0yHU+PjWDzAfBgNVHSMEGDAW VR0gBFAwTjAIBgZngQwBAgEwQgYKKwYBBAGgMgoBAzA0MDIGCCsGAQUFBwIBFiZo
gBSxPsNpA/i/RwHUmCYaCALvY2QrwzANBgkqhkiG9w0BAQsFAAOCAQEAnbbQkIbh dHRwczovL3d3dy5nbG9iYWxzaWduLmNvbS9yZXBvc2l0b3J5LzANBgkqhkiG9w0B
hgLtxaDwNBx0wY12zIYKqPBKikLWP8ipTa18CK3mtlC4ohpNiAexKSHc59rGPCHg AQsFAAOCAQEAEsIwXEhdAfoUGaKAnYfVI7zsOY7Sx8bpC/obGxXa4Kyu8CVx+TtT
4xFJcKx6HQGkyhE6V6t9VypAdP3THYUYUN9XR3WhfVUgLkc3UHKMf4Ib0mKPLQNa g8WmKNF7+I7C51NZEmhvb8UDI1G9ny7iYIRDajQD5AeZowbfC69aHQSI9LiOeAZb
2sPIoc4sUqIAY+tzunHISScjl2SFnjgOrWNoPLpSgVh5oywM395t6zHyuqB8bPEs YaRDJfWps9redPwoaC0iT5R4xLOnWwCtmIho1bv/YG3pMAvaQ+qn04kuUvWO7LEp
1OG9d4Q3A84ytciagRpKkk47RpqF/oOi+Z6Mo8wNXrM9zwR4jxQUezKcxwCmXMS1 u7FdHmx1DdgkefcqYgN/rAZ8E39S9VxWV+64PNUDey8vkAIH8FCTxbWiITty6dsH
oVWNWlZopCJwqjyBcdmdqEU79OX2olHdx3ti6G8MdOu42vi/hw15UJGQmxg7kVkn SulKQ9pSa93k9PHTf+di08mMQBq5WBWTiFeMYZEWyE/z7NHdU3eLMZjq6y/nKlF9
8TUoE6smftX3eg== nywrToh4AgdZK6JnbU+lqbNiexJbaBoA3w==
-----END CERTIFICATE----- -----END CERTIFICATE-----
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp
d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4
ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG
MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI
LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8
RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT
+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm
PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd
xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ
Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3 XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw
hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o
EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU
MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp
FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK
nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z 6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX
eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs
hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH
Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe WD9f
vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep
+OkuE6N36B9K
-----END CERTIFICATE----- -----END CERTIFICATE-----

View file

@ -1,28 +1,28 @@
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIEtjCCA56gAwIBAgIQDHmpRLCMEZUgkmFf4msdgzANBgkqhkiG9w0BAQsFADBs MIIExTCCA62gAwIBAgIQeimFGrf0XWZ5UGZBtv/XHTANBgkqhkiG9w0BAQsFADBM
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 MSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xv
d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j YmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjAeFw0yMTA2MTYxMjAwMDBaFw0y
ZSBFViBSb290IENBMB4XDTEzMTAyMjEyMDAwMFoXDTI4MTAyMjEyMDAwMFowdTEL NDA2MTYwMDAwMDBaMFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWdu
MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 IG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFIzIERWIFRMUyBDQSBI
LmRpZ2ljZXJ0LmNvbTE0MDIGA1UEAxMrRGlnaUNlcnQgU0hBMiBFeHRlbmRlZCBW MiAyMDIxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1JTAQMj+QUYF
YWxpZGF0aW9uIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC 3d9X5eOWFOphbB6GpHE3J0uvUXcQwxnd8Jz26aQCE1ZYxJFEc2WmsxuVeVXU+rZj
ggEBANdTpARR+JmmFkhLZyeqk0nQOe0MsLAAh/FnKIaFjI5j2ryxQDji0/XspQUY 7+MYD7Mg72bhuiwUdwRGRN4a2N122LfIQlTFlHu/fwcNqYX/fe3phvZt9upnH4oJ
uD0+xZkXMuwYjPrxDKZkIYXLBxA0sFKIKx9om9KxjxKws9LniB8f7zh3VFNfgHk/ aLBbay+t+HPPC4em74x2WKaIl31ZXzgzllLomnlLISLOKiQe1rEHp4yy3/yE2a4G
LhqqqB5LKw2rt2O5Nbd9FLxZS99RStKh4gzikIKHaq7q12TWmFXo/a8aUGxUvBHy 1l/lprA49dcyM/oylm9Bbkum2F4C+EOjHgTAoDVJrJpdWvPj0CU+HkmftujfFp4S
/Urynbt/DvTVvo4WiRJV2MBxNO723C3sxIclho3YIeSwTQyJ3DkmF93215SF2AQh 55LECSr2TfJt7xjgR3eLUx12nlpoauWEzZ0/i6OIDPfbmqcksw4ani/YO07LbRM6
cJ1vb/9cuhnhRctWVyh+HA1BV6q3uCe7seT6Ku8hI3UarS2bhjWMnHe1c63YlC3k cY9VZzkAvwIDAQABo4IBlTCCAZEwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQG
8wyd7sFOYn4XwHGeLN7x+RAoGTMCAwEAAaOCAUkwggFFMBIGA1UdEwEB/wQIMAYB CCsGAQUFBwMBBggrBgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQW
Af8CAQAwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEF BBQqNLmq+r88iPFH8tISeL7F5aqwaTAfBgNVHSMEGDAWgBSP8Et/qC5FJK5NUPpj
BQcDAjA0BggrBgEFBQcBAQQoMCYwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRp move4t0bvDB7BggrBgEFBQcBAQRvMG0wLgYIKwYBBQUHMAGGImh0dHA6Ly9vY3Nw
Z2ljZXJ0LmNvbTBLBgNVHR8ERDBCMECgPqA8hjpodHRwOi8vY3JsNC5kaWdpY2Vy Mi5nbG9iYWxzaWduLmNvbS9yb290cjMwOwYIKwYBBQUHMAKGL2h0dHA6Ly9zZWN1
dC5jb20vRGlnaUNlcnRIaWdoQXNzdXJhbmNlRVZSb290Q0EuY3JsMD0GA1UdIAQ2 cmUuZ2xvYmFsc2lnbi5jb20vY2FjZXJ0L3Jvb3QtcjMuY3J0MDYGA1UdHwQvMC0w
MDQwMgYEVR0gADAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5j K6ApoCeGJWh0dHA6Ly9jcmwuZ2xvYmFsc2lnbi5jb20vcm9vdC1yMy5jcmwwVwYD
b20vQ1BTMB0GA1UdDgQWBBQ901Cl1qCt7vNKYApl0yHU+PjWDzAfBgNVHSMEGDAW VR0gBFAwTjAIBgZngQwBAgEwQgYKKwYBBAGgMgoBAzA0MDIGCCsGAQUFBwIBFiZo
gBSxPsNpA/i/RwHUmCYaCALvY2QrwzANBgkqhkiG9w0BAQsFAAOCAQEAnbbQkIbh dHRwczovL3d3dy5nbG9iYWxzaWduLmNvbS9yZXBvc2l0b3J5LzANBgkqhkiG9w0B
hgLtxaDwNBx0wY12zIYKqPBKikLWP8ipTa18CK3mtlC4ohpNiAexKSHc59rGPCHg AQsFAAOCAQEAEsIwXEhdAfoUGaKAnYfVI7zsOY7Sx8bpC/obGxXa4Kyu8CVx+TtT
4xFJcKx6HQGkyhE6V6t9VypAdP3THYUYUN9XR3WhfVUgLkc3UHKMf4Ib0mKPLQNa g8WmKNF7+I7C51NZEmhvb8UDI1G9ny7iYIRDajQD5AeZowbfC69aHQSI9LiOeAZb
2sPIoc4sUqIAY+tzunHISScjl2SFnjgOrWNoPLpSgVh5oywM395t6zHyuqB8bPEs YaRDJfWps9redPwoaC0iT5R4xLOnWwCtmIho1bv/YG3pMAvaQ+qn04kuUvWO7LEp
1OG9d4Q3A84ytciagRpKkk47RpqF/oOi+Z6Mo8wNXrM9zwR4jxQUezKcxwCmXMS1 u7FdHmx1DdgkefcqYgN/rAZ8E39S9VxWV+64PNUDey8vkAIH8FCTxbWiITty6dsH
oVWNWlZopCJwqjyBcdmdqEU79OX2olHdx3ti6G8MdOu42vi/hw15UJGQmxg7kVkn SulKQ9pSa93k9PHTf+di08mMQBq5WBWTiFeMYZEWyE/z7NHdU3eLMZjq6y/nKlF9
8TUoE6smftX3eg== nywrToh4AgdZK6JnbU+lqbNiexJbaBoA3w==
-----END CERTIFICATE----- -----END CERTIFICATE-----

View file

@ -0,0 +1,37 @@
-----BEGIN CERTIFICATE-----
MIIGYzCCBUugAwIBAgIQAaQHyOeT/PBR4ioLKYneZDANBgkqhkiG9w0BAQsFADBY
MQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTEuMCwGA1UE
AxMlR2xvYmFsU2lnbiBBdGxhcyBSMyBEViBUTFMgQ0EgSDIgMjAyMTAeFw0yMTEw
MTgxODUwMDRaFw0yMjExMTkxODUwMDNaMBsxGTAXBgNVBAMMEGFib3V0LmdpdGxh
Yi5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDWSo0eziN/0lq5
dIcS7ZceJw2odzZeT0tRkcKEW8iagNul6JetrFlk6h5lxoLEu35+MK6/fWHNmt7u
eQk7HS0uRipskAzeGrL1Hvk8EjIcHXXTxpRu7JqWOu7ZSXwNxW5cqn7L9/N2gYwt
Jg/sfkv9AFQiNOdKrarKfbcBstxmra6rQbh5ggLG5UBT23N4ZrA3XnzvEx3+GjtO
u/a5izbk7FQP3gyXKyfm/SQRpNsytYa9jJqu5Hmyzfap5KaueOJbtJEOk8dR/HWR
i/gmAUevq62MNxorYbz8YU/P1468tS7iORkD31Tc2QWCMQSPya5qGaCGnz7dVgWy
E1xTPbBXAgMBAAGjggNkMIIDYDAbBgNVHREEFDASghBhYm91dC5naXRsYWIuY29t
MA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw
HQYDVR0OBBYEFJFVruwpjWeUfGJXl3m5grAjhAwPMFcGA1UdIARQME4wCAYGZ4EM
AQIBMEIGCisGAQQBoDIKAQMwNDAyBggrBgEFBQcCARYmaHR0cHM6Ly93d3cuZ2xv
YmFsc2lnbi5jb20vcmVwb3NpdG9yeS8wDAYDVR0TAQH/BAIwADCBngYIKwYBBQUH
AQEEgZEwgY4wQAYIKwYBBQUHMAGGNGh0dHA6Ly9vY3NwLmdsb2JhbHNpZ24uY29t
L2NhL2dzYXRsYXNyM2R2dGxzY2FoMjIwMjEwSgYIKwYBBQUHMAKGPmh0dHA6Ly9z
ZWN1cmUuZ2xvYmFsc2lnbi5jb20vY2FjZXJ0L2dzYXRsYXNyM2R2dGxzY2FoMjIw
MjEuY3J0MB8GA1UdIwQYMBaAFCo0uar6vzyI8Ufy0hJ4vsXlqrBpMEgGA1UdHwRB
MD8wPaA7oDmGN2h0dHA6Ly9jcmwuZ2xvYmFsc2lnbi5jb20vY2EvZ3NhdGxhc3Iz
ZHZ0bHNjYWgyMjAyMS5jcmwwggF+BgorBgEEAdZ5AgQCBIIBbgSCAWoBaAB3AG9T
dqwx8DEZ2JkApFEV/3cVHBHZAsEAKQaNsgiaN9kTAAABfJS9R5YAAAQDAEgwRgIh
AOOZmc41vB2ICwkwEB5Bmpm/X8UHfjbxwrCXEdeRmO+qAiEAg/JugZIrG2PeV4bA
Gm6rry7HUfB954bQJ4p0PeQVmwsAdABGpVXrdfqRIDC1oolp9PN9ESxBdL79SbiF
q/L8cP5tRwAAAXyUvUeOAAAEAwBFMEMCHyRAiTz2fZ8DuQF6hrVP+IMTCPBtjB3D
m4naI8tC/foCIDXFCRIYjRb00CFI6piLYGihRy+GYF5nMQhQ9uE6hltzAHcAUaOw
9f0BeZxWbbg3eI8MpHrMGyfL956IQpoN/tSLBeUAAAF8lL1ICgAABAMASDBGAiEA
5d/bXb9TPZWhwSH8GGji/LDFL6OJnZtOV94sBaDiFgMCIQCtl00oCRMFFnqsvBo6
SRtnDqJkEHYBS12I4LyC+D1onjANBgkqhkiG9w0BAQsFAAOCAQEAE5xcno79J+Ec
DIPJKnJCugKiM7yKjCjCp/63osCbRC+jUwRyXBIe/oTdY3geKwDOQAvyEeJPSWP1
LbNp0l3yHbYXfsYl/NMTrJpjrJrrRO5BxG/d3IPwXIlcZrrdDSoGfGYIF9N23iqB
in15L7B+PodTl8/mSQZTjbLoecPvl+AOcLyStcWCKYQUlQb3x4UV3R4Z1ukwGbBC
cDbTR2XOSJzA9ECJcxKnWjQRQUc54pdG3pt13Wu2dVapX5sWZpV05rga3bBDjCqw
DcfKuYbOChm2i6CQ578lAntPTIS02EkGFHrmYxrIAvlhGksHpJNJtRoff1KkQKni
r8emWp7D2Q==
-----END CERTIFICATE-----

View file

@ -1,49 +1,21 @@
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIItjCCB56gAwIBAgIQCu5Ga1hR41iahM0SWhyeNjANBgkqhkiG9w0BAQsFADB1 MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp
d3cuZGlnaWNlcnQuY29tMTQwMgYDVQQDEytEaWdpQ2VydCBTSEEyIEV4dGVuZGVk Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4
IFZhbGlkYXRpb24gU2VydmVyIENBMB4XDTE5MTIwNDAwMDAwMFoXDTIxMTIwODEy MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG
MDAwMFowgb0xHTAbBgNVBA8MFFByaXZhdGUgT3JnYW5pemF0aW9uMRMwEQYLKwYB A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI
BAGCNzwCAQMTAlVTMRUwEwYLKwYBBAGCNzwCAQITBFV0YWgxFTATBgNVBAUTDDUy hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8
OTk1MzctMDE0MjELMAkGA1UEBhMCVVMxDTALBgNVBAgTBFV0YWgxDTALBgNVBAcT RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT
BExlaGkxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMRUwEwYDVQQDEwxkaWdpY2Vy gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm
dC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAeRYb/RLbljGZ KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd
IB//DrEdyKYMQqqaJwBlrr3t2paAWNuDJizvVkTMIzdJesI1pA58Myenxp5Dp8GJ QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ
u/VhBf//v/HAZHUE4xwu104Fg6A1BwUEKgVKERf+7kTt17Lf9fcMIjMyL+FeyPXb XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw
DOFbH+ej/nYaneFLch2j2xWZg1+Thk0qBlGE8WWAK+fvbEuM0SOeH9RkYFCNGPRS DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o
KsLn0GvaCnnD4LfNDyMqYop0IpaqXoREEnkRv1MVSOw+hBj497wnnO+/GZegfzwU LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU
iS60h+PjlDfmdCP18qOS7tRd0qnfU3N3S+PYEd3R63LMcIfbgXNEEWBNKpiH9+8f RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp
eXq6bXKPAgMBAAGjggT3MIIE8zAfBgNVHSMEGDAWgBQ901Cl1qCt7vNKYApl0yHU jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK
+PjWDzAdBgNVHQ4EFgQUTx0XO7HqD5DOhwlm2p+70uYPBmgwggGjBgNVHREEggGa 6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX
MIIBloIMZGlnaWNlcnQuY29tggl0aGF3dGUuZGWCC2ZyZWVzc2wuY29tggxyYXBp mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs
ZHNzbC5jb22CDGdlb3RydXN0LmNvbYIJdGhhd3RlLmZyggp0aGF3dGUuY29tghB3 Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH
d3cucmFwaWRzc2wuY29tghB3d3cuZ2VvdHJ1c3QuY29tgg13d3cudGhhd3RlLmZy WD9f
gg13d3cudGhhd3RlLmRlgg53d3cudGhhd3RlLmNvbYIQd3d3LmRpZ2ljZXJ0LmNv
bYIYa2ItaW50ZXJuYWwuZGlnaWNlcnQuY29tghprbm93bGVkZ2ViYXNlLmRpZ2lj
ZXJ0LmNvbYIWa25vd2xlZGdlLmRpZ2ljZXJ0LmNvbYIPa2guZGlnaWNlcnQuY29t
ghlrbm93bGVkZ2VodWIuZGlnaWNlcnQuY29tghh3ZWJzZWN1cml0eS5kaWdpY2Vy
dC5jb22CFGNvbnRlbnQuZGlnaWNlcnQuY29tgg93d3cuZnJlZXNzbC5jb22CHHd3
dy53ZWJzZWN1cml0eS5kaWdpY2VydC5jb20wDgYDVR0PAQH/BAQDAgWgMB0GA1Ud
JQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjB1BgNVHR8EbjBsMDSgMqAwhi5odHRw
Oi8vY3JsMy5kaWdpY2VydC5jb20vc2hhMi1ldi1zZXJ2ZXItZzIuY3JsMDSgMqAw
hi5odHRwOi8vY3JsNC5kaWdpY2VydC5jb20vc2hhMi1ldi1zZXJ2ZXItZzIuY3Js
MEsGA1UdIAREMEIwNwYJYIZIAYb9bAIBMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8v
d3d3LmRpZ2ljZXJ0LmNvbS9DUFMwBwYFZ4EMAQEwgYgGCCsGAQUFBwEBBHwwejAk
BggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFIGCCsGAQUFBzAC
hkZodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRTSEEyRXh0ZW5k
ZWRWYWxpZGF0aW9uU2VydmVyQ0EuY3J0MAwGA1UdEwEB/wQCMAAwggF8BgorBgEE
AdZ5AgQCBIIBbASCAWgBZgB1AKS5CZC0GFgUh7sTosxncAo8NZgE+RvfuON3zQ7I
DdwQAAABbtLkOs4AAAQDAEYwRAIgQ7gh393PInhYfPOhg/lF9yZNRdvjBeufFoG8
VnBuPNMCIBP8YGC83ig5ttw3ipSRjH0bKj4Ak5O4rynoql9Dy8x3AHYAVhQGmi/X
wuzT9eG9RLI+x0Z2ubyZEVzA75SYVdaJ0N0AAAFu0uQ7VgAABAMARzBFAiEAhzE7
1c48wn3s/30IB4WgxfpLburH0Ku8cchv8QeqcgACIBrWpUlDD18AOfkPCOcB2kWU
vRXsdptVm3jPeU5TtDSoAHUAu9nfvB+KcbWTlCOXqpJ7RzhXlQqrUugakJZkNo4e
0YUAAAFu0uQ60gAABAMARjBEAiBBpH5m7ntGKFTOFgSLcFXRDg66xJqerMy0gOHj
4TIBYAIgfFABPNy6P61hjiOWwjq73lvoEdAyh18GeFHIp0BgsWEwDQYJKoZIhvcN
AQELBQADggEBAInaSEqteyQA1zUKiXVqgffhHKZsUq9UnMows6X+UoFPoby9xqm6
IaY/77zaFZYwXJlP/SvrlbgTLHAdir3y38uhAlfPX4iRuwggOpFFF5hqDckzCm91
ocGnoG6sUY5mOqKu2vIcZkUQDe+K5gOxI6ME/4YwzWCIcTmBPQ6NQmqiFLPoQty1
gdbGCcLQNFCuNq4n5OK2NmBjcbtyT4gglat7C4+KV8RkEubZ+MkXzyDkpEXjjzsK
7iuNB0hRgyyhGzHrlZ/l0OLoT0Cb4I5PzzRSseFEyPKCC1WSF7aE9rFfUqhpqSAT
7NV7SEijYyFFtuZfz9RGglcqnRlAfgTy+tU=
-----END CERTIFICATE----- -----END CERTIFICATE-----

View file

@ -1,98 +0,0 @@
-----BEGIN CERTIFICATE-----
MIIGJDCCBQygAwIBAgISBOSAE/WwQGsTbDJI1vDL9+eKMA0GCSqGSIb3DQEBCwUA
MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQD
EwJSMzAeFw0yMTEwMDEyMjIxMTlaFw0yMTEyMzAyMjIxMThaMBkxFzAVBgNVBAMT
DndlYmRpb3hpZGUuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA
wf/TpE5AjzoLXMFQ+WHle7Dn5rlEe0bPee2JU386cZmMYnGFS5DR251FerSX28U4
pqk2yS8oefHGi2PS6h8/MWxr+Zy/6hk3WkgwdIK3uPiUcfCdPV/btXDd4YqikEDm
BoOE4fQlqKQwtLOnhEZu9y8FQoxxoQ+7DndHrDixDoMbpUloxpqUZwziQnH4QHXE
32rQhq25+NUK/lVFGKOFnmZ2s/yUildKafqulHrLHOhumKMOEivzlFDZbtqP+RKt
nsrJ3i9O+nSQz6j5dv3Du6eaResrtK7tT1MFDNhcg2cgjNW64VLXQdFXYXE1OYsw
yAuXUnHNzWFhinyf80qeh2046YR21dlG8voIDQH4fGG5GmWLyu7glsWYVwQQ36VA
TTxPmAoaqUTl8A7cnlJpAo+BJ00mS/9DwJ7pkgGC7dYOhJzWlI7lPqzEfmJ+o8pj
CJlLIuqsn0vcCZQlmqCFMxK4asn+puLLnMjRLHIYEJKDNyPGHQEr2e5t4GUYZKaN
MEpXMwJd97tUamUKWeBPNIND/kOuqexe+okbOTRp34VAsK5oCpawEJckoNkK+sv0
OrSWFOdfLBHv66p9qsrz8LQXxmN5JUBUe51SBSUo1Ul4/vGYdhuKd/8KcLw9/Al+
HJN2hAeo3v+2fVey4hgGna7XNe8e3+E+OEQb4zpQDLkCAwEAAaOCAkswggJHMA4G
A1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYD
VR0TAQH/BAIwADAdBgNVHQ4EFgQU4PbvqCKatjx6GZMXy7v9GwykZq4wHwYDVR0j
BBgwFoAUFC6zF7dYVsuuUAlA5h+vnYsUwsYwVQYIKwYBBQUHAQEESTBHMCEGCCsG
AQUFBzABhhVodHRwOi8vcjMuby5sZW5jci5vcmcwIgYIKwYBBQUHMAKGFmh0dHA6
Ly9yMy5pLmxlbmNyLm9yZy8wGQYDVR0RBBIwEIIOd2ViZGlveGlkZS5jb20wTAYD
VR0gBEUwQzAIBgZngQwBAgEwNwYLKwYBBAGC3xMBAQEwKDAmBggrBgEFBQcCARYa
aHR0cDovL2Nwcy5sZXRzZW5jcnlwdC5vcmcwggEGBgorBgEEAdZ5AgQCBIH3BIH0
APIAdwBc3EOS/uarRUSxXprUVuYQN/vV+kfcoXOUsl7m9scOygAAAXw+KYGHAAAE
AwBIMEYCIQCqD6jMtHrGlE02Qh1FzFd4+qYzJTrChHmHBFIncPGQKAIhALeYk0Vf
/Lw2tX2beVlKN4/h1o8srNJv+06xkr1N6XmiAHcAfT7y+I//iFVoJMLAyp5SiXkr
xQ54CX8uapdomX4i8NcAAAF8PimBogAABAMASDBGAiEA0h883FFj1dSYKGym9+Wa
XgJRj526X7YlkhkZ5J1TjioCIQDyjMPrbo5liVi/e5b8gfDw5Fd9WNiTu1W1LKKu
UpE/qTANBgkqhkiG9w0BAQsFAAOCAQEAcx10nqp1kh2awwoqwf7Jo8Gycqx2bA2O
E2rveQ/BK9UhwvrNeEpE9SG6liMsYJKxGar0vbbBHvxzuMU00bhGjXFtUT5XuQ8q
FcU0OdycyZj8fjZmUNsJr82l8HvfJ50jfxFORTgj8Ln5MWVUFlbl0nD+06l28sDc
V+r/B4394fkoMsKXtiTA4/ZeOD1tHNsdxQ7sNQtEfqCG0wFCYHK3rs7XTZ1K0F3c
M051JShko1UKP/k5blrendOwVRwLtq+9pavGnJBeqNIVgugTER/IHlp4427WyhdY
KYjKoytW+XQyWqxU/Mh/O4rxkD8cZaE+FdZpP67VZ185AuZMbn+LcQ==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFFjCCAv6gAwIBAgIRAJErCErPDBinU/bWLiWnX1owDQYJKoZIhvcNAQELBQAw
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjAwOTA0MDAwMDAw
WhcNMjUwOTE1MTYwMDAwWjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg
RW5jcnlwdDELMAkGA1UEAxMCUjMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQC7AhUozPaglNMPEuyNVZLD+ILxmaZ6QoinXSaqtSu5xUyxr45r+XXIo9cP
R5QUVTVXjJ6oojkZ9YI8QqlObvU7wy7bjcCwXPNZOOftz2nwWgsbvsCUJCWH+jdx
sxPnHKzhm+/b5DtFUkWWqcFTzjTIUu61ru2P3mBw4qVUq7ZtDpelQDRrK9O8Zutm
NHz6a4uPVymZ+DAXXbpyb/uBxa3Shlg9F8fnCbvxK/eG3MHacV3URuPMrSXBiLxg
Z3Vms/EY96Jc5lP/Ooi2R6X/ExjqmAl3P51T+c8B5fWmcBcUr2Ok/5mzk53cU6cG
/kiFHaFpriV1uxPMUgP17VGhi9sVAgMBAAGjggEIMIIBBDAOBgNVHQ8BAf8EBAMC
AYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYB
Af8CAQAwHQYDVR0OBBYEFBQusxe3WFbLrlAJQOYfr52LFMLGMB8GA1UdIwQYMBaA
FHm0WeZ7tuXkAXOACIjIGlj26ZtuMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcw
AoYWaHR0cDovL3gxLmkubGVuY3Iub3JnLzAnBgNVHR8EIDAeMBygGqAYhhZodHRw
Oi8veDEuYy5sZW5jci5vcmcvMCIGA1UdIAQbMBkwCAYGZ4EMAQIBMA0GCysGAQQB
gt8TAQEBMA0GCSqGSIb3DQEBCwUAA4ICAQCFyk5HPqP3hUSFvNVneLKYY611TR6W
PTNlclQtgaDqw+34IL9fzLdwALduO/ZelN7kIJ+m74uyA+eitRY8kc607TkC53wl
ikfmZW4/RvTZ8M6UK+5UzhK8jCdLuMGYL6KvzXGRSgi3yLgjewQtCPkIVz6D2QQz
CkcheAmCJ8MqyJu5zlzyZMjAvnnAT45tRAxekrsu94sQ4egdRCnbWSDtY7kh+BIm
lJNXoB1lBMEKIq4QDUOXoRgffuDghje1WrG9ML+Hbisq/yFOGwXD9RiX8F6sw6W4
avAuvDszue5L3sz85K+EC4Y/wFVDNvZo4TYXao6Z0f+lQKc0t8DQYzk1OXVu8rp2
yJMC6alLbBfODALZvYH7n7do1AZls4I9d1P4jnkDrQoxB3UqQ9hVl3LEKQ73xF1O
yK5GhDDX8oVfGKF5u+decIsH4YaTw7mP3GFxJSqv3+0lUFJoi5Lc5da149p90Ids
hCExroL1+7mryIkXPeFM5TgO9r0rvZaBFOvV2z0gp35Z0+L4WPlbuEjN/lxPFin+
HlUjr8gRsI3qfJOQFy/9rKIJR0Y/8Omwt/8oTWgy1mdeHmmjk7j1nYsvC9JSQ6Zv
MldlTTKB3zhThV1+XWYp6rjd5JW1zbVWEkLNxE7GJThEUG3szgBVGP7pSWTUTsqX
nLRbwHOoq7hHwg==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFYDCCBEigAwIBAgIQQAF3ITfU6UK47naqPGQKtzANBgkqhkiG9w0BAQsFADA/
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
DkRTVCBSb290IENBIFgzMB4XDTIxMDEyMDE5MTQwM1oXDTI0MDkzMDE4MTQwM1ow
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwggIiMA0GCSqGSIb3DQEB
AQUAA4ICDwAwggIKAoICAQCt6CRz9BQ385ueK1coHIe+3LffOJCMbjzmV6B493XC
ov71am72AE8o295ohmxEk7axY/0UEmu/H9LqMZshftEzPLpI9d1537O4/xLxIZpL
wYqGcWlKZmZsj348cL+tKSIG8+TA5oCu4kuPt5l+lAOf00eXfJlII1PoOK5PCm+D
LtFJV4yAdLbaL9A4jXsDcCEbdfIwPPqPrt3aY6vrFk/CjhFLfs8L6P+1dy70sntK
4EwSJQxwjQMpoOFTJOwT2e4ZvxCzSow/iaNhUd6shweU9GNx7C7ib1uYgeGJXDR5
bHbvO5BieebbpJovJsXQEOEO3tkQjhb7t/eo98flAgeYjzYIlefiN5YNNnWe+w5y
sR2bvAP5SQXYgd0FtCrWQemsAXaVCg/Y39W9Eh81LygXbNKYwagJZHduRze6zqxZ
Xmidf3LWicUGQSk+WT7dJvUkyRGnWqNMQB9GoZm1pzpRboY7nn1ypxIFeFntPlF4
FQsDj43QLwWyPntKHEtzBRL8xurgUBN8Q5N0s8p0544fAQjQMNRbcTa0B7rBMDBc
SLeCO5imfWCKoqMpgsy6vYMEG6KDA0Gh1gXxG8K28Kh8hjtGqEgqiNx2mna/H2ql
PRmP6zjzZN7IKw0KKP/32+IVQtQi0Cdd4Xn+GOdwiK1O5tmLOsbdJ1Fu/7xk9TND
TwIDAQABo4IBRjCCAUIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw
SwYIKwYBBQUHAQEEPzA9MDsGCCsGAQUFBzAChi9odHRwOi8vYXBwcy5pZGVudHJ1
c3QuY29tL3Jvb3RzL2RzdHJvb3RjYXgzLnA3YzAfBgNVHSMEGDAWgBTEp7Gkeyxx
+tvhS5B1/8QVYIWJEDBUBgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEB
ATAwMC4GCCsGAQUFBwIBFiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQu
b3JnMDwGA1UdHwQ1MDMwMaAvoC2GK2h0dHA6Ly9jcmwuaWRlbnRydXN0LmNvbS9E
U1RST09UQ0FYM0NSTC5jcmwwHQYDVR0OBBYEFHm0WeZ7tuXkAXOACIjIGlj26Ztu
MA0GCSqGSIb3DQEBCwUAA4IBAQAKcwBslm7/DlLQrt2M51oGrS+o44+/yQoDFVDC
5WxCu2+b9LRPwkSICHXM6webFGJueN7sJ7o5XPWioW5WlHAQU7G75K/QosMrAdSW
9MUgNTP52GE24HGNtLi1qoJFlcDyqSMo59ahy2cI2qBDLKobkx/J3vWraV0T9VuG
WCLKTVXkcGdtwlfFRjlBz4pYg1htmf5X6DYO8A4jqv2Il9DjXA6USbW1FzXSLr9O
he8Y4IWS6wY7bCkjCWDcRQJMEhg76fsO3txE+FiYruq9RUWhiF1myv4Q6W+CyBFC
Dfvp7OOGAN6dEOM4+qR9sdjoSYKEBpsr6GtPAQw4dy753ec5
-----END CERTIFICATE-----

View file

@ -102,6 +102,18 @@ describe('gl_emoji', () => {
}); });
}); });
it('escapes gl-emoji name', async () => {
const glEmojiElement = markupToDomElement(
"<gl-emoji data-name='&#34;x=&#34y&#34 onload=&#34;alert(document.location.href)&#34;' data-unicode-version='x'>abc</gl-emoji>",
);
await waitForPromises();
expect(glEmojiElement.outerHTML).toBe(
'<gl-emoji data-name="&quot;x=&quot;y&quot; onload=&quot;alert(document.location.href)&quot;" data-unicode-version="x"><img class="emoji" title=":&quot;x=&quot;y&quot; onload=&quot;alert(document.location.href)&quot;:" alt=":&quot;x=&quot;y&quot; onload=&quot;alert(document.location.href)&quot;:" src="/-/emojis/1/grey_question.png" width="20" height="20" align="absmiddle"></gl-emoji>',
);
});
it('Adds sprite CSS if emojis are not supported', async () => { it('Adds sprite CSS if emojis are not supported', async () => {
const testPath = '/test-path.css'; const testPath = '/test-path.css';
jest.spyOn(EmojiUnicodeSupport, 'default').mockReturnValue(false); jest.spyOn(EmojiUnicodeSupport, 'default').mockReturnValue(false);

View file

@ -560,13 +560,13 @@ RSpec.describe Resolvers::IssuesResolver do
end end
it 'finds a specific issue with iid', :request_store do it 'finds a specific issue with iid', :request_store do
result = batch_sync(max_queries: 5) { resolve_issues(iid: issue1.iid).to_a } result = batch_sync(max_queries: 6) { resolve_issues(iid: issue1.iid).to_a }
expect(result).to contain_exactly(issue1) expect(result).to contain_exactly(issue1)
end end
it 'batches queries that only include IIDs', :request_store do it 'batches queries that only include IIDs', :request_store do
result = batch_sync(max_queries: 5) do result = batch_sync(max_queries: 6) do
[issue1, issue2] [issue1, issue2]
.map { |issue| resolve_issues(iid: issue.iid.to_s) } .map { |issue| resolve_issues(iid: issue.iid.to_s) }
.flat_map(&:to_a) .flat_map(&:to_a)
@ -576,7 +576,7 @@ RSpec.describe Resolvers::IssuesResolver do
end end
it 'finds a specific issue with iids', :request_store do it 'finds a specific issue with iids', :request_store do
result = batch_sync(max_queries: 5) do result = batch_sync(max_queries: 6) do
resolve_issues(iids: [issue1.iid]).to_a resolve_issues(iids: [issue1.iid]).to_a
end end

View file

@ -531,24 +531,6 @@ RSpec.describe Gitlab::UrlBlocker, :stub_invalid_dns_only do
end end
end end
end end
def stub_domain_resolv(domain, ip, port = 80, &block)
address = instance_double(Addrinfo,
ip_address: ip,
ipv4_private?: true,
ipv6_linklocal?: false,
ipv4_loopback?: false,
ipv6_loopback?: false,
ipv4?: false,
ip_port: port
)
allow(Addrinfo).to receive(:getaddrinfo).with(domain, port, any_args).and_return([address])
allow(address).to receive(:ipv6_v4mapped?).and_return(false)
yield
allow(Addrinfo).to receive(:getaddrinfo).and_call_original
end
end end
context 'when enforce_user is' do context 'when enforce_user is' do
@ -611,6 +593,44 @@ RSpec.describe Gitlab::UrlBlocker, :stub_invalid_dns_only do
expect(described_class).to be_blocked_url('http://foobar.x') expect(described_class).to be_blocked_url('http://foobar.x')
end end
context 'when gitlab is running on a non-default port' do
let(:gitlab_port) { 3000 }
before do
stub_config(gitlab: { protocol: 'http', host: 'gitlab.local', port: gitlab_port })
end
it 'returns true for url targeting the wrong port' do
stub_domain_resolv('gitlab.local', '127.0.0.1') do
expect(described_class).to be_blocked_url("http://gitlab.local/foo")
end
end
it 'does not block url on gitlab port' do
stub_domain_resolv('gitlab.local', '127.0.0.1') do
expect(described_class).not_to be_blocked_url("http://gitlab.local:#{gitlab_port}/foo")
end
end
end
def stub_domain_resolv(domain, ip, port = 80, &block)
address = instance_double(Addrinfo,
ip_address: ip,
ipv4_private?: true,
ipv6_linklocal?: false,
ipv4_loopback?: false,
ipv6_loopback?: false,
ipv4?: false,
ip_port: port
)
allow(Addrinfo).to receive(:getaddrinfo).with(domain, port, any_args).and_return([address])
allow(address).to receive(:ipv6_v4mapped?).and_return(false)
yield
allow(Addrinfo).to receive(:getaddrinfo).and_call_original
end
end end
describe '#validate_hostname' do describe '#validate_hostname' do

View file

@ -201,7 +201,7 @@ RSpec.describe Clusters::Platforms::Kubernetes do
it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::KubeClient) } it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::KubeClient) }
context 'ca_pem is a single certificate' do context 'ca_pem is a single certificate' do
let(:ca_pem) { File.read(Rails.root.join('spec/fixtures/clusters/ca_certificate.pem')) } let(:ca_pem) { File.read(Rails.root.join('spec/fixtures/clusters/root_certificate.pem')) }
let(:kubernetes) do let(:kubernetes) do
build(:cluster_platform_kubernetes, build(:cluster_platform_kubernetes,
:configured, :configured,
@ -228,21 +228,22 @@ RSpec.describe Clusters::Platforms::Kubernetes do
ca_pem: cert_chain) ca_pem: cert_chain)
end end
where(:fixture_path) do
%w[
spec/fixtures/clusters/root_certificate.pem
spec/fixtures/clusters/intermediate_certificate.pem
spec/fixtures/clusters/leaf_certificate.pem
]
end
with_them do
it 'includes chain of certificates' do it 'includes chain of certificates' do
cert1_file = File.read(Rails.root.join('spec/fixtures/clusters/root_certificate.pem'))
cert1 = OpenSSL::X509::Certificate.new(cert1_file)
cert2_file = File.read(Rails.root.join('spec/fixtures/clusters/intermediate_certificate.pem'))
cert2 = OpenSSL::X509::Certificate.new(cert2_file)
cert3_file = File.read(Rails.root.join('spec/fixtures/clusters/ca_certificate.pem'))
cert3 = OpenSSL::X509::Certificate.new(cert3_file)
cert_store = kubernetes.kubeclient.kubeclient_options[:ssl_options][:cert_store] cert_store = kubernetes.kubeclient.kubeclient_options[:ssl_options][:cert_store]
cert_file = File.read(Rails.root.join(fixture_path))
certificate = OpenSSL::X509::Certificate.new(cert_file)
expect(cert_store.verify(cert1)).to be true expect(cert_store.verify(certificate)).to be true
expect(cert_store.verify(cert2)).to be true end
expect(cert_store.verify(cert3)).to be true
end end
end end
end end

View file

@ -16,6 +16,8 @@ RSpec.describe Integrations::ChatMessage::AlertMessage do
}.merge(Gitlab::DataBuilder::Alert.build(alert)) }.merge(Gitlab::DataBuilder::Alert.build(alert))
end end
it_behaves_like Integrations::ChatMessage
describe '#message' do describe '#message' do
it 'returns the correct message' do it 'returns the correct message' do
expect(subject.message).to eq("Alert firing in #{args[:project_name]}") expect(subject.message).to eq("Alert firing in #{args[:project_name]}")

View file

@ -31,4 +31,22 @@ RSpec.describe Integrations::ChatMessage::BaseMessage do
it { is_expected.to eq('Check this out https://gitlab-domain.com/uploads/Screenshot1.png. And this https://gitlab-domain.com/uploads/Screenshot2.png') } it { is_expected.to eq('Check this out https://gitlab-domain.com/uploads/Screenshot1.png. And this https://gitlab-domain.com/uploads/Screenshot2.png') }
end end
end end
describe '#strip_markup' do
using RSpec::Parameterized::TableSyntax
where(:input, :output) do
nil | nil
'' | ''
'[label](url)' | 'label(url)'
'<url|label>' | 'urllabel'
'<a href="url">label</a>' | 'a href="url"label/a'
end
with_them do
it 'returns the expected output' do
expect(base_message.send(:strip_markup, input)).to eq(output)
end
end
end
end end

View file

@ -3,83 +3,79 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Integrations::ChatMessage::DeploymentMessage do RSpec.describe Integrations::ChatMessage::DeploymentMessage do
subject { described_class.new(args) }
let_it_be(:user) { create(:user, name: 'John Smith', username: 'smith') }
let_it_be(:namespace) { create(:namespace, name: 'myspace') }
let_it_be(:project) { create(:project, :repository, namespace: namespace, name: 'myproject') }
let_it_be(:commit) { project.commit('HEAD') }
let_it_be(:ci_build) { create(:ci_build, project: project) }
let_it_be(:environment) { create(:environment, name: 'myenvironment', project: project) }
let_it_be(:deployment) { create(:deployment, status: :success, deployable: ci_build, environment: environment, project: project, user: user, sha: commit.sha) }
let(:args) do
Gitlab::DataBuilder::Deployment.build(deployment, Time.current)
end
it_behaves_like Integrations::ChatMessage
describe '#pretext' do describe '#pretext' do
it 'returns a message with the data returned by the deployment data builder' do it 'returns a message with the data returned by the deployment data builder' do
environment = create(:environment, name: "myenvironment") expect(subject.pretext).to eq("Deploy to myenvironment succeeded")
project = create(:project, :repository)
commit = project.commit('HEAD')
deployment = create(:deployment, status: :success, environment: environment, project: project, sha: commit.sha)
data = Gitlab::DataBuilder::Deployment.build(deployment, Time.current)
message = described_class.new(data)
expect(message.pretext).to eq("Deploy to myenvironment succeeded")
end end
it 'returns a message for a successful deployment' do it 'returns a message for a successful deployment' do
data = { args.merge!(
status: 'success', status: 'success',
environment: 'production' environment: 'production'
} )
message = described_class.new(data) expect(subject.pretext).to eq('Deploy to production succeeded')
expect(message.pretext).to eq('Deploy to production succeeded')
end end
it 'returns a message for a failed deployment' do it 'returns a message for a failed deployment' do
data = { args.merge!(
status: 'failed', status: 'failed',
environment: 'production' environment: 'production'
} )
message = described_class.new(data) expect(subject.pretext).to eq('Deploy to production failed')
expect(message.pretext).to eq('Deploy to production failed')
end end
it 'returns a message for a canceled deployment' do it 'returns a message for a canceled deployment' do
data = { args.merge!(
status: 'canceled', status: 'canceled',
environment: 'production' environment: 'production'
} )
message = described_class.new(data) expect(subject.pretext).to eq('Deploy to production canceled')
expect(message.pretext).to eq('Deploy to production canceled')
end end
it 'returns a message for a deployment to another environment' do it 'returns a message for a deployment to another environment' do
data = { args.merge!(
status: 'success', status: 'success',
environment: 'staging' environment: 'staging'
} )
message = described_class.new(data) expect(subject.pretext).to eq('Deploy to staging succeeded')
expect(message.pretext).to eq('Deploy to staging succeeded')
end end
it 'returns a message for a deployment with any other status' do it 'returns a message for a deployment with any other status' do
data = { args.merge!(
status: 'unknown', status: 'unknown',
environment: 'staging' environment: 'staging'
} )
message = described_class.new(data) expect(subject.pretext).to eq('Deploy to staging unknown')
expect(message.pretext).to eq('Deploy to staging unknown')
end end
it 'returns a message for a running deployment' do it 'returns a message for a running deployment' do
data = { args.merge!(
status: 'running', status: 'running',
environment: 'production' environment: 'production'
} )
message = described_class.new(data) expect(subject.pretext).to eq('Starting deploy to production')
expect(message.pretext).to eq('Starting deploy to production')
end end
end end
@ -108,21 +104,11 @@ RSpec.describe Integrations::ChatMessage::DeploymentMessage do
end end
it 'returns attachments with the data returned by the deployment data builder' do it 'returns attachments with the data returned by the deployment data builder' do
user = create(:user, name: "John Smith", username: "smith")
namespace = create(:namespace, name: "myspace")
project = create(:project, :repository, namespace: namespace, name: "myproject")
commit = project.commit('HEAD')
environment = create(:environment, name: "myenvironment", project: project)
ci_build = create(:ci_build, project: project)
deployment = create(:deployment, :success, deployable: ci_build, environment: environment, project: project, user: user, sha: commit.sha)
job_url = Gitlab::Routing.url_helpers.project_job_url(project, ci_build) job_url = Gitlab::Routing.url_helpers.project_job_url(project, ci_build)
commit_url = Gitlab::UrlBuilder.build(deployment.commit) commit_url = Gitlab::UrlBuilder.build(deployment.commit)
user_url = Gitlab::Routing.url_helpers.user_url(user) user_url = Gitlab::Routing.url_helpers.user_url(user)
data = Gitlab::DataBuilder::Deployment.build(deployment, Time.current)
message = described_class.new(data) expect(subject.attachments).to eq([{
expect(message.attachments).to eq([{
text: "[myspace/myproject](#{project.web_url}) with job [##{ci_build.id}](#{job_url}) by [John Smith (smith)](#{user_url})\n[#{deployment.short_sha}](#{commit_url}): #{commit.title}", text: "[myspace/myproject](#{project.web_url}) with job [##{ci_build.id}](#{job_url}) by [John Smith (smith)](#{user_url})\n[#{deployment.short_sha}](#{commit_url}): #{commit.title}",
color: "good" color: "good"
}]) }])

View file

@ -28,6 +28,8 @@ RSpec.describe Integrations::ChatMessage::IssueMessage do
} }
end end
it_behaves_like Integrations::ChatMessage
context 'without markdown' do context 'without markdown' do
let(:color) { '#C95823' } let(:color) { '#C95823' }

View file

@ -29,6 +29,8 @@ RSpec.describe Integrations::ChatMessage::MergeMessage do
} }
end end
it_behaves_like Integrations::ChatMessage
context 'without markdown' do context 'without markdown' do
let(:color) { '#345' } let(:color) { '#345' }

View file

@ -19,6 +19,10 @@ RSpec.describe Integrations::ChatMessage::NoteMessage do
name: 'project_name', name: 'project_name',
url: 'http://somewhere.com' url: 'http://somewhere.com'
}, },
commit: {
id: '5f163b2b95e6f53cbd428f5f0b103702a52b9a23',
message: "Added a commit message\ndetails\n123\n"
},
object_attributes: { object_attributes: {
id: 10, id: 10,
note: 'comment on a commit', note: 'comment on a commit',
@ -28,16 +32,9 @@ RSpec.describe Integrations::ChatMessage::NoteMessage do
} }
end end
context 'commit notes' do it_behaves_like Integrations::ChatMessage
before do
args[:object_attributes][:note] = 'comment on a commit'
args[:object_attributes][:noteable_type] = 'Commit'
args[:commit] = {
id: '5f163b2b95e6f53cbd428f5f0b103702a52b9a23',
message: "Added a commit message\ndetails\n123\n"
}
end
context 'commit notes' do
context 'without markdown' do context 'without markdown' do
it 'returns a message regarding notes on commits' do it 'returns a message regarding notes on commits' do
expect(subject.pretext).to eq("Test User (test.user) <http://url.com|commented on " \ expect(subject.pretext).to eq("Test User (test.user) <http://url.com|commented on " \

View file

@ -40,6 +40,8 @@ RSpec.describe Integrations::ChatMessage::PipelineMessage do
let(:has_yaml_errors) { false } let(:has_yaml_errors) { false }
it_behaves_like Integrations::ChatMessage
before do before do
test_commit = double("A test commit", committer: args[:user], title: "A test commit message") test_commit = double("A test commit", committer: args[:user], title: "A test commit message")
test_project = double("A test project", commit_by: test_commit, name: args[:project][:name], web_url: args[:project][:web_url]) test_project = double("A test project", commit_by: test_commit, name: args[:project][:name], web_url: args[:project][:web_url])

View file

@ -19,6 +19,8 @@ RSpec.describe Integrations::ChatMessage::PushMessage do
let(:color) { '#345' } let(:color) { '#345' }
it_behaves_like Integrations::ChatMessage
context 'push' do context 'push' do
before do before do
args[:commits] = [ args[:commits] = [

View file

@ -33,6 +33,8 @@ RSpec.describe Integrations::ChatMessage::WikiPageMessage do
} }
end end
it_behaves_like Integrations::ChatMessage
context 'without markdown' do context 'without markdown' do
describe '#pretext' do describe '#pretext' do
context 'when :action == "create"' do context 'when :action == "create"' do

View file

@ -287,19 +287,6 @@ RSpec.describe PagesDomain do
it { is_expected.to be_truthy } it { is_expected.to be_truthy }
end end
# The LetsEncrypt DST Root CA X3 expired on 2021-09-30, but the
# cross-sign in ISRG Root X1 enables it to function provided a chain
# of trust can be established with the system store. See:
#
# 1. https://community.letsencrypt.org/t/production-chain-changes/150739
# 2. https://letsencrypt.org/2020/12/21/extending-android-compatibility.html
# 3. https://www.openssl.org/blog/blog/2021/09/13/LetsEncryptRootCertExpire/
context 'with a LetsEncrypt bundle with an expired DST Root CA X3' do
let(:domain) { build(:pages_domain, :letsencrypt_expired_x3_root) }
it { is_expected.to be_truthy }
end
end end
describe '#expired?' do describe '#expired?' do

View file

@ -3,8 +3,6 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe API::DependencyProxy, api: true do RSpec.describe API::DependencyProxy, api: true do
include ExclusiveLeaseHelpers
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:blob) { create(:dependency_proxy_blob )} let_it_be(:blob) { create(:dependency_proxy_blob )}
let_it_be(:group, reload: true) { blob.group } let_it_be(:group, reload: true) { blob.group }
@ -20,11 +18,8 @@ RSpec.describe API::DependencyProxy, api: true do
shared_examples 'responding to purge requests' do shared_examples 'responding to purge requests' do
context 'with feature available and enabled' do context 'with feature available and enabled' do
let_it_be(:lease_key) { "dependency_proxy:delete_group_blobs:#{group.id}" }
context 'an admin user' do context 'an admin user' do
it 'deletes the blobs and returns no content' do it 'deletes the blobs and returns no content' do
stub_exclusive_lease(lease_key, timeout: 1.hour)
expect(PurgeDependencyProxyCacheWorker).to receive(:perform_async) expect(PurgeDependencyProxyCacheWorker).to receive(:perform_async)
subject subject
@ -32,23 +27,6 @@ RSpec.describe API::DependencyProxy, api: true do
expect(response).to have_gitlab_http_status(:accepted) expect(response).to have_gitlab_http_status(:accepted)
expect(response.body).to eq('202') expect(response.body).to eq('202')
end end
context 'called multiple times in one hour', :clean_gitlab_redis_shared_state do
it 'returns 409 with an error message' do
stub_exclusive_lease_taken(lease_key, timeout: 1.hour)
subject
expect(response).to have_gitlab_http_status(:conflict)
expect(response.body).to include('This request has already been made.')
end
it 'executes service only for the first time' do
expect(PurgeDependencyProxyCacheWorker).to receive(:perform_async).once
2.times { subject }
end
end
end end
context 'a non-admin' do context 'a non-admin' do

View file

@ -253,7 +253,7 @@ RSpec.describe 'GraphQL' do
end end
context 'with token authentication' do context 'with token authentication' do
let(:token) { create(:personal_access_token) } let(:token) { create(:personal_access_token, user: user) }
it 'authenticates users with a PAT' do it 'authenticates users with a PAT' do
stub_authentication_activity_metrics(debug: false) stub_authentication_activity_metrics(debug: false)
@ -276,6 +276,32 @@ RSpec.describe 'GraphQL' do
expect(graphql_errors).to include({ 'message' => /API not accessible/ }) expect(graphql_errors).to include({ 'message' => /API not accessible/ })
end end
context 'when user with expired password' do
let_it_be(:user) { create(:user, password_expires_at: 2.minutes.ago) }
it 'does not authenticate user' do
post_graphql(query, headers: { 'PRIVATE-TOKEN' => token.token })
expect(response).to have_gitlab_http_status(:ok)
expect(graphql_data['echo']).to eq('nil says: Hello world')
end
end
context 'when password expiration is not applicable' do
context 'when ldap user' do
let_it_be(:user) { create(:omniauth_user, provider: 'ldap', password_expires_at: 2.minutes.ago) }
it 'authenticates user' do
post_graphql(query, headers: { 'PRIVATE-TOKEN' => token.token })
expect(response).to have_gitlab_http_status(:ok)
expect(graphql_data['echo']).to eq("\"#{token.user.username}\" says: Hello world")
end
end
end
context 'when the personal access token has no api scope' do context 'when the personal access token has no api scope' do
it 'does not log the user in' do it 'does not log the user in' do
token.update!(scopes: [:read_user]) token.update!(scopes: [:read_user])

View file

@ -3115,6 +3115,29 @@ RSpec.describe API::Projects do
expect(json_response['message']).to eq('404 Project Not Found') expect(json_response['message']).to eq('404 Project Not Found')
end end
it 'returns 404 if the source project members cannot be viewed by the requester' do
private_project = create(:project, :private)
expect do
post api("/projects/#{project.id}/import_project_members/#{private_project.id}", user)
end.not_to change { project.members.count }
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['message']).to eq('404 Project Not Found')
end
it 'returns 403 if the source project members cannot be administered by the requester' do
project.add_maintainer(user2)
project2.add_developer(user2)
expect do
post api("/projects/#{project.id}/import_project_members/#{project2.id}", user2)
end.not_to change { project.members.count }
expect(response).to have_gitlab_http_status(:forbidden)
expect(json_response['message']).to eq('403 Forbidden - Project')
end
it 'returns 422 if the import failed for valid projects' do it 'returns 422 if the import failed for valid projects' do
allow_next_instance_of(::ProjectTeam) do |project_team| allow_next_instance_of(::ProjectTeam) do |project_team|
allow(project_team).to receive(:import).and_return(false) allow(project_team).to receive(:import).and_return(false)

View file

@ -0,0 +1,11 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Dashboard::ProjectsController do
context 'token authentication' do
it_behaves_like 'authenticates sessionless user for the request spec', 'index atom', public_resource: false do
let(:url) { dashboard_projects_url(:atom) }
end
end
end

View file

@ -0,0 +1,15 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe DashboardController do
context 'token authentication' do
it_behaves_like 'authenticates sessionless user for the request spec', 'issues atom', public_resource: false do
let(:url) { issues_dashboard_url(:atom, assignee_username: user.username) }
end
it_behaves_like 'authenticates sessionless user for the request spec', 'issues_calendar ics', public_resource: false do
let(:url) { issues_dashboard_url(:ics, assignee_username: user.username) }
end
end
end

View file

@ -0,0 +1,51 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GroupsController do
context 'token authentication' do
context 'when public group' do
let_it_be(:public_group) { create(:group, :public) }
it_behaves_like 'authenticates sessionless user for the request spec', 'show atom', public_resource: true do
let(:url) { group_path(public_group, format: :atom) }
end
it_behaves_like 'authenticates sessionless user for the request spec', 'issues atom', public_resource: true do
let(:url) { issues_group_path(public_group, format: :atom) }
end
it_behaves_like 'authenticates sessionless user for the request spec', 'issues_calendar ics', public_resource: true do
let(:url) { issues_group_calendar_url(public_group, format: :ics) }
end
end
context 'when private project' do
let_it_be(:private_group) { create(:group, :private) }
it_behaves_like 'authenticates sessionless user for the request spec', 'show atom', public_resource: false, ignore_metrics: true do
let(:url) { group_path(private_group, format: :atom) }
before do
private_group.add_maintainer(user)
end
end
it_behaves_like 'authenticates sessionless user for the request spec', 'issues atom', public_resource: false, ignore_metrics: true do
let(:url) { issues_group_path(private_group, format: :atom) }
before do
private_group.add_maintainer(user)
end
end
it_behaves_like 'authenticates sessionless user for the request spec', 'issues_calendar ics', public_resource: false, ignore_metrics: true do
let(:url) { issues_group_calendar_url(private_group, format: :ics) }
before do
private_group.add_maintainer(user)
end
end
end
end
end

View file

@ -0,0 +1,27 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::CommitsController do
context 'token authentication' do
context 'when public project' do
let_it_be(:public_project) { create(:project, :repository, :public) }
it_behaves_like 'authenticates sessionless user for the request spec', 'show atom', public_resource: true do
let(:url) { project_commits_url(public_project, public_project.default_branch, format: :atom) }
end
end
context 'when private project' do
let_it_be(:private_project) { create(:project, :repository, :private) }
it_behaves_like 'authenticates sessionless user for the request spec', 'show atom', public_resource: false, ignore_metrics: true do
let(:url) { project_commits_url(private_project, private_project.default_branch, format: :atom) }
before do
private_project.add_maintainer(user)
end
end
end
end
end

View file

@ -8,11 +8,11 @@ RSpec.describe Projects::IssuesController do
let_it_be(:project) { issue.project } let_it_be(:project) { issue.project }
let_it_be(:user) { issue.author } let_it_be(:user) { issue.author }
describe 'GET #discussions' do
before do before do
login_as(user) login_as(user)
end end
describe 'GET #discussions' do
let_it_be(:discussion) { create(:discussion_note_on_issue, noteable: issue, project: issue.project) } let_it_be(:discussion) { create(:discussion_note_on_issue, noteable: issue, project: issue.project) }
let_it_be(:discussion_reply) { create(:discussion_note_on_issue, noteable: issue, project: issue.project, in_reply_to: discussion) } let_it_be(:discussion_reply) { create(:discussion_note_on_issue, noteable: issue, project: issue.project, in_reply_to: discussion) }
let_it_be(:state_event) { create(:resource_state_event, issue: issue) } let_it_be(:state_event) { create(:resource_state_event, issue: issue) }
@ -68,4 +68,38 @@ RSpec.describe Projects::IssuesController do
end end
end end
end end
context 'token authentication' do
context 'when public project' do
let_it_be(:public_project) { create(:project, :public) }
it_behaves_like 'authenticates sessionless user for the request spec', 'index atom', public_resource: true do
let(:url) { project_issues_url(public_project, format: :atom) }
end
it_behaves_like 'authenticates sessionless user for the request spec', 'calendar ics', public_resource: true do
let(:url) { project_issues_url(public_project, format: :ics) }
end
end
context 'when private project' do
let_it_be(:private_project) { create(:project, :private) }
it_behaves_like 'authenticates sessionless user for the request spec', 'index atom', public_resource: false, ignore_metrics: true do
let(:url) { project_issues_url(private_project, format: :atom) }
before do
private_project.add_maintainer(user)
end
end
it_behaves_like 'authenticates sessionless user for the request spec', 'calendar ics', public_resource: false, ignore_metrics: true do
let(:url) { project_issues_url(private_project, format: :ics) }
before do
private_project.add_maintainer(user)
end
end
end
end
end end

View file

@ -0,0 +1,27 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::MergeRequestsController do
context 'token authentication' do
context 'when public project' do
let_it_be(:public_project) { create(:project, :public) }
it_behaves_like 'authenticates sessionless user for the request spec', 'index atom', public_resource: true do
let(:url) { project_merge_requests_url(public_project, format: :atom) }
end
end
context 'when private project' do
let_it_be(:private_project) { create(:project, :private) }
it_behaves_like 'authenticates sessionless user for the request spec', 'index atom', public_resource: false, ignore_metrics: true do
let(:url) { project_merge_requests_url(private_project, format: :atom) }
before do
private_project.add_maintainer(user)
end
end
end
end
end

View file

@ -0,0 +1,27 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::TagsController do
context 'token authentication' do
context 'when public project' do
let_it_be(:public_project) { create(:project, :repository, :public) }
it_behaves_like 'authenticates sessionless user for the request spec', 'index atom', public_resource: true do
let(:url) { project_tags_url(public_project, format: :atom) }
end
end
context 'when private project' do
let_it_be(:private_project) { create(:project, :repository, :private) }
it_behaves_like 'authenticates sessionless user for the request spec', 'index atom', public_resource: false, ignore_metrics: true do
let(:url) { project_tags_url(private_project, format: :atom) }
before do
private_project.add_maintainer(user)
end
end
end
end
end

View file

@ -0,0 +1,27 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ProjectsController do
context 'token authentication' do
context 'when public project' do
let_it_be(:public_project) { create(:project, :public) }
it_behaves_like 'authenticates sessionless user for the request spec', 'show atom', public_resource: true do
let(:url) { project_url(public_project, format: :atom) }
end
end
context 'when private project' do
let_it_be(:private_project) { create(:project, :private) }
it_behaves_like 'authenticates sessionless user for the request spec', 'show atom', public_resource: false, ignore_metrics: true do
let(:url) { project_url(private_project, format: :atom) }
before do
private_project.add_maintainer(user)
end
end
end
end
end

View file

@ -792,9 +792,9 @@ RSpec.describe UsersController do
end end
context 'token authentication' do context 'token authentication' do
let(:url) { user_url(user.username, format: :atom) } it_behaves_like 'authenticates sessionless user for the request spec', 'show atom', public_resource: true do
let(:url) { user_url(user, format: :atom) }
it_behaves_like 'authenticates sessionless user for the request spec', public: true end
end end
def user_moved_message(redirect_route, user) def user_moved_message(redirect_route, user)

View file

@ -11,10 +11,8 @@ RSpec.describe Packages::Npm::CreatePackageService do
Gitlab::Json.parse(fixture_file('packages/npm/payload.json') Gitlab::Json.parse(fixture_file('packages/npm/payload.json')
.gsub('@root/npm-test', package_name) .gsub('@root/npm-test', package_name)
.gsub('1.0.1', version)).with_indifferent_access .gsub('1.0.1', version)).with_indifferent_access
.merge!(override)
end end
let(:override) { {} }
let(:package_name) { "@#{namespace.path}/my-app" } let(:package_name) { "@#{namespace.path}/my-app" }
let(:version_data) { params.dig('versions', '1.0.1') } let(:version_data) { params.dig('versions', '1.0.1') }
@ -127,13 +125,53 @@ RSpec.describe Packages::Npm::CreatePackageService do
it { expect(subject[:message]).to be 'Package already exists.' } it { expect(subject[:message]).to be 'Package already exists.' }
end end
context 'file size above maximum limit' do describe 'max file size validation' do
before do let(:max_file_size) { 5.bytes}
params['_attachments']["#{package_name}-#{version}.tgz"]['length'] = project.actual_limits.npm_max_file_size + 1
shared_examples_for 'max file size validation failure' do
it 'returns a 400 error', :aggregate_failures do
expect(subject[:http_status]).to eq 400
expect(subject[:message]).to be 'File is too large.'
end
end end
it { expect(subject[:http_status]).to eq 400 } before do
it { expect(subject[:message]).to be 'File is too large.' } project.actual_limits.update!(npm_max_file_size: max_file_size)
end
context 'when max file size is exceeded' do
# NOTE: The base64 encoded package data in the fixture file is the "hello\n" string, whose byte size is 6.
it_behaves_like 'max file size validation failure'
end
context 'when file size is faked by setting the attachment length param to a lower size' do
let(:params) { super().deep_merge!( { _attachments: { "#{package_name}-#{version}.tgz" => { data: encoded_package_data, length: 1 } } }) }
# TODO (technical debt): Extract the package size calculation outside the service and add separate specs for it.
# Right now we have several contexts here to test the calculation's different scenarios.
context "when encoded package data is not padded" do
# 'Hello!' (size = 6 bytes) => 'SGVsbG8h'
let(:encoded_package_data) { 'SGVsbG8h' }
it_behaves_like 'max file size validation failure'
end
context "when encoded package data is padded with '='" do
let(:max_file_size) { 4.bytes}
# 'Hello' (size = 5 bytes) => 'SGVsbG8='
let(:encoded_package_data) { 'SGVsbG8=' }
it_behaves_like 'max file size validation failure'
end
context "when encoded package data is padded with '=='" do
let(:max_file_size) { 3.bytes}
# 'Hell' (size = 4 bytes) => 'SGVsbA=='
let(:encoded_package_data) { 'SGVsbA==' }
it_behaves_like 'max file size validation failure'
end
end
end end
[ [
@ -152,7 +190,7 @@ RSpec.describe Packages::Npm::CreatePackageService do
end end
context 'with empty versions' do context 'with empty versions' do
let(:override) { { versions: {} } } let(:params) { super().merge!({ versions: {} } ) }
it { expect(subject[:http_status]).to eq 400 } it { expect(subject[:http_status]).to eq 400 }
it { expect(subject[:message]).to eq 'Version is empty.' } it { expect(subject[:message]).to eq 'Version is empty.' }

View file

@ -1,112 +0,0 @@
# frozen_string_literal: true
# This controller shared examples will be migrated to
# spec/support/shared_examples/requests/sessionless_auth_request_shared_examples.rb
# See also https://gitlab.com/groups/gitlab-org/-/epics/5076
RSpec.shared_examples 'authenticates sessionless user' do |path, format, params|
params ||= {}
before do
stub_authentication_activity_metrics(debug: false)
end
let(:user) { create(:user) }
let(:personal_access_token) { create(:personal_access_token, user: user) }
let(:default_params) { { format: format }.merge(params.except(:public) || {}) }
context "when the 'personal_access_token' param is populated with the personal access token" do
it 'logs the user in' do
expect(authentication_metrics)
.to increment(:user_authenticated_counter)
.and increment(:user_session_override_counter)
.and increment(:user_sessionless_authentication_counter)
get path, params: default_params.merge(private_token: personal_access_token.token)
expect(response).to have_gitlab_http_status(:ok)
expect(controller.current_user).to eq(user)
end
it 'does not log the user in if page is public', if: params[:public] do
get path, params: default_params
expect(response).to have_gitlab_http_status(:ok)
expect(controller.current_user).to be_nil
end
end
context 'when the personal access token has no api scope', unless: params[:public] do
it 'does not log the user in' do
# Several instances of where these specs are shared route the request
# through ApplicationController#route_not_found which does not involve
# the usual auth code from Devise, so does not increment the
# :user_unauthenticated_counter
#
unless params[:ignore_incrementing]
expect(authentication_metrics)
.to increment(:user_unauthenticated_counter)
end
personal_access_token.update!(scopes: [:read_user])
get path, params: default_params.merge(private_token: personal_access_token.token)
expect(response).not_to have_gitlab_http_status(:ok)
end
end
context "when the 'PERSONAL_ACCESS_TOKEN' header is populated with the personal access token" do
it 'logs the user in' do
expect(authentication_metrics)
.to increment(:user_authenticated_counter)
.and increment(:user_session_override_counter)
.and increment(:user_sessionless_authentication_counter)
@request.headers['PRIVATE-TOKEN'] = personal_access_token.token
get path, params: default_params
expect(response).to have_gitlab_http_status(:ok)
end
end
context "when the 'feed_token' param is populated with the feed token", if: format == :rss do
it "logs the user in" do
expect(authentication_metrics)
.to increment(:user_authenticated_counter)
.and increment(:user_session_override_counter)
.and increment(:user_sessionless_authentication_counter)
get path, params: default_params.merge(feed_token: user.feed_token)
expect(response).to have_gitlab_http_status(:ok)
end
end
context "when the 'feed_token' param is populated with an invalid feed token", if: format == :rss, unless: params[:public] do
it "logs the user" do
expect(authentication_metrics)
.to increment(:user_unauthenticated_counter)
get path, params: default_params.merge(feed_token: 'token')
expect(response).not_to have_gitlab_http_status(:ok)
end
end
it "doesn't log the user in otherwise", unless: params[:public] do
# Several instances of where these specs are shared route the request
# through ApplicationController#route_not_found which does not involve
# the usual auth code from Devise, so does not increment the
# :user_unauthenticated_counter
#
unless params[:ignore_incrementing]
expect(authentication_metrics)
.to increment(:user_unauthenticated_counter)
end
get path, params: default_params.merge(private_token: 'token')
expect(response).not_to have_gitlab_http_status(:ok)
end
end

View file

@ -0,0 +1,56 @@
# frozen_string_literal: true
RSpec.shared_examples Integrations::ChatMessage do
context 'when input contains link markup' do
let(:evil_input) { '[Markdown](http://evil.com) <a href="http://evil.com">HTML</a> <http://evil.com|Slack>' }
# Attributes returned from #activity and #attributes which should be sanitized.
let(:sanitized_attributes) do
%i[title subtitle text fallback author_name]
end
# Attributes passed to #initialize which can contain user input.
before do
args.deep_merge!(
project_name: evil_input,
user_name: evil_input,
user_full_name: evil_input,
commit_title: evil_input,
environment: evil_input,
project: {
name: evil_input
},
user: {
name: evil_input,
username: evil_input
},
object_attributes: {
title: evil_input
}
)
end
# NOTE: The `include` matcher is used here so the RSpec error messages will tell us
# which method or attribute is failing, even though it makes the spec a bit less readable.
it 'strips all link markup characters', :aggregate_failures do
expect(subject).not_to have_attributes(
pretext: include(evil_input),
summary: include(evil_input)
)
begin
sanitized_attributes.each do |attribute|
expect(subject.activity).not_to include(attribute => include(evil_input))
end
rescue NotImplementedError
end
begin
sanitized_attributes.each do |attribute|
expect(subject.attachments).not_to include(include(attribute => include(evil_input)))
end
rescue NotImplementedError
end
end
end
end

View file

@ -1,84 +1,160 @@
# frozen_string_literal: true # frozen_string_literal: true
RSpec.shared_examples 'authenticates sessionless user for the request spec' do |params| RSpec.shared_examples 'authenticates sessionless user for the request spec' do |name, public_resource:, ignore_metrics: false, params: {}|
params ||= {}
before do before do
stub_authentication_activity_metrics(debug: false) stub_authentication_activity_metrics(debug: false)
end end
let(:user) { create(:user) } let_it_be(:user) { create(:user) }
let(:personal_access_token) { create(:personal_access_token, user: user) } let(:personal_access_token) { create(:personal_access_token, user: user) }
let(:default_params) { params.except(:public) || {} }
context "when the 'personal_access_token' param is populated with the personal access token" do shared_examples 'authenticates user and returns response with ok status' do
it 'logs the user in' do it 'authenticates user and returns response with ok status' do
expect(authentication_metrics) expect(authentication_metrics)
.to increment(:user_authenticated_counter) .to increment(:user_authenticated_counter)
.and increment(:user_session_override_counter) .and increment(:user_session_override_counter)
.and increment(:user_sessionless_authentication_counter) .and increment(:user_sessionless_authentication_counter)
get url, params: default_params.merge(private_token: personal_access_token.token) subject
expect(response).to have_gitlab_http_status(:ok)
expect(controller.current_user).to eq(user) expect(controller.current_user).to eq(user)
expect(response).to have_gitlab_http_status(:ok)
end
end end
it 'does not log the user in if page is public', if: params[:public] do shared_examples 'does not authenticate user and returns response with ok status' do
get url, params: default_params it 'does not authenticate user and returns response with ok status' do
subject
expect(response).to have_gitlab_http_status(:ok)
expect(controller.current_user).to be_nil expect(controller.current_user).to be_nil
end
end
context 'when the personal access token has no api scope', unless: params[:public] do
it 'does not log the user in' do
# Several instances of where these specs are shared route the request
# through ApplicationController#route_not_found which does not involve
# the usual auth code from Devise, so does not increment the
# :user_unauthenticated_counter
#
unless params[:ignore_incrementing]
expect(authentication_metrics)
.to increment(:user_unauthenticated_counter)
end
personal_access_token.update!(scopes: [:read_user])
get url, params: default_params.merge(private_token: personal_access_token.token)
expect(response).not_to have_gitlab_http_status(:ok)
end
end
context "when the 'PERSONAL_ACCESS_TOKEN' header is populated with the personal access token" do
it 'logs the user in' do
expect(authentication_metrics)
.to increment(:user_authenticated_counter)
.and increment(:user_session_override_counter)
.and increment(:user_sessionless_authentication_counter)
headers = { 'PRIVATE-TOKEN': personal_access_token.token }
get url, params: default_params, headers: headers
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
end end
end end
it "doesn't log the user in otherwise", unless: params[:public] do shared_examples 'does not return response with ok status' do
it 'does not return response with ok status' do
# Several instances of where these specs are shared route the request # Several instances of where these specs are shared route the request
# through ApplicationController#route_not_found which does not involve # through ApplicationController#route_not_found which does not involve
# the usual auth code from Devise, so does not increment the # the usual auth code from Devise, so does not increment the
# :user_unauthenticated_counter # :user_unauthenticated_counter
# unless ignore_metrics
unless params[:ignore_incrementing]
expect(authentication_metrics) expect(authentication_metrics)
.to increment(:user_unauthenticated_counter) .to increment(:user_unauthenticated_counter)
end end
get url, params: default_params.merge(private_token: 'token') subject
expect(response).not_to have_gitlab_http_status(:ok) expect(response).not_to have_gitlab_http_status(:ok)
end end
end end
shared_examples 'using valid token' do
context 'when resource is private', unless: public_resource do
include_examples 'authenticates user and returns response with ok status'
context 'when user with expired password' do
let_it_be(:user) { create(:user, password_expires_at: 2.minutes.ago) }
include_examples 'does not return response with ok status'
end
context 'when password expiration is not applicable' do
context 'when ldap user' do
let_it_be(:user) { create(:omniauth_user, provider: 'ldap', password_expires_at: 2.minutes.ago) }
include_examples 'authenticates user and returns response with ok status'
end
end
end
context 'when resource is public', if: public_resource do
include_examples 'authenticates user and returns response with ok status'
context 'when user with expired password' do
let_it_be(:user) { create(:user, password_expires_at: 2.minutes.ago) }
include_examples 'does not authenticate user and returns response with ok status'
end
end
end
shared_examples 'using invalid token' do
context 'when resource is private', unless: public_resource do
include_examples 'does not return response with ok status'
end
context 'when resource is public', if: public_resource do
include_examples 'does not authenticate user and returns response with ok status'
end
end
shared_examples 'personal access token has no api scope' do
context 'when the personal access token has no api scope' do
before do
personal_access_token.update!(scopes: [:read_user])
end
context 'when resource is private', unless: public_resource do
include_examples 'does not return response with ok status'
end
context 'when resource is public', if: public_resource do
include_examples 'does not authenticate user and returns response with ok status'
end
end
end
describe name do
context "when the 'private_token' param is populated with the personal access token" do
context 'when valid token' do
subject { get url, params: params.merge(private_token: personal_access_token.token) }
include_examples 'using valid token'
include_examples 'personal access token has no api scope'
end
context 'when invalid token' do
subject { get url, params: params.merge(private_token: 'invalid token') }
include_examples 'using invalid token'
end
end
context "when the 'PRIVATE-TOKEN' header is populated with the personal access token" do
context 'when valid token' do
subject do
headers = { 'PRIVATE-TOKEN': personal_access_token.token }
get url, params: params, headers: headers
end
include_examples 'using valid token'
include_examples 'personal access token has no api scope'
end
context 'when invalid token' do
subject do
headers = { 'PRIVATE-TOKEN': 'invalid token' }
get url, params: params, headers: headers
end
include_examples 'using invalid token'
end
end
context "when the 'feed_token' param is populated with the feed token" do
context 'when valid token' do
subject { get url, params: params.merge(feed_token: user.feed_token) }
include_examples 'using valid token'
end
context 'when invalid token' do
subject { get url, params: params.merge(feed_token: 'invalid token') }
include_examples 'using invalid token'
end
end
end
end

View file

@ -0,0 +1,34 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe DependencyProxy::CleanupDependencyProxyWorker do
describe '#perform' do
subject { described_class.new.perform }
context 'when there are records to be deleted' do
it_behaves_like 'an idempotent worker' do
it 'queues the cleanup jobs', :aggregate_failures do
create(:dependency_proxy_blob, :expired)
create(:dependency_proxy_manifest, :expired)
expect(DependencyProxy::CleanupBlobWorker).to receive(:perform_with_capacity).twice
expect(DependencyProxy::CleanupManifestWorker).to receive(:perform_with_capacity).twice
subject
end
end
end
context 'when there are not records to be deleted' do
it_behaves_like 'an idempotent worker' do
it 'does not queue the cleanup jobs', :aggregate_failures do
expect(DependencyProxy::CleanupBlobWorker).not_to receive(:perform_with_capacity)
expect(DependencyProxy::CleanupManifestWorker).not_to receive(:perform_with_capacity)
subject
end
end
end
end
end

View file

@ -17,13 +17,6 @@ RSpec.describe DependencyProxy::ImageTtlGroupPolicyWorker do
let_it_be_with_reload(:new_blob) { create(:dependency_proxy_blob, group: group) } let_it_be_with_reload(:new_blob) { create(:dependency_proxy_blob, group: group) }
let_it_be_with_reload(:new_manifest) { create(:dependency_proxy_manifest, group: group) } let_it_be_with_reload(:new_manifest) { create(:dependency_proxy_manifest, group: group) }
it 'calls the limited capacity workers', :aggregate_failures do
expect(DependencyProxy::CleanupBlobWorker).to receive(:perform_with_capacity)
expect(DependencyProxy::CleanupManifestWorker).to receive(:perform_with_capacity)
subject
end
it 'updates the old images to expired' do it 'updates the old images to expired' do
expect { subject } expect { subject }
.to change { old_blob.reload.status }.from('default').to('expired') .to change { old_blob.reload.status }.from('default').to('expired')
@ -33,15 +26,6 @@ RSpec.describe DependencyProxy::ImageTtlGroupPolicyWorker do
end end
end end
context 'when there are no images to expire' do
it 'does not do anything', :aggregate_failures do
expect(DependencyProxy::CleanupBlobWorker).not_to receive(:perform_with_capacity)
expect(DependencyProxy::CleanupManifestWorker).not_to receive(:perform_with_capacity)
subject
end
end
context 'counts logging' do context 'counts logging' do
let_it_be(:expired_blob) { create(:dependency_proxy_blob, :expired, group: group) } let_it_be(:expired_blob) { create(:dependency_proxy_blob, :expired, group: group) }
let_it_be(:expired_blob2) { create(:dependency_proxy_blob, :expired, group: group) } let_it_be(:expired_blob2) { create(:dependency_proxy_blob, :expired, group: group) }

View file

@ -4,18 +4,18 @@ require 'spec_helper'
RSpec.describe PurgeDependencyProxyCacheWorker do RSpec.describe PurgeDependencyProxyCacheWorker do
let_it_be(:user) { create(:admin) } let_it_be(:user) { create(:admin) }
let_it_be(:blob) { create(:dependency_proxy_blob )} let_it_be_with_refind(:blob) { create(:dependency_proxy_blob )}
let_it_be(:group, reload: true) { blob.group } let_it_be_with_reload(:group) { blob.group }
let_it_be(:manifest) { create(:dependency_proxy_manifest, group: group )} let_it_be_with_refind(:manifest) { create(:dependency_proxy_manifest, group: group )}
let_it_be(:group_id) { group.id } let_it_be(:group_id) { group.id }
subject { described_class.new.perform(user.id, group_id) } subject { described_class.new.perform(user.id, group_id) }
describe '#perform' do describe '#perform' do
shared_examples 'not removing blobs and manifests' do shared_examples 'not expiring blobs and manifests' do
it 'does not remove blobs and manifests', :aggregate_failures do it 'does not expire blobs and manifests', :aggregate_failures do
expect { subject }.not_to change { group.dependency_proxy_blobs.size } expect { subject }.not_to change { blob.status }
expect { subject }.not_to change { group.dependency_proxy_manifests.size } expect { subject }.not_to change { manifest.status }
expect(subject).to be_nil expect(subject).to be_nil
end end
end end
@ -25,39 +25,36 @@ RSpec.describe PurgeDependencyProxyCacheWorker do
include_examples 'an idempotent worker' do include_examples 'an idempotent worker' do
let(:job_args) { [user.id, group_id] } let(:job_args) { [user.id, group_id] }
it 'deletes the blobs and returns ok', :aggregate_failures do it 'expires the blobs and returns ok', :aggregate_failures do
expect(group.dependency_proxy_blobs.size).to eq(1)
expect(group.dependency_proxy_manifests.size).to eq(1)
subject subject
expect(group.dependency_proxy_blobs.size).to eq(0) expect(blob).to be_expired
expect(group.dependency_proxy_manifests.size).to eq(0) expect(manifest).to be_expired
end end
end end
end end
context 'when admin mode is disabled' do context 'when admin mode is disabled' do
it_behaves_like 'not removing blobs and manifests' it_behaves_like 'not expiring blobs and manifests'
end end
end end
context 'a non-admin user' do context 'a non-admin user' do
let(:user) { create(:user) } let(:user) { create(:user) }
it_behaves_like 'not removing blobs and manifests' it_behaves_like 'not expiring blobs and manifests'
end end
context 'an invalid user id' do context 'an invalid user id' do
let(:user) { double('User', id: 99999 ) } let(:user) { double('User', id: 99999 ) }
it_behaves_like 'not removing blobs and manifests' it_behaves_like 'not expiring blobs and manifests'
end end
context 'an invalid group' do context 'an invalid group' do
let(:group_id) { 99999 } let(:group_id) { 99999 }
it_behaves_like 'not removing blobs and manifests' it_behaves_like 'not expiring blobs and manifests'
end end
end end
end end