diff --git a/CHANGELOG.md b/CHANGELOG.md index fce415ec96..ff4d35620f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 14.5.3 (2022-01-11) + +No changes. + ## 14.5.2 (2021-12-03) No changes. diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index e9d8f88edb..7f0423b3c4 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -14.5.2 \ No newline at end of file +14.5.3 \ No newline at end of file diff --git a/VERSION b/VERSION index e9d8f88edb..7f0423b3c4 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -14.5.2 \ No newline at end of file +14.5.3 \ No newline at end of file diff --git a/app/assets/javascripts/behaviors/gl_emoji.js b/app/assets/javascripts/behaviors/gl_emoji.js index ef445548e6..d59e77ed31 100644 --- a/app/assets/javascripts/behaviors/gl_emoji.js +++ b/app/assets/javascripts/behaviors/gl_emoji.js @@ -64,10 +64,12 @@ class GlEmoji extends HTMLElement { this.classList.add('emoji-icon'); this.classList.add(fallbackSpriteClass); } else if (hasImageFallback) { - this.innerHTML = emojiImageTag(name, fallbackSrc); + this.innerHTML = ''; + this.appendChild(emojiImageTag(name, fallbackSrc)); } else { const src = emojiFallbackImageSrc(name); - this.innerHTML = emojiImageTag(name, src); + this.innerHTML = ''; + this.appendChild(emojiImageTag(name, src)); } } }); diff --git a/app/assets/javascripts/emoji/index.js b/app/assets/javascripts/emoji/index.js index 478e3f6aed..b7169f98f8 100644 --- a/app/assets/javascripts/emoji/index.js +++ b/app/assets/javascripts/emoji/index.js @@ -211,7 +211,17 @@ export function emojiFallbackImageSrc(inputName) { } export function emojiImageTag(name, src) { - return `:${name}:`; + 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) { diff --git a/app/controllers/concerns/sessionless_authentication.rb b/app/controllers/concerns/sessionless_authentication.rb index 58e65ba20e..c6d926c8a8 100644 --- a/app/controllers/concerns/sessionless_authentication.rb +++ b/app/controllers/concerns/sessionless_authentication.rb @@ -20,7 +20,7 @@ module SessionlessAuthentication end 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 # actually stored in the session and a token is needed # for every request. If you want the token to work as a diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb index d7aebd2543..55f4563285 100644 --- a/app/controllers/import/github_controller.rb +++ b/app/controllers/import/github_controller.rb @@ -28,8 +28,14 @@ class Import::GithubController < Import::BaseController end def callback - session[access_token_key] = get_token(params[:code]) - redirect_to status_import_url + 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]) + redirect_to status_import_url + end end def personal_access_token @@ -154,13 +160,16 @@ class Import::GithubController < Import::BaseController end def authorize_url + state = SecureRandom.base64(64) + session[auth_state_key] = state if Feature.enabled?(:remove_legacy_github_client) oauth_client.auth_code.authorize_url( redirect_uri: callback_import_url, - scope: 'repo, user, user:email' + scope: 'repo, user, user:email', + state: state ) else - client.authorize_url(callback_import_url) + client.authorize_url(callback_import_url, state) end end @@ -219,6 +228,10 @@ class Import::GithubController < Import::BaseController alert: _('Missing OAuth configuration for GitHub.') end + def auth_state_key + :"#{provider_name}_auth_state_key" + end + def access_token_key :"#{provider_name}_access_token" end diff --git a/app/graphql/resolvers/base_issues_resolver.rb b/app/graphql/resolvers/base_issues_resolver.rb index 54ebb697cb..a6f9254f20 100644 --- a/app/graphql/resolvers/base_issues_resolver.rb +++ b/app/graphql/resolvers/base_issues_resolver.rb @@ -36,7 +36,7 @@ module Resolvers def unconditional_includes [ { - project: [:project_feature] + project: [:project_feature, :group] }, :author ] diff --git a/app/models/concerns/integrations/slack_mattermost_notifier.rb b/app/models/concerns/integrations/slack_mattermost_notifier.rb index cb6fafa8de..be13701289 100644 --- a/app/models/concerns/integrations/slack_mattermost_notifier.rb +++ b/app/models/concerns/integrations/slack_mattermost_notifier.rb @@ -6,6 +6,13 @@ module Integrations def notify(message, opts) # 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.ping( message.pretext, diff --git a/app/models/integrations/chat_message/alert_message.rb b/app/models/integrations/chat_message/alert_message.rb index ef0579124f..e2c689f943 100644 --- a/app/models/integrations/chat_message/alert_message.rb +++ b/app/models/integrations/chat_message/alert_message.rb @@ -23,7 +23,7 @@ module Integrations def attachments [{ - title: title, + title: strip_markup(title), title_link: alert_url, color: attachment_color, fields: attachment_fields @@ -31,7 +31,7 @@ module Integrations end def message - "Alert firing in #{project_name}" + "Alert firing in #{strip_markup(project_name)}" end private diff --git a/app/models/integrations/chat_message/base_message.rb b/app/models/integrations/chat_message/base_message.rb index afe3ffc45a..ab213f4b43 100644 --- a/app/models/integrations/chat_message/base_message.rb +++ b/app/models/integrations/chat_message/base_message.rb @@ -5,6 +5,10 @@ module Integrations class BaseMessage RELATIVE_LINK_REGEX = %r{!\[[^\]]*\]\((/uploads/[^\)]*)\)}.freeze + # Markup characters which are used for links in HTML, Markdown, + # and Slack "mrkdwn" syntax (``). + UNSAFE_MARKUP_CHARACTERS = '<>[]|' + attr_reader :markdown attr_reader :user_full_name attr_reader :user_name @@ -65,12 +69,26 @@ module Integrations string.gsub(RELATIVE_LINK_REGEX, "#{project_url}\\1") 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 '#345' end def link(text, url) - "[#{text}](#{url})" + "[#{strip_markup(text)}](#{url})" end def pretty_duration(seconds) diff --git a/app/models/integrations/chat_message/deployment_message.rb b/app/models/integrations/chat_message/deployment_message.rb index c4f3bf9610..b28edeecb4 100644 --- a/app/models/integrations/chat_message/deployment_message.rb +++ b/app/models/integrations/chat_message/deployment_message.rb @@ -27,7 +27,7 @@ module Integrations 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 }] end @@ -40,9 +40,9 @@ module Integrations def message if running? - "Starting deploy to #{environment}" + "Starting deploy to #{strip_markup(environment)}" else - "Deploy to #{environment} #{humanized_status}" + "Deploy to #{strip_markup(environment)} #{humanized_status}" end end diff --git a/app/models/integrations/chat_message/issue_message.rb b/app/models/integrations/chat_message/issue_message.rb index 5fa6bd4090..ca8ef670e6 100644 --- a/app/models/integrations/chat_message/issue_message.rb +++ b/app/models/integrations/chat_message/issue_message.rb @@ -32,7 +32,7 @@ module Integrations def activity { - title: "Issue #{state} by #{user_combined_name}", + title: "Issue #{state} by #{strip_markup(user_combined_name)}", subtitle: "in #{project_link}", text: issue_link, image: user_avatar @@ -42,7 +42,7 @@ module Integrations private 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 def opened_issue? @@ -67,7 +67,7 @@ module Integrations end def issue_title - "#{Issue.reference_prefix}#{issue_iid} #{title}" + "#{Issue.reference_prefix}#{issue_iid} #{strip_markup(title)}" end end end diff --git a/app/models/integrations/chat_message/merge_message.rb b/app/models/integrations/chat_message/merge_message.rb index d2f48699f5..98da38de27 100644 --- a/app/models/integrations/chat_message/merge_message.rb +++ b/app/models/integrations/chat_message/merge_message.rb @@ -29,7 +29,7 @@ module Integrations 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}", text: merge_request_link, image: user_avatar @@ -39,7 +39,7 @@ module Integrations private def format_title(title) - '*' + title.lines.first.chomp + '*' + '*' + strip_markup(title.lines.first.chomp) + '*' end def message @@ -51,7 +51,7 @@ module Integrations end 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 def merge_request_link @@ -59,7 +59,7 @@ module Integrations end def merge_request_title - "#{MergeRequest.reference_prefix}#{merge_request_iid} #{title}" + "#{MergeRequest.reference_prefix}#{merge_request_iid} #{strip_markup(title)}" end def merge_request_url diff --git a/app/models/integrations/chat_message/note_message.rb b/app/models/integrations/chat_message/note_message.rb index 96675d2b27..b2b2059536 100644 --- a/app/models/integrations/chat_message/note_message.rb +++ b/app/models/integrations/chat_message/note_message.rb @@ -35,9 +35,9 @@ module Integrations 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}", - text: formatted_title, + text: strip_markup(formatted_title), image: user_avatar } end @@ -45,7 +45,7 @@ module Integrations private 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 def format_title(title) diff --git a/app/models/integrations/chat_message/pipeline_message.rb b/app/models/integrations/chat_message/pipeline_message.rb index a3f68d3403..b3502905bf 100644 --- a/app/models/integrations/chat_message/pipeline_message.rb +++ b/app/models/integrations/chat_message/pipeline_message.rb @@ -56,7 +56,7 @@ module Integrations [{ fallback: format(message), color: attachment_color, - author_name: user_combined_name, + author_name: strip_markup(user_combined_name), author_icon: user_avatar, author_link: author_url, title: s_("ChatMessage|Pipeline #%{pipeline_id} %{humanized_status} in %{duration}") % @@ -80,7 +80,7 @@ module Integrations pipeline_link: pipeline_link, ref_type: ref_type, ref_link: ref_link, - user_combined_name: user_combined_name, + user_combined_name: strip_markup(user_combined_name), humanized_status: humanized_status }, subtitle: s_("ChatMessage|in %{project_link}") % { project_link: project_link }, @@ -154,7 +154,7 @@ module Integrations pipeline_link: pipeline_link, ref_type: ref_type, ref_link: ref_link, - user_combined_name: user_combined_name, + user_combined_name: strip_markup(user_combined_name), humanized_status: humanized_status, duration: pretty_duration(duration) } @@ -189,7 +189,7 @@ module Integrations end def ref_link - "[#{ref}](#{ref_url})" + link(ref, ref_url) end def project_url @@ -197,7 +197,7 @@ module Integrations end def project_link - "[#{project.name}](#{project_url})" + link(project.name, project_url) end def pipeline_failed_jobs_url @@ -213,7 +213,7 @@ module Integrations end def pipeline_link - "[##{pipeline_id}](#{pipeline_url})" + link("##{pipeline_id}", pipeline_url) end def job_url(job) @@ -221,7 +221,7 @@ module Integrations end def job_link(job) - "[#{job[:name]}](#{job_url(job)})" + link(job[:name], job_url(job)) end def failed_jobs_links @@ -242,7 +242,7 @@ module Integrations def stage_link(stage) # All stages link to the pipeline page - "[#{stage}](#{pipeline_url})" + link(stage, pipeline_url) end def failed_stages_links @@ -254,7 +254,7 @@ module Integrations end def commit_link - "[#{commit.title}](#{commit_url})" + link(commit.title, commit_url) end def author_url diff --git a/app/models/integrations/chat_message/push_message.rb b/app/models/integrations/chat_message/push_message.rb index fabd214633..60a3105d1c 100644 --- a/app/models/integrations/chat_message/push_message.rb +++ b/app/models/integrations/chat_message/push_message.rb @@ -39,7 +39,7 @@ module Integrations def humanized_action(short: false) 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.join(' ') end @@ -67,7 +67,7 @@ module Integrations url = commit[:url] - "[#{id}](#{url}): #{title} - #{author}" + "#{link(id, url)}: #{strip_markup(title)} - #{strip_markup(author)}" end def new_branch? @@ -91,15 +91,15 @@ module Integrations end def ref_link - "[#{ref}](#{ref_url})" + link(ref, ref_url) end def project_link - "[#{project_name}](#{project_url})" + link(project_name, project_url) end def compare_link - "[Compare changes](#{compare_url})" + link('Compare changes', compare_url) end def compose_action_details diff --git a/app/models/integrations/chat_message/wiki_page_message.rb b/app/models/integrations/chat_message/wiki_page_message.rb index 00f0f911b0..e729969685 100644 --- a/app/models/integrations/chat_message/wiki_page_message.rb +++ b/app/models/integrations/chat_message/wiki_page_message.rb @@ -36,9 +36,9 @@ module Integrations def activity { - title: "#{user_combined_name} #{action} #{wiki_page_link}", + title: "#{strip_markup(user_combined_name)} #{action} #{wiki_page_link}", subtitle: "in #{project_link}", - text: title, + text: strip_markup(title), image: user_avatar } end @@ -46,7 +46,7 @@ module Integrations private 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 def description_message diff --git a/app/services/packages/npm/create_package_service.rb b/app/services/packages/npm/create_package_service.rb index ae9c92a3d3..f1f589c318 100644 --- a/app/services/packages/npm/create_package_service.rb +++ b/app/services/packages/npm/create_package_service.rb @@ -72,10 +72,24 @@ module Packages 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 { file: CarrierWaveStringFile.new(Base64.decode64(attachment['data'])), - size: attachment['length'], + size: calculated_package_file_size, file_sha1: version_data[:dist][:shasum], file_name: package_file_name, build: params[:build] @@ -88,7 +102,7 @@ module Packages end 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 diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index 699744b355..1441bcfa40 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -291,6 +291,15 @@ :weight: 1 :idempotent: true :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 :worker_name: DependencyProxy::ImageTtlGroupPolicyWorker :feature_category: :dependency_proxy diff --git a/app/workers/concerns/dependency_proxy/expireable.rb b/app/workers/concerns/dependency_proxy/expireable.rb new file mode 100644 index 0000000000..9650ac85c6 --- /dev/null +++ b/app/workers/concerns/dependency_proxy/expireable.rb @@ -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 diff --git a/app/workers/dependency_proxy/cleanup_dependency_proxy_worker.rb b/app/workers/dependency_proxy/cleanup_dependency_proxy_worker.rb new file mode 100644 index 0000000000..d77c782267 --- /dev/null +++ b/app/workers/dependency_proxy/cleanup_dependency_proxy_worker.rb @@ -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 diff --git a/app/workers/dependency_proxy/image_ttl_group_policy_worker.rb b/app/workers/dependency_proxy/image_ttl_group_policy_worker.rb index 6a1de00ce8..3de2364fc7 100644 --- a/app/workers/dependency_proxy/image_ttl_group_policy_worker.rb +++ b/app/workers/dependency_proxy/image_ttl_group_policy_worker.rb @@ -4,20 +4,19 @@ module DependencyProxy class ImageTtlGroupPolicyWorker # rubocop:disable Scalability/IdempotentWorker include ApplicationWorker include CronjobQueue # rubocop:disable Scalability/CronWorkerContext + include DependencyProxy::Expireable data_consistency :always feature_category :dependency_proxy - UPDATE_BATCH_SIZE = 100 - def perform DependencyProxy::ImageTtlGroupPolicy.enabled.each do |policy| qualified_blobs = policy.group.dependency_proxy_blobs.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) - enqueue_manifest_cleanup_job if expire_artifacts(qualified_manifests, DependencyProxy::Manifest) + expire_artifacts(qualified_blobs) + expire_artifacts(qualified_manifests) end log_counts @@ -25,25 +24,6 @@ module DependencyProxy 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 use_replica_if_available do expired_blob_count = DependencyProxy::Blob.expired.count diff --git a/app/workers/purge_dependency_proxy_cache_worker.rb b/app/workers/purge_dependency_proxy_cache_worker.rb index db43e4adf2..c0ddf19021 100644 --- a/app/workers/purge_dependency_proxy_cache_worker.rb +++ b/app/workers/purge_dependency_proxy_cache_worker.rb @@ -2,6 +2,7 @@ class PurgeDependencyProxyCacheWorker include ApplicationWorker + include DependencyProxy::Expireable data_consistency :always @@ -18,8 +19,8 @@ class PurgeDependencyProxyCacheWorker return unless valid? - @group.dependency_proxy_blobs.destroy_all # rubocop:disable Cop/DestroyAll - @group.dependency_proxy_manifests.destroy_all # rubocop:disable Cop/DestroyAll + expire_artifacts(@group.dependency_proxy_blobs) + expire_artifacts(@group.dependency_proxy_manifests) end private diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 8fb2161b14..58ff11737d 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -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']['cron'] ||= '40 0 * * *' 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']['cron'] ||= '30 1 * * *' Settings.cron_jobs['x509_issuer_crl_check_worker']['job_class'] = 'X509IssuerCrlCheckWorker' diff --git a/doc/api/dependency_proxy.md b/doc/api/dependency_proxy.md index 535c6607ca..5401c007c0 100644 --- a/doc/api/dependency_proxy.md +++ b/doc/api/dependency_proxy.md @@ -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. > - [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. ```plaintext diff --git a/lib/api/dependency_proxy.rb b/lib/api/dependency_proxy.rb index 185b8d5a15..9d0b1bf442 100644 --- a/lib/api/dependency_proxy.rb +++ b/lib/api/dependency_proxy.rb @@ -6,15 +6,6 @@ module API 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 authorize! :admin_group, user_group end @@ -29,9 +20,6 @@ module API delete ':id/dependency_proxy/cache' do 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 PurgeDependencyProxyCacheWorker.perform_async(current_user.id, user_group.id) # rubocop:enable CodeReuse/Worker diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 9f0077d23d..1e77257401 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -613,6 +613,7 @@ module API source_project = Project.find_by_id(params[:project_id]) 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 diff --git a/lib/gitlab/legacy_github_import/client.rb b/lib/gitlab/legacy_github_import/client.rb index 48a8e0ce6d..7a9dae3a3d 100644 --- a/lib/gitlab/legacy_github_import/client.rb +++ b/lib/gitlab/legacy_github_import/client.rb @@ -48,10 +48,11 @@ module Gitlab ) end - def authorize_url(redirect_uri) + def authorize_url(redirect_uri, state = nil) client.auth_code.authorize_url({ redirect_uri: redirect_uri, - scope: "repo, user, user:email" + scope: "repo, user, user:email", + state: state }) end diff --git a/lib/gitlab/url_blocker.rb b/lib/gitlab/url_blocker.rb index 2c5d76ba41..f092e03046 100644 --- a/lib/gitlab/url_blocker.rb +++ b/lib/gitlab/url_blocker.rb @@ -252,13 +252,13 @@ module Gitlab def internal_web?(uri) uri.scheme == config.gitlab.protocol && uri.hostname == config.gitlab.host && - (uri.port.blank? || uri.port == config.gitlab.port) + get_port(uri) == config.gitlab.port end def internal_shell?(uri) uri.scheme == 'ssh' && 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 def domain_allowed?(uri) diff --git a/spec/controllers/dashboard/projects_controller_spec.rb b/spec/controllers/dashboard/projects_controller_spec.rb index 9b13025cbe..743759d502 100644 --- a/spec/controllers/dashboard/projects_controller_spec.rb +++ b/spec/controllers/dashboard/projects_controller_spec.rb @@ -8,10 +8,6 @@ RSpec.describe Dashboard::ProjectsController, :aggregate_failures do let_it_be(:user) { create(:user) } describe '#index' do - context 'user not logged in' do - it_behaves_like 'authenticates sessionless user', :index, :atom - end - context 'user logged in' do let_it_be(:project) { create(:project, name: 'Project 1') } let_it_be(:project2) { create(:project, name: 'Project Two') } diff --git a/spec/controllers/dashboard_controller_spec.rb b/spec/controllers/dashboard_controller_spec.rb index c838affa23..8fae617ea6 100644 --- a/spec/controllers/dashboard_controller_spec.rb +++ b/spec/controllers/dashboard_controller_spec.rb @@ -72,9 +72,6 @@ RSpec.describe DashboardController do 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 let(:user) { create(:user) } diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb index 2525146c67..2238d82d26 100644 --- a/spec/controllers/groups_controller_spec.rb +++ b/spec/controllers/groups_controller_spec.rb @@ -1219,26 +1219,6 @@ RSpec.describe GroupsController, factory_default: :keep do 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 before do group.add_owner(user) diff --git a/spec/controllers/import/github_controller_spec.rb b/spec/controllers/import/github_controller_spec.rb index d82fff1f7a..fd380f9b76 100644 --- a/spec/controllers/import/github_controller_spec.rb +++ b/spec/controllers/import/github_controller_spec.rb @@ -6,6 +6,7 @@ RSpec.describe Import::GithubController do include ImportSpecHelper let(:provider) { :github } + let(:new_import_url) { public_send("new_import_#{provider}_url") } include_context 'a GitHub-ish import controller' @@ -50,13 +51,37 @@ RSpec.describe Import::GithubController do stub_omniauth_provider('github') end - it "updates access token" do - token = "asdasd12345" + context "when auth state param is missing from session" do + it "reports an error" do + get :callback - get :callback + expect(controller).to redirect_to(new_import_url) + expect(flash[:alert]).to eq('Access denied to your GitHub account.') + end + end - expect(session[:github_access_token]).to eq(token) - expect(controller).to redirect_to(status_import_github_url) + 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" + + get :callback, params: { state: valid_auth_state } + + expect(session[:github_access_token]).to eq(token) + expect(controller).to redirect_to(status_import_github_url) + 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 @@ -71,8 +96,6 @@ RSpec.describe Import::GithubController do end context 'when OAuth config is missing' do - let(:new_import_url) { public_send("new_import_#{provider}_url") } - before do allow(controller).to receive(:oauth_config).and_return(nil) end @@ -108,6 +131,16 @@ RSpec.describe Import::GithubController do get :status 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 context 'when feature remove_legacy_github_client is enabled' do @@ -130,6 +163,16 @@ RSpec.describe Import::GithubController do get :status 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 'when no page is specified' do it 'requests first page' do diff --git a/spec/controllers/projects/commits_controller_spec.rb b/spec/controllers/projects/commits_controller_spec.rb index a8e71d73be..fd840fafa6 100644 --- a/spec/controllers/projects/commits_controller_spec.rb +++ b/spec/controllers/projects/commits_controller_spec.rb @@ -162,27 +162,4 @@ RSpec.describe Projects::CommitsController do 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 diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index 68cccfa8bd..39b432db13 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -1965,40 +1965,4 @@ RSpec.describe Projects::IssuesController do 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 diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb index 79da18f2d6..4d99afb6b1 100644 --- a/spec/controllers/projects/raw_controller_spec.rb +++ b/spec/controllers/projects/raw_controller_spec.rb @@ -193,6 +193,8 @@ RSpec.describe Projects::RawController do let_it_be(:user) { create(:user, static_object_token: 'very-secure-token') } let_it_be(:file_path) { 'master/README.md' } + let(:token) { user.static_object_token } + before do project.add_developer(user) end @@ -207,17 +209,46 @@ RSpec.describe Projects::RawController do end 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 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) + end + end + end end context 'when token is incorrect' do + let(:token) { 'foobar' } + 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.location).to end_with('/users/sign_in') @@ -226,19 +257,47 @@ RSpec.describe Projects::RawController do end 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 it 'calls the action normally' do - request.headers['X-Gitlab-Static-Object-Token'] = user.static_object_token - execute_raw_requests(requests: 1, project: project, file_path: file_path) + execute_raw_request_with_token_in_headers 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) + end + end + end end context 'when token is incorrect' do + let(:token) { 'foobar' } + it 'redirects to sign in page' do - request.headers['X-Gitlab-Static-Object-Token'] = 'foobar' - execute_raw_requests(requests: 1, project: project, file_path: file_path) + execute_raw_request_with_token_in_headers expect(response).to have_gitlab_http_status(:found) expect(response.location).to end_with('/users/sign_in') diff --git a/spec/controllers/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb index cb2579b800..a1755f3ab4 100644 --- a/spec/controllers/projects/repositories_controller_spec.rb +++ b/spec/controllers/projects/repositories_controller_spec.rb @@ -178,6 +178,29 @@ RSpec.describe Projects::RepositoriesController do 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 + 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 context 'when token is incorrect' do @@ -197,6 +220,31 @@ RSpec.describe Projects::RepositoriesController do 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 + 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 context 'when token is incorrect' do diff --git a/spec/controllers/projects/tags_controller_spec.rb b/spec/controllers/projects/tags_controller_spec.rb index 0045c0a484..9823c36cb8 100644 --- a/spec/controllers/projects/tags_controller_spec.rb +++ b/spec/controllers/projects/tags_controller_spec.rb @@ -117,28 +117,6 @@ RSpec.describe Projects::TagsController do 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 before do project.add_developer(user) diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index dafa639a2d..43466c67e1 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -1555,28 +1555,6 @@ RSpec.describe ProjectsController do 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 let_it_be(:public_project) { create(:project, :public) } let_it_be(:event) { create(:event, :commented, project: public_project, target: create(:note, project: public_project)) } diff --git a/spec/factories/pages_domains.rb b/spec/factories/pages_domains.rb index f3f2af79b7..2ba5cbb48b 100644 --- a/spec/factories/pages_domains.rb +++ b/spec/factories/pages_domains.rb @@ -258,18 +258,6 @@ ZDXgrA== certificate_source { :gitlab_provided } 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 certificate do '-----BEGIN CERTIFICATE----- diff --git a/spec/factories_spec.rb b/spec/factories_spec.rb index 811ed18dce..08286f57b3 100644 --- a/spec/factories_spec.rb +++ b/spec/factories_spec.rb @@ -31,7 +31,6 @@ RSpec.describe 'factories' do [:pages_domain, :with_trusted_chain], [:pages_domain, :with_trusted_expired_chain], [:pages_domain, :explicit_ecdsa], - [:pages_domain, :letsencrypt_expired_x3_root], [:project_member, :blocked], [:remote_mirror, :ssh], [:user_preference, :only_comments], diff --git a/spec/fixtures/clusters/ca_certificate.pem b/spec/fixtures/clusters/ca_certificate.pem deleted file mode 100644 index 9e6810ab70..0000000000 --- a/spec/fixtures/clusters/ca_certificate.pem +++ /dev/null @@ -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----- diff --git a/spec/fixtures/clusters/chain_certificates.pem b/spec/fixtures/clusters/chain_certificates.pem index b8e64d58ee..fe6affec17 100644 --- a/spec/fixtures/clusters/chain_certificates.pem +++ b/spec/fixtures/clusters/chain_certificates.pem @@ -1,100 +1,86 @@ -----BEGIN CERTIFICATE----- -MIIItjCCB56gAwIBAgIQCu5Ga1hR41iahM0SWhyeNjANBgkqhkiG9w0BAQsFADB1 -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMTQwMgYDVQQDEytEaWdpQ2VydCBTSEEyIEV4dGVuZGVk -IFZhbGlkYXRpb24gU2VydmVyIENBMB4XDTE5MTIwNDAwMDAwMFoXDTIxMTIwODEy -MDAwMFowgb0xHTAbBgNVBA8MFFByaXZhdGUgT3JnYW5pemF0aW9uMRMwEQYLKwYB -BAGCNzwCAQMTAlVTMRUwEwYLKwYBBAGCNzwCAQITBFV0YWgxFTATBgNVBAUTDDUy -OTk1MzctMDE0MjELMAkGA1UEBhMCVVMxDTALBgNVBAgTBFV0YWgxDTALBgNVBAcT -BExlaGkxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMRUwEwYDVQQDEwxkaWdpY2Vy -dC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAeRYb/RLbljGZ -IB//DrEdyKYMQqqaJwBlrr3t2paAWNuDJizvVkTMIzdJesI1pA58Myenxp5Dp8GJ -u/VhBf//v/HAZHUE4xwu104Fg6A1BwUEKgVKERf+7kTt17Lf9fcMIjMyL+FeyPXb -DOFbH+ej/nYaneFLch2j2xWZg1+Thk0qBlGE8WWAK+fvbEuM0SOeH9RkYFCNGPRS -KsLn0GvaCnnD4LfNDyMqYop0IpaqXoREEnkRv1MVSOw+hBj497wnnO+/GZegfzwU -iS60h+PjlDfmdCP18qOS7tRd0qnfU3N3S+PYEd3R63LMcIfbgXNEEWBNKpiH9+8f -eXq6bXKPAgMBAAGjggT3MIIE8zAfBgNVHSMEGDAWgBQ901Cl1qCt7vNKYApl0yHU -+PjWDzAdBgNVHQ4EFgQUTx0XO7HqD5DOhwlm2p+70uYPBmgwggGjBgNVHREEggGa -MIIBloIMZGlnaWNlcnQuY29tggl0aGF3dGUuZGWCC2ZyZWVzc2wuY29tggxyYXBp -ZHNzbC5jb22CDGdlb3RydXN0LmNvbYIJdGhhd3RlLmZyggp0aGF3dGUuY29tghB3 -d3cucmFwaWRzc2wuY29tghB3d3cuZ2VvdHJ1c3QuY29tgg13d3cudGhhd3RlLmZy -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= +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----- -----BEGIN CERTIFICATE----- -MIIEtjCCA56gAwIBAgIQDHmpRLCMEZUgkmFf4msdgzANBgkqhkiG9w0BAQsFADBs -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j -ZSBFViBSb290IENBMB4XDTEzMTAyMjEyMDAwMFoXDTI4MTAyMjEyMDAwMFowdTEL -MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 -LmRpZ2ljZXJ0LmNvbTE0MDIGA1UEAxMrRGlnaUNlcnQgU0hBMiBFeHRlbmRlZCBW -YWxpZGF0aW9uIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC -ggEBANdTpARR+JmmFkhLZyeqk0nQOe0MsLAAh/FnKIaFjI5j2ryxQDji0/XspQUY -uD0+xZkXMuwYjPrxDKZkIYXLBxA0sFKIKx9om9KxjxKws9LniB8f7zh3VFNfgHk/ -LhqqqB5LKw2rt2O5Nbd9FLxZS99RStKh4gzikIKHaq7q12TWmFXo/a8aUGxUvBHy -/Urynbt/DvTVvo4WiRJV2MBxNO723C3sxIclho3YIeSwTQyJ3DkmF93215SF2AQh -cJ1vb/9cuhnhRctWVyh+HA1BV6q3uCe7seT6Ku8hI3UarS2bhjWMnHe1c63YlC3k -8wyd7sFOYn4XwHGeLN7x+RAoGTMCAwEAAaOCAUkwggFFMBIGA1UdEwEB/wQIMAYB -Af8CAQAwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEF -BQcDAjA0BggrBgEFBQcBAQQoMCYwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRp -Z2ljZXJ0LmNvbTBLBgNVHR8ERDBCMECgPqA8hjpodHRwOi8vY3JsNC5kaWdpY2Vy -dC5jb20vRGlnaUNlcnRIaWdoQXNzdXJhbmNlRVZSb290Q0EuY3JsMD0GA1UdIAQ2 -MDQwMgYEVR0gADAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5j -b20vQ1BTMB0GA1UdDgQWBBQ901Cl1qCt7vNKYApl0yHU+PjWDzAfBgNVHSMEGDAW -gBSxPsNpA/i/RwHUmCYaCALvY2QrwzANBgkqhkiG9w0BAQsFAAOCAQEAnbbQkIbh -hgLtxaDwNBx0wY12zIYKqPBKikLWP8ipTa18CK3mtlC4ohpNiAexKSHc59rGPCHg -4xFJcKx6HQGkyhE6V6t9VypAdP3THYUYUN9XR3WhfVUgLkc3UHKMf4Ib0mKPLQNa -2sPIoc4sUqIAY+tzunHISScjl2SFnjgOrWNoPLpSgVh5oywM395t6zHyuqB8bPEs -1OG9d4Q3A84ytciagRpKkk47RpqF/oOi+Z6Mo8wNXrM9zwR4jxQUezKcxwCmXMS1 -oVWNWlZopCJwqjyBcdmdqEU79OX2olHdx3ti6G8MdOu42vi/hw15UJGQmxg7kVkn -8TUoE6smftX3eg== +MIIExTCCA62gAwIBAgIQeimFGrf0XWZ5UGZBtv/XHTANBgkqhkiG9w0BAQsFADBM +MSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xv +YmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjAeFw0yMTA2MTYxMjAwMDBaFw0y +NDA2MTYwMDAwMDBaMFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWdu +IG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFIzIERWIFRMUyBDQSBI +MiAyMDIxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1JTAQMj+QUYF +3d9X5eOWFOphbB6GpHE3J0uvUXcQwxnd8Jz26aQCE1ZYxJFEc2WmsxuVeVXU+rZj +7+MYD7Mg72bhuiwUdwRGRN4a2N122LfIQlTFlHu/fwcNqYX/fe3phvZt9upnH4oJ +aLBbay+t+HPPC4em74x2WKaIl31ZXzgzllLomnlLISLOKiQe1rEHp4yy3/yE2a4G +1l/lprA49dcyM/oylm9Bbkum2F4C+EOjHgTAoDVJrJpdWvPj0CU+HkmftujfFp4S +55LECSr2TfJt7xjgR3eLUx12nlpoauWEzZ0/i6OIDPfbmqcksw4ani/YO07LbRM6 +cY9VZzkAvwIDAQABo4IBlTCCAZEwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQG +CCsGAQUFBwMBBggrBgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQW +BBQqNLmq+r88iPFH8tISeL7F5aqwaTAfBgNVHSMEGDAWgBSP8Et/qC5FJK5NUPpj +move4t0bvDB7BggrBgEFBQcBAQRvMG0wLgYIKwYBBQUHMAGGImh0dHA6Ly9vY3Nw +Mi5nbG9iYWxzaWduLmNvbS9yb290cjMwOwYIKwYBBQUHMAKGL2h0dHA6Ly9zZWN1 +cmUuZ2xvYmFsc2lnbi5jb20vY2FjZXJ0L3Jvb3QtcjMuY3J0MDYGA1UdHwQvMC0w +K6ApoCeGJWh0dHA6Ly9jcmwuZ2xvYmFsc2lnbi5jb20vcm9vdC1yMy5jcmwwVwYD +VR0gBFAwTjAIBgZngQwBAgEwQgYKKwYBBAGgMgoBAzA0MDIGCCsGAQUFBwIBFiZo +dHRwczovL3d3dy5nbG9iYWxzaWduLmNvbS9yZXBvc2l0b3J5LzANBgkqhkiG9w0B +AQsFAAOCAQEAEsIwXEhdAfoUGaKAnYfVI7zsOY7Sx8bpC/obGxXa4Kyu8CVx+TtT +g8WmKNF7+I7C51NZEmhvb8UDI1G9ny7iYIRDajQD5AeZowbfC69aHQSI9LiOeAZb +YaRDJfWps9redPwoaC0iT5R4xLOnWwCtmIho1bv/YG3pMAvaQ+qn04kuUvWO7LEp +u7FdHmx1DdgkefcqYgN/rAZ8E39S9VxWV+64PNUDey8vkAIH8FCTxbWiITty6dsH +SulKQ9pSa93k9PHTf+di08mMQBq5WBWTiFeMYZEWyE/z7NHdU3eLMZjq6y/nKlF9 +nywrToh4AgdZK6JnbU+lqbNiexJbaBoA3w== -----END CERTIFICATE----- -----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 +MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G +A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp +Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4 +MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG +A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8 +RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT +gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm +KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd +QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ +XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw +DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o +LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU +RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp +jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK +6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX +mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs +Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH +WD9f -----END CERTIFICATE----- diff --git a/spec/fixtures/clusters/intermediate_certificate.pem b/spec/fixtures/clusters/intermediate_certificate.pem index 8a81175b74..21bada7356 100644 --- a/spec/fixtures/clusters/intermediate_certificate.pem +++ b/spec/fixtures/clusters/intermediate_certificate.pem @@ -1,28 +1,28 @@ -----BEGIN CERTIFICATE----- -MIIEtjCCA56gAwIBAgIQDHmpRLCMEZUgkmFf4msdgzANBgkqhkiG9w0BAQsFADBs -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j -ZSBFViBSb290IENBMB4XDTEzMTAyMjEyMDAwMFoXDTI4MTAyMjEyMDAwMFowdTEL -MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 -LmRpZ2ljZXJ0LmNvbTE0MDIGA1UEAxMrRGlnaUNlcnQgU0hBMiBFeHRlbmRlZCBW -YWxpZGF0aW9uIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC -ggEBANdTpARR+JmmFkhLZyeqk0nQOe0MsLAAh/FnKIaFjI5j2ryxQDji0/XspQUY -uD0+xZkXMuwYjPrxDKZkIYXLBxA0sFKIKx9om9KxjxKws9LniB8f7zh3VFNfgHk/ -LhqqqB5LKw2rt2O5Nbd9FLxZS99RStKh4gzikIKHaq7q12TWmFXo/a8aUGxUvBHy -/Urynbt/DvTVvo4WiRJV2MBxNO723C3sxIclho3YIeSwTQyJ3DkmF93215SF2AQh -cJ1vb/9cuhnhRctWVyh+HA1BV6q3uCe7seT6Ku8hI3UarS2bhjWMnHe1c63YlC3k -8wyd7sFOYn4XwHGeLN7x+RAoGTMCAwEAAaOCAUkwggFFMBIGA1UdEwEB/wQIMAYB -Af8CAQAwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEF -BQcDAjA0BggrBgEFBQcBAQQoMCYwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRp -Z2ljZXJ0LmNvbTBLBgNVHR8ERDBCMECgPqA8hjpodHRwOi8vY3JsNC5kaWdpY2Vy -dC5jb20vRGlnaUNlcnRIaWdoQXNzdXJhbmNlRVZSb290Q0EuY3JsMD0GA1UdIAQ2 -MDQwMgYEVR0gADAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5j -b20vQ1BTMB0GA1UdDgQWBBQ901Cl1qCt7vNKYApl0yHU+PjWDzAfBgNVHSMEGDAW -gBSxPsNpA/i/RwHUmCYaCALvY2QrwzANBgkqhkiG9w0BAQsFAAOCAQEAnbbQkIbh -hgLtxaDwNBx0wY12zIYKqPBKikLWP8ipTa18CK3mtlC4ohpNiAexKSHc59rGPCHg -4xFJcKx6HQGkyhE6V6t9VypAdP3THYUYUN9XR3WhfVUgLkc3UHKMf4Ib0mKPLQNa -2sPIoc4sUqIAY+tzunHISScjl2SFnjgOrWNoPLpSgVh5oywM395t6zHyuqB8bPEs -1OG9d4Q3A84ytciagRpKkk47RpqF/oOi+Z6Mo8wNXrM9zwR4jxQUezKcxwCmXMS1 -oVWNWlZopCJwqjyBcdmdqEU79OX2olHdx3ti6G8MdOu42vi/hw15UJGQmxg7kVkn -8TUoE6smftX3eg== +MIIExTCCA62gAwIBAgIQeimFGrf0XWZ5UGZBtv/XHTANBgkqhkiG9w0BAQsFADBM +MSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xv +YmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjAeFw0yMTA2MTYxMjAwMDBaFw0y +NDA2MTYwMDAwMDBaMFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWdu +IG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFIzIERWIFRMUyBDQSBI +MiAyMDIxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1JTAQMj+QUYF +3d9X5eOWFOphbB6GpHE3J0uvUXcQwxnd8Jz26aQCE1ZYxJFEc2WmsxuVeVXU+rZj +7+MYD7Mg72bhuiwUdwRGRN4a2N122LfIQlTFlHu/fwcNqYX/fe3phvZt9upnH4oJ +aLBbay+t+HPPC4em74x2WKaIl31ZXzgzllLomnlLISLOKiQe1rEHp4yy3/yE2a4G +1l/lprA49dcyM/oylm9Bbkum2F4C+EOjHgTAoDVJrJpdWvPj0CU+HkmftujfFp4S +55LECSr2TfJt7xjgR3eLUx12nlpoauWEzZ0/i6OIDPfbmqcksw4ani/YO07LbRM6 +cY9VZzkAvwIDAQABo4IBlTCCAZEwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQG +CCsGAQUFBwMBBggrBgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQW +BBQqNLmq+r88iPFH8tISeL7F5aqwaTAfBgNVHSMEGDAWgBSP8Et/qC5FJK5NUPpj +move4t0bvDB7BggrBgEFBQcBAQRvMG0wLgYIKwYBBQUHMAGGImh0dHA6Ly9vY3Nw +Mi5nbG9iYWxzaWduLmNvbS9yb290cjMwOwYIKwYBBQUHMAKGL2h0dHA6Ly9zZWN1 +cmUuZ2xvYmFsc2lnbi5jb20vY2FjZXJ0L3Jvb3QtcjMuY3J0MDYGA1UdHwQvMC0w +K6ApoCeGJWh0dHA6Ly9jcmwuZ2xvYmFsc2lnbi5jb20vcm9vdC1yMy5jcmwwVwYD +VR0gBFAwTjAIBgZngQwBAgEwQgYKKwYBBAGgMgoBAzA0MDIGCCsGAQUFBwIBFiZo +dHRwczovL3d3dy5nbG9iYWxzaWduLmNvbS9yZXBvc2l0b3J5LzANBgkqhkiG9w0B +AQsFAAOCAQEAEsIwXEhdAfoUGaKAnYfVI7zsOY7Sx8bpC/obGxXa4Kyu8CVx+TtT +g8WmKNF7+I7C51NZEmhvb8UDI1G9ny7iYIRDajQD5AeZowbfC69aHQSI9LiOeAZb +YaRDJfWps9redPwoaC0iT5R4xLOnWwCtmIho1bv/YG3pMAvaQ+qn04kuUvWO7LEp +u7FdHmx1DdgkefcqYgN/rAZ8E39S9VxWV+64PNUDey8vkAIH8FCTxbWiITty6dsH +SulKQ9pSa93k9PHTf+di08mMQBq5WBWTiFeMYZEWyE/z7NHdU3eLMZjq6y/nKlF9 +nywrToh4AgdZK6JnbU+lqbNiexJbaBoA3w== -----END CERTIFICATE----- diff --git a/spec/fixtures/clusters/leaf_certificate.pem b/spec/fixtures/clusters/leaf_certificate.pem new file mode 100644 index 0000000000..aecb3fc8d4 --- /dev/null +++ b/spec/fixtures/clusters/leaf_certificate.pem @@ -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----- diff --git a/spec/fixtures/clusters/root_certificate.pem b/spec/fixtures/clusters/root_certificate.pem index 40107bd837..8afb219058 100644 --- a/spec/fixtures/clusters/root_certificate.pem +++ b/spec/fixtures/clusters/root_certificate.pem @@ -1,49 +1,21 @@ -----BEGIN CERTIFICATE----- -MIIItjCCB56gAwIBAgIQCu5Ga1hR41iahM0SWhyeNjANBgkqhkiG9w0BAQsFADB1 -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMTQwMgYDVQQDEytEaWdpQ2VydCBTSEEyIEV4dGVuZGVk -IFZhbGlkYXRpb24gU2VydmVyIENBMB4XDTE5MTIwNDAwMDAwMFoXDTIxMTIwODEy -MDAwMFowgb0xHTAbBgNVBA8MFFByaXZhdGUgT3JnYW5pemF0aW9uMRMwEQYLKwYB -BAGCNzwCAQMTAlVTMRUwEwYLKwYBBAGCNzwCAQITBFV0YWgxFTATBgNVBAUTDDUy -OTk1MzctMDE0MjELMAkGA1UEBhMCVVMxDTALBgNVBAgTBFV0YWgxDTALBgNVBAcT -BExlaGkxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMRUwEwYDVQQDEwxkaWdpY2Vy -dC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDAeRYb/RLbljGZ -IB//DrEdyKYMQqqaJwBlrr3t2paAWNuDJizvVkTMIzdJesI1pA58Myenxp5Dp8GJ -u/VhBf//v/HAZHUE4xwu104Fg6A1BwUEKgVKERf+7kTt17Lf9fcMIjMyL+FeyPXb -DOFbH+ej/nYaneFLch2j2xWZg1+Thk0qBlGE8WWAK+fvbEuM0SOeH9RkYFCNGPRS -KsLn0GvaCnnD4LfNDyMqYop0IpaqXoREEnkRv1MVSOw+hBj497wnnO+/GZegfzwU -iS60h+PjlDfmdCP18qOS7tRd0qnfU3N3S+PYEd3R63LMcIfbgXNEEWBNKpiH9+8f -eXq6bXKPAgMBAAGjggT3MIIE8zAfBgNVHSMEGDAWgBQ901Cl1qCt7vNKYApl0yHU -+PjWDzAdBgNVHQ4EFgQUTx0XO7HqD5DOhwlm2p+70uYPBmgwggGjBgNVHREEggGa -MIIBloIMZGlnaWNlcnQuY29tggl0aGF3dGUuZGWCC2ZyZWVzc2wuY29tggxyYXBp -ZHNzbC5jb22CDGdlb3RydXN0LmNvbYIJdGhhd3RlLmZyggp0aGF3dGUuY29tghB3 -d3cucmFwaWRzc2wuY29tghB3d3cuZ2VvdHJ1c3QuY29tgg13d3cudGhhd3RlLmZy -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= +MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G +A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp +Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4 +MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG +A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8 +RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT +gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm +KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd +QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ +XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw +DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o +LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU +RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp +jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK +6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX +mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs +Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH +WD9f -----END CERTIFICATE----- diff --git a/spec/fixtures/ssl/letsencrypt_expired_x3.pem b/spec/fixtures/ssl/letsencrypt_expired_x3.pem deleted file mode 100644 index 462df721ed..0000000000 --- a/spec/fixtures/ssl/letsencrypt_expired_x3.pem +++ /dev/null @@ -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----- diff --git a/spec/frontend/behaviors/gl_emoji_spec.js b/spec/frontend/behaviors/gl_emoji_spec.js index d23a0a8499..01e3f59d99 100644 --- a/spec/frontend/behaviors/gl_emoji_spec.js +++ b/spec/frontend/behaviors/gl_emoji_spec.js @@ -102,6 +102,18 @@ describe('gl_emoji', () => { }); }); + it('escapes gl-emoji name', async () => { + const glEmojiElement = markupToDomElement( + "abc", + ); + + await waitForPromises(); + + expect(glEmojiElement.outerHTML).toBe( + ':"x="y" onload="alert(document.location.href)":', + ); + }); + it('Adds sprite CSS if emojis are not supported', async () => { const testPath = '/test-path.css'; jest.spyOn(EmojiUnicodeSupport, 'default').mockReturnValue(false); diff --git a/spec/graphql/resolvers/issues_resolver_spec.rb b/spec/graphql/resolvers/issues_resolver_spec.rb index 3c892214aa..dc717b113c 100644 --- a/spec/graphql/resolvers/issues_resolver_spec.rb +++ b/spec/graphql/resolvers/issues_resolver_spec.rb @@ -560,13 +560,13 @@ RSpec.describe Resolvers::IssuesResolver do end 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) end 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] .map { |issue| resolve_issues(iid: issue.iid.to_s) } .flat_map(&:to_a) @@ -576,7 +576,7 @@ RSpec.describe Resolvers::IssuesResolver do end 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 end diff --git a/spec/lib/gitlab/url_blocker_spec.rb b/spec/lib/gitlab/url_blocker_spec.rb index e076815c4f..0713475d59 100644 --- a/spec/lib/gitlab/url_blocker_spec.rb +++ b/spec/lib/gitlab/url_blocker_spec.rb @@ -531,24 +531,6 @@ RSpec.describe Gitlab::UrlBlocker, :stub_invalid_dns_only do 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 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') 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 describe '#validate_hostname' do diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb index a4cae93ff8..b298bf2c8b 100644 --- a/spec/models/clusters/platforms/kubernetes_spec.rb +++ b/spec/models/clusters/platforms/kubernetes_spec.rb @@ -201,7 +201,7 @@ RSpec.describe Clusters::Platforms::Kubernetes do it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::KubeClient) } 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 build(:cluster_platform_kubernetes, :configured, @@ -228,21 +228,22 @@ RSpec.describe Clusters::Platforms::Kubernetes do ca_pem: cert_chain) end - 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) + where(:fixture_path) do + %w[ + spec/fixtures/clusters/root_certificate.pem + spec/fixtures/clusters/intermediate_certificate.pem + spec/fixtures/clusters/leaf_certificate.pem + ] + end - cert2_file = File.read(Rails.root.join('spec/fixtures/clusters/intermediate_certificate.pem')) - cert2 = OpenSSL::X509::Certificate.new(cert2_file) + with_them do + it 'includes chain of certificates' do + 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) - 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] - - expect(cert_store.verify(cert1)).to be true - expect(cert_store.verify(cert2)).to be true - expect(cert_store.verify(cert3)).to be true + expect(cert_store.verify(certificate)).to be true + end end end end diff --git a/spec/models/integrations/chat_message/alert_message_spec.rb b/spec/models/integrations/chat_message/alert_message_spec.rb index 9866b2d918..162df1a774 100644 --- a/spec/models/integrations/chat_message/alert_message_spec.rb +++ b/spec/models/integrations/chat_message/alert_message_spec.rb @@ -16,6 +16,8 @@ RSpec.describe Integrations::ChatMessage::AlertMessage do }.merge(Gitlab::DataBuilder::Alert.build(alert)) end + it_behaves_like Integrations::ChatMessage + describe '#message' do it 'returns the correct message' do expect(subject.message).to eq("Alert firing in #{args[:project_name]}") diff --git a/spec/models/integrations/chat_message/base_message_spec.rb b/spec/models/integrations/chat_message/base_message_spec.rb index eada5d1031..0f0ab11f2a 100644 --- a/spec/models/integrations/chat_message/base_message_spec.rb +++ b/spec/models/integrations/chat_message/base_message_spec.rb @@ -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') } end end + + describe '#strip_markup' do + using RSpec::Parameterized::TableSyntax + + where(:input, :output) do + nil | nil + '' | '' + '[label](url)' | 'label(url)' + '' | 'urllabel' + 'label' | '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 diff --git a/spec/models/integrations/chat_message/deployment_message_spec.rb b/spec/models/integrations/chat_message/deployment_message_spec.rb index ff255af11a..6bcd29c0a0 100644 --- a/spec/models/integrations/chat_message/deployment_message_spec.rb +++ b/spec/models/integrations/chat_message/deployment_message_spec.rb @@ -3,83 +3,79 @@ require 'spec_helper' 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 it 'returns a message with the data returned by the deployment data builder' do - environment = create(:environment, name: "myenvironment") - 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") + expect(subject.pretext).to eq("Deploy to myenvironment succeeded") end it 'returns a message for a successful deployment' do - data = { + args.merge!( status: 'success', environment: 'production' - } + ) - message = described_class.new(data) - - expect(message.pretext).to eq('Deploy to production succeeded') + expect(subject.pretext).to eq('Deploy to production succeeded') end it 'returns a message for a failed deployment' do - data = { + args.merge!( status: 'failed', environment: 'production' - } + ) - message = described_class.new(data) - - expect(message.pretext).to eq('Deploy to production failed') + expect(subject.pretext).to eq('Deploy to production failed') end it 'returns a message for a canceled deployment' do - data = { + args.merge!( status: 'canceled', environment: 'production' - } + ) - message = described_class.new(data) - - expect(message.pretext).to eq('Deploy to production canceled') + expect(subject.pretext).to eq('Deploy to production canceled') end it 'returns a message for a deployment to another environment' do - data = { + args.merge!( status: 'success', environment: 'staging' - } + ) - message = described_class.new(data) - - expect(message.pretext).to eq('Deploy to staging succeeded') + expect(subject.pretext).to eq('Deploy to staging succeeded') end it 'returns a message for a deployment with any other status' do - data = { + args.merge!( status: 'unknown', environment: 'staging' - } + ) - message = described_class.new(data) - - expect(message.pretext).to eq('Deploy to staging unknown') + expect(subject.pretext).to eq('Deploy to staging unknown') end it 'returns a message for a running deployment' do - data = { - status: 'running', - environment: 'production' - } + args.merge!( + status: 'running', + environment: 'production' + ) - message = described_class.new(data) - - expect(message.pretext).to eq('Starting deploy to production') + expect(subject.pretext).to eq('Starting deploy to production') end end @@ -108,21 +104,11 @@ RSpec.describe Integrations::ChatMessage::DeploymentMessage do end 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) commit_url = Gitlab::UrlBuilder.build(deployment.commit) user_url = Gitlab::Routing.url_helpers.user_url(user) - data = Gitlab::DataBuilder::Deployment.build(deployment, Time.current) - message = described_class.new(data) - - expect(message.attachments).to eq([{ + expect(subject.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}", color: "good" }]) diff --git a/spec/models/integrations/chat_message/issue_message_spec.rb b/spec/models/integrations/chat_message/issue_message_spec.rb index 31b80ad316..7026a314b7 100644 --- a/spec/models/integrations/chat_message/issue_message_spec.rb +++ b/spec/models/integrations/chat_message/issue_message_spec.rb @@ -28,6 +28,8 @@ RSpec.describe Integrations::ChatMessage::IssueMessage do } end + it_behaves_like Integrations::ChatMessage + context 'without markdown' do let(:color) { '#C95823' } diff --git a/spec/models/integrations/chat_message/merge_message_spec.rb b/spec/models/integrations/chat_message/merge_message_spec.rb index ed1ad6837e..52f15667b0 100644 --- a/spec/models/integrations/chat_message/merge_message_spec.rb +++ b/spec/models/integrations/chat_message/merge_message_spec.rb @@ -29,6 +29,8 @@ RSpec.describe Integrations::ChatMessage::MergeMessage do } end + it_behaves_like Integrations::ChatMessage + context 'without markdown' do let(:color) { '#345' } diff --git a/spec/models/integrations/chat_message/note_message_spec.rb b/spec/models/integrations/chat_message/note_message_spec.rb index 668c0da26a..197df21681 100644 --- a/spec/models/integrations/chat_message/note_message_spec.rb +++ b/spec/models/integrations/chat_message/note_message_spec.rb @@ -19,6 +19,10 @@ RSpec.describe Integrations::ChatMessage::NoteMessage do name: 'project_name', url: 'http://somewhere.com' }, + commit: { + id: '5f163b2b95e6f53cbd428f5f0b103702a52b9a23', + message: "Added a commit message\ndetails\n123\n" + }, object_attributes: { id: 10, note: 'comment on a commit', @@ -28,16 +32,9 @@ RSpec.describe Integrations::ChatMessage::NoteMessage do } end - context 'commit notes' do - 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 + it_behaves_like Integrations::ChatMessage + context 'commit notes' do context 'without markdown' do it 'returns a message regarding notes on commits' do expect(subject.pretext).to eq("Test User (test.user) /API not accessible/ }) 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 it 'does not log the user in' do token.update!(scopes: [:read_user]) diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index cc546cbcda..9c50c7d60a 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -3115,6 +3115,29 @@ RSpec.describe API::Projects do expect(json_response['message']).to eq('404 Project Not Found') 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 allow_next_instance_of(::ProjectTeam) do |project_team| allow(project_team).to receive(:import).and_return(false) diff --git a/spec/requests/dashboard/projects_controller_spec.rb b/spec/requests/dashboard/projects_controller_spec.rb new file mode 100644 index 0000000000..4cd3b6c4f9 --- /dev/null +++ b/spec/requests/dashboard/projects_controller_spec.rb @@ -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 diff --git a/spec/requests/dashboard_controller_spec.rb b/spec/requests/dashboard_controller_spec.rb new file mode 100644 index 0000000000..62655d720c --- /dev/null +++ b/spec/requests/dashboard_controller_spec.rb @@ -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 diff --git a/spec/requests/groups_controller_spec.rb b/spec/requests/groups_controller_spec.rb new file mode 100644 index 0000000000..422c108f2a --- /dev/null +++ b/spec/requests/groups_controller_spec.rb @@ -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 diff --git a/spec/requests/projects/commits_controller_spec.rb b/spec/requests/projects/commits_controller_spec.rb new file mode 100644 index 0000000000..158902c0ff --- /dev/null +++ b/spec/requests/projects/commits_controller_spec.rb @@ -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 diff --git a/spec/requests/projects/issues_controller_spec.rb b/spec/requests/projects/issues_controller_spec.rb index f44b1f4d50..248e3e3a92 100644 --- a/spec/requests/projects/issues_controller_spec.rb +++ b/spec/requests/projects/issues_controller_spec.rb @@ -8,11 +8,11 @@ RSpec.describe Projects::IssuesController do let_it_be(:project) { issue.project } let_it_be(:user) { issue.author } - before do - login_as(user) - end - describe 'GET #discussions' do + before do + login_as(user) + end + 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(:state_event) { create(:resource_state_event, issue: issue) } @@ -68,4 +68,38 @@ RSpec.describe Projects::IssuesController do 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 diff --git a/spec/requests/projects/merge_requests_controller_spec.rb b/spec/requests/projects/merge_requests_controller_spec.rb new file mode 100644 index 0000000000..3b1ce56903 --- /dev/null +++ b/spec/requests/projects/merge_requests_controller_spec.rb @@ -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 diff --git a/spec/requests/projects/tags_controller_spec.rb b/spec/requests/projects/tags_controller_spec.rb new file mode 100644 index 0000000000..b9531a2739 --- /dev/null +++ b/spec/requests/projects/tags_controller_spec.rb @@ -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 diff --git a/spec/requests/projects_controller_spec.rb b/spec/requests/projects_controller_spec.rb new file mode 100644 index 0000000000..d2200d5a4e --- /dev/null +++ b/spec/requests/projects_controller_spec.rb @@ -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 diff --git a/spec/requests/users_controller_spec.rb b/spec/requests/users_controller_spec.rb index 701a73761f..eefc24f782 100644 --- a/spec/requests/users_controller_spec.rb +++ b/spec/requests/users_controller_spec.rb @@ -792,9 +792,9 @@ RSpec.describe UsersController do end context 'token authentication' do - let(:url) { user_url(user.username, format: :atom) } - - it_behaves_like 'authenticates sessionless user for the request spec', public: true + it_behaves_like 'authenticates sessionless user for the request spec', 'show atom', public_resource: true do + let(:url) { user_url(user, format: :atom) } + end end def user_moved_message(redirect_route, user) diff --git a/spec/services/packages/npm/create_package_service_spec.rb b/spec/services/packages/npm/create_package_service_spec.rb index b1beb2adb3..d007cda44d 100644 --- a/spec/services/packages/npm/create_package_service_spec.rb +++ b/spec/services/packages/npm/create_package_service_spec.rb @@ -11,10 +11,8 @@ RSpec.describe Packages::Npm::CreatePackageService do Gitlab::Json.parse(fixture_file('packages/npm/payload.json') .gsub('@root/npm-test', package_name) .gsub('1.0.1', version)).with_indifferent_access - .merge!(override) end - let(:override) { {} } let(:package_name) { "@#{namespace.path}/my-app" } 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.' } end - context 'file size above maximum limit' do - before do - params['_attachments']["#{package_name}-#{version}.tgz"]['length'] = project.actual_limits.npm_max_file_size + 1 + describe 'max file size validation' do + let(:max_file_size) { 5.bytes} + + 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 - it { expect(subject[:http_status]).to eq 400 } - it { expect(subject[:message]).to be 'File is too large.' } + before do + 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 [ @@ -152,7 +190,7 @@ RSpec.describe Packages::Npm::CreatePackageService do end context 'with empty versions' do - let(:override) { { versions: {} } } + let(:params) { super().merge!({ versions: {} } ) } it { expect(subject[:http_status]).to eq 400 } it { expect(subject[:message]).to eq 'Version is empty.' } diff --git a/spec/support/shared_examples/controllers/sessionless_auth_controller_shared_examples.rb b/spec/support/shared_examples/controllers/sessionless_auth_controller_shared_examples.rb deleted file mode 100644 index 041695d811..0000000000 --- a/spec/support/shared_examples/controllers/sessionless_auth_controller_shared_examples.rb +++ /dev/null @@ -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 diff --git a/spec/support/shared_examples/models/integrations/chat_message_shared_examples.rb b/spec/support/shared_examples/models/integrations/chat_message_shared_examples.rb new file mode 100644 index 0000000000..2665f249de --- /dev/null +++ b/spec/support/shared_examples/models/integrations/chat_message_shared_examples.rb @@ -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) HTML ' } + + # 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 diff --git a/spec/support/shared_examples/requests/sessionless_auth_request_shared_examples.rb b/spec/support/shared_examples/requests/sessionless_auth_request_shared_examples.rb index d82da1b01e..56e90a6ec3 100644 --- a/spec/support/shared_examples/requests/sessionless_auth_request_shared_examples.rb +++ b/spec/support/shared_examples/requests/sessionless_auth_request_shared_examples.rb @@ -1,84 +1,160 @@ # frozen_string_literal: true -RSpec.shared_examples 'authenticates sessionless user for the request spec' do |params| - params ||= {} - +RSpec.shared_examples 'authenticates sessionless user for the request spec' do |name, public_resource:, ignore_metrics: false, params: {}| before do stub_authentication_activity_metrics(debug: false) end - let(:user) { create(:user) } + let_it_be(:user) { create(: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 - it 'logs the user in' do + shared_examples 'authenticates user and returns response with ok status' do + it 'authenticates user and returns response with ok status' do expect(authentication_metrics) .to increment(:user_authenticated_counter) - .and increment(:user_session_override_counter) - .and increment(:user_sessionless_authentication_counter) + .and increment(:user_session_override_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) - end - - it 'does not log the user in if page is public', if: params[:public] do - get url, 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 + shared_examples 'does not authenticate user and returns response with ok status' do + it 'does not authenticate user and returns response with ok status' do + subject + + expect(controller.current_user).to be_nil + expect(response).to have_gitlab_http_status(:ok) + end + end + + 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 # 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] + unless ignore_metrics 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) + subject 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) + 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' - headers = { 'PRIVATE-TOKEN': personal_access_token.token } - get url, params: default_params, headers: headers + context 'when user with expired password' do + let_it_be(:user) { create(:user, password_expires_at: 2.minutes.ago) } - expect(response).to have_gitlab_http_status(:ok) + 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 - 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) + 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 - get url, params: default_params.merge(private_token: 'token') + context 'when resource is public', if: public_resource do + include_examples 'does not authenticate user and returns response with ok status' + end + end - expect(response).not_to have_gitlab_http_status(:ok) + 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 diff --git a/spec/workers/dependency_proxy/cleanup_dependency_proxy_worker_spec.rb b/spec/workers/dependency_proxy/cleanup_dependency_proxy_worker_spec.rb new file mode 100644 index 0000000000..ed0bdefbdb --- /dev/null +++ b/spec/workers/dependency_proxy/cleanup_dependency_proxy_worker_spec.rb @@ -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 diff --git a/spec/workers/dependency_proxy/image_ttl_group_policy_worker_spec.rb b/spec/workers/dependency_proxy/image_ttl_group_policy_worker_spec.rb index ae0cb097eb..b035a2ec0b 100644 --- a/spec/workers/dependency_proxy/image_ttl_group_policy_worker_spec.rb +++ b/spec/workers/dependency_proxy/image_ttl_group_policy_worker_spec.rb @@ -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_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 expect { subject } .to change { old_blob.reload.status }.from('default').to('expired') @@ -33,15 +26,6 @@ RSpec.describe DependencyProxy::ImageTtlGroupPolicyWorker do 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 let_it_be(:expired_blob) { create(:dependency_proxy_blob, :expired, group: group) } let_it_be(:expired_blob2) { create(:dependency_proxy_blob, :expired, group: group) } diff --git a/spec/workers/purge_dependency_proxy_cache_worker_spec.rb b/spec/workers/purge_dependency_proxy_cache_worker_spec.rb index 393745958b..b928104fb5 100644 --- a/spec/workers/purge_dependency_proxy_cache_worker_spec.rb +++ b/spec/workers/purge_dependency_proxy_cache_worker_spec.rb @@ -4,18 +4,18 @@ require 'spec_helper' RSpec.describe PurgeDependencyProxyCacheWorker do let_it_be(:user) { create(:admin) } - let_it_be(:blob) { create(:dependency_proxy_blob )} - let_it_be(:group, reload: true) { blob.group } - let_it_be(:manifest) { create(:dependency_proxy_manifest, group: group )} + let_it_be_with_refind(:blob) { create(:dependency_proxy_blob )} + let_it_be_with_reload(:group) { blob.group } + let_it_be_with_refind(:manifest) { create(:dependency_proxy_manifest, group: group )} let_it_be(:group_id) { group.id } subject { described_class.new.perform(user.id, group_id) } describe '#perform' do - shared_examples 'not removing blobs and manifests' do - it 'does not remove blobs and manifests', :aggregate_failures do - expect { subject }.not_to change { group.dependency_proxy_blobs.size } - expect { subject }.not_to change { group.dependency_proxy_manifests.size } + shared_examples 'not expiring blobs and manifests' do + it 'does not expire blobs and manifests', :aggregate_failures do + expect { subject }.not_to change { blob.status } + expect { subject }.not_to change { manifest.status } expect(subject).to be_nil end end @@ -25,39 +25,36 @@ RSpec.describe PurgeDependencyProxyCacheWorker do include_examples 'an idempotent worker' do let(:job_args) { [user.id, group_id] } - it 'deletes 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) - + it 'expires the blobs and returns ok', :aggregate_failures do subject - expect(group.dependency_proxy_blobs.size).to eq(0) - expect(group.dependency_proxy_manifests.size).to eq(0) + expect(blob).to be_expired + expect(manifest).to be_expired end end end 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 context 'a non-admin user' do let(:user) { create(:user) } - it_behaves_like 'not removing blobs and manifests' + it_behaves_like 'not expiring blobs and manifests' end context 'an invalid user id' do let(:user) { double('User', id: 99999 ) } - it_behaves_like 'not removing blobs and manifests' + it_behaves_like 'not expiring blobs and manifests' end context 'an invalid group' do let(:group_id) { 99999 } - it_behaves_like 'not removing blobs and manifests' + it_behaves_like 'not expiring blobs and manifests' end end end