Merge tag 'debian/15.8.5+ds1-1' into bullseye-fasttrack

gitlab Debian release 15.8.5+ds1-1
This commit is contained in:
Vinay Keshava 2023-04-05 01:29:52 +05:30
commit 4ff576b45c
66 changed files with 1410 additions and 457 deletions

View file

@ -30,7 +30,7 @@ workflow:
# If `$FORCE_GITLAB_CI` is set, create a pipeline.
- if: '$FORCE_GITLAB_CI'
variables:
RUBY_VERSION: "3.0"
RUBY_VERSION: "2.7"
# As part of the process of creating RCs automatically, we update stable
# branches with the changes of the most recent production deployment. The
# merge requests used for this merge a branch release-tools/X into a stable
@ -46,10 +46,10 @@ workflow:
# For (detached) merge request pipelines.
- if: '$CI_MERGE_REQUEST_IID'
variables:
RUBY_VERSION: "3.0"
OMNIBUS_GITLAB_RUBY3_BUILD: "true"
OMNIBUS_GITLAB_CACHE_EDITION: "GITLAB_RUBY3"
PIPELINE_NAME: 'Ruby 3 $CI_MERGE_REQUEST_EVENT_TYPE MR pipeline'
RUBY_VERSION: "2.7"
OMNIBUS_GITLAB_RUBY2_BUILD: "true"
OMNIBUS_GITLAB_CACHE_EDITION: "GITLAB_RUBY2"
PIPELINE_NAME: 'Ruby 2 $CI_MERGE_REQUEST_EVENT_TYPE MR pipeline'
# For the scheduled pipelines, we set specific variables.
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE == "schedule"'
variables:

View file

@ -2,6 +2,27 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
## 15.8.5 (2023-03-30)
### Security (16 changes)
- [Fix rubocop offenses in lib/gitlab/url_sanitizer.rb](gitlab-org/security/gitlab@ddc04cf7059e411e20033b95e1297381d64d4b22) ([merge request](gitlab-org/security/gitlab!3175))
- [Add checks to remove open redirects from Observability URL](gitlab-org/security/gitlab@a22ce3851128eb900dbabe9e38c07889967a2915) ([merge request](gitlab-org/security/gitlab!3032))
- [Redirect to tree from project root on ref collision](gitlab-org/security/gitlab@fad24ae9d8fa0e7bd9eff0c9e6914c8267451b4d) ([merge request](gitlab-org/security/gitlab!3134))
- [Fixes soft email confirmation alert vulnerability](gitlab-org/security/gitlab@85be0fbfc98cdb774d68070479e35be22f6ba40a) ([merge request](gitlab-org/security/gitlab!3125))
- [Restrict Prometheus API access on public projects](gitlab-org/security/gitlab@2df2fa2dc4b9015d044d0ddc5d26e17e9e5f85c0) ([merge request](gitlab-org/security/gitlab!3164))
- [Verify that users have access to the parent of the fork](gitlab-org/security/gitlab@53f7f06843eea4d666d361f5a1d349bd1e3f4312) ([merge request](gitlab-org/security/gitlab!3085))
- [Protect webhook secrets by resetting url_variables](gitlab-org/security/gitlab@9fa9dbff463f6015ffaf8d082db3d41ae623763e) ([merge request](gitlab-org/security/gitlab!3141))
- [Replace Unicode space chars with spaces](gitlab-org/security/gitlab@20d77d4d680d13f916fb69de0d79802753421c8f) ([merge request](gitlab-org/security/gitlab!3137))
- [Check access to parent when creating and updating epics](gitlab-org/security/gitlab@0fed113756b27a3a078f87f29711b225e1ed4cce) ([merge request](gitlab-org/security/gitlab!3150))
- [Improve Gitlab::UrlSanitizer regex to match more URIs](gitlab-org/security/gitlab@2285088f37aca877b1dcd59c728cdf33171b30cb) ([merge request](gitlab-org/security/gitlab!3109))
- [Check access to target project before looking for branch](gitlab-org/security/gitlab@37b8d855d87c88170322e6a6d4c285fee6c6cb64) ([merge request](gitlab-org/security/gitlab!3038))
- [Fix the potential leak of internal notes](gitlab-org/security/gitlab@66f8cc2eb13509397b980d53a4b67ca03d8903f7) ([merge request](gitlab-org/security/gitlab!3121))
- [Filter namespace environments by feature visibility](gitlab-org/security/gitlab@e1859de393b4794e1356d6318e56ede4b557c059) ([merge request](gitlab-org/security/gitlab!3112))
- [Check access to reorder issues in epic tree](gitlab-org/security/gitlab@13f9c6231cea956f73355c5b5b820163f523e7d8) ([merge request](gitlab-org/security/gitlab!3100))
- [Fix security report authorization](gitlab-org/security/gitlab@19baab85c7a5a64a09e3e4808e8550fc72e18323) ([merge request](gitlab-org/security/gitlab!3105))
- [Prevent XSS attack in "Maximum page reached" page](gitlab-org/security/gitlab@be5491c5db05161e4b14d53900dd19b66848de48) ([merge request](gitlab-org/security/gitlab!3131))
## 15.8.4 (2023-03-02)
### Security (12 changes)

View file

@ -1 +1 @@
15.8.4
15.8.5

View file

@ -1 +1 @@
15.8.4
15.8.5

View file

@ -1 +1 @@
15.8.4
15.8.5

View file

@ -7,23 +7,36 @@ export function getFrameSrc(url) {
}
const mountVueComponent = (element) => {
const url = [element.dataset.frameUrl];
const { frameUrl, observabilityUrl } = element.dataset;
return new Vue({
el: element,
render(h) {
return h('iframe', {
style: {
height: '366px',
width: '768px',
},
attrs: {
src: getFrameSrc(url),
frameBorder: '0',
},
});
},
});
try {
if (
!observabilityUrl ||
!frameUrl ||
new URL(frameUrl)?.host !== new URL(observabilityUrl).host
)
return;
// eslint-disable-next-line no-new
new Vue({
el: element,
render(h) {
return h('iframe', {
style: {
height: '366px',
width: '768px',
},
attrs: {
src: getFrameSrc(frameUrl),
frameBorder: '0',
},
});
},
});
} catch (e) {
// eslint-disable-next-line no-console
console.error(e);
}
};
export default function renderObservability(elements) {

View file

@ -118,9 +118,10 @@ export default class Project {
const urlParams = { [fieldName]: ref };
if (params.group === BRANCH_GROUP_NAME) {
urlParams.ref_type = BRANCH_REF_TYPE;
} else {
} else if (params.group === TAG_GROUP_NAME) {
urlParams.ref_type = TAG_REF_TYPE;
}
link.href = mergeUrlParams(urlParams, linkTarget);
}

View file

@ -2,7 +2,7 @@ import { GlButton } from '@gitlab/ui';
import Vue from 'vue';
import Vuex from 'vuex';
import { parseBoolean } from '~/lib/utils/common_utils';
import { escapeFileUrl, visitUrl } from '~/lib/utils/url_utility';
import { joinPaths, escapeFileUrl, visitUrl } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
import initWebIdeLink from '~/pages/projects/shared/web_ide_link';
import PerformancePlugin from '~/performance/vue_performance_plugin';
@ -119,7 +119,7 @@ export default function setupVueRepositoryList() {
if (!refSwitcherEl) return false;
const { projectId, projectRootPath } = refSwitcherEl.dataset;
const { projectId, projectRootPath, refType } = refSwitcherEl.dataset;
return new Vue({
el: refSwitcherEl,
@ -127,11 +127,12 @@ export default function setupVueRepositoryList() {
return createElement(RefSelector, {
props: {
projectId,
value: ref,
value: refType ? joinPaths('refs', refType, ref) : ref,
useSymbolicRefNames: true,
},
on: {
input(selectedRef) {
visitUrl(generateRefDestinationPath(projectRootPath, selectedRef));
visitUrl(generateRefDestinationPath(projectRootPath, ref, selectedRef));
},
},
});

View file

@ -15,22 +15,30 @@ const NAMESPACE_TARGET_REGEX = /(\/-\/(blob|tree))\/.*?\/(.*)/;
* @param {string} projectRootPath - The root path for a project.
* @param {string} selectedRef - The selected ref from the ref dropdown.
*/
export function generateRefDestinationPath(projectRootPath, selectedRef) {
const currentPath = window.location.pathname;
const encodedHash = '%23';
export function generateRefDestinationPath(projectRootPath, ref, selectedRef) {
const url = new URL(window.location.href);
const currentPath = url.pathname;
let refType = null;
let namespace = '/-/tree';
let target;
let actualRef = selectedRef;
const matches = selectedRef.match(/^refs\/(heads|tags)\/(.+)/);
if (matches) {
[, refType, actualRef] = matches;
}
if (refType) {
url.searchParams.set('ref_type', refType);
} else {
url.searchParams.delete('ref_type');
}
const match = NAMESPACE_TARGET_REGEX.exec(currentPath);
if (match) {
[, namespace, , target] = match;
}
url.pathname = joinPaths(projectRootPath, namespace, actualRef, target);
const destinationPath = joinPaths(
projectRootPath,
namespace,
encodeURI(selectedRef).replace(/#/g, encodedHash),
target,
);
return `${destinationPath}${window.location.hash}`;
return url.toString();
}

View file

@ -1,6 +1,7 @@
# frozen_string_literal: true
module ConfirmEmailWarning
include Gitlab::Utils::StrongMemoize
extend ActiveSupport::Concern
included do
@ -17,11 +18,9 @@ module ConfirmEmailWarning
return unless current_user
return if current_user.confirmed?
email = current_user.unconfirmed_email || current_user.email
flash.now[:warning] = format(
confirm_warning_message,
email: email,
email: email_to_display,
resend_link: view_context.link_to(_('Resend it'), user_confirmation_path(user: { email: email }), method: :post),
update_link: view_context.link_to(_('Update it'), profile_path)
).html_safe
@ -29,7 +28,16 @@ module ConfirmEmailWarning
private
def email
current_user.unconfirmed_email || current_user.email
end
strong_memoize_attr :email
def confirm_warning_message
_("Please check your email (%{email}) to verify that you own this address and unlock the power of CI/CD. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}.")
end
def email_to_display
html_escape(email)
end
end

View file

@ -31,6 +31,7 @@ class Projects::BlobController < Projects::ApplicationController
before_action :authorize_edit_tree!, only: [:new, :create, :update, :destroy]
before_action :commit, except: [:new, :create]
before_action :check_for_ambiguous_ref, only: [:show]
before_action :blob, except: [:new, :create]
before_action :require_branch_head, only: [:edit, :update]
before_action :editor_variables, except: [:show, :preview, :diff]
@ -145,6 +146,15 @@ class Projects::BlobController < Projects::ApplicationController
end
end
def check_for_ambiguous_ref
@ref_type = ref_type
if @ref_type == ExtractsRef::BRANCH_REF_TYPE && ambiguous_ref?(@project, @ref)
branch = @project.repository.find_branch(@ref)
redirect_to project_blob_path(@project, File.join(branch.target, @path))
end
end
def commit
@commit ||= @repository.commit(@ref)

View file

@ -22,7 +22,7 @@ class Projects::RefsController < Projects::ApplicationController
when "tree"
project_tree_path(@project, @id)
when "blob"
project_blob_path(@project, @id)
project_blob_path(@project, @id, ref_type: ref_type)
when "graph"
if Feature.enabled?(:use_ref_type_parameter, @project)
project_network_path(@project, @id, ref_type: ref_type)

View file

@ -28,6 +28,15 @@ class Projects::TreeController < Projects::ApplicationController
def show
return render_404 unless @commit
@ref_type = ref_type
if @ref_type == BRANCH_REF_TYPE && ambiguous_ref?(@project, @ref)
branch = @project.repository.find_branch(@ref)
if branch
redirect_to project_tree_path(@project, branch.target)
return
end
end
if tree.entries.empty?
if @repository.blob_at(@commit.id, @path)
redirect_to project_blob_path(@project, File.join(@ref, @path))

View file

@ -172,11 +172,19 @@ class ProjectsController < Projects::ApplicationController
flash.now[:alert] = _("Project '%{project_name}' queued for deletion.") % { project_name: @project.name }
end
if ambiguous_ref?(@project, @ref)
branch = @project.repository.find_branch(@ref)
# The files view would render a ref other than the default branch
# This redirect can be removed once the view is fixed
redirect_to(project_tree_path(@project, branch.target), alert: _("The default branch of this project clashes with another ref"))
return
end
respond_to do |format|
format.html do
@notification_setting = current_user.notification_settings_for(@project) if current_user
@project = @project.present(current_user: current_user)
render_landing_page
end

View file

@ -32,18 +32,9 @@ module Environments
end
def namespace_environments
# We assume reporter access is needed for the :read_environment permission
# here. This expection is also present in
# IssuableFinder::Params#min_access_level, which is used for filtering out
# merge requests that don't have the right permissions.
#
# We use this approach so we don't need to load every project into memory
# just to verify if we can see their environments. Doing so would not be
# efficient, and possibly mess up pagination if certain projects are not
# meant to be visible.
projects = project_or_group
.all_projects
.public_or_visible_to_user(current_user, Gitlab::Access::REPORTER)
.filter_by_feature_visibility(:environments, current_user)
Environment.for_project(projects)
end

View file

@ -30,6 +30,7 @@ class NotesFinder
notes = init_collection
notes = since_fetch_at(notes)
notes = notes.with_notes_filter(@params[:notes_filter]) if notes_filter?
notes = redact_internal(notes)
sort(notes)
end
@ -181,6 +182,13 @@ class NotesFinder
notes.order_by(sort)
end
def redact_internal(notes)
subject = @project || target
return notes if Ability.allowed?(@current_user, :reporter_access, subject)
notes.not_internal
end
end
NotesFinder.prepend_mod_with('NotesFinder')

View file

@ -41,7 +41,7 @@ class WebHook < ApplicationRecord
after_initialize :initialize_url_variables
before_validation :reset_token
before_validation :reset_url_variables, unless: ->(hook) { hook.is_a?(ServiceHook) }
before_validation :reset_url_variables, unless: ->(hook) { hook.is_a?(ServiceHook) }, on: :update
before_validation :set_branch_filter_nil, if: :branch_filter_strategy_all_branches?
validates :push_events_branch_filter, untrusted_regexp: true, if: :branch_filter_strategy_regex?
validates :push_events_branch_filter, "web_hooks/wildcard_branch_filter": true, if: :branch_filter_strategy_wildcard?
@ -189,7 +189,7 @@ class WebHook < ApplicationRecord
# See app/validators/json_schemas/web_hooks_url_variables.json
VARIABLE_REFERENCE_RE = /\{([A-Za-z]+[0-9]*(?:[._-][A-Za-z0-9]+)*)\}/.freeze
def interpolated_url
def interpolated_url(url = self.url, url_variables = self.url_variables)
return url unless url.include?('{')
vars = url_variables
@ -215,7 +215,19 @@ class WebHook < ApplicationRecord
end
def reset_url_variables
self.url_variables = {} if url_changed? && !encrypted_url_variables_changed?
interpolated_url_was = interpolated_url(decrypt_url_was, url_variables_were)
return if url_variables_were.empty? || interpolated_url_was == interpolated_url
self.url_variables = {} if url_changed? && url_variables_were.to_a.intersection(url_variables.to_a).any?
end
def decrypt_url_was
self.class.decrypt_url(encrypted_url_was, iv: Base64.decode64(encrypted_url_iv_was))
end
def url_variables_were
self.class.decrypt_url_variables(encrypted_url_variables_was, iv: encrypted_url_variables_iv_was)
end
def next_failure_count

View file

@ -36,7 +36,8 @@ class ProjectFeature < ApplicationRecord
merge_requests: Gitlab::Access::REPORTER,
metrics_dashboard: Gitlab::Access::REPORTER,
container_registry: Gitlab::Access::REPORTER,
package_registry: Gitlab::Access::REPORTER
package_registry: Gitlab::Access::REPORTER,
environments: Gitlab::Access::REPORTER
}.freeze
PRIVATE_FEATURES_MIN_ACCESS_LEVEL_FOR_PRIVATE_PROJECT = { repository: Gitlab::Access::REPORTER }.freeze

View file

@ -404,7 +404,6 @@ class ProjectPolicy < BasePolicy
end
rule { can?(:metrics_dashboard) }.policy do
enable :read_prometheus
enable :read_deployment
end

View file

@ -54,7 +54,15 @@ module MergeRequests
end
def validate_service
errors << 'User is required' if current_user.nil?
if current_user.nil?
errors << 'User is required'
return
end
unless current_user&.can?(:read_code, target_project)
errors << 'User access was denied'
return
end
unless target_project.merge_requests_enabled?
errors << "Merge requests are not enabled for project #{target_project.full_path}"

View file

@ -18,5 +18,5 @@
%h5= _("Maximum page reached")
%p= _("Sorry, you have exceeded the maximum browsable page number. Please use the API to explore further.")
= render Pajamas::ButtonComponent.new(href: request.params.merge(page: @max_page_number)) do
= render Pajamas::ButtonComponent.new(href: safe_params.merge(page: @max_page_number)) do
= _("Back to page %{number}") % { number: @max_page_number }

View file

@ -2,7 +2,7 @@
.tree-ref-container.gl-display-flex.mb-2.mb-md-0
.tree-ref-holder
#js-tree-ref-switcher{ data: { project_id: @project.id, project_root_path: project_path(@project) } }
#js-tree-ref-switcher{ data: { project_id: @project.id, ref_type: @ref_type.to_s, project_root_path: project_path(@project) } }
#js-repo-breadcrumb{ data: breadcrumb_data_attributes }

View file

@ -0,0 +1,26 @@
# frozen_string_literal: true
class NullifyLastErrorFromProjectMirrorData < Gitlab::Database::Migration[2.1]
MIGRATION = 'NullifyLastErrorFromProjectMirrorData'
INTERVAL = 2.minutes
BATCH_SIZE = 10_000
SUB_BATCH_SIZE = 1_000
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_main
def up
queue_batched_background_migration(
MIGRATION,
:project_mirror_data,
:id,
job_interval: INTERVAL,
batch_size: BATCH_SIZE,
sub_batch_size: SUB_BATCH_SIZE
)
end
def down
delete_batched_background_migration(MIGRATION, :project_mirror_data, :id, [])
end
end

View file

@ -0,0 +1 @@
784f8f189eee7b5cf3136f0a859874a1d170d2b148f4c260f968b144816f1322

29
debian/changelog vendored
View file

@ -1,3 +1,16 @@
gitlab (15.8.5+ds1-1) experimental; urgency=medium
* Install rack gem from rubygems.org in postinst
* use ruby-rack package instead of gem install
* use minimum version of ruby-rack(2.2.6~)
* New upstream version 15.8.5+ds1(Fixes:CVE-2022-3513,CVE-2023-0485,
CVE-2023-1098,CVE-2023-1733,CVE-2023-0319,CVE-2023-1708,
CVE-2023-0838,CVE-2023-0523,CVE-2023-0155, CVE-2023-1167,
CVE-2023-1787,CVE-2023-1417,CVE-2023-1710,CVE-2023-0450,
CVE-2023-1071,CVE-2022-3375)
-- Vinay Keshava <vinaykeshava@disroot.org> Mon, 03 Apr 2023 14:16:49 +0530
gitlab (15.8.4+ds1-1~fto11+3) bullseye-fasttrack; urgency=medium
* Update rack version for gem install to ~> 2.2.6
@ -17,6 +30,22 @@ gitlab (15.8.4+ds1-1~fto11+1) bullseye-fasttrack; urgency=medium
-- Vinay Keshava <vinaykeshava@disroot.org> Sun, 19 Mar 2023 16:10:30 +0530
gitlab (15.8.4+ds1-3) experimental; urgency=medium
* Drop ruby-omniauth-shibboleth dependency (dropped in 15.9)
-- Pirate Praveen <praveen@debian.org> Wed, 22 Mar 2023 21:14:43 +0530
gitlab (15.8.4+ds1-2) experimental; urgency=medium
* Don't depend on node-mermaid (version in archive is older and not in
bookworm)
* Relax dependency on omniauth-auth0 to allow version 3.1 (upstream ci is
green and this allows us to use ruby-omniauth-auth0 from archive as 2.0
tests fail with ruby 3.1 in bookworm)
-- Pirate Praveen <praveen@debian.org> Wed, 22 Mar 2023 08:51:35 +0530
gitlab (15.8.4+ds1-1) experimental; urgency=medium
* Don't try to remove /etc/gitlab/metrics/aggregates

7
debian/control vendored
View file

@ -128,7 +128,7 @@ Depends: ${shlibs:Depends}, ${misc:Depends},
ruby-rexml (>= 3.2.3.1~),
ruby-saml (>= 1.13~),
ruby-omniauth (>= 2.1~),
ruby-omniauth-auth0 (>= 2.0~),
ruby-omniauth-auth0 (>= 3.1~),
ruby-omniauth-azure-activedirectory-v2 (>= 2.0~),
ruby-omniauth-dingtalk-oauth2 (>= 1.0.1~),
ruby-omniauth-alicloud (>= 2.0~),
@ -139,7 +139,6 @@ Depends: ${shlibs:Depends}, ${misc:Depends},
ruby-omniauth-kerberos (>= 0.3.0-3~),
ruby-omniauth-oauth2-generic (>= 0.2.2~),
ruby-omniauth-saml (>= 2.0~),
ruby-omniauth-shibboleth (>= 1.3~),
ruby-omniauth-twitter (>= 1.4~),
ruby-omniauth-oauth (>= 1.2~),
ruby-omniauth-authentiq (>= 0.3.3~),
@ -247,7 +246,7 @@ Depends: ${shlibs:Depends}, ${misc:Depends},
ruby-diffy (>= 3.4~),
ruby-diff-match-patch (>= 0.1~),
# Application server
ruby-rack (>= 2.2.4~),
ruby-rack (>= 2.2.6~),
ruby-rack-timeout (>= 0.6.0~),
puma (>= 5.6.5~),
ruby-puma-worker-killer (>= 0.3.1~),
@ -499,7 +498,7 @@ Depends: ${shlibs:Depends}, ${misc:Depends},
node-katex (>= 0.13~),
node-lodash (>= 4.17.21+dfsg+~cs8.31.198.20210220-9~bpo11+2),
node-marked (>= 0.3~),
node-mermaid (>= 8.13.10~),
# node-mermaid (>= 8.13.10~),
node-minimatch,
node-miragejs,
node-mousetrap,

View file

@ -0,0 +1,22 @@
From e401d71c70d9d259726bd56ab739d4e514678f28 Mon Sep 17 00:00:00 2001
From: Pirate Praveen <praveen@debian.org>
Date: Mon, 20 Mar 2023 14:49:32 +0000
Subject: [PATCH] Update omniauth-auth0 rubygem to 3.1
---
Gemfile | 2 +-
Gemfile.checksum | 2 +-
Gemfile.lock | 7 ++++---
3 files changed, 6 insertions(+), 5 deletions(-)
--- a/Gemfile
+++ b/Gemfile
@@ -60,7 +60,7 @@
gem 'rexml', '~> 3.2','>= 3.2.3.1'
gem 'ruby-saml', '~> 1.13'
gem 'omniauth', '~> 2.1'
-gem 'omniauth-auth0', '~> 2.0'
+gem 'omniauth-auth0', '~> 3.1'
gem 'omniauth-azure-activedirectory-v2', '~> 2.0'
gem 'omniauth-azure-oauth2', '~> 0.0.9', path: 'vendor/gems/omniauth-azure-oauth2' # See gem README.md
gem 'omniauth-cas3', '~> 1.1.4', path: 'vendor/gems/omniauth-cas3' # See vendor/gems/omniauth-cas3/README.md

View file

@ -0,0 +1,83 @@
From 93d13533cdad1aa5e7b508f88b44fb9a758d06d5 Mon Sep 17 00:00:00 2001
From: Imre Farkas <ifarkas@gitlab.com>
Date: Mon, 23 Jan 2023 17:11:50 +0100
Subject: [PATCH] Remove omniauth-shibboleth gem
Integration with Shibboleth is recommended via omniauth-saml since
GitLab 10.
---
Gemfile | 1 -
Gemfile.checksum | 1 -
Gemfile.lock | 3 ---
app/assets/images/auth_buttons/shibboleth_64.png | Bin 2993 -> 0 bytes
app/helpers/auth_helper.rb | 1 -
spec/lib/gitlab/omniauth_initializer_spec.rb | 8 --------
6 files changed, 14 deletions(-)
delete mode 100644 app/assets/images/auth_buttons/shibboleth_64.png
--- a/Gemfile
+++ b/Gemfile
@@ -72,7 +72,6 @@
gem 'omniauth-google-oauth2', '~> 1.1'
gem 'omniauth-oauth2-generic', '~> 0.2.2'
gem 'omniauth-saml', '~> 2.0'
-gem 'omniauth-shibboleth', '~> 1.3'
gem 'omniauth-twitter', '~> 1.4'
gem 'omniauth_crowd', '~> 2.4.0', path: 'vendor/gems/omniauth_crowd' # See vendor/gems/omniauth_crowd/README.md
gem 'omniauth-authentiq', '~> 0.3.3'
--- a/Gemfile.checksum
+++ b/Gemfile.checksum
@@ -397,7 +397,6 @@
{"name":"omniauth-oauth2","version":"1.8.0","platform":"ruby","checksum":"b2f8e9559cc7e2d4efba57607691d6d2b634b879fc5b5b6ccfefa3da85089e78"},
{"name":"omniauth-oauth2-generic","version":"0.2.8","platform":"ruby","checksum":"ce6e8539019d5ebf2f48867072b9f248f148bb4cbe7166dee655865abfae7613"},
{"name":"omniauth-saml","version":"2.0.0","platform":"ruby","checksum":"02594fd6630de26a9e65a2e64223e9ad32324fa97a6c7f1f22a1553ea3dd44c7"},
-{"name":"omniauth-shibboleth","version":"1.3.0","platform":"ruby","checksum":"b0bb725ced5cb76fbfc187ddbb8ad6864d0cd5df714cab36a528df8ee4b1d113"},
{"name":"omniauth-twitter","version":"1.4.0","platform":"ruby","checksum":"c5cc6c77cd767745ffa9ebbd5fbd694a3fa99d1d2d82a4d7def0bf3b6131b264"},
{"name":"open4","version":"1.3.4","platform":"ruby","checksum":"a1df037310624ecc1ea1d81264b11c83e96d0c3c1c6043108d37d396dcd0f4b1"},
{"name":"openid_connect","version":"1.3.0","platform":"ruby","checksum":"a796855096850cc01140e37ea6ae9fd14f2be818b9b5bc698418063dfe228770"},
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1015,8 +1015,6 @@
omniauth-saml (2.0.0)
omniauth (~> 2.0)
ruby-saml (~> 1.9)
- omniauth-shibboleth (1.3.0)
- omniauth (>= 1.0.0)
omniauth-twitter (1.4.0)
omniauth-oauth (~> 1.1)
rack
@@ -1762,7 +1760,6 @@
omniauth-oauth2-generic (~> 0.2.2)
omniauth-salesforce (~> 1.0.5)!
omniauth-saml (~> 2.0.0)
- omniauth-shibboleth (~> 1.3.0)
omniauth-twitter (~> 1.4)
omniauth_crowd (~> 2.4.0)!
openssl (= 2.2.2)
--- a/app/helpers/auth_helper.rb
+++ b/app/helpers/auth_helper.rb
@@ -17,7 +17,6 @@
jwt
openid_connect
salesforce
- shibboleth
twitter
).freeze
LDAP_PROVIDER = /\Aldap/.freeze
--- a/spec/lib/gitlab/omniauth_initializer_spec.rb
+++ b/spec/lib/gitlab/omniauth_initializer_spec.rb
@@ -216,14 +216,6 @@
expect { subject.execute([hash_config]) }.to raise_error(NameError)
end
- it 'configures fail_with_empty_uid for shibboleth' do
- shibboleth_config = { 'name' => 'shibboleth', 'args' => {} }
-
- expect(devise_config).to receive(:omniauth).with(:shibboleth, fail_with_empty_uid: true)
-
- subject.execute([shibboleth_config])
- end
-
it 'configures remote_sign_out_handler proc for authentiq' do
authentiq_config = { 'name' => 'authentiq', 'args' => {} }

View file

@ -13,6 +13,8 @@ Gemfile/0190-relax-rdoc.patch
Gemfile/0200-add-gitlab-dangerfiles.patch
Gemfile/0210-comment-out-openssl.patch
Gemfile/0220-relax-oj-in-ipynbdiff.patch
Gemfile/0230-relax-omniauth-auth0.patch
Gemfile/0240-remove-omniauth-shibboleth.patch
nodejs/0010-set-webpack-root.patch
nodejs/0020-remove-dev-dependencies.patch
nodejs/0030-use-yarnpkg.patch

View file

@ -7028,7 +7028,7 @@ references and their corresponding code points.</p>
<copy-code></copy-code>
</div>
<div class="gl-relative markdown-code-block js-markdown-code">
<pre data-sourcepos="7591:1-7595:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">&lt;p&gt;  &amp;amp; © Æ Ď</span>
<pre data-sourcepos="7591:1-7595:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">&lt;p&gt; &amp;amp; © Æ Ď</span>
<span id="LC2" class="line" lang="plaintext">¾ </span>
<span id="LC3" class="line" lang="plaintext">∲ ≧̸&lt;/p&gt;</span></code></pre>
<copy-code></copy-code>
@ -7344,11 +7344,11 @@ stripped in this way:</p>
<div>
<div><a href="#example-343">Example 343</a></div>
<div class="gl-relative markdown-code-block js-markdown-code">
<pre data-sourcepos="7960:1-7962:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="example" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">` b `</span></code></pre>
<pre data-sourcepos="7960:1-7962:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="example" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">` b `</span></code></pre>
<copy-code></copy-code>
</div>
<div class="gl-relative markdown-code-block js-markdown-code">
<pre data-sourcepos="7964:1-7966:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">&lt;p&gt;&lt;code&gt; b &lt;/code&gt;&lt;/p&gt;</span></code></pre>
<pre data-sourcepos="7964:1-7966:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">&lt;p&gt;&lt;code&gt; b &lt;/code&gt;&lt;/p&gt;</span></code></pre>
<copy-code></copy-code>
</div>
</div>
@ -7356,12 +7356,12 @@ stripped in this way:</p>
<div>
<div><a href="#example-344">Example 344</a></div>
<div class="gl-relative markdown-code-block js-markdown-code">
<pre data-sourcepos="7974:1-7977:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="example" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">` `</span>
<pre data-sourcepos="7974:1-7977:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="example" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">` `</span>
<span id="LC2" class="line" lang="plaintext">` `</span></code></pre>
<copy-code></copy-code>
</div>
<div class="gl-relative markdown-code-block js-markdown-code">
<pre data-sourcepos="7979:1-7982:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">&lt;p&gt;&lt;code&gt; &lt;/code&gt;</span>
<pre data-sourcepos="7979:1-7982:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">&lt;p&gt;&lt;code&gt; &lt;/code&gt;</span>
<span id="LC2" class="line" lang="plaintext">&lt;code&gt; &lt;/code&gt;&lt;/p&gt;</span></code></pre>
<copy-code></copy-code>
</div>
@ -7832,11 +7832,11 @@ not part of a [left-flanking delimiter run]:</p>
<div>
<div><a href="#example-363">Example 363</a></div>
<div class="gl-relative markdown-code-block js-markdown-code">
<pre data-sourcepos="8485:1-8487:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="example" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">* a *</span></code></pre>
<pre data-sourcepos="8485:1-8487:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="example" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">* a *</span></code></pre>
<copy-code></copy-code>
</div>
<div class="gl-relative markdown-code-block js-markdown-code">
<pre data-sourcepos="8489:1-8491:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">&lt;p&gt;* a *&lt;/p&gt;</span></code></pre>
<pre data-sourcepos="8489:1-8491:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">&lt;p&gt;* a *&lt;/p&gt;</span></code></pre>
<copy-code></copy-code>
</div>
</div>
@ -9790,7 +9790,7 @@ Other [Unicode whitespace] like non-breaking space doesn't work.</p>
<div>
<div><a href="#example-515">Example 515</a></div>
<div class="gl-relative markdown-code-block js-markdown-code">
<pre data-sourcepos="10823:1-10825:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="example" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">[link](/url "title")</span></code></pre>
<pre data-sourcepos="10823:1-10825:32" lang="plaintext" class="code highlight js-syntax-highlight language-plaintext" data-canonical-lang="example" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">[link](/url "title")</span></code></pre>
<copy-code></copy-code>
</div>
<div class="gl-relative markdown-code-block js-markdown-code">

View file

@ -203,6 +203,10 @@ module API
render_api_error!("Target project id:#{params[:from_project_id]} is not a fork of project id:#{params[:id]}", 400)
end
unless can?(current_user, :read_code, target_project)
forbidden!("You don't have access to this fork's parent project")
end
cache_key = compare_cache_key(current_user, user_project, target_project, declared_params)
cache_action(cache_key, expires_in: 1.minute) do

View file

@ -1,4 +1,5 @@
# frozen_string_literal: true
require 'uri'
module Banzai
module Filter
@ -15,7 +16,8 @@ module Banzai
doc.document.create_element(
'div',
class: 'js-render-observability',
'data-frame-url': url
'data-frame-url': url,
'data-observability-url': Gitlab::Observability.observability_url
)
end
@ -28,8 +30,15 @@ module Banzai
# obtained from the target link
def element_to_embed(node)
url = node['href']
uri = URI.parse(url)
observability_uri = URI.parse(Gitlab::Observability.observability_url)
create_element(url)
if uri.scheme == observability_uri.scheme &&
uri.port == observability_uri.port &&
uri.host.casecmp?(observability_uri.host) &&
uri.path.downcase.exclude?("auth/start")
create_element(url)
end
end
private

View file

@ -5,7 +5,8 @@
# Can be extended for different types of repository object, e.g. Project or Snippet
module ExtractsRef
InvalidPathError = Class.new(StandardError)
BRANCH_REF_TYPE = 'heads'
TAG_REF_TYPE = 'tags'
# Given a string containing both a Git tree-ish, such as a branch or tag, and
# a filesystem path joined by forward slashes, attempts to separate the two.
#
@ -91,7 +92,7 @@ module ExtractsRef
def ref_type
return unless params[:ref_type].present?
params[:ref_type] == 'tags' ? 'tags' : 'heads'
params[:ref_type] == TAG_REF_TYPE ? TAG_REF_TYPE : BRANCH_REF_TYPE
end
private
@ -154,4 +155,13 @@ module ExtractsRef
def repository_container
raise NotImplementedError
end
def ambiguous_ref?(project, ref)
return true if project.repository.ambiguous_ref?(ref)
return false unless ref&.starts_with?('refs/')
unprefixed_ref = ref.sub(%r{^refs/(heads|tags)/}, '')
project.repository.commit(unprefixed_ref).present?
end
end

View file

@ -0,0 +1,17 @@
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
# Nullifies last_error value from project_mirror_data table as they
# potentially included sensitive data.
# https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/3041
class NullifyLastErrorFromProjectMirrorData < BatchedMigrationJob
feature_category :source_code_management
operation_name :update_all
def perform
each_sub_batch { |rel| rel.update_all(last_error: nil) }
end
end
end
end

View file

@ -9,6 +9,12 @@ module Gitlab
# https://idiosyncratic-ruby.com/41-proper-unicoding.html
BIDI_REGEXP = /\p{Bidi Control}/.freeze
# Regular expression for identifying space characters
#
# In web browsers space characters can be confused with simple
# spaces which may be misleading
SPACE_REGEXP = /\p{Space_Separator}/.freeze
class << self
# Warning message used to highlight bidi characters in the GUI
def bidi_warning

View file

@ -2,15 +2,37 @@
module Gitlab
class UrlSanitizer
include Gitlab::Utils::StrongMemoize
ALLOWED_SCHEMES = %w[http https ssh git].freeze
ALLOWED_WEB_SCHEMES = %w[http https].freeze
SCHEMIFIED_SCHEME = 'glschemelessuri'
SCHEMIFY_PLACEHOLDER = "#{SCHEMIFIED_SCHEME}://"
# URI::DEFAULT_PARSER.make_regexp will only match URLs with schemes or
# relative URLs. This section will match schemeless URIs with userinfo
# e.g. user:pass@gitlab.com but will not match scp-style URIs e.g.
# user@server:path/to/file)
#
# The userinfo part is very loose compared to URI's implementation so we
# also match non-escaped userinfo e.g foo:b?r@gitlab.com which should be
# encoded as foo:b%3Fr@gitlab.com
URI_REGEXP = %r{
(?:
#{URI::DEFAULT_PARSER.make_regexp(ALLOWED_SCHEMES)}
|
(?:(?:(?!@)[%#{URI::REGEXP::PATTERN::UNRESERVED}#{URI::REGEXP::PATTERN::RESERVED}])+(?:@))
(?# negative lookahead ensures this isn't an SCP-style URL: [host]:[rel_path|abs_path] server:path/to/file)
(?!#{URI::REGEXP::PATTERN::HOST}:(?:#{URI::REGEXP::PATTERN::REL_PATH}|#{URI::REGEXP::PATTERN::ABS_PATH}))
#{URI::REGEXP::PATTERN::HOSTPORT}
)
}x.freeze
def self.sanitize(content)
regexp = URI::DEFAULT_PARSER.make_regexp(ALLOWED_SCHEMES)
content.gsub(regexp) { |url| new(url).masked_url }
rescue Addressable::URI::InvalidURIError
content.gsub(regexp, '')
content.gsub(URI_REGEXP) do |url|
new(url).masked_url
rescue Addressable::URI::InvalidURIError
''
end
end
def self.valid?(url, allowed_schemes: ALLOWED_SCHEMES)
@ -37,17 +59,6 @@ module Gitlab
@url = parse_url(url)
end
def sanitized_url
@sanitized_url ||= safe_url.to_s
end
def masked_url
url = @url.dup
url.password = "*****" if url.password.present?
url.user = "*****" if url.user.present?
url.to_s
end
def credentials
@credentials ||= { user: @url.user.presence, password: @url.password.presence }
end
@ -56,15 +67,37 @@ module Gitlab
credentials[:user]
end
def full_url
@full_url ||= generate_full_url.to_s
def sanitized_url
safe_url = @url.dup
safe_url.password = nil
safe_url.user = nil
reverse_schemify(safe_url.to_s)
end
strong_memoize_attr :sanitized_url
def masked_url
url = @url.dup
url.password = "*****" if url.password.present?
url.user = "*****" if url.user.present?
reverse_schemify(url.to_s)
end
strong_memoize_attr :masked_url
def full_url
return reverse_schemify(@url.to_s) unless valid_credentials?
url = @url.dup
url.password = encode_percent(credentials[:password]) if credentials[:password].present?
url.user = encode_percent(credentials[:user]) if credentials[:user].present?
reverse_schemify(url.to_s)
end
strong_memoize_attr :full_url
private
def parse_url(url)
url = url.to_s.strip
match = url.match(%r{\A(?:git|ssh|http(?:s?))\://(?:(.+)(?:@))?(.+)})
url = schemify(url.to_s.strip)
match = url.match(%r{\A(?:(?:#{SCHEMIFIED_SCHEME}|git|ssh|http(?:s?)):)?//(?:(.+)(?:@))?(.+)}o)
raw_credentials = match[1] if match
if raw_credentials.present?
@ -83,24 +116,19 @@ module Gitlab
url
end
def generate_full_url
return @url unless valid_credentials?
@url.dup.tap do |generated|
generated.password = encode_percent(credentials[:password]) if credentials[:password].present?
generated.user = encode_percent(credentials[:user]) if credentials[:user].present?
end
def schemify(url)
# Prepend the placeholder scheme unless the URL has a scheme or is relative
url.prepend(SCHEMIFY_PLACEHOLDER) unless url.starts_with?(%r{(?:#{URI::REGEXP::PATTERN::SCHEME}:)?//}o)
url
end
def safe_url
safe_url = @url.dup
safe_url.password = nil
safe_url.user = nil
safe_url
def reverse_schemify(url)
url.slice!(SCHEMIFY_PLACEHOLDER) if url.starts_with?(SCHEMIFY_PLACEHOLDER)
url
end
def valid_credentials?
credentials && credentials.is_a?(Hash) && credentials.any?
credentials.is_a?(Hash) && credentials.values.any?
end
def encode_percent(string)

View file

@ -25,7 +25,10 @@ module Rouge
yield %(<span id="LC#{@line_number}" class="line" lang="#{@tag}">)
line.each do |token, value|
yield highlight_unicode_control_characters(span(token, value.chomp! || value))
value = value.chomp! || value
value = replace_space_characters(value)
yield highlight_unicode_control_characters(span(token, value))
end
yield ellipsis if @ellipsis_indexes.include?(@line_number - 1) && @ellipsis_svg.present?
@ -42,6 +45,10 @@ module Rouge
%(<span class="gl-px-2 gl-rounded-base gl-mx-2 gl-bg-gray-100 gl-cursor-help has-tooltip" title="Content has been trimmed">#{@ellipsis_svg}</span>)
end
def replace_space_characters(text)
text.gsub(Gitlab::Unicode::SPACE_REGEXP, ' ')
end
def highlight_unicode_control_characters(text)
text.gsub(Gitlab::Unicode::BIDI_REGEXP) do |char|
%(<span class="unicode-bidi has-tooltip" data-toggle="tooltip" title="#{Gitlab::Unicode.bidi_warning}">#{char}</span>)

View file

@ -42005,6 +42005,9 @@ msgstr ""
msgid "The default branch for this project has been changed. Please update your bookmarks."
msgstr ""
msgid "The default branch of this project clashes with another ref"
msgstr ""
msgid "The dependency list details information about the components used within your project."
msgstr ""

View file

@ -59,6 +59,7 @@ RSpec.describe Admin::HooksController do
enable_ssl_verification: false,
url_variables: [
{ key: 'token', value: 'some secret value' },
{ key: 'baz', value: 'qux' },
{ key: 'foo', value: nil }
]
}
@ -71,7 +72,7 @@ RSpec.describe Admin::HooksController do
expect(flash[:notice]).to include('was updated')
expect(hook).to have_attributes(hook_params.except(:url_variables))
expect(hook).to have_attributes(
url_variables: { 'token' => 'some secret value', 'baz' => 'woo' }
url_variables: { 'token' => 'some secret value', 'baz' => 'qux' }
)
end
end

View file

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe ConfirmEmailWarning do
RSpec.describe ConfirmEmailWarning, feature_category: :system_access do
before do
stub_feature_flags(soft_email_confirmation: true)
end
@ -82,6 +82,38 @@ RSpec.describe ConfirmEmailWarning do
it { is_expected.to set_confirm_warning_for(user.email) }
end
end
context 'when user is being impersonated' do
let(:impersonator) { create(:admin) }
before do
allow(controller).to receive(:session).and_return({ impersonator_id: impersonator.id })
get :index
end
it { is_expected.to set_confirm_warning_for(user.email) }
context 'when impersonated user email has html in their email' do
let(:user) { create(:user, confirmed_at: nil, unconfirmed_email: "malicious@test.com<form><input/title='<script>alert(document.domain)</script>'>") }
it { is_expected.to set_confirm_warning_for("malicious@test.com&lt;form&gt;&lt;input/title=&#39;&lt;script&gt;alert(document.domain)&lt;/script&gt;&#39;&gt;") }
end
end
context 'when user is not being impersonated' do
before do
get :index
end
it { is_expected.to set_confirm_warning_for(user.email) }
context 'when user email has html in their email' do
let(:user) { create(:user, confirmed_at: nil, unconfirmed_email: "malicious@test.com<form><input/title='<script>alert(document.domain)</script>'>") }
it { is_expected.to set_confirm_warning_for("malicious@test.com&lt;form&gt;&lt;input/title=&#39;&lt;script&gt;alert(document.domain)&lt;/script&gt;&#39;&gt;") }
end
end
end
end
end

View file

@ -2,15 +2,16 @@
require 'spec_helper'
RSpec.describe Projects::BlobController do
RSpec.describe Projects::BlobController, feature_category: :source_code_management do
include ProjectForksHelper
let(:project) { create(:project, :public, :repository, previous_default_branch: previous_default_branch) }
let(:previous_default_branch) { nil }
describe "GET show" do
def request
get(:show, params: { namespace_id: project.namespace, project_id: project, id: id })
let(:params) { { namespace_id: project.namespace, project_id: project, id: id } }
let(:request) do
get(:show, params: params)
end
render_views
@ -18,10 +19,34 @@ RSpec.describe Projects::BlobController do
context 'with file path' do
before do
expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
project.repository.add_tag(project.creator, 'ambiguous_ref', RepoHelpers.sample_commit.id)
project.repository.add_branch(project.creator, 'ambiguous_ref', RepoHelpers.another_sample_commit.id)
request
end
context 'when the ref is ambiguous' do
let(:ref) { 'ambiguous_ref' }
let(:path) { 'README.md' }
let(:id) { "#{ref}/#{path}" }
let(:params) { { namespace_id: project.namespace, project_id: project, id: id, ref_type: ref_type } }
context 'and explicitly requesting a branch' do
let(:ref_type) { 'heads' }
it 'redirects to blob#show with sha for the branch' do
expect(response).to redirect_to(project_blob_path(project, "#{RepoHelpers.another_sample_commit.id}/#{path}"))
end
end
context 'and explicitly requesting a tag' do
let(:ref_type) { 'tags' }
it 'responds with success' do
expect(response).to be_ok
end
end
end
context "valid branch, valid file" do
let(:id) { 'master/README.md' }

View file

@ -7,7 +7,7 @@ RSpec.describe Projects::ClustersController do
include GoogleApi::CloudPlatformHelpers
include KubernetesHelpers
let_it_be(:project) { create(:project) }
let_it_be_with_reload(:project) { create(:project) }
let(:user) { create(:user) }
@ -140,6 +140,27 @@ RSpec.describe Projects::ClustersController do
expect(response).to redirect_to(new_user_session_path)
end
end
context 'with a public project' do
before do
project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
project.project_feature.update!(metrics_dashboard_access_level: ProjectFeature::ENABLED)
end
context 'with guest user' do
let(:prometheus_body) { nil }
before do
project.add_guest(user)
end
it 'returns 404' do
get :prometheus_proxy, params: prometheus_proxy_params
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
end
end

View file

@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe Projects::Environments::PrometheusApiController do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be_with_reload(:project) { create(:project) }
let_it_be(:proxyable) { create(:environment, project: project) }
before do
@ -70,6 +70,27 @@ RSpec.describe Projects::Environments::PrometheusApiController do
expect(response).to redirect_to(new_user_session_path)
end
end
context 'with a public project' do
before do
project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
project.project_feature.update!(metrics_dashboard_access_level: ProjectFeature::ENABLED)
end
context 'with guest user' do
let(:prometheus_body) { nil }
before do
project.add_guest(user)
end
it 'returns 404' do
get :prometheus_proxy, params: prometheus_proxy_params
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
end
end
end

View file

@ -31,7 +31,7 @@ RSpec.describe Projects::RefsController, feature_category: :source_code_manageme
'tree' | nil | lazy { project_tree_path(project, id) }
'tree' | 'heads' | lazy { project_tree_path(project, id) }
'blob' | nil | lazy { project_blob_path(project, id) }
'blob' | 'heads' | lazy { project_blob_path(project, id) }
'blob' | 'heads' | lazy { project_blob_path(project, id, ref_type: 'heads') }
'graph' | nil | lazy { project_network_path(project, id) }
'graph' | 'heads' | lazy { project_network_path(project, id) }
'graphs' | nil | lazy { project_graph_path(project, id) }
@ -60,7 +60,7 @@ RSpec.describe Projects::RefsController, feature_category: :source_code_manageme
'tree' | nil | lazy { project_tree_path(project, id) }
'tree' | 'heads' | lazy { project_tree_path(project, id) }
'blob' | nil | lazy { project_blob_path(project, id) }
'blob' | 'heads' | lazy { project_blob_path(project, id) }
'blob' | 'heads' | lazy { project_blob_path(project, id, ref_type: 'heads') }
'graph' | nil | lazy { project_network_path(project, id) }
'graph' | 'heads' | lazy { project_network_path(project, id, ref_type: 'heads') }
'graphs' | nil | lazy { project_graph_path(project, id) }

View file

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Projects::TreeController do
RSpec.describe Projects::TreeController, feature_category: :source_code_management do
let(:project) { create(:project, :repository, previous_default_branch: previous_default_branch) }
let(:previous_default_branch) { nil }
let(:user) { create(:user) }
@ -15,18 +15,41 @@ RSpec.describe Projects::TreeController do
end
describe "GET show" do
let(:params) do
{
namespace_id: project.namespace.to_param, project_id: project, id: id
}
end
# Make sure any errors accessing the tree in our views bubble up to this spec
render_views
before do
expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
project.repository.add_tag(project.creator, 'ambiguous_ref', RepoHelpers.sample_commit.id)
project.repository.add_branch(project.creator, 'ambiguous_ref', RepoHelpers.another_sample_commit.id)
get :show, params: params
end
get(:show,
params: {
namespace_id: project.namespace.to_param,
project_id: project,
id: id
})
context 'when the ref is ambiguous' do
let(:id) { 'ambiguous_ref' }
let(:params) { { namespace_id: project.namespace, project_id: project, id: id, ref_type: ref_type } }
context 'and explicitly requesting a branch' do
let(:ref_type) { 'heads' }
it 'redirects to blob#show with sha for the branch' do
expect(response).to redirect_to(project_tree_path(project, RepoHelpers.another_sample_commit.id))
end
end
context 'and explicitly requesting a tag' do
let(:ref_type) { 'tags' }
it 'responds with success' do
expect(response).to be_ok
end
end
end
context "valid branch, no path" do

View file

@ -163,6 +163,69 @@ RSpec.describe ProjectsController do
expect(assigns(:notification_setting).level).to eq("watch")
end
end
context 'when there is a tag with the same name as the default branch' do
let_it_be(:tagged_project) { create(:project, :public, :custom_repo, files: ['somefile']) }
let(:tree_with_default_branch) do
branch = tagged_project.repository.find_branch(tagged_project.default_branch)
project_tree_path(tagged_project, branch.target)
end
before do
tagged_project.repository.create_file(
tagged_project.creator,
'file_for_tag',
'content for file',
message: "Automatically created file",
branch_name: 'branch-to-tag'
)
tagged_project.repository.add_tag(
tagged_project.creator,
tagged_project.default_branch, # tag name
'branch-to-tag' # target
)
end
it 'redirects to tree view for the default branch' do
get :show, params: { namespace_id: tagged_project.namespace, id: tagged_project }
expect(response).to redirect_to(tree_with_default_branch)
end
end
context 'when the default branch name can resolve to another ref' do
let!(:project_with_default_branch) do
create(:project, :public, :custom_repo, files: ['somefile']).tap do |p|
p.repository.create_branch("refs/heads/refs/heads/#{other_ref}", 'master')
p.change_head("refs/heads/#{other_ref}")
end.reload
end
let(:other_ref) { 'branch-name' }
context 'but there is no other ref' do
it 'responds with ok' do
get :show, params: { namespace_id: project_with_default_branch.namespace, id: project_with_default_branch }
expect(response).to be_ok
end
end
context 'and that other ref exists' do
let(:tree_with_default_branch) do
branch = project_with_default_branch.repository.find_branch(project_with_default_branch.default_branch)
project_tree_path(project_with_default_branch, branch.target)
end
before do
project_with_default_branch.repository.create_branch(other_ref, 'master')
end
it 'redirects to tree view for the default branch' do
get :show, params: { namespace_id: project_with_default_branch.namespace, id: project_with_default_branch }
expect(response).to redirect_to(tree_with_default_branch)
end
end
end
end
describe "when project repository is disabled" do

View file

@ -7,7 +7,7 @@ FactoryBot.define do
project
trait :url_variables do
url_variables { { 'abc' => 'supers3cret' } }
url_variables { { 'abc' => 'supers3cret', 'def' => 'foobar' } }
end
trait :token do

View file

@ -271,6 +271,36 @@ RSpec.describe 'Admin::Users::User', feature_category: :user_management do
icon = first('[data-testid="incognito-icon"]')
expect(icon).not_to be nil
end
context 'when viewing the confirm email warning', :js do
let_it_be(:another_user) { create(:user, :unconfirmed) }
let(:warning_alert) { page.find(:css, '[data-testid="alert-warning"]') }
let(:expected_styling) { { 'pointer-events' => 'none', 'cursor' => 'default' } }
context 'with an email that does not contain HTML' do
before do
subject
end
it 'displays the warning alert including the email' do
expect(warning_alert.text).to include("Please check your email (#{another_user.email}) to verify")
end
end
context 'with an email that contains HTML' do
let(:malicious_email) { "malicious@test.com<form><input/title='<script>alert(document.domain)</script>'>" }
let(:another_user) { create(:user, confirmed_at: nil, unconfirmed_email: malicious_email) }
before do
subject
end
it 'displays the impersonation alert, excludes email, and disables links' do
expect(warning_alert.text).to include("check your email (#{another_user.unconfirmed_email}) to verify")
end
end
end
end
context 'ending impersonation' do

View file

@ -6,6 +6,7 @@ RSpec.describe Environments::EnvironmentNamesFinder do
describe '#execute' do
let!(:group) { create(:group) }
let!(:public_project) { create(:project, :public, namespace: group) }
let_it_be_with_reload(:public_project_with_private_environments) { create(:project, :public) }
let!(:private_project) { create(:project, :private, namespace: group) }
let!(:user) { create(:user) }
@ -14,6 +15,11 @@ RSpec.describe Environments::EnvironmentNamesFinder do
create(:environment, name: 'gprd', project: public_project)
create(:environment, name: 'gprd', project: private_project)
create(:environment, name: 'gcny', project: private_project)
create(:environment, name: 'gprivprd', project: public_project_with_private_environments)
create(:environment, name: 'gprivstg', project: public_project_with_private_environments)
public_project_with_private_environments.update!(namespace: group)
public_project_with_private_environments.project_feature.update!(environments_access_level: Featurable::PRIVATE)
end
context 'using a group' do
@ -23,7 +29,7 @@ RSpec.describe Environments::EnvironmentNamesFinder do
names = described_class.new(group, user).execute
expect(names).to eq(%w[gcny gprd gstg])
expect(names).to eq(%w[gcny gprd gprivprd gprivstg gstg])
end
end
@ -33,7 +39,7 @@ RSpec.describe Environments::EnvironmentNamesFinder do
names = described_class.new(group, user).execute
expect(names).to eq(%w[gcny gprd gstg])
expect(names).to eq(%w[gcny gprd gprivprd gprivstg gstg])
end
end
@ -57,8 +63,18 @@ RSpec.describe Environments::EnvironmentNamesFinder do
end
end
context 'with a public project reporter which has private environments' do
it 'returns environment names for public projects' do
public_project_with_private_environments.add_reporter(user)
names = described_class.new(group, user).execute
expect(names).to eq(%w[gprd gprivprd gprivstg gstg])
end
end
context 'with a group guest' do
it 'returns environment names for all public projects' do
it 'returns environment names for public projects' do
group.add_guest(user)
names = described_class.new(group, user).execute
@ -68,7 +84,7 @@ RSpec.describe Environments::EnvironmentNamesFinder do
end
context 'with a non-member' do
it 'returns environment names for all public projects' do
it 'returns environment names for only public projects with public environments' do
names = described_class.new(group, user).execute
expect(names).to eq(%w[gprd gstg])
@ -76,7 +92,7 @@ RSpec.describe Environments::EnvironmentNamesFinder do
end
context 'without a user' do
it 'returns environment names for all public projects' do
it 'returns environment names for only public projects with public environments' do
names = described_class.new(group).execute
expect(names).to eq(%w[gprd gstg])

View file

@ -106,6 +106,26 @@ RSpec.describe NotesFinder do
end
end
context 'for notes on public issue in public project' do
let_it_be(:public_project) { create(:project, :public) }
let_it_be(:guest_member) { create(:user) }
let_it_be(:reporter_member) { create(:user) }
let_it_be(:guest_project_member) { create(:project_member, :guest, user: guest_member, project: public_project) }
let_it_be(:reporter_project_member) { create(:project_member, :reporter, user: reporter_member, project: public_project) }
let_it_be(:internal_note) { create(:note_on_issue, project: public_project, internal: true) }
let_it_be(:public_note) { create(:note_on_issue, project: public_project) }
it 'shows all notes when the current_user has reporter access' do
notes = described_class.new(reporter_member, project: public_project).execute
expect(notes).to contain_exactly internal_note, public_note
end
it 'shows only public notes when the current_user has guest access' do
notes = described_class.new(guest_member, project: public_project).execute
expect(notes).to contain_exactly public_note
end
end
context 'for target type' do
let(:project) { create(:project, :repository) }
let!(:note1) { create :note_on_issue, project: project }

View file

@ -1,294 +1,294 @@
User-Agent: Microsoft-MacOutlook/10.22.0.200209
Date: Mon, 17 Feb 2020 22:56:47 +0100
Subject: Re: htmltest | test issue (#1)
From: "Louzan Martinez, Diego (ext) (SI BP R&D ZG)"
<diego.louzan.ext@siemens.com>
To: Administrator / htmltest
<dlouzan.dummy+c034670b1623e617e15a3df64223d363@gmail.com>
Message-ID: <012E37D9-2A3F-4AC8-B79A-871F42914D86@siemens.com>
Thread-Topic: htmltest | test issue (#1)
References: <reply-c034670b1623e617e15a3df64223d363@169.254.169.254>
<issue_451@169.254.169.254>
<note_1797@169.254.169.254>
In-Reply-To: <note_1797@169.254.169.254>
Content-type: multipart/signed;
protocol="application/pkcs7-signature";
micalg=sha256;
boundary="B_3664825007_1904734766"
MIME-Version: 1.0
--B_3664825007_1904734766
Content-type: multipart/mixed;
boundary="B_3664825007_384940722"
--B_3664825007_384940722
Content-type: multipart/alternative;
boundary="B_3664825007_1519466360"
--B_3664825007_1519466360
Content-type: text/plain;
charset="UTF-8"
Content-transfer-encoding: quoted-printable
Me too, with an attachment
=20
From: Administrator <dlouzan.dummy@gmail.com>
Reply to: Administrator / htmltest <dlouzan.dummy+c034670b1623e617e15a3df64=
223d363@gmail.com>
Date: Monday, 17 February 2020 at 22:55
To: "Louzan Martinez, Diego (ext) (SOP IT STG XS)" <diego.louzan.ext@siemen=
s.com>
Subject: Re: htmltest | test issue (#1)
=20
Administrator commented:=20
I pity the foo !!!
=E2=80=94=20
Reply to this email directly or view it on GitLab.=20
You're receiving this email because of your account on 169.254.169.254. If =
you'd like to receive fewer emails, you can unsubscribe from this thread or =
adjust your notification settings.=20
--B_3664825007_1519466360
Content-type: text/html;
charset="UTF-8"
Content-transfer-encoding: quoted-printable
<html xmlns:o=3D"urn:schemas-microsoft-com:office:office" xmlns:w=3D"urn:schema=
s-microsoft-com:office:word" xmlns:m=3D"http://schemas.microsoft.com/office/20=
04/12/omml" xmlns=3D"http://www.w3.org/TR/REC-html40"><head><meta http-equiv=3DC=
ontent-Type content=3D"text/html; charset=3Dutf-8"><meta name=3DGenerator content=3D=
"Microsoft Word 15 (filtered medium)"><title>GitLab</title><style><!--
/* Font Definitions */
@font-face
{font-family:"Cambria Math";
panose-1:2 4 5 3 5 4 6 3 2 4;}
@font-face
{font-family:Calibri;
panose-1:2 15 5 2 2 2 4 3 2 4;}
/* Style Definitions */
p.MsoNormal, li.MsoNormal, div.MsoNormal
{margin:0cm;
margin-bottom:.0001pt;
font-size:11.0pt;
font-family:"Calibri",sans-serif;}
a:link, span.MsoHyperlink
{mso-style-priority:99;
color:blue;
text-decoration:underline;}
span.EmailStyle19
{mso-style-type:personal-reply;
font-family:"Calibri",sans-serif;
color:windowtext;}
.MsoChpDefault
{mso-style-type:export-only;
font-size:10.0pt;}
@page WordSection1
{size:612.0pt 792.0pt;
margin:72.0pt 72.0pt 72.0pt 72.0pt;}
div.WordSection1
{page:WordSection1;}
--></style></head><body lang=3Den-ES link=3Dblue vlink=3Dpurple><div class=3DWordSe=
ction1><p class=3DMsoNormal><span lang=3DEN-US style=3D'mso-fareast-language:EN-US=
'>Me too, with an attachment<o:p></o:p></span></p><p class=3DMsoNormal><span s=
tyle=3D'mso-fareast-language:EN-US'><o:p>&nbsp;</o:p></span></p><div style=3D'bo=
rder:none;border-top:solid #B5C4DF 1.0pt;padding:3.0pt 0cm 0cm 0cm'><p class=
=3DMsoNormal><b><span style=3D'font-size:12.0pt;color:black'>From: </span></b><s=
pan style=3D'font-size:12.0pt;color:black'>Administrator &lt;dlouzan.dummy@gma=
il.com&gt;<br><b>Reply to: </b>Administrator / htmltest &lt;dlouzan.dummy+c0=
34670b1623e617e15a3df64223d363@gmail.com&gt;<br><b>Date: </b>Monday, 17 Febr=
uary 2020 at 22:55<br><b>To: </b>&quot;Louzan Martinez, Diego (ext) (SOP IT =
STG XS)&quot; &lt;diego.louzan.ext@siemens.com&gt;<br><b>Subject: </b>Re: ht=
mltest | test issue (#1)<o:p></o:p></span></p></div><div><p class=3DMsoNormal>=
<o:p>&nbsp;</o:p></p></div><div><p><span style=3D'color:#777777'><a href=3D"http=
://localhost:3000/root">Administrator</a> commented: <o:p></o:p></span></p><=
div><p>I pity the foo !!!<o:p></o:p></p></div></div><div style=3D'margin-top:7=
.5pt'><p><span style=3D'font-size:12.0pt;color:#777777'>=E2=80=94 <br>Reply to this =
email directly or <a href=3D"http://localhost:3000/root/htmltest/issues/1#note=
_1797">view it on GitLab</a>. <br>You're receiving this email because of you=
r account on 169.254.169.254. If you'd like to receive fewer emails, you can=
<a href=3D"http://localhost:3000/sent_notifications/c034670b1623e617e15a3df64=
223d363/unsubscribe">unsubscribe</a> from this thread or adjust your notific=
ation settings. <o:p></o:p></span></p></div></div></body></html>
--B_3664825007_1519466360--
--B_3664825007_384940722
Content-type: image/png; name="gitlab_logo.png";
x-mac-creator="4F50494D";
x-mac-type="504E4766"
Content-disposition: attachment;
filename="gitlab_logo.png"
Content-transfer-encoding: base64
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
--B_3664825007_384940722--
--B_3664825007_1904734766
Content-type: application/pkcs7-signature; name="smime.p7s"
Content-transfer-encoding: base64
Content-disposition: attachment;
filename="smime.p7s"
MIIRpwYJKoZIhvcNAQcCoIIRmDCCEZQCAQExDzANBglghkgBZQMEAgEFADALBgkqhkiG9w0B
BwGggg8VMIIHojCCBYqgAwIBAgIEZ5a6PTANBgkqhkiG9w0BAQsFADCBtjELMAkGA1UEBhMC
REUxDzANBgNVBAgMBkJheWVybjERMA8GA1UEBwwITXVlbmNoZW4xEDAOBgNVBAoMB1NpZW1l
bnMxETAPBgNVBAUTCFpaWlpaWkE2MR0wGwYDVQQLDBRTaWVtZW5zIFRydXN0IENlbnRlcjE/
MD0GA1UEAww2U2llbWVucyBJc3N1aW5nIENBIE1lZGl1bSBTdHJlbmd0aCBBdXRoZW50aWNh
dGlvbiAyMDE2MB4XDTE5MTEyMTE0NDQ0N1oXDTIwMTEyMTE0NDQ0N1owdzERMA8GA1UEBRMI
WjAwM0gwOFQxDjAMBgNVBCoMBURpZWdvMRgwFgYDVQQEDA9Mb3V6YW4gTWFydGluZXoxGDAW
BgNVBAoMD1NpZW1lbnMtUGFydG5lcjEeMBwGA1UEAwwVTG91emFuIE1hcnRpbmV6IERpZWdv
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuInpNaC7NRYD+0pOpHDz2pk9xmPt
JGj860SF6Nmn6Eu9EMYKEDfneC6z5QcH+mPS2d0VWgqVVGbRXSPsxJtbi9TCWjQUZdHglEZK
z9zxoFDh2dvW5/+TOT5Jf78FXyqak0YtY6+oMjQ/i9RUqPL7sIlyXLrBYrILzQ9Afo+7bXZg
v3ypp6xtqAV2ctHzQWFi0onJzxLVYguiVb7fFF9rBEMvSZonuw5tvOwJIhbe5FDFOrDcfbyU
ofZ/wikIZ+A+CE5GryXuuQmGxJaC2QqOkRAWQDzLDx9nG+rKiEs5OvlfEZC7EV1PyjZ93coM
faCVdlAgcFZ5fvd37CjyjKl+1QIDAQABo4IC9DCCAvAwggEEBggrBgEFBQcBAQSB9zCB9DAy
BggrBgEFBQcwAoYmaHR0cDovL2FoLnNpZW1lbnMuY29tL3BraT9aWlpaWlpBNi5jcnQwQQYI
KwYBBQUHMAKGNWxkYXA6Ly9hbC5zaWVtZW5zLm5ldC9DTj1aWlpaWlpBNixMPVBLST9jQUNl
cnRpZmljYXRlMEkGCCsGAQUFBzAChj1sZGFwOi8vYWwuc2llbWVucy5jb20vQ049WlpaWlpa
QTYsbz1UcnVzdGNlbnRlcj9jQUNlcnRpZmljYXRlMDAGCCsGAQUFBzABhiRodHRwOi8vb2Nz
cC5wa2ktc2VydmljZXMuc2llbWVucy5jb20wHwYDVR0jBBgwFoAU+BVdRwxsd3tyxAIXkWii
tvdqCUQwDAYDVR0TAQH/BAIwADBFBgNVHSAEPjA8MDoGDSsGAQQBoWkHAgIEAQMwKTAnBggr
BgEFBQcCARYbaHR0cDovL3d3dy5zaWVtZW5zLmNvbS9wa2kvMIHKBgNVHR8EgcIwgb8wgbyg
gbmggbaGJmh0dHA6Ly9jaC5zaWVtZW5zLmNvbS9wa2k/WlpaWlpaQTYuY3JshkFsZGFwOi8v
Y2wuc2llbWVucy5uZXQvQ049WlpaWlpaQTYsTD1QS0k/Y2VydGlmaWNhdGVSZXZvY2F0aW9u
TGlzdIZJbGRhcDovL2NsLnNpZW1lbnMuY29tL0NOPVpaWlpaWkE2LG89VHJ1c3RjZW50ZXI/
Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlzdDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUH
AwQwDgYDVR0PAQH/BAQDAgeAMFUGA1UdEQROMEygLAYKKwYBBAGCNxQCA6AeDBxkaWVnby5s
b3V6YW4uZXh0QHNpZW1lbnMuY29tgRxkaWVnby5sb3V6YW4uZXh0QHNpZW1lbnMuY29tMB0G
A1UdDgQWBBQj8k8aqZey68w8ALYKGJSGMt5hZDANBgkqhkiG9w0BAQsFAAOCAgEAFDHqxpb1
R9cB4noC9vx09bkNbmXCpVfl3XCQUmAWTznC0nwEssTTjo0PWuIV4C3jnsp0MRUeHZ6lsyhZ
OzS1ETwYgvj6wzjb8RF3wgn7N/JOvFGaErMz5HZpKOfzGiNpW6/Rmd4hsRDjAwOVQOXUTqc/
0Bj3FMoLRCSWSnTp5HdyvrY2xOKHfTrTjzmcLdFaKE2F5n7+dBkwCKVfzut8CqfVq/I7ks4m
D1IHk93/P6l9U34R2FHPt6zRTNZcWmDirRSlMH4L18CnfiNPuDN/PtRYlt3Vng5EdYN0VCg2
NM/uees0U4ingCb0NFjg66uQ/tjfPQk55MN4Wpls4N6TkMoTCWLiqZzYTGdmVQexzroL6940
tmMr8LoN3TpPf0OdvdKEpyH7fzsx5QlmQyywIWec6X+Fx6+l0g91VJnPEtqACpfZIBZtviHl
gfX298w+SsvBK8C48Pqs8Ijh7tLrCxx7VMLVHZqwWWPK53ga+CDWmjoSQPxi+CPZF7kao6N5
4GrJWwSHlHh6WzTbLyLvTJZZ775Utp4W8s8xMUsQJ413iYzEaC8FcSeNjSk5UiDDiHrKmzpM
tbApD3pUXStblUMKYGTG1Mj9BcEBFkCdoGlw/ulszIrKFfOyRNDG3Ay+Dj/oMjoKsJphu3px
wyft82rTer7UW/I7o0h0DAG4lkMwggdrMIIFU6ADAgECAgR5nlqfMA0GCSqGSIb3DQEBCwUA
MIGeMQswCQYDVQQGEwJERTEPMA0GA1UECAwGQmF5ZXJuMREwDwYDVQQHDAhNdWVuY2hlbjEQ
MA4GA1UECgwHU2llbWVuczERMA8GA1UEBRMIWlpaWlpaQTMxHTAbBgNVBAsMFFNpZW1lbnMg
VHJ1c3QgQ2VudGVyMScwJQYDVQQDDB5TaWVtZW5zIElzc3VpbmcgQ0EgRUUgRW5jIDIwMTYw
HhcNMTkwOTI3MDgwMTM5WhcNMjAwOTI3MDgwMTM3WjB3MREwDwYDVQQFEwhaMDAzSDA4VDEO
MAwGA1UEKgwFRGllZ28xGDAWBgNVBAQMD0xvdXphbiBNYXJ0aW5lejEYMBYGA1UECgwPU2ll
bWVucy1QYXJ0bmVyMR4wHAYDVQQDDBVMb3V6YW4gTWFydGluZXogRGllZ28wggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCyby5qKzZIrGYWRqxnaAyMt/a/uc0uMk0F3MjwxvPM
vh5DllUpqx0l8ZDakDjPhlEXTeoL4DHNgmh+CDCs76CppM3cNG/1W1Ajo/L2iwMoXaxYuQ/F
q7ED+02KEkWX2DDVVG3fhrUGP20QAq77xPDptmVWZnUnuobZBNYkC49Xfl9HJvkJL8P0+Jqb
Eae7p4roiEr7wNkGriwrVXgA3oPNF/W+OuI76JTNTajS/6PAK/GeqIvLjfuBXpdBZTY031nE
Cztca8vI1jUjQzVhS+0dWpvpfhkVumbvOnid8DI9lapYsX8dpZFsa3ya+T3tjUdGSOOKi0kg
lWf/XYyyfhmDAgMBAAGjggLVMIIC0TAdBgNVHQ4EFgQUprhTCDwNLfPImpSfWdq+QvPTo9Mw
JwYDVR0RBCAwHoEcZGllZ28ubG91emFuLmV4dEBzaWVtZW5zLmNvbTAOBgNVHQ8BAf8EBAMC
BDAwLAYDVR0lBCUwIwYIKwYBBQUHAwQGCisGAQQBgjcKAwQGCysGAQQBgjcKAwQBMIHKBgNV
HR8EgcIwgb8wgbyggbmggbaGJmh0dHA6Ly9jaC5zaWVtZW5zLmNvbS9wa2k/WlpaWlpaQTMu
Y3JshkFsZGFwOi8vY2wuc2llbWVucy5uZXQvQ049WlpaWlpaQTMsTD1QS0k/Y2VydGlmaWNh
dGVSZXZvY2F0aW9uTGlzdIZJbGRhcDovL2NsLnNpZW1lbnMuY29tL0NOPVpaWlpaWkEzLG89
VHJ1c3RjZW50ZXI/Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlzdDBFBgNVHSAEPjA8MDoGDSsG
AQQBoWkHAgIEAQMwKTAnBggrBgEFBQcCARYbaHR0cDovL3d3dy5zaWVtZW5zLmNvbS9wa2kv
MAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUoassbqB68NPCTeof8R4hivwMre8wggEEBggr
BgEFBQcBAQSB9zCB9DAyBggrBgEFBQcwAoYmaHR0cDovL2FoLnNpZW1lbnMuY29tL3BraT9a
WlpaWlpBMy5jcnQwQQYIKwYBBQUHMAKGNWxkYXA6Ly9hbC5zaWVtZW5zLm5ldC9DTj1aWlpa
WlpBMyxMPVBLST9jQUNlcnRpZmljYXRlMEkGCCsGAQUFBzAChj1sZGFwOi8vYWwuc2llbWVu
cy5jb20vQ049WlpaWlpaQTMsbz1UcnVzdGNlbnRlcj9jQUNlcnRpZmljYXRlMDAGCCsGAQUF
BzABhiRodHRwOi8vb2NzcC5wa2ktc2VydmljZXMuc2llbWVucy5jb20wDQYJKoZIhvcNAQEL
BQADggIBAF98ZMNg28LgkwdjOdvOGbC1QitsWjZTyotmQESF0nClDLUhb0O5675vVixntbrf
eB8xy1+KRiadk40GnAIJ0YzmNl4Tav6hPYv9VBWe5olsWG7C4qB3Q/SwhvW/e+owxv1cBra8
R3oRudiN81eTZQHyNghRephVqQG/dpPYqydoANfIhEpHa79QlpaCAeYl4896AZOS8HYbkDFs
hLdv7sEHtl79YuSWI1wBjbJl70c0Sb4wLRgCPuHyQj2Uw/vQ5xJlEvBDZAIXXe1TP/nqiuY6
7nweJbbeqfFE6ZP3kCe+mEIWGSaO0iThZyLGer8fHs1XiEmhhPgvC7P7KodzpXU6+hX+ZzbD
DxEjFfetV5sh0aNSXG9xx4hZmS9bpImBGR8MvZ7cgxqItvLtY2xvfUbYW244d4RcWesaCDq3
ZEIo6uCIzOzJAwjUdLIac+lLV0rxiHmb7O3cQ19kjpWDB31hmfrus/TKJ55pBKVWBX5m/mFv
K8Ep5USpGrNS0EzOP7I1kQZv2VsvAhSxk/m5FMLpDy8T0O8YgbLypTXoeJFWCF6RduSjVsaZ
lkAtTQYud683pjyOMxJXaQUYGU1PmEYSOonMkVsT9aBcxYkXLp+Ln/+8G0OCYu7dRdwnj+Ut
7yR/ltxtgDcaFApCb0qBTKbgbqZk1fASmkOp+kbdYmoUMYICVjCCAlICAQEwgb8wgbYxCzAJ
BgNVBAYTAkRFMQ8wDQYDVQQIDAZCYXllcm4xETAPBgNVBAcMCE11ZW5jaGVuMRAwDgYDVQQK
DAdTaWVtZW5zMREwDwYDVQQFEwhaWlpaWlpBNjEdMBsGA1UECwwUU2llbWVucyBUcnVzdCBD
ZW50ZXIxPzA9BgNVBAMMNlNpZW1lbnMgSXNzdWluZyBDQSBNZWRpdW0gU3RyZW5ndGggQXV0
aGVudGljYXRpb24gMjAxNgIEZ5a6PTANBglghkgBZQMEAgEFAKBpMC8GCSqGSIb3DQEJBDEi
BCAOR58AbNfSrI+vtMs+dgAQtn3IVZ3RjYC5hz3j9k+6TTAYBgkqhkiG9w0BCQMxCwYJKoZI
hvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0yMDAyMTcyMTU2NDdaMA0GCSqGSIb3DQEBAQUABIIB
AHLSBcFHhNHPevbwqvA2ecuVb/aKnj45CFF6l8esP1H5DRm1ee5qMKuIS84NFuFC9RUENNhW
DBzsB+BVGz64o1f8QgIklYVrIJ4JZ0q1abNG7NbkVKWIpS3CQo//YWShUTYg+JpKx4YbahGR
sP5zbufbU4eagrrqBChjPTLy+njdjwCNu0XPykBTKOOf6BMjnS33AYjHJyh83JOY7rw3IDLx
8POQH4g5EMRpl9354s0rEkIezMt7pfUAsqY3QnQ8hvlE4KTikPQ+tvLMK1l/ffcLAP8BdBNI
YA3ikb3qCoGNSLKieYzNnBPhNOIJELUtEEaljAFZYMQzMKCbI4JdiDs=
--B_3664825007_1904734766--
User-Agent: Microsoft-MacOutlook/10.22.0.200209
Date: Mon, 17 Feb 2020 22:56:47 +0100
Subject: Re: htmltest | test issue (#1)
From: "Louzan Martinez, Diego (ext) (SI BP R&D ZG)"
<diego.louzan.ext@siemens.com>
To: Administrator / htmltest
<dlouzan.dummy+c034670b1623e617e15a3df64223d363@gmail.com>
Message-ID: <012E37D9-2A3F-4AC8-B79A-871F42914D86@siemens.com>
Thread-Topic: htmltest | test issue (#1)
References: <reply-c034670b1623e617e15a3df64223d363@169.254.169.254>
<issue_451@169.254.169.254>
<note_1797@169.254.169.254>
In-Reply-To: <note_1797@169.254.169.254>
Content-type: multipart/signed;
protocol="application/pkcs7-signature";
micalg=sha256;
boundary="B_3664825007_1904734766"
MIME-Version: 1.0
--B_3664825007_1904734766
Content-type: multipart/mixed;
boundary="B_3664825007_384940722"
--B_3664825007_384940722
Content-type: multipart/alternative;
boundary="B_3664825007_1519466360"
--B_3664825007_1519466360
Content-type: text/plain;
charset="UTF-8"
Content-transfer-encoding: quoted-printable
Me too, with an attachment
=20
From: Administrator <dlouzan.dummy@gmail.com>
Reply to: Administrator / htmltest <dlouzan.dummy+c034670b1623e617e15a3df64=
223d363@gmail.com>
Date: Monday, 17 February 2020 at 22:55
To: "Louzan Martinez, Diego (ext) (SOP IT STG XS)" <diego.louzan.ext@siemen=
s.com>
Subject: Re: htmltest | test issue (#1)
=20
Administrator commented:=20
I pity the foo !!!
=E2=80=94=20
Reply to this email directly or view it on GitLab.=20
You're receiving this email because of your account on 169.254.169.254. If =
you'd like to receive fewer emails, you can unsubscribe from this thread or =
adjust your notification settings.=20
--B_3664825007_1519466360
Content-type: text/html;
charset="UTF-8"
Content-transfer-encoding: quoted-printable
<html xmlns:o=3D"urn:schemas-microsoft-com:office:office" xmlns:w=3D"urn:schema=
s-microsoft-com:office:word" xmlns:m=3D"http://schemas.microsoft.com/office/20=
04/12/omml" xmlns=3D"http://www.w3.org/TR/REC-html40"><head><meta http-equiv=3DC=
ontent-Type content=3D"text/html; charset=3Dutf-8"><meta name=3DGenerator content=3D=
"Microsoft Word 15 (filtered medium)"><title>GitLab</title><style><!--
/* Font Definitions */
@font-face
{font-family:"Cambria Math";
panose-1:2 4 5 3 5 4 6 3 2 4;}
@font-face
{font-family:Calibri;
panose-1:2 15 5 2 2 2 4 3 2 4;}
/* Style Definitions */
p.MsoNormal, li.MsoNormal, div.MsoNormal
{margin:0cm;
margin-bottom:.0001pt;
font-size:11.0pt;
font-family:"Calibri",sans-serif;}
a:link, span.MsoHyperlink
{mso-style-priority:99;
color:blue;
text-decoration:underline;}
span.EmailStyle19
{mso-style-type:personal-reply;
font-family:"Calibri",sans-serif;
color:windowtext;}
.MsoChpDefault
{mso-style-type:export-only;
font-size:10.0pt;}
@page WordSection1
{size:612.0pt 792.0pt;
margin:72.0pt 72.0pt 72.0pt 72.0pt;}
div.WordSection1
{page:WordSection1;}
--></style></head><body lang=3Den-ES link=3Dblue vlink=3Dpurple><div class=3DWordSe=
ction1><p class=3DMsoNormal><span lang=3DEN-US style=3D'mso-fareast-language:EN-US=
'>Me too, with an attachment<o:p></o:p></span></p><p class=3DMsoNormal><span s=
tyle=3D'mso-fareast-language:EN-US'><o:p>&nbsp;</o:p></span></p><div style=3D'bo=
rder:none;border-top:solid #B5C4DF 1.0pt;padding:3.0pt 0cm 0cm 0cm'><p class=
=3DMsoNormal><b><span style=3D'font-size:12.0pt;color:black'>From: </span></b><s=
pan style=3D'font-size:12.0pt;color:black'>Administrator &lt;dlouzan.dummy@gma=
il.com&gt;<br><b>Reply to: </b>Administrator / htmltest &lt;dlouzan.dummy+c0=
34670b1623e617e15a3df64223d363@gmail.com&gt;<br><b>Date: </b>Monday, 17 Febr=
uary 2020 at 22:55<br><b>To: </b>&quot;Louzan Martinez, Diego (ext) (SOP IT =
STG XS)&quot; &lt;diego.louzan.ext@siemens.com&gt;<br><b>Subject: </b>Re: ht=
mltest | test issue (#1)<o:p></o:p></span></p></div><div><p class=3DMsoNormal>=
<o:p>&nbsp;</o:p></p></div><div><p><span style=3D'color:#777777'><a href=3D"http=
://localhost:3000/root">Administrator</a> commented: <o:p></o:p></span></p><=
div><p>I pity the foo !!!<o:p></o:p></p></div></div><div style=3D'margin-top:7=
.5pt'><p><span style=3D'font-size:12.0pt;color:#777777'>=E2=80=94 <br>Reply to this =
email directly or <a href=3D"http://localhost:3000/root/htmltest/issues/1#note=
_1797">view it on GitLab</a>. <br>You're receiving this email because of you=
r account on 169.254.169.254. If you'd like to receive fewer emails, you can=
<a href=3D"http://localhost:3000/sent_notifications/c034670b1623e617e15a3df64=
223d363/unsubscribe">unsubscribe</a> from this thread or adjust your notific=
ation settings. <o:p></o:p></span></p></div></div></body></html>
--B_3664825007_1519466360--
--B_3664825007_384940722
Content-type: image/png; name="gitlab_logo.png";
x-mac-creator="4F50494D";
x-mac-type="504E4766"
Content-disposition: attachment;
filename="gitlab_logo.png"
Content-transfer-encoding: base64
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
--B_3664825007_384940722--
--B_3664825007_1904734766
Content-type: application/pkcs7-signature; name="smime.p7s"
Content-transfer-encoding: base64
Content-disposition: attachment;
filename="smime.p7s"
MIIRpwYJKoZIhvcNAQcCoIIRmDCCEZQCAQExDzANBglghkgBZQMEAgEFADALBgkqhkiG9w0B
BwGggg8VMIIHojCCBYqgAwIBAgIEZ5a6PTANBgkqhkiG9w0BAQsFADCBtjELMAkGA1UEBhMC
REUxDzANBgNVBAgMBkJheWVybjERMA8GA1UEBwwITXVlbmNoZW4xEDAOBgNVBAoMB1NpZW1l
bnMxETAPBgNVBAUTCFpaWlpaWkE2MR0wGwYDVQQLDBRTaWVtZW5zIFRydXN0IENlbnRlcjE/
MD0GA1UEAww2U2llbWVucyBJc3N1aW5nIENBIE1lZGl1bSBTdHJlbmd0aCBBdXRoZW50aWNh
dGlvbiAyMDE2MB4XDTE5MTEyMTE0NDQ0N1oXDTIwMTEyMTE0NDQ0N1owdzERMA8GA1UEBRMI
WjAwM0gwOFQxDjAMBgNVBCoMBURpZWdvMRgwFgYDVQQEDA9Mb3V6YW4gTWFydGluZXoxGDAW
BgNVBAoMD1NpZW1lbnMtUGFydG5lcjEeMBwGA1UEAwwVTG91emFuIE1hcnRpbmV6IERpZWdv
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuInpNaC7NRYD+0pOpHDz2pk9xmPt
JGj860SF6Nmn6Eu9EMYKEDfneC6z5QcH+mPS2d0VWgqVVGbRXSPsxJtbi9TCWjQUZdHglEZK
z9zxoFDh2dvW5/+TOT5Jf78FXyqak0YtY6+oMjQ/i9RUqPL7sIlyXLrBYrILzQ9Afo+7bXZg
v3ypp6xtqAV2ctHzQWFi0onJzxLVYguiVb7fFF9rBEMvSZonuw5tvOwJIhbe5FDFOrDcfbyU
ofZ/wikIZ+A+CE5GryXuuQmGxJaC2QqOkRAWQDzLDx9nG+rKiEs5OvlfEZC7EV1PyjZ93coM
faCVdlAgcFZ5fvd37CjyjKl+1QIDAQABo4IC9DCCAvAwggEEBggrBgEFBQcBAQSB9zCB9DAy
BggrBgEFBQcwAoYmaHR0cDovL2FoLnNpZW1lbnMuY29tL3BraT9aWlpaWlpBNi5jcnQwQQYI
KwYBBQUHMAKGNWxkYXA6Ly9hbC5zaWVtZW5zLm5ldC9DTj1aWlpaWlpBNixMPVBLST9jQUNl
cnRpZmljYXRlMEkGCCsGAQUFBzAChj1sZGFwOi8vYWwuc2llbWVucy5jb20vQ049WlpaWlpa
QTYsbz1UcnVzdGNlbnRlcj9jQUNlcnRpZmljYXRlMDAGCCsGAQUFBzABhiRodHRwOi8vb2Nz
cC5wa2ktc2VydmljZXMuc2llbWVucy5jb20wHwYDVR0jBBgwFoAU+BVdRwxsd3tyxAIXkWii
tvdqCUQwDAYDVR0TAQH/BAIwADBFBgNVHSAEPjA8MDoGDSsGAQQBoWkHAgIEAQMwKTAnBggr
BgEFBQcCARYbaHR0cDovL3d3dy5zaWVtZW5zLmNvbS9wa2kvMIHKBgNVHR8EgcIwgb8wgbyg
gbmggbaGJmh0dHA6Ly9jaC5zaWVtZW5zLmNvbS9wa2k/WlpaWlpaQTYuY3JshkFsZGFwOi8v
Y2wuc2llbWVucy5uZXQvQ049WlpaWlpaQTYsTD1QS0k/Y2VydGlmaWNhdGVSZXZvY2F0aW9u
TGlzdIZJbGRhcDovL2NsLnNpZW1lbnMuY29tL0NOPVpaWlpaWkE2LG89VHJ1c3RjZW50ZXI/
Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlzdDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUH
AwQwDgYDVR0PAQH/BAQDAgeAMFUGA1UdEQROMEygLAYKKwYBBAGCNxQCA6AeDBxkaWVnby5s
b3V6YW4uZXh0QHNpZW1lbnMuY29tgRxkaWVnby5sb3V6YW4uZXh0QHNpZW1lbnMuY29tMB0G
A1UdDgQWBBQj8k8aqZey68w8ALYKGJSGMt5hZDANBgkqhkiG9w0BAQsFAAOCAgEAFDHqxpb1
R9cB4noC9vx09bkNbmXCpVfl3XCQUmAWTznC0nwEssTTjo0PWuIV4C3jnsp0MRUeHZ6lsyhZ
OzS1ETwYgvj6wzjb8RF3wgn7N/JOvFGaErMz5HZpKOfzGiNpW6/Rmd4hsRDjAwOVQOXUTqc/
0Bj3FMoLRCSWSnTp5HdyvrY2xOKHfTrTjzmcLdFaKE2F5n7+dBkwCKVfzut8CqfVq/I7ks4m
D1IHk93/P6l9U34R2FHPt6zRTNZcWmDirRSlMH4L18CnfiNPuDN/PtRYlt3Vng5EdYN0VCg2
NM/uees0U4ingCb0NFjg66uQ/tjfPQk55MN4Wpls4N6TkMoTCWLiqZzYTGdmVQexzroL6940
tmMr8LoN3TpPf0OdvdKEpyH7fzsx5QlmQyywIWec6X+Fx6+l0g91VJnPEtqACpfZIBZtviHl
gfX298w+SsvBK8C48Pqs8Ijh7tLrCxx7VMLVHZqwWWPK53ga+CDWmjoSQPxi+CPZF7kao6N5
4GrJWwSHlHh6WzTbLyLvTJZZ775Utp4W8s8xMUsQJ413iYzEaC8FcSeNjSk5UiDDiHrKmzpM
tbApD3pUXStblUMKYGTG1Mj9BcEBFkCdoGlw/ulszIrKFfOyRNDG3Ay+Dj/oMjoKsJphu3px
wyft82rTer7UW/I7o0h0DAG4lkMwggdrMIIFU6ADAgECAgR5nlqfMA0GCSqGSIb3DQEBCwUA
MIGeMQswCQYDVQQGEwJERTEPMA0GA1UECAwGQmF5ZXJuMREwDwYDVQQHDAhNdWVuY2hlbjEQ
MA4GA1UECgwHU2llbWVuczERMA8GA1UEBRMIWlpaWlpaQTMxHTAbBgNVBAsMFFNpZW1lbnMg
VHJ1c3QgQ2VudGVyMScwJQYDVQQDDB5TaWVtZW5zIElzc3VpbmcgQ0EgRUUgRW5jIDIwMTYw
HhcNMTkwOTI3MDgwMTM5WhcNMjAwOTI3MDgwMTM3WjB3MREwDwYDVQQFEwhaMDAzSDA4VDEO
MAwGA1UEKgwFRGllZ28xGDAWBgNVBAQMD0xvdXphbiBNYXJ0aW5lejEYMBYGA1UECgwPU2ll
bWVucy1QYXJ0bmVyMR4wHAYDVQQDDBVMb3V6YW4gTWFydGluZXogRGllZ28wggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCyby5qKzZIrGYWRqxnaAyMt/a/uc0uMk0F3MjwxvPM
vh5DllUpqx0l8ZDakDjPhlEXTeoL4DHNgmh+CDCs76CppM3cNG/1W1Ajo/L2iwMoXaxYuQ/F
q7ED+02KEkWX2DDVVG3fhrUGP20QAq77xPDptmVWZnUnuobZBNYkC49Xfl9HJvkJL8P0+Jqb
Eae7p4roiEr7wNkGriwrVXgA3oPNF/W+OuI76JTNTajS/6PAK/GeqIvLjfuBXpdBZTY031nE
Cztca8vI1jUjQzVhS+0dWpvpfhkVumbvOnid8DI9lapYsX8dpZFsa3ya+T3tjUdGSOOKi0kg
lWf/XYyyfhmDAgMBAAGjggLVMIIC0TAdBgNVHQ4EFgQUprhTCDwNLfPImpSfWdq+QvPTo9Mw
JwYDVR0RBCAwHoEcZGllZ28ubG91emFuLmV4dEBzaWVtZW5zLmNvbTAOBgNVHQ8BAf8EBAMC
BDAwLAYDVR0lBCUwIwYIKwYBBQUHAwQGCisGAQQBgjcKAwQGCysGAQQBgjcKAwQBMIHKBgNV
HR8EgcIwgb8wgbyggbmggbaGJmh0dHA6Ly9jaC5zaWVtZW5zLmNvbS9wa2k/WlpaWlpaQTMu
Y3JshkFsZGFwOi8vY2wuc2llbWVucy5uZXQvQ049WlpaWlpaQTMsTD1QS0k/Y2VydGlmaWNh
dGVSZXZvY2F0aW9uTGlzdIZJbGRhcDovL2NsLnNpZW1lbnMuY29tL0NOPVpaWlpaWkEzLG89
VHJ1c3RjZW50ZXI/Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlzdDBFBgNVHSAEPjA8MDoGDSsG
AQQBoWkHAgIEAQMwKTAnBggrBgEFBQcCARYbaHR0cDovL3d3dy5zaWVtZW5zLmNvbS9wa2kv
MAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUoassbqB68NPCTeof8R4hivwMre8wggEEBggr
BgEFBQcBAQSB9zCB9DAyBggrBgEFBQcwAoYmaHR0cDovL2FoLnNpZW1lbnMuY29tL3BraT9a
WlpaWlpBMy5jcnQwQQYIKwYBBQUHMAKGNWxkYXA6Ly9hbC5zaWVtZW5zLm5ldC9DTj1aWlpa
WlpBMyxMPVBLST9jQUNlcnRpZmljYXRlMEkGCCsGAQUFBzAChj1sZGFwOi8vYWwuc2llbWVu
cy5jb20vQ049WlpaWlpaQTMsbz1UcnVzdGNlbnRlcj9jQUNlcnRpZmljYXRlMDAGCCsGAQUF
BzABhiRodHRwOi8vb2NzcC5wa2ktc2VydmljZXMuc2llbWVucy5jb20wDQYJKoZIhvcNAQEL
BQADggIBAF98ZMNg28LgkwdjOdvOGbC1QitsWjZTyotmQESF0nClDLUhb0O5675vVixntbrf
eB8xy1+KRiadk40GnAIJ0YzmNl4Tav6hPYv9VBWe5olsWG7C4qB3Q/SwhvW/e+owxv1cBra8
R3oRudiN81eTZQHyNghRephVqQG/dpPYqydoANfIhEpHa79QlpaCAeYl4896AZOS8HYbkDFs
hLdv7sEHtl79YuSWI1wBjbJl70c0Sb4wLRgCPuHyQj2Uw/vQ5xJlEvBDZAIXXe1TP/nqiuY6
7nweJbbeqfFE6ZP3kCe+mEIWGSaO0iThZyLGer8fHs1XiEmhhPgvC7P7KodzpXU6+hX+ZzbD
DxEjFfetV5sh0aNSXG9xx4hZmS9bpImBGR8MvZ7cgxqItvLtY2xvfUbYW244d4RcWesaCDq3
ZEIo6uCIzOzJAwjUdLIac+lLV0rxiHmb7O3cQ19kjpWDB31hmfrus/TKJ55pBKVWBX5m/mFv
K8Ep5USpGrNS0EzOP7I1kQZv2VsvAhSxk/m5FMLpDy8T0O8YgbLypTXoeJFWCF6RduSjVsaZ
lkAtTQYud683pjyOMxJXaQUYGU1PmEYSOonMkVsT9aBcxYkXLp+Ln/+8G0OCYu7dRdwnj+Ut
7yR/ltxtgDcaFApCb0qBTKbgbqZk1fASmkOp+kbdYmoUMYICVjCCAlICAQEwgb8wgbYxCzAJ
BgNVBAYTAkRFMQ8wDQYDVQQIDAZCYXllcm4xETAPBgNVBAcMCE11ZW5jaGVuMRAwDgYDVQQK
DAdTaWVtZW5zMREwDwYDVQQFEwhaWlpaWlpBNjEdMBsGA1UECwwUU2llbWVucyBUcnVzdCBD
ZW50ZXIxPzA9BgNVBAMMNlNpZW1lbnMgSXNzdWluZyBDQSBNZWRpdW0gU3RyZW5ndGggQXV0
aGVudGljYXRpb24gMjAxNgIEZ5a6PTANBglghkgBZQMEAgEFAKBpMC8GCSqGSIb3DQEJBDEi
BCAOR58AbNfSrI+vtMs+dgAQtn3IVZ3RjYC5hz3j9k+6TTAYBgkqhkiG9w0BCQMxCwYJKoZI
hvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0yMDAyMTcyMTU2NDdaMA0GCSqGSIb3DQEBAQUABIIB
AHLSBcFHhNHPevbwqvA2ecuVb/aKnj45CFF6l8esP1H5DRm1ee5qMKuIS84NFuFC9RUENNhW
DBzsB+BVGz64o1f8QgIklYVrIJ4JZ0q1abNG7NbkVKWIpS3CQo//YWShUTYg+JpKx4YbahGR
sP5zbufbU4eagrrqBChjPTLy+njdjwCNu0XPykBTKOOf6BMjnS33AYjHJyh83JOY7rw3IDLx
8POQH4g5EMRpl9354s0rEkIezMt7pfUAsqY3QnQ8hvlE4KTikPQ+tvLMK1l/ffcLAP8BdBNI
YA3ikb3qCoGNSLKieYzNnBPhNOIJELUtEEaljAFZYMQzMKCbI4JdiDs=
--B_3664825007_1904734766--

View file

@ -16,7 +16,7 @@ describe('Observability iframe renderer', () => {
});
it('renders an observability iframe', () => {
document.body.innerHTML = `<div class="js-render-observability" data-frame-url="https://observe.gitlab.com/"></div>`;
document.body.innerHTML = `<div class="js-render-observability" data-frame-url="https://observe.gitlab.com/" data-observability-url="https://observe.gitlab.com/" ></div>`;
expect(findObservabilityIframes()).toHaveLength(0);
@ -26,7 +26,7 @@ describe('Observability iframe renderer', () => {
});
it('renders iframe with dark param when GL has dark theme', () => {
document.body.innerHTML = `<div class="js-render-observability" data-frame-url="https://observe.gitlab.com/"></div>`;
document.body.innerHTML = `<div class="js-render-observability" data-frame-url="https://observe.gitlab.com/" data-observability-url="https://observe.gitlab.com/"></div>`;
jest.spyOn(ColorUtils, 'darkModeEnabled').mockImplementation(() => true);
expect(findObservabilityIframes('dark')).toHaveLength(0);
@ -35,4 +35,12 @@ describe('Observability iframe renderer', () => {
expect(findObservabilityIframes('dark')).toHaveLength(1);
});
it('does not render if url is different from observability url', () => {
document.body.innerHTML = `<div class="js-render-observability" data-frame-url="https://example.com/" data-observability-url="https://observe.gitlab.com/"></div>`;
renderEmbeddedObservability();
expect(findObservabilityIframes()).toHaveLength(0);
});
});

View file

@ -1,5 +1,6 @@
import { generateRefDestinationPath } from '~/repository/utils/ref_switcher_utils';
import setWindowLocation from 'helpers/set_window_location_helper';
import { TEST_HOST } from 'spec/test_constants';
import { refWithSpecialCharMock, encodedRefWithSpecialCharMock } from '../mock_data';
const projectRootPath = 'root/Project1';
@ -16,14 +17,38 @@ describe('generateRefDestinationPath', () => {
${`${projectRootPath}/-/blob/${currentRef}/dir1/test.js`} | ${`${projectRootPath}/-/blob/${selectedRef}/dir1/test.js`}
${`${projectRootPath}/-/blob/${currentRef}/dir1/dir2/test.js`} | ${`${projectRootPath}/-/blob/${selectedRef}/dir1/dir2/test.js`}
${`${projectRootPath}/-/blob/${currentRef}/dir1/dir2/test.js#L123`} | ${`${projectRootPath}/-/blob/${selectedRef}/dir1/dir2/test.js#L123`}
`('generates the correct destination path for $currentPath', ({ currentPath, result }) => {
`('generates the correct destination path for $currentPath', ({ currentPath, result }) => {
setWindowLocation(currentPath);
expect(generateRefDestinationPath(projectRootPath, selectedRef)).toBe(result);
expect(generateRefDestinationPath(projectRootPath, currentRef, selectedRef)).toBe(
`${TEST_HOST}/${result}`,
);
});
describe('when using symbolic ref names', () => {
it.each`
currentPath | nextRef | result
${`${projectRootPath}/-/blob/${currentRef}/dir1/dir2/test.js#L123`} | ${'someHash'} | ${`${projectRootPath}/-/blob/someHash/dir1/dir2/test.js#L123`}
${`${projectRootPath}/-/blob/${currentRef}/dir1/dir2/test.js#L123`} | ${'refs/heads/prefixedByUseSymbolicRefNames'} | ${`${projectRootPath}/-/blob/prefixedByUseSymbolicRefNames/dir1/dir2/test.js?ref_type=heads#L123`}
${`${projectRootPath}/-/blob/${currentRef}/dir1/dir2/test.js#L123`} | ${'refs/tags/prefixedByUseSymbolicRefNames'} | ${`${projectRootPath}/-/blob/prefixedByUseSymbolicRefNames/dir1/dir2/test.js?ref_type=tags#L123`}
${`${projectRootPath}/-/tree/${currentRef}/dir1/dir2/test.js#L123`} | ${'refs/heads/prefixedByUseSymbolicRefNames'} | ${`${projectRootPath}/-/tree/prefixedByUseSymbolicRefNames/dir1/dir2/test.js?ref_type=heads#L123`}
${`${projectRootPath}/-/tree/${currentRef}/dir1/dir2/test.js#L123`} | ${'refs/tags/prefixedByUseSymbolicRefNames'} | ${`${projectRootPath}/-/tree/prefixedByUseSymbolicRefNames/dir1/dir2/test.js?ref_type=tags#L123`}
${`${projectRootPath}/-/tree/${currentRef}/dir1/dir2/test.js#L123`} | ${'refs/heads/refs/heads/branchNameContainsPrefix'} | ${`${projectRootPath}/-/tree/refs/heads/branchNameContainsPrefix/dir1/dir2/test.js?ref_type=heads#L123`}
`(
'generates the correct destination path for $currentPath with ref type when it can be extracted',
({ currentPath, result, nextRef }) => {
setWindowLocation(currentPath);
expect(generateRefDestinationPath(projectRootPath, currentRef, nextRef)).toBe(
`${TEST_HOST}/${result}`,
);
},
);
});
it('encodes the selected ref', () => {
const result = `${projectRootPath}/-/tree/${encodedRefWithSpecialCharMock}`;
expect(generateRefDestinationPath(projectRootPath, refWithSpecialCharMock)).toBe(result);
expect(generateRefDestinationPath(projectRootPath, currentRef, refWithSpecialCharMock)).toBe(
`${TEST_HOST}/${result}`,
);
});
});

View file

@ -26,7 +26,7 @@ RSpec.describe HooksHelper do
it 'returns proper data' do
expect(subject).to match(
url: project_hook.url,
url_variables: Gitlab::Json.dump([{ key: 'abc' }])
url_variables: Gitlab::Json.dump([{ key: 'abc' }, { key: 'def' }])
)
end
end

View file

@ -34,6 +34,58 @@ RSpec.describe Banzai::Filter::InlineObservabilityFilter do
end
end
context 'when the document contains an embeddable observability link with redirect' do
let(:url) { 'https://observe.gitlab.com@example.com/12345' }
it 'leaves the original link unchanged' do
expect(doc.at_css('a').to_s).to eq(input)
end
it 'does not append an observability charts placeholder' do
node = doc.at_css('.js-render-observability')
expect(node).not_to be_present
end
end
context 'when the document contains an embeddable observability link with different port' do
let(:url) { 'https://observe.gitlab.com:3000/12345' }
let(:observe_url) { 'https://observe.gitlab.com:3001' }
before do
stub_env('OVERRIDE_OBSERVABILITY_URL', observe_url)
end
it 'leaves the original link unchanged' do
expect(doc.at_css('a').to_s).to eq(input)
end
it 'does not append an observability charts placeholder' do
node = doc.at_css('.js-render-observability')
expect(node).not_to be_present
end
end
context 'when the document contains an embeddable observability link with auth/start' do
let(:url) { 'https://observe.gitlab.com/auth/start' }
let(:observe_url) { 'https://observe.gitlab.com' }
before do
stub_env('OVERRIDE_OBSERVABILITY_URL', observe_url)
end
it 'leaves the original link unchanged' do
expect(doc.at_css('a').to_s).to eq(input)
end
it 'does not append an observability charts placeholder' do
node = doc.at_css('.js-render-observability')
expect(node).not_to be_present
end
end
context 'when feature flag is disabled' do
let(:url) { 'https://observe.gitlab.com/12345' }

View file

@ -0,0 +1,84 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::BackgroundMigration::NullifyLastErrorFromProjectMirrorData, feature_category: :source_code_management do # rubocop:disable Layout/LineLength
it 'nullifies last_error column on all rows' do
namespaces = table(:namespaces)
projects = table(:projects)
project_import_states = table(:project_mirror_data)
group = namespaces.create!(name: 'gitlab', path: 'gitlab-org')
project_namespace_1 = namespaces.create!(name: 'gitlab', path: 'gitlab-org')
project_namespace_2 = namespaces.create!(name: 'gitlab', path: 'gitlab-org')
project_namespace_3 = namespaces.create!(name: 'gitlab', path: 'gitlab-org')
project_1 = projects.create!(
namespace_id: group.id,
project_namespace_id: project_namespace_1.id,
name: 'test1'
)
project_2 = projects.create!(
namespace_id: group.id,
project_namespace_id: project_namespace_2.id,
name: 'test2'
)
project_3 = projects.create!(
namespace_id: group.id,
project_namespace_id: project_namespace_3.id,
name: 'test3'
)
project_import_state_1 = project_import_states.create!(
project_id: project_1.id,
status: 0,
last_update_started_at: 1.hour.ago,
last_update_scheduled_at: 1.hour.ago,
last_update_at: 1.hour.ago,
last_successful_update_at: 2.days.ago,
last_error: '13:fetch remote: "fatal: unable to look up user:pass@gitlab.com (port 9418) (nodename nor servname provided, or not known)\n": exit status 128.', # rubocop:disable Layout/LineLength
correlation_id_value: SecureRandom.uuid,
jid: SecureRandom.uuid
)
project_import_states.create!(
project_id: project_2.id,
status: 1,
last_update_started_at: 1.hour.ago,
last_update_scheduled_at: 1.hour.ago,
last_update_at: 1.hour.ago,
last_successful_update_at: nil,
next_execution_timestamp: 1.day.from_now,
last_error: '',
correlation_id_value: SecureRandom.uuid,
jid: SecureRandom.uuid
)
project_import_state_3 = project_import_states.create!(
project_id: project_3.id,
status: 2,
last_update_started_at: 1.hour.ago,
last_update_scheduled_at: 1.hour.ago,
last_update_at: 1.hour.ago,
last_successful_update_at: 1.hour.ago,
next_execution_timestamp: 1.day.from_now,
last_error: nil,
correlation_id_value: SecureRandom.uuid,
jid: SecureRandom.uuid
)
migration = described_class.new(
start_id: project_import_state_1.id,
end_id: project_import_state_3.id,
batch_table: :project_mirror_data,
batch_column: :id,
sub_batch_size: 1,
pause_ms: 0,
connection: ApplicationRecord.connection
)
w_last_error_count = -> { project_import_states.where.not(last_error: nil).count } # rubocop:disable CodeReuse/ActiveRecord
expect { migration.perform }.to change(&w_last_error_count).from(2).to(0)
end
end

View file

@ -10,29 +10,36 @@ RSpec.describe Gitlab::UrlSanitizer do
# We want to try with multi-line content because is how error messages are formatted
described_class.sanitize(%Q{
remote: Not Found
fatal: repository '#{url}' not found
fatal: repository `#{url}` not found
})
end
where(:input, :output) do
'http://user:pass@test.com/root/repoC.git/' | 'http://*****:*****@test.com/root/repoC.git/'
'https://user:pass@test.com/root/repoA.git/' | 'https://*****:*****@test.com/root/repoA.git/'
'ssh://user@host.test/path/to/repo.git' | 'ssh://*****@host.test/path/to/repo.git'
# git protocol does not support authentication but clean any details anyway
'git://user:pass@host.test/path/to/repo.git' | 'git://*****:*****@host.test/path/to/repo.git'
'git://host.test/path/to/repo.git' | 'git://host.test/path/to/repo.git'
# http(s), ssh, git, relative, and schemeless URLs should all be masked correctly
urls = ['http://', 'https://', 'ssh://', 'git://', '//', ''].flat_map do |protocol|
[
["#{protocol}test.com", "#{protocol}test.com"],
["#{protocol}test.com/", "#{protocol}test.com/"],
["#{protocol}test.com/path/to/repo.git", "#{protocol}test.com/path/to/repo.git"],
["#{protocol}user@test.com", "#{protocol}*****@test.com"],
["#{protocol}user:pass@test.com", "#{protocol}*****:*****@test.com"],
["#{protocol}user:@test.com", "#{protocol}*****@test.com"],
["#{protocol}:pass@test.com", "#{protocol}:*****@test.com"]
]
end
# SCP-style URLs are left unmodified
'user@server:project.git' | 'user@server:project.git'
'user:pass@server:project.git' | 'user:pass@server:project.git'
urls << ['user@server:project.git', 'user@server:project.git']
urls << ['user:@server:project.git', 'user:@server:project.git']
urls << [':pass@server:project.git', ':pass@server:project.git']
urls << ['user:pass@server:project.git', 'user:pass@server:project.git']
# return an empty string for invalid URLs
'ssh://' | ''
urls << ['ssh://', '']
end
with_them do
it { expect(sanitize_url(input)).to include("repository '#{output}' not found") }
it { expect(sanitize_url(input)).to include("repository `#{output}` not found") }
end
end

View file

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Rouge::Formatters::HTMLGitlab do
RSpec.describe Rouge::Formatters::HTMLGitlab, feature_category: :source_code_management do
describe '#format' do
subject { described_class.format(tokens, **options) }
@ -67,5 +67,24 @@ RSpec.describe Rouge::Formatters::HTMLGitlab do
is_expected.to include(%{<span class="unicode-bidi has-tooltip" data-toggle="tooltip" title="#{message}">}).exactly(4).times
end
end
context 'when space characters and zero-width spaces are used' do
let(:lang) { 'ruby' }
let(:tokens) { lexer.lex(code, continue: false) }
let(:code) do
<<~JS
def\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000hello
JS
end
it 'replaces the space characters with spaces' do
is_expected.to eq(
"<span id=\"LC1\" class=\"line\" lang=\"ruby\">" \
"<span class=\"k\">def</span><span class=\"err\"> </span><span class=\"n\">hello</span>" \
"</span>"
)
end
end
end
end

View file

@ -0,0 +1,37 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe NullifyLastErrorFromProjectMirrorData, feature_category: :source_code_management do
let(:migration) { described_class::MIGRATION }
before do
migrate!
end
describe '#up' do
it 'schedules background jobs for each batch of projects' do
expect(migration).to(
have_scheduled_batched_migration(
table_name: :project_mirror_data,
column_name: :id,
interval: described_class::INTERVAL,
batch_size: described_class::BATCH_SIZE,
sub_batch_size: described_class::SUB_BATCH_SIZE
)
)
end
end
describe '#down' do
before do
schema_migrate_down!
end
it 'deletes all batched migration records' do
expect(migration).not_to have_scheduled_batched_migration
end
end
end

View file

@ -302,7 +302,7 @@ RSpec.describe ErrorTracking::ProjectErrorTrackingSetting do
it { expect(result[:issue].gitlab_commit_path).to eq(nil) }
end
context 'when repo commit matches first relase version' do
context 'when repo commit matches first release version' do
let(:commit) { instance_double(Commit, id: commit_id) }
let(:repository) { instance_double(Repository, commit: commit) }

View file

@ -242,6 +242,22 @@ RSpec.describe WebHook, feature_category: :integrations do
expect(hook.url_variables).to eq({})
end
it 'resets url variables if url is changed and url variables are appended' do
hook.url = 'http://suspicious.example.com/{abc}/{foo}'
hook.url_variables = hook.url_variables.merge('foo' => 'bar')
expect(hook).not_to be_valid
expect(hook.url_variables).to eq({})
end
it 'resets url variables if url is changed and url variables are removed' do
hook.url = 'http://suspicious.example.com/{abc}'
hook.url_variables = hook.url_variables.except("def")
expect(hook).not_to be_valid
expect(hook.url_variables).to eq({})
end
it 'does not reset url variables if both url and url variables are changed' do
hook.url = 'http://example.com/{one}/{two}'
hook.url_variables = { 'one' => 'foo', 'two' => 'bar' }
@ -249,6 +265,18 @@ RSpec.describe WebHook, feature_category: :integrations do
expect(hook).to be_valid
expect(hook.url_variables).to eq({ 'one' => 'foo', 'two' => 'bar' })
end
context 'without url variables' do
subject(:hook) { build_stubbed(:project_hook, project: project, url: 'http://example.com') }
it 'does not reset url variables' do
hook.url = 'http://example.com/{one}/{two}'
hook.url_variables = { 'one' => 'foo', 'two' => 'bar' }
expect(hook).to be_valid
expect(hook.url_variables).to eq({ 'one' => 'foo', 'two' => 'bar' })
end
end
end
it "only consider these branch filter strategies are valid" do

View file

@ -697,6 +697,39 @@ RSpec.describe ProjectPolicy, feature_category: :authentication_and_authorizatio
end
end
describe 'read_prometheus', feature_category: :metrics do
using RSpec::Parameterized::TableSyntax
before do
project.project_feature.update!(metrics_dashboard_access_level: ProjectFeature::ENABLED)
end
let(:policy) { :read_prometheus }
where(:project_visibility, :role, :allowed) do
:public | :anonymous | false
:public | :guest | false
:public | :reporter | true
:internal | :anonymous | false
:internal | :guest | false
:internal | :reporter | true
:private | :anonymous | false
:private | :guest | false
:private | :reporter | true
end
with_them do
let(:current_user) { public_send(role) }
let(:project) { public_send("#{project_visibility}_project") }
if params[:allowed]
it { is_expected.to be_allowed(policy) }
else
it { is_expected.not_to be_allowed(policy) }
end
end
end
describe 'update_max_artifacts_size' do
context 'when no user' do
let(:current_user) { anonymous }
@ -972,7 +1005,7 @@ RSpec.describe ProjectPolicy, feature_category: :authentication_and_authorizatio
let(:current_user) { guest }
it { is_expected.to be_allowed(:metrics_dashboard) }
it { is_expected.to be_allowed(:read_prometheus) }
it { is_expected.to be_disallowed(:read_prometheus) }
it { is_expected.to be_allowed(:read_deployment) }
it { is_expected.to be_allowed(:read_metrics_user_starred_dashboard) }
it { is_expected.to be_allowed(:create_metrics_user_starred_dashboard) }
@ -982,7 +1015,7 @@ RSpec.describe ProjectPolicy, feature_category: :authentication_and_authorizatio
let(:current_user) { anonymous }
it { is_expected.to be_allowed(:metrics_dashboard) }
it { is_expected.to be_allowed(:read_prometheus) }
it { is_expected.to be_disallowed(:read_prometheus) }
it { is_expected.to be_allowed(:read_deployment) }
it { is_expected.to be_disallowed(:read_metrics_user_starred_dashboard) }
it { is_expected.to be_disallowed(:create_metrics_user_starred_dashboard) }
@ -1008,12 +1041,14 @@ RSpec.describe ProjectPolicy, feature_category: :authentication_and_authorizatio
let(:current_user) { guest }
it { is_expected.to be_disallowed(:metrics_dashboard) }
it { is_expected.to be_disallowed(:read_prometheus) }
end
context 'with anonymous' do
let(:current_user) { anonymous }
it { is_expected.to be_disallowed(:metrics_dashboard) }
it { is_expected.to be_disallowed(:read_prometheus) }
end
end
@ -1036,7 +1071,7 @@ RSpec.describe ProjectPolicy, feature_category: :authentication_and_authorizatio
let(:current_user) { guest }
it { is_expected.to be_allowed(:metrics_dashboard) }
it { is_expected.to be_allowed(:read_prometheus) }
it { is_expected.to be_disallowed(:read_prometheus) }
it { is_expected.to be_allowed(:read_deployment) }
it { is_expected.to be_allowed(:read_metrics_user_starred_dashboard) }
it { is_expected.to be_allowed(:create_metrics_user_starred_dashboard) }
@ -1046,6 +1081,7 @@ RSpec.describe ProjectPolicy, feature_category: :authentication_and_authorizatio
let(:current_user) { anonymous }
it { is_expected.to be_disallowed(:metrics_dashboard) }
it { is_expected.to be_disallowed(:read_prometheus) }
end
end
end
@ -1068,12 +1104,14 @@ RSpec.describe ProjectPolicy, feature_category: :authentication_and_authorizatio
let(:current_user) { guest }
it { is_expected.to be_disallowed(:metrics_dashboard) }
it { is_expected.to be_disallowed(:read_prometheus) }
end
context 'with anonymous' do
let(:current_user) { anonymous }
it { is_expected.to be_disallowed(:metrics_dashboard) }
it { is_expected.to be_disallowed(:read_prometheus) }
end
end
@ -1092,12 +1130,14 @@ RSpec.describe ProjectPolicy, feature_category: :authentication_and_authorizatio
let(:current_user) { guest }
it { is_expected.to be_disallowed(:metrics_dashboard) }
it { is_expected.to be_disallowed(:read_prometheus) }
end
context 'with anonymous' do
let(:current_user) { anonymous }
it { is_expected.to be_disallowed(:metrics_dashboard) }
it { is_expected.to be_disallowed(:read_prometheus) }
end
end
end
@ -2016,7 +2056,7 @@ RSpec.describe ProjectPolicy, feature_category: :authentication_and_authorizatio
:public | ProjectFeature::ENABLED | :anonymous | true
:public | ProjectFeature::PRIVATE | :maintainer | true
:public | ProjectFeature::PRIVATE | :developer | true
:public | ProjectFeature::PRIVATE | :guest | true
:public | ProjectFeature::PRIVATE | :guest | false
:public | ProjectFeature::PRIVATE | :anonymous | false
:public | ProjectFeature::DISABLED | :maintainer | false
:public | ProjectFeature::DISABLED | :developer | false
@ -2028,7 +2068,7 @@ RSpec.describe ProjectPolicy, feature_category: :authentication_and_authorizatio
:internal | ProjectFeature::ENABLED | :anonymous | false
:internal | ProjectFeature::PRIVATE | :maintainer | true
:internal | ProjectFeature::PRIVATE | :developer | true
:internal | ProjectFeature::PRIVATE | :guest | true
:internal | ProjectFeature::PRIVATE | :guest | false
:internal | ProjectFeature::PRIVATE | :anonymous | false
:internal | ProjectFeature::DISABLED | :maintainer | false
:internal | ProjectFeature::DISABLED | :developer | false

View file

@ -573,6 +573,22 @@ RSpec.describe API::Repositories, feature_category: :source_code_management do
context 'when authenticated', 'as a developer' do
it_behaves_like 'repository compare' do
let(:current_user) { user }
context 'when user does not have read access to the parent project' do
let_it_be(:group) { create(:group) }
let(:forked_project) { fork_project(project, current_user, repository: true, namespace: group) }
before do
forked_project.add_guest(current_user)
end
it 'returns 403 error' do
get api(route, current_user), params: { from: 'improve/awesome', to: 'feature', from_project_id: forked_project.id }
expect(response).to have_gitlab_http_status(:forbidden)
expect(json_response['message']).to eq("403 Forbidden - You don't have access to this fork's parent project")
end
end
end
end

View file

@ -861,6 +861,21 @@ RSpec.describe MergeRequests::PushOptionsHandlerService do
end
end
describe 'when user does not have access to target project' do
let(:push_options) { { create: true, target: 'my-branch' } }
let(:changes) { default_branch_changes }
before do
allow(user1).to receive(:can?).with(:read_code, project).and_return(false)
end
it 'records an error', :sidekiq_inline do
service.execute
expect(service.errors).to eq(["User access was denied"])
end
end
describe 'when MRs are not enabled' do
let(:project) { create(:project, :public, :repository).tap { |pr| pr.add_developer(user1) } }
let(:push_options) { { create: true } }

View file

@ -130,8 +130,8 @@ RSpec.describe WebHookService, :request_store, :clean_gitlab_redis_shared_state
context 'there is userinfo' do
before do
project_hook.update!(
url: 'http://{one}:{two}@example.com',
url_variables: { 'one' => 'a', 'two' => 'b' }
url: 'http://{foo}:{bar}@example.com',
url_variables: { 'foo' => 'a', 'bar' => 'b' }
)
stub_full_request('http://example.com', method: :post)
end

View file

@ -0,0 +1,26 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'explore/projects/page_out_of_bounds.html.haml', feature_category: :projects do
let(:page_limit) { 10 }
let(:unsafe_param) { 'hacked_using_unsafe_param!' }
before do
assign(:max_page_number, page_limit)
controller.params[:action] = 'index'
controller.params[:host] = unsafe_param
controller.params[:protocol] = unsafe_param
controller.params[:sort] = 'name_asc'
end
it 'removes unsafe params from the link' do
render
href = "/explore/projects?page=#{page_limit}&sort=name_asc"
button_text = format(_("Back to page %{number}"), number: page_limit)
expect(rendered).to have_link(button_text, href: href)
expect(rendered).not_to include(unsafe_param)
end
end