Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-11-27 21:22:32 +00:00
parent 959e358268
commit d221c6bf4a
39 changed files with 672 additions and 127 deletions

View File

@ -108,6 +108,9 @@
.if-merge-request-labels-run-observability-e2e-tests-current-branch: &if-merge-request-labels-run-observability-e2e-tests-current-branch
if: '($CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_EVENT_TYPE != "merge_train") && $CI_MERGE_REQUEST_LABELS =~ /pipeline:run-observability-e2e-tests-current-branch/'
.if-merge-request-labels-skip-observability-e2e-tests: &if-merge-request-labels-skip-observability-e2e-tests
if: '($CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_EVENT_TYPE != "merge_train") && $CI_MERGE_REQUEST_LABELS =~ /pipeline:skip-observability-e2e-tests/'
.if-merge-request-labels-run-single-db: &if-merge-request-labels-run-single-db
if: '($CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_EVENT_TYPE != "merge_train") && $CI_MERGE_REQUEST_LABELS =~ /pipeline:run-single-db/'
@ -3397,6 +3400,8 @@
rules:
- <<: *if-observability-skip-e2e-jobs
when: never
- <<: *if-merge-request-labels-skip-observability-e2e-tests
when: never
- <<: *if-merge-request-labels-run-observability-e2e-tests-current-branch
when: never
- <<: *if-merge-request-labels-run-observability-e2e-tests-main-branch
@ -3407,6 +3412,8 @@
rules:
- <<: *if-observability-skip-e2e-jobs
when: never
- <<: *if-merge-request-labels-skip-observability-e2e-tests
when: never
- <<: *if-merge-request-labels-run-observability-e2e-tests-main-branch
when: never
- <<: *if-merge-request-labels-run-observability-e2e-tests-current-branch

View File

@ -256,7 +256,7 @@ gem 'creole', '~> 0.5.0', feature_category: :markdown
gem 'wikicloth', '0.8.1', feature_category: :markdown
gem 'asciidoctor', '~> 2.0.18', feature_category: :markdown
gem 'asciidoctor-include-ext', '~> 0.4.0', require: false, feature_category: :markdown
gem 'asciidoctor-plantuml', '~> 0.0.16', feature_category: :markdown
gem 'asciidoctor-plantuml', '~> 0.1.1', feature_category: :markdown
gem 'asciidoctor-kroki', '~> 0.10.0', require: false, feature_category: :markdown
gem 'rouge', '~> 4.4.0', feature_category: :shared
gem 'truncato', '~> 0.7.12', feature_category: :team_planning

View File

@ -26,7 +26,7 @@
{"name":"asciidoctor","version":"2.0.23","platform":"ruby","checksum":"52208807f237dfa0ca29882f8b13d60b820496116ad191cf197ca56f2b7fddf3"},
{"name":"asciidoctor-include-ext","version":"0.4.0","platform":"ruby","checksum":"406adb9d2fbfc25536609ca13b787ed704dc06a4e49d6709b83f3bad578f7878"},
{"name":"asciidoctor-kroki","version":"0.10.0","platform":"ruby","checksum":"8e4225d88f120e2e7b5d3f5ddb67c5e69496d7344a16c57db5036ac900123062"},
{"name":"asciidoctor-plantuml","version":"0.0.16","platform":"ruby","checksum":"407e47cd1186ded5ccc75f0c812e5524c26c571d542247c5132abb8f47bd1793"},
{"name":"asciidoctor-plantuml","version":"0.1.1","platform":"ruby","checksum":"2bfa1a79349aa3fff611cdc54ade674e91423e38c20a21b24d8ca59006a0b0ae"},
{"name":"ast","version":"2.4.2","platform":"ruby","checksum":"1e280232e6a33754cde542bc5ef85520b74db2aac73ec14acef453784447cc12"},
{"name":"async","version":"2.12.1","platform":"ruby","checksum":"146fb3acf6d05ad40abb9ae659dd3b574067a3420fe7d6d5d6a3cf5413de3ea5"},
{"name":"atlassian-jwt","version":"0.2.1","platform":"ruby","checksum":"2fd2d87418773f2e140c038cb22e049069708aff2bd0a423a7e1740574e97823"},

View File

@ -331,7 +331,7 @@ GEM
asciidoctor (>= 1.5.6, < 3.0.0)
asciidoctor-kroki (0.10.0)
asciidoctor (~> 2.0)
asciidoctor-plantuml (0.0.16)
asciidoctor-plantuml (0.1.1)
asciidoctor (>= 2.0.17, < 3.0.0)
ast (2.4.2)
async (2.12.1)
@ -1985,7 +1985,7 @@ DEPENDENCIES
asciidoctor (~> 2.0.18)
asciidoctor-include-ext (~> 0.4.0)
asciidoctor-kroki (~> 0.10.0)
asciidoctor-plantuml (~> 0.0.16)
asciidoctor-plantuml (~> 0.1.1)
async (~> 2.12.1)
atlassian-jwt (~> 0.2.1)
attr_encrypted (~> 3.2.4)!

View File

@ -26,7 +26,7 @@
{"name":"asciidoctor","version":"2.0.23","platform":"ruby","checksum":"52208807f237dfa0ca29882f8b13d60b820496116ad191cf197ca56f2b7fddf3"},
{"name":"asciidoctor-include-ext","version":"0.4.0","platform":"ruby","checksum":"406adb9d2fbfc25536609ca13b787ed704dc06a4e49d6709b83f3bad578f7878"},
{"name":"asciidoctor-kroki","version":"0.10.0","platform":"ruby","checksum":"8e4225d88f120e2e7b5d3f5ddb67c5e69496d7344a16c57db5036ac900123062"},
{"name":"asciidoctor-plantuml","version":"0.0.16","platform":"ruby","checksum":"407e47cd1186ded5ccc75f0c812e5524c26c571d542247c5132abb8f47bd1793"},
{"name":"asciidoctor-plantuml","version":"0.1.1","platform":"ruby","checksum":"2bfa1a79349aa3fff611cdc54ade674e91423e38c20a21b24d8ca59006a0b0ae"},
{"name":"ast","version":"2.4.2","platform":"ruby","checksum":"1e280232e6a33754cde542bc5ef85520b74db2aac73ec14acef453784447cc12"},
{"name":"async","version":"2.12.1","platform":"ruby","checksum":"146fb3acf6d05ad40abb9ae659dd3b574067a3420fe7d6d5d6a3cf5413de3ea5"},
{"name":"atlassian-jwt","version":"0.2.1","platform":"ruby","checksum":"2fd2d87418773f2e140c038cb22e049069708aff2bd0a423a7e1740574e97823"},

View File

@ -340,7 +340,7 @@ GEM
asciidoctor (>= 1.5.6, < 3.0.0)
asciidoctor-kroki (0.10.0)
asciidoctor (~> 2.0)
asciidoctor-plantuml (0.0.16)
asciidoctor-plantuml (0.1.1)
asciidoctor (>= 2.0.17, < 3.0.0)
ast (2.4.2)
async (2.12.1)
@ -2012,7 +2012,7 @@ DEPENDENCIES
asciidoctor (~> 2.0.18)
asciidoctor-include-ext (~> 0.4.0)
asciidoctor-kroki (~> 0.10.0)
asciidoctor-plantuml (~> 0.0.16)
asciidoctor-plantuml (~> 0.1.1)
async (~> 2.12.1)
atlassian-jwt (~> 0.2.1)
attr_encrypted (~> 3.2.4)!

View File

@ -80,14 +80,23 @@ class Ability
before_check(policy, ability.to_sym, user, subject, opts)
case opts[:scope]
when :user
DeclarativePolicy.user_scope { policy.allowed?(ability) }
when :subject
DeclarativePolicy.subject_scope { policy.allowed?(ability) }
result = case opts[:scope]
when :user
DeclarativePolicy.user_scope { policy.allowed?(ability) }
when :subject
DeclarativePolicy.subject_scope { policy.allowed?(ability) }
else
policy.allowed?(ability)
end
identity = ::Gitlab::Auth::Identity.fabricate(user)
if identity.present? && identity.composite?
result && allowed?(identity.scoped_user, ability, subject, **opts)
else
policy.allowed?(ability)
result
end
ensure
# TODO: replace with runner invalidation:
# See: https://gitlab.com/gitlab-org/declarative-policy/-/merge_requests/24

View File

@ -886,10 +886,6 @@ class ApplicationSetting < ApplicationRecord
validate_url(parsed_kroki_url, :kroki_url, KROKI_URL_ERROR_MESSAGE)
end
def kroki_url_absolute?
parsed_kroki_url&.absolute?
end
def sourcegraph_url_is_com?
!!(sourcegraph_url =~ %r{\Ahttps://(www\.)?sourcegraph\.com})
end

View File

@ -30,6 +30,7 @@ class NotificationRecipient
end
def notifiable?
return false if has_composite_identity?
return false unless has_access?
return false if emails_disabled?
return false if own_activity?
@ -121,6 +122,10 @@ class NotificationRecipient
end
end
def has_composite_identity?
user.has_composite_identity?
end
def excluded_watcher_action?
return false unless notification_level == :watch
return false unless @custom_action

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true
class OauthAccessToken < Doorkeeper::AccessToken
include Gitlab::Utils::StrongMemoize
belongs_to :resource_owner, class_name: 'User'
belongs_to :application, class_name: 'Doorkeeper::Application'
belongs_to :organization, class_name: 'Organizations::Organization'
@ -12,6 +14,9 @@ class OauthAccessToken < Doorkeeper::AccessToken
scope :latest_per_application, -> { select('distinct on(application_id) *').order(application_id: :desc, created_at: :desc) }
scope :preload_application, -> { preload(:application) }
# user scope format is: `user:$USER_ID`
SCOPED_USER_REGEX = /user:(\d+)(?:\s|$)/
def scopes=(value)
if value.is_a?(Array)
super(Doorkeeper::OAuth::Scopes.from_array(value).to_s)
@ -36,4 +41,20 @@ class OauthAccessToken < Doorkeeper::AccessToken
def self.matching_token_for(application, resource_owner, scopes)
# no-op
end
def scope_user
user_id = extract_user_id_from_scopes
return unless user_id
::User.find_by_id(user_id)
end
strong_memoize_attr :scope_user
private
def extract_user_id_from_scopes
return false unless scopes.present?
scopes.to_s[SCOPED_USER_REGEX, 1]&.to_i
end
end

View File

@ -2522,6 +2522,10 @@ class User < ApplicationRecord
true
end
def has_composite_identity?
false
end
protected
# override, from Devise::Validatable

View File

@ -49,7 +49,7 @@ module Webauthn
#
def verify_webauthn_credential(webauthn_credential, stored_credential, challenge, encoder)
# We need to adjust the relaying party id (RP id) we verify against if the registration in question
# is a migrated U2F registration. This is beacuse the appid of U2F and the rp id of WebAuthn differ.
# is a migrated U2F registration. This is because the appid of U2F and the rp id of WebAuthn differ.
rp_id = webauthn_credential.client_extension_outputs['appid'] ? WebAuthn.configuration.origin : URI(WebAuthn.configuration.origin).host
webauthn_credential.response.verify(
encoder.decode(challenge),

View File

@ -0,0 +1,9 @@
---
name: composite_identity
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/468370
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/173006
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/506473
milestone: "17.7"
group: group::authorization
type: wip
default_enabled: false

View File

@ -9,18 +9,23 @@ module Banzai
#
class PlantumlFilter < HTML::Pipeline::Filter
prepend Concerns::PipelineTimingCheck
include ActionView::Helpers::TagHelper
include Gitlab::Utils::StrongMemoize
def call
return doc unless settings.plantuml_enabled? && doc.at_xpath(lang_tag)
return doc unless plantuml_url_valid?
Gitlab::Plantuml.configure
doc.xpath(lang_tag).each do |node|
img_tag = Nokogiri::HTML::DocumentFragment.parse(
Asciidoctor::PlantUml::Processor.plantuml_content(node.content, {})).css('img').first
next if node.content.blank?
next if img_tag.nil?
image_src = create_image_src('png', node.content)
img_tag = Nokogiri::HTML::DocumentFragment.parse(content_tag(:img, nil, src: image_src))
img_tag = img_tag.children.first
img_tag.add_class('plantuml')
img_tag.set_attribute('data-diagram', 'plantuml')
img_tag.set_attribute('data-diagram-src', "data:text/plain;base64,#{Base64.strict_encode64(node.content)}")
@ -40,6 +45,15 @@ module Banzai
def settings
Gitlab::CurrentSettings.current_application_settings
end
strong_memoize_attr :settings
def create_image_src(format, text)
Asciidoctor::PlantUml::Processor.gen_url(text, format)
end
def plantuml_url_valid?
::Gitlab::UrlSanitizer.valid_web?(settings.plantuml_url)
end
end
end
end

View File

@ -71,8 +71,7 @@ module Gitlab
end
def find_user_from_bearer_token
find_user_from_job_bearer_token ||
find_user_from_access_token
find_user_from_job_bearer_token || find_user_from_access_token
end
def find_user_from_job_token
@ -299,6 +298,11 @@ module Gitlab
raise UnauthorizedError unless oauth_token
oauth_token.revoke_previous_refresh_token!
::Gitlab::Auth::Identity.link_from_oauth_token(oauth_token).tap do |identity|
raise UnauthorizedError if identity && !identity.valid?
end
oauth_token
end

106
lib/gitlab/auth/identity.rb Normal file
View File

@ -0,0 +1,106 @@
# frozen_string_literal: true
module Gitlab
module Auth
##
# Identity class represents identity which we want to use in authorization policies.
#
# It decides if an identity is a single or composite identity and finds identity scope.
#
class Identity
COMPOSITE_IDENTITY_USERS_KEY = 'composite_identities'
COMPOSITE_IDENTITY_KEY_FORMAT = 'user:%s:composite_identity'
IdentityError = Class.new(StandardError)
IdentityLinkMismatchError = Class.new(IdentityError)
UnexpectedIdentityError = Class.new(IdentityError)
TooManyIdentitiesLinkedError = Class.new(IdentityError)
MissingCompositeIdentityError = Class.new(::Gitlab::Access::AccessDeniedError)
# TODO: why is this called 3 times in doorkeeper_access_spec.rb specs?
def self.link_from_oauth_token(oauth_token)
fabricate(oauth_token.user).tap do |identity|
identity.link!(oauth_token.scope_user) if identity&.composite?
end
end
def self.fabricate(user)
new(user) if user.is_a?(::User)
end
def initialize(user, store: ::Gitlab::SafeRequestStore)
raise UnexpectedIdentityError unless user.is_a?(::User)
@user = user
@request_store = store
end
def composite?
return false unless Feature.enabled?(:composite_identity, @user)
@user.has_composite_identity?
end
def linked?
@request_store.exist?(store_key)
end
def valid?
return true unless composite?
linked?
end
def scoped_user
@request_store.fetch(store_key) do
raise MissingCompositeIdentityError, 'composite identity missing'
end
end
def link!(scope_user)
return unless scope_user
validate_link!(scope_user)
store_identity_link!(scope_user)
self
end
private
def scoped_user_id
scoped_user.id
end
def scoped_user_present?
@request_store.exist?(store_key)
end
def validate_link!(scope_user)
return unless scoped_user_present? && saved_scoped_user_different_from_new_scope_user?(scope_user)
raise IdentityLinkMismatchError, 'identity link change detected'
end
def saved_scoped_user_different_from_new_scope_user?(scope_user)
scoped_user_id != scope_user.id
end
def store_identity_link!(scope_user)
@request_store.store[store_key] = scope_user
composite_identities.add(@user)
raise TooManyIdentitiesLinkedError if composite_identities.size > 1
end
def composite_identities
@request_store.store[COMPOSITE_IDENTITY_USERS_KEY] ||= Set.new
end
def store_key
@store_key ||= format(COMPOSITE_IDENTITY_KEY_FORMAT, @user.id)
end
end
end
end

View File

@ -28996,7 +28996,7 @@ msgstr ""
msgid "Insights|Configure a custom report for insights into your group processes such as amount of issues, bugs, and merge requests per month. %{linkStart}How do I configure an insights report?%{linkEnd}"
msgstr ""
msgid "Insights|Some items are not visible beacuse the project was filtered out in the insights.yml file (see the projects.only config in the YAML file or the enabled project features (issues, merge requests) in the project settings)."
msgid "Insights|Some items are not visible because the project was filtered out in the insights.yml file (see the projects.only config in the YAML file or the enabled project features (issues, merge requests) in the project settings)."
msgstr ""
msgid "Insights|This project is filtered out in the insights.yml file (see the projects.only config for more information)."

View File

@ -83,7 +83,7 @@
"@mattiasbuelens/web-streams-adapter": "^0.1.0",
"@rails/actioncable": "7.0.8-4",
"@rails/ujs": "7.0.8-4",
"@sentry/browser": "8.38.0",
"@sentry/browser": "8.41.0",
"@snowplow/browser-plugin-client-hints": "^3.24.2",
"@snowplow/browser-plugin-form-tracking": "^3.24.2",
"@snowplow/browser-plugin-ga-cookies": "^3.24.2",

View File

@ -1,5 +1,5 @@
const { spawnSync } = require('node:child_process');
const { readFile, open } = require('node:fs/promises');
const { readFile, open, stat } = require('node:fs/promises');
const parser = require('fast-xml-parser');
const defaultChalk = require('chalk');
const { getLocalQuarantinedFiles } = require('./jest_vue3_quarantine_utils');
@ -7,6 +7,9 @@ const { getLocalQuarantinedFiles } = require('./jest_vue3_quarantine_utils');
// Always use basic color output
const chalk = new defaultChalk.constructor({ level: 1 });
let quarantinedFiles;
let filesThatChanged;
async function parseJUnitReport() {
let junit;
try {
@ -21,7 +24,7 @@ async function parseJUnitReport() {
console.warn(e);
// No JUnit report exists, or there was a parsing error. Either way, we
// should not block the MR.
return { passed: [], total: 0 };
return [];
}
const failuresByFile = new Map();
@ -41,32 +44,28 @@ async function parseJUnitReport() {
}
}
const quarantinedFiles = new Set(await getLocalQuarantinedFiles());
const passed = [];
for (const [file, failures] of failuresByFile.entries()) {
if (failures === 0 && quarantinedFiles.has(file)) passed.push(file);
}
return {
passed,
total: failuresByFile.size,
};
return passed;
}
function reportPassingSpecsShouldBeUnquarantined(passed) {
function reportSpecsShouldBeUnquarantined(files) {
const docsLink =
// eslint-disable-next-line no-restricted-syntax
'https://docs.gitlab.com/ee/development/testing_guide/testing_vue3.html#quarantine-list';
console.warn(' ');
console.warn(
`The following ${passed.length} spec file(s) now pass(es) under Vue 3, and so must be removed from quarantine:`,
`The following ${files.length} spec files either now pass under Vue 3, or no longer exist, and so must be removed from quarantine:`,
);
console.warn(' ');
console.warn(passed.join('\n'));
console.warn(files.join('\n'));
console.warn(' ');
console.warn(
chalk.red(
`To fix this job, remove the file(s) listed above from the file ${chalk.underline('scripts/frontend/quarantined_vue3_specs.txt')}.`,
`To fix this job, remove the files listed above from the file ${chalk.underline('scripts/frontend/quarantined_vue3_specs.txt')}.`,
),
);
console.warn(`For more information, please see ${docsLink}.`);
@ -84,8 +83,34 @@ async function changedFiles() {
return files.flat();
}
function intersection(a, b) {
const result = new Set();
for (const element of a) {
if (b.has(element)) result.add(element);
}
return result;
}
async function getRemovedQuarantinedSpecs() {
const removedQuarantinedSpecs = [];
for (const file of intersection(filesThatChanged, quarantinedFiles)) {
try {
// eslint-disable-next-line no-await-in-loop
await stat(file);
} catch (e) {
if (e.code === 'ENOENT') removedQuarantinedSpecs.push(file);
}
}
return removedQuarantinedSpecs;
}
async function main() {
const filesThatChanged = await changedFiles();
filesThatChanged = await changedFiles();
quarantinedFiles = new Set(await getLocalQuarantinedFiles());
const jestStdout = (await open('jest_stdout', 'w')).createWriteStream();
const jestStderr = (await open('jest_stderr', 'w')).createWriteStream();
@ -126,18 +151,18 @@ async function main() {
},
);
const { passed, total } = await parseJUnitReport();
const passed = await parseJUnitReport();
const removedQuarantinedSpecs = await getRemovedQuarantinedSpecs();
const filesToReport = [...passed, ...removedQuarantinedSpecs];
if (total.length === 0) {
if (filesToReport.length === 0) {
// No tests ran, or there was some unexpected error. Either way, exit
// successfully.
return;
}
if (passed.length > 0) {
process.exitCode = 1;
reportPassingSpecsShouldBeUnquarantined(passed);
}
process.exitCode = 1;
reportSpecsShouldBeUnquarantined(filesToReport);
}
main().catch((e) => {

View File

@ -13,7 +13,6 @@ ee/spec/frontend/analytics/analytics_dashboards/components/analytics_dashboards_
ee/spec/frontend/analytics/analytics_dashboards/components/analytics_data_explorer_spec.js
ee/spec/frontend/analytics/analytics_dashboards/components/list/feature_list_item_spec.js
ee/spec/frontend/analytics/cycle_analytics/components/value_stream_aggregation_status_spec.js
ee/spec/frontend/analytics/cycle_analytics/components/value_stream_form_content_spec.js
ee/spec/frontend/analytics/cycle_analytics/vsa_settings/components/value_stream_form_content_spec.js
ee/spec/frontend/analytics/dashboards/ai_impact/components/metric_table_spec.js
ee/spec/frontend/analytics/devops_reports/devops_adoption/components/devops_adoption_app_spec.js
@ -46,7 +45,6 @@ ee/spec/frontend/clusters/components/environments_spec.js
ee/spec/frontend/compliance_dashboard/components/frameworks_report/edit_framework/components/policies_section_spec.js
ee/spec/frontend/compliance_dashboard/components/frameworks_report/report_spec.js
ee/spec/frontend/compliance_dashboard/components/standards_adherence_report/base_table_spec.js
ee/spec/frontend/contextual_sidebar/components/trial_status_widget_spec.js
ee/spec/frontend/dependencies/components/app_spec.js
ee/spec/frontend/dependencies/components/dependency_location_spec.js
ee/spec/frontend/dependencies/components/dependency_path_viewer_spec.js
@ -171,7 +169,6 @@ spec/frontend/ci/pipeline_editor/components/job_assistant_drawer/accordion_items
spec/frontend/ci/pipeline_editor/components/lint/ci_lint_warnings_spec.js
spec/frontend/ci/pipeline_editor/components/pipeline_editor_tabs_spec.js
spec/frontend/ci/pipeline_editor/pipeline_editor_app_spec.js
spec/frontend/ci/pipeline_mini_graph/legacy_pipeline_mini_graph/legacy_pipeline_stage_spec.js
spec/frontend/ci/pipeline_mini_graph/pipeline_mini_graph_spec.js
spec/frontend/ci/pipelines_page/components/pipeline_multi_actions_spec.js
spec/frontend/ci/pipelines_page/components/pipelines_artifacts_spec.js

View File

@ -9,7 +9,7 @@ RSpec.describe Banzai::Filter::PlantumlFilter, feature_category: :markdown do
stub_application_setting(plantuml_enabled: true, plantuml_url: "http://localhost:8080")
input = '<pre data-canonical-lang="plantuml"><code>Bob -> Sara : Hello</code></pre>'
output = '<img class="plantuml" src="http://localhost:8080/png/U9npoazIqBLJ24uiIbImKl18pSd91m0rkGMq" data-diagram="plantuml" data-diagram-src="data:text/plain;base64,Qm9iIC0+IFNhcmEgOiBIZWxsbw==">'
output = '<img src="http://localhost:8080/png/U9npoazIqBLJ24uiIbImKl18pSd91m0rkGMq" class="plantuml" data-diagram="plantuml" data-diagram-src="data:text/plain;base64,Qm9iIC0+IFNhcmEgOiBIZWxsbw==">'
doc = filter(input)
expect(doc.to_s).to eq output
@ -19,7 +19,7 @@ RSpec.describe Banzai::Filter::PlantumlFilter, feature_category: :markdown do
stub_application_setting(plantuml_enabled: true, plantuml_url: "http://localhost:8080")
input = '<pre><code data-canonical-lang="plantuml">Bob -> Sara : Hello</code></pre>'
output = '<img class="plantuml" src="http://localhost:8080/png/U9npoazIqBLJ24uiIbImKl18pSd91m0rkGMq" data-diagram="plantuml" data-diagram-src="data:text/plain;base64,Qm9iIC0+IFNhcmEgOiBIZWxsbw==">'
output = '<img src="http://localhost:8080/png/U9npoazIqBLJ24uiIbImKl18pSd91m0rkGMq" class="plantuml" data-diagram="plantuml" data-diagram-src="data:text/plain;base64,Qm9iIC0+IFNhcmEgOiBIZWxsbw==">'
doc = filter(input)
expect(doc.to_s).to eq output
@ -35,8 +35,17 @@ RSpec.describe Banzai::Filter::PlantumlFilter, feature_category: :markdown do
expect(doc.to_s).to eq output
end
it 'does not replace plantuml pre tag with img tag if url is invalid' do
stub_application_setting(plantuml_enabled: true, plantuml_url: "invalid")
it 'does not replace plantuml pre tag if there is no content' do
stub_application_setting(plantuml_enabled: true, plantuml_url: "http://localhost:8080")
input = '<pre><code data-canonical-lang="plantuml"></code></pre>'
doc = filter(input)
expect(doc.to_s).to eq input
end
it 'does not replace plantuml pre tag if the url is not valid' do
stub_application_setting(plantuml_enabled: true, plantuml_url: "invalid://localhost:8080")
input = '<pre data-canonical-lang="plantuml"><code>Bob -> Sara : Hello</code></pre>'
output = '<pre data-canonical-lang="plantuml"><code>Bob -&gt; Sara : Hello</code></pre>'

View File

@ -708,8 +708,22 @@ RSpec.describe Gitlab::Auth::AuthFinders, feature_category: :system_access do
end
describe '#find_oauth_access_token' do
let(:application) { Doorkeeper::Application.create!(name: 'MyApp', redirect_uri: 'https://app.com', owner: user) }
let(:doorkeeper_access_token) { Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: 'api', organization_id: organization.id) }
let(:scopes) { 'api' }
let(:application) do
Doorkeeper::Application.create!(
name: 'MyApp',
redirect_uri: 'https://app.com',
owner: user)
end
let(:doorkeeper_access_token) do
Doorkeeper::AccessToken.create!(
application_id: application.id,
resource_owner_id: user.id,
scopes: scopes,
organization_id: organization.id)
end
context 'passed as header' do
before do
@ -742,6 +756,48 @@ RSpec.describe Gitlab::Auth::AuthFinders, feature_category: :system_access do
expect { find_oauth_access_token }.to raise_error(Gitlab::Auth::UnauthorizedError)
end
end
context 'with composite identity', :request_store do
let(:user) { create(:user, username: 'gitlab-duo') }
before do
allow_any_instance_of(::User).to receive(:has_composite_identity?) do |user|
user.username == 'gitlab-duo'
end
set_bearer_token(doorkeeper_access_token.plaintext_token)
end
context 'when scoped user is specified' do
let(:scopes) { "user:#{user.id}" }
context 'when linking composite identitiy succeeds' do
it 'returns the oauth token' do
expect(find_oauth_access_token.token).to eq(doorkeeper_access_token.token)
end
end
context 'when linking composite identity raises an error' do
before do
allow(Gitlab::Auth::Identity).to(
receive(:link_from_oauth_token).and_raise(::Gitlab::Auth::Identity::IdentityLinkMismatchError)
)
end
it 'raises an error' do
expect { find_oauth_access_token }.to raise_error(::Gitlab::Auth::Identity::IdentityLinkMismatchError)
end
end
end
context 'when composite identity link can not be created' do
let(:scopes) { 'api' }
it 'raises an exception' do
expect { find_oauth_access_token }.to raise_error(Gitlab::Auth::UnauthorizedError)
end
end
end
end
describe '#find_personal_access_token_from_http_basic_auth' do

View File

@ -0,0 +1,94 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Auth::Identity, :request_store, feature_category: :system_access do
describe '.link_from_oauth_token' do
let_it_be(:actor_user) { create(:user) }
let_it_be(:delegating_user) { create(:user) }
let_it_be(:token_scopes) { [:api, :"user:#{delegating_user.id}"] }
let_it_be(:oauth_access_token) { create(:oauth_access_token, user: actor_user, scopes: token_scopes) }
subject(:identity) { described_class.link_from_oauth_token(oauth_access_token) }
context 'when composite identity is required for the actor' do
before do
allow(actor_user).to receive(:has_composite_identity?).and_return(true)
end
it 'returns an identity' do
expect(identity).to be_composite
expect(identity).to be_linked
expect(identity).to be_valid
expect(identity.scoped_user).to eq(delegating_user)
end
context 'when oauth token does not have required scopes' do
let(:oauth_access_token) { create(:oauth_access_token, user: actor_user, scopes: [:api]) }
it 'fabricates a composite identity which is not valid' do
expect(identity).to be_composite
expect(identity).not_to be_linked
expect(identity).not_to be_valid
end
end
context 'when an identity link was already done for a different composite user' do
let_it_be(:different_user) { create(:user) }
let_it_be(:new_token_scopes) { [:api, :"user:#{different_user.id}"] }
let_it_be(:new_oauth_access_token) { create(:oauth_access_token, user: actor_user, scopes: new_token_scopes) }
it 'raises an error' do
expect(identity).to be_valid
expect { described_class.link_from_oauth_token(new_oauth_access_token) }
.to raise_error(::Gitlab::Auth::Identity::IdentityLinkMismatchError)
end
end
context 'when actor user does not have composite identity enforced' do
before do
allow(actor_user).to receive(:has_composite_identity?).and_return(false)
end
context 'when token has composite user scope' do
it 'returns an identity' do
expect(identity).not_to be_composite
expect(identity).not_to be_linked
end
end
context 'when token does not have composite user scope' do
let_it_be(:token_scopes) { [:api] }
let_it_be(:oauth_access_token) { create(:oauth_access_token, user: actor_user, scopes: token_scopes) }
it 'returns an identity' do
expect(identity).not_to be_composite
expect(identity).not_to be_linked
end
end
end
end
context 'when composite identity is not required for the actor' do
it 'fabricates a valid identity' do
expect(identity).not_to be_composite
expect(identity).to be_valid
end
end
end
describe '.fabricate' do
let_it_be(:user) { create(:user) }
subject(:identity) { described_class.fabricate(user) }
it 'returns a valid identity without a scoped user' do
expect(identity).to be_valid
expect { identity.scoped_user }
.to raise_error(::Gitlab::Auth::Identity::MissingCompositeIdentityError)
end
end
end

View File

@ -14,7 +14,7 @@ RSpec.describe Gitlab::Auth::OAuth::IdentityLinker do
let!(:identity) { user.identities.create!(provider: provider, extern_uid: uid) }
it "doesn't create new identity" do
expect { subject.link }.not_to change { Identity.count }
expect { subject.link }.not_to change { ::Identity.count }
end
it "sets #changed? to false" do

View File

@ -8,7 +8,7 @@ RSpec.describe Gitlab::Database::Count::ExactCountStrategy do
create(:identity)
end
let(:models) { [Project, Identity] }
let(:models) { [::Project, ::Identity] }
subject { described_class.new(models).count }
@ -16,7 +16,7 @@ RSpec.describe Gitlab::Database::Count::ExactCountStrategy do
it 'counts all models' do
expect(models).to all(receive(:count).and_call_original)
expect(subject).to eq({ Project => 3, Identity => 1 })
expect(subject).to eq({ ::Project => 3, ::Identity => 1 })
end
it 'returns default value if count times out' do

View File

@ -10,7 +10,7 @@ RSpec.describe Gitlab::Database::Count::TablesampleCountStrategy do
create(:group)
end
let(:models) { [Project, Ci::InstanceVariable, Identity, Group, Namespace] }
let(:models) { [Project, Ci::InstanceVariable, ::Identity, Group, Namespace] }
let(:strategy) { described_class.new(models) }
subject { strategy.count }

View File

@ -8,7 +8,7 @@ RSpec.describe Gitlab::Database::Count do
create(:identity)
end
let(:models) { [Project, Identity] }
let(:models) { [::Project, ::Identity] }
describe '.approximate_counts' do
context 'fallbacks' do
@ -32,15 +32,15 @@ RSpec.describe Gitlab::Database::Count do
end
it 'gets more results from second strategy if some counts are missing' do
expect(first_strategy).to receive(:count).and_return({ Project => 3 })
expect(strategies[1]).to receive(:new).with([Identity]).and_return(second_strategy)
expect(second_strategy).to receive(:count).and_return({ Identity => 1 })
expect(first_strategy).to receive(:count).and_return({ ::Project => 3 })
expect(strategies[1]).to receive(:new).with([::Identity]).and_return(second_strategy)
expect(second_strategy).to receive(:count).and_return({ ::Identity => 1 })
expect(subject).to eq({ Project => 3, Identity => 1 })
expect(subject).to eq({ ::Project => 3, ::Identity => 1 })
end
it 'does not get more results as soon as all counts are present' do
expect(first_strategy).to receive(:count).and_return({ Project => 3, Identity => 1 })
expect(first_strategy).to receive(:count).and_return({ ::Project => 3, ::Identity => 1 })
expect(strategies[1]).not_to receive(:new)
subject

View File

@ -6,7 +6,7 @@ RSpec.describe ::Gitlab::Graphql::Authorize::ObjectAuthorization do
describe '#ok?' do
subject(:authorization) { described_class.new(%i[go_fast go_slow]) }
let(:user) { double(:User, id: 10001) }
let_it_be(:user) { create(:user) }
let(:scope_validator) { instance_double(::Gitlab::Auth::ScopeValidator, valid_for?: true) }
let(:policy) do

View File

@ -35,7 +35,7 @@ RSpec.describe Gitlab::OtherMarkup, feature_category: :wiki do
RST
output = <<~HTML
<img class="plantuml" src="https://plantuml.com/plantuml/png/U9npoazIqBLJSCp9J4wrKiX8pSd9vm9pGA9E-Kb0iKm0o4SAt000" data-diagram="plantuml" data-diagram-src="data:text/plain;base64,Qm9iIC0+IEFsaWNlOiBoZWxsbwpBbGljZSAtPiBCb2I6IGhp">
<img src="https://plantuml.com/plantuml/png/U9npoazIqBLJSCp9J4wrKiX8pSd9vm9pGA9E-Kb0iKm0o4SAt000" class="plantuml" data-diagram="plantuml" data-diagram-src="data:text/plain;base64,Qm9iIC0+IEFsaWNlOiBoZWxsbwpBbGljZSAtPiBCb2I6IGhp">
<p>Caption with <strong>bold</strong> and <em>italic</em></p>
HTML

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Ability do
RSpec.describe Ability, feature_category: :system_access do
describe '#policy_for' do
subject(:policy) { described_class.policy_for(user, subject, **options) }
@ -476,6 +476,89 @@ RSpec.describe Ability do
end
end
describe '.allowed?' do
let_it_be(:group) { create(:group, :private) }
let_it_be(:user) { create(:user) }
let_it_be(:delegated_user) { create(:user) }
let(:request_store_key) { format(::Gitlab::Auth::Identity::COMPOSITE_IDENTITY_KEY_FORMAT, user.id) }
subject { described_class.allowed?(user, :read_group, group) }
context 'with composite identity', :request_store do
before do
::Gitlab::SafeRequestStore[request_store_key] = delegated_user
allow(user).to receive(:has_composite_identity?).and_return(true)
end
context 'when both users are members' do
before_all do
group.add_developer(delegated_user)
group.add_developer(user)
end
it 'returns true' do
expect(subject).to be_truthy
end
context 'when delegating user is not set in RequestStore' do
before do
::Gitlab::SafeRequestStore.delete(request_store_key)
end
it 'raises an exception' do
expect { subject }.to raise_exception(::Gitlab::Auth::Identity::MissingCompositeIdentityError)
end
end
end
context 'when only delegating user is a member' do
before_all do
group.add_developer(user)
end
it 'returns false' do
expect(subject).to be_falsey
end
context 'with disabled composite_identity feature flag' do
before do
stub_feature_flags(composite_identity: false)
end
it 'returns true' do
expect(subject).to be_truthy
end
end
context 'with unenforced composite identity' do
before do
allow(user).to receive(:has_composite_identity?).and_return(false)
end
it 'returns true' do
expect(subject).to be_truthy
end
end
end
context 'when only delegated user is a member' do
before_all do
group.add_developer(delegated_user)
end
it 'returns false' do
expect(subject).to be_falsey
end
end
context 'when neither user is a member' do
it 'returns false' do
expect(subject).to be_falsey
end
end
end
end
describe 'forgetting', :request_store do
it 'allows us to discard specific values from the DeclarativePolicy cache' do
user_a = build_stubbed(:user)

View File

@ -14,6 +14,16 @@ RSpec.describe NotificationRecipient, feature_category: :team_planning do
describe '#notifiable?' do
let(:recipient) { described_class.new(user, :mention, target: target, project: project) }
context 'when user has a composite identity' do
before do
allow(recipient).to receive(:has_composite_identity?).and_return(true)
end
it 'returns false' do
expect(recipient.notifiable?).to eq false
end
end
context 'when emails are disabled' do
it 'returns false if group disabled' do
expect(project.namespace).to receive(:emails_enabled?).and_return(false)

View File

@ -77,4 +77,66 @@ RSpec.describe OauthAccessToken, feature_category: :system_access do
end
end
end
describe '#scope_user' do
let_it_be(:user) { create(:user) }
let_it_be_with_refind(:oauth_access_token) { create(:oauth_access_token) }
let(:user_id) { user.id }
before do
allow(oauth_access_token).to receive(:scopes).and_return(scopes)
end
context 'when scopes match expected format' do
context 'when scopes only include the composite scope' do
let(:scopes) { "user:#{user_id}" }
it 'returns the user' do
expect(oauth_access_token.scope_user).to eq user
end
end
context 'when scopes include another scope before composite scope' do
let(:scopes) { "other:scope user:#{user_id}" }
it 'returns the user' do
expect(oauth_access_token.scope_user).to eq user
end
end
context "when scopes include another scope after composite scope" do
let(:scopes) { "user:#{user_id} other:scope" }
it 'returns the user' do
expect(oauth_access_token.scope_user).to eq user
end
end
context 'when scopes include another scope before and after composite scope' do
let(:scopes) { "api user:#{user_id} read_api" }
it 'returns the user' do
expect(oauth_access_token.scope_user).to eq user
end
end
end
context 'when scopes do not match composite scope format' do
where(:scopes) do
[
"user:#{non_existing_record_id}",
'user:not_a_number',
'some:other:scope',
nil,
""
]
end
with_them do
it 'returns false' do
expect(oauth_access_token.scope_user).to be_nil
end
end
end
end
end

View File

@ -8828,6 +8828,12 @@ RSpec.describe User, feature_category: :user_profile do
end
end
describe '#has_composite_identity?' do
it 'is false' do
expect(build(:user).has_composite_identity?).to be false
end
end
describe 'color_mode_id' do
context 'when theme_id is 11' do
let(:user) { build(:user, theme_id: 11) }

View File

@ -8,6 +8,44 @@ RSpec.describe 'doorkeeper access', feature_category: :system_access do
let!(:application) { Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user) }
let!(:token) { Doorkeeper::AccessToken.create! application_id: application.id, resource_owner_id: user.id, scopes: "api", organization_id: organization.id }
describe 'access token with composite identity scope', :request_store do
let!(:scopes) { "user:#{scope_user.id} api" }
let!(:scoped_token) { OauthAccessToken.create! application_id: application.id, user: user, scopes: scopes, organization_id: organization.id }
let(:scope_user) { create(:user) }
let(:group) { create(:group, :private) }
let(:user) do
user = create(:user)
allow(user).to receive(:has_composite_identity?).and_return(true)
user
end
context 'when user one has a composite identity token scoped to user two' do
before do
group.add_developer(user) # scoped user doesn't have access
allow(OauthAccessToken).to receive(:by_token).and_return(scoped_token) # this is required for the has_composite_identity? stub to work
end
it 'restricts user access permissions' do
get api("/groups/#{group.id}"), params: { access_token: scoped_token.plaintext_token }
expect(response).to have_gitlab_http_status(:not_found)
end
context 'when both users have access to a resource' do
before do
group.add_developer(scope_user)
end
it 'allows access' do
get api("/groups/#{group.id}"), params: { access_token: token.plaintext_token }
expect(response).to have_gitlab_http_status(:ok)
end
end
end
end
describe "unauthenticated" do
it "returns authentication success" do
get api("/user"), params: { access_token: token.plaintext_token }

View File

@ -214,7 +214,7 @@ RSpec.describe API::VirtualRegistries::Packages::Maven, :aggregate_failures, fea
group.add_maintainer(user)
end
it 'returns a bad request beacuse it is not a top level group' do
it 'returns a bad request because it is not a top level group' do
api_request
expect(response).to have_gitlab_http_status(:bad_request)

View File

@ -211,8 +211,7 @@ RSpec.describe ::Ci::Runners::SetRunnerAssociatedProjectsService, '#execute', fe
let(:new_projects) { [project3, project4] }
let(:owner_project) { new_projects.first }
it 'assigns associated projects and returns success response',
quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/481411' do
it 'assigns associated projects and returns success response' do
expect(execute).to be_success
runner.reload

View File

@ -535,6 +535,8 @@ module TestEnv
end
def component_ahead_of_target?(component_folder, expected_version)
return false unless Dir.exist?(File.join(component_folder, '.git'))
# The HEAD of the component_folder will be used as heuristic for the version
# of the binaries, allowing to use Git to determine if HEAD is later than
# the expected version. Note: Git considers HEAD to be an anchestor of HEAD.
@ -556,6 +558,8 @@ module TestEnv
return false unless Dir.exist?(component_folder)
return false unless Dir.exist?(File.join(component_folder, '.git'))
sha, exit_status = Gitlab::Popen.popen(%W[#{Gitlab.config.git.bin_path} rev-parse HEAD], component_folder)
return false if exit_status != 0

View File

@ -299,7 +299,7 @@ module MarkdownMatchers
end
end
# PLantumlFilter
# PlantumlFilter
matcher :parse_plantuml do
set_default_markdown_messages

103
yarn.lock
View File

@ -2502,76 +2502,63 @@
resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8"
integrity sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==
"@sentry-internal/browser-utils@8.38.0":
version "8.38.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.38.0.tgz#d7f6d398778906efb0c017e63d3c59d3203dfa7d"
integrity sha512-5QMVcssrAcmjKT0NdFYcX0b0wwZovGAZ9L2GajErXtHkBenjI2sgR2+5J7n+QZGuk2SC1qhGmT1O9i3p3UEwew==
"@sentry-internal/browser-utils@8.41.0":
version "8.41.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.41.0.tgz#9dc30a8c88aa6e1e542e5acae29ceabd1b377cc4"
integrity sha512-nU7Bn3jEUmf1QXRUT3j2ewUBlFJpe9vnAnjqpeVPDWTsVI52BwVNcJHuE37PrGs66OZ1ZkGMfKnQk43oCAa+oQ==
dependencies:
"@sentry/core" "8.38.0"
"@sentry/types" "8.38.0"
"@sentry/utils" "8.38.0"
"@sentry/core" "8.41.0"
"@sentry/types" "8.41.0"
"@sentry-internal/feedback@8.38.0":
version "8.38.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-8.38.0.tgz#726661a01f7ff40b93c8ee05c985fd0436a1c033"
integrity sha512-AW5HCCAlc3T1jcSuNhbFVNO1CHyJ5g5tsGKEP4VKgu+D1Gg2kZ5S2eFatLBUP/BD5JYb1A7p6XPuzYp1XfMq0A==
"@sentry-internal/feedback@8.41.0":
version "8.41.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-8.41.0.tgz#9c3c95e6f7738a0d00fcb89061c284baef313ba2"
integrity sha512-bw+BrSNw8abOnu/IpD8YSbYubXkkT8jyNS7TM4e4UPZMuXcbtia7/r5d7kAiUfKv/sV5PNMlZLOk+EYJeLTANg==
dependencies:
"@sentry/core" "8.38.0"
"@sentry/types" "8.38.0"
"@sentry/utils" "8.38.0"
"@sentry/core" "8.41.0"
"@sentry/types" "8.41.0"
"@sentry-internal/replay-canvas@8.38.0":
version "8.38.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-8.38.0.tgz#26e9bc937dab73e1a26d57dc1015b7ff1f2d76c5"
integrity sha512-OxmlWzK9J8mRM+KxdSnQ5xuxq+p7TiBzTz70FT3HltxmeugvDkyp6803UcFqHOPHR35OYeVLOalym+FmvNn9kw==
"@sentry-internal/replay-canvas@8.41.0":
version "8.41.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-8.41.0.tgz#9da984adc54fcd8ebe07cbbc13132fa78396dd01"
integrity sha512-lpgOBHWr1ZNxidD72A2pfoUMjIpwonOPYoQZWAHr86Oa3eIVQOyfklZlHW+gKPFl2/IEl9Lbtcke0JiDp3dkIQ==
dependencies:
"@sentry-internal/replay" "8.38.0"
"@sentry/core" "8.38.0"
"@sentry/types" "8.38.0"
"@sentry/utils" "8.38.0"
"@sentry-internal/replay" "8.41.0"
"@sentry/core" "8.41.0"
"@sentry/types" "8.41.0"
"@sentry-internal/replay@8.38.0":
version "8.38.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-8.38.0.tgz#9a9b945a3c066f5610a363774e3c99420c3f4fce"
integrity sha512-mQPShKnIab7oKwkwrRxP/D8fZYHSkDY+cvqORzgi+wAwgnunytJQjz9g6Ww2lJu98rHEkr5SH4V4rs6PZYZmnQ==
"@sentry-internal/replay@8.41.0":
version "8.41.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-8.41.0.tgz#b1112a52a0cf1727589ad4d42a8fac9f98f6d733"
integrity sha512-ByXEY7JI95y4Qr9fS3d28l9uuVU5Qa0HgL+xDmYElNx7CXz3Q9hFN6ibgUeC3h8BO5pDULxWNgAppl7FRY8N5w==
dependencies:
"@sentry-internal/browser-utils" "8.38.0"
"@sentry/core" "8.38.0"
"@sentry/types" "8.38.0"
"@sentry/utils" "8.38.0"
"@sentry-internal/browser-utils" "8.41.0"
"@sentry/core" "8.41.0"
"@sentry/types" "8.41.0"
"@sentry/browser@8.38.0":
version "8.38.0"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-8.38.0.tgz#c562accdc2bbe0b0074d98bfe7ff460e39ce3109"
integrity sha512-AZR+b0EteNZEGv6JSdBD22S9VhQ7nrljKsSnzxobBULf3BpwmhmCzTbDrqWszKDAIDYmL+yQJIR2glxbknneWQ==
"@sentry/browser@8.41.0":
version "8.41.0"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-8.41.0.tgz#f594012e6377a92db72127ef39aee812efe3a3b7"
integrity sha512-FfAU55eYwW2lG4M3dEw2472RvHrD5YWSfHCZvuRf/4skX38kFvKghZQ+epL+CVHTzvIRHOrbj8qQK6YLTGl9ew==
dependencies:
"@sentry-internal/browser-utils" "8.38.0"
"@sentry-internal/feedback" "8.38.0"
"@sentry-internal/replay" "8.38.0"
"@sentry-internal/replay-canvas" "8.38.0"
"@sentry/core" "8.38.0"
"@sentry/types" "8.38.0"
"@sentry/utils" "8.38.0"
"@sentry-internal/browser-utils" "8.41.0"
"@sentry-internal/feedback" "8.41.0"
"@sentry-internal/replay" "8.41.0"
"@sentry-internal/replay-canvas" "8.41.0"
"@sentry/core" "8.41.0"
"@sentry/types" "8.41.0"
"@sentry/core@8.38.0":
version "8.38.0"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-8.38.0.tgz#5d1b74770c79e489e786018a3e514cddeb777bcb"
integrity sha512-sGD+5TEHU9G7X7zpyaoJxpOtwjTjvOd1f/MKBrWW2vf9UbYK+GUJrOzLhMoSWp/pHSYgvObkJkDb/HwieQjvhQ==
"@sentry/core@8.41.0":
version "8.41.0"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-8.41.0.tgz#e8a25cacd25fe4358f3179e85f3c2697427fe5a6"
integrity sha512-3v7u3t4LozCA5SpZY4yqUN2U3jSrkXNoLgz6L2SUUiydyCuSwXZIFEwpLJfgQyidpNDifeQbBI5E1O910XkPsA==
dependencies:
"@sentry/types" "8.38.0"
"@sentry/utils" "8.38.0"
"@sentry/types" "8.41.0"
"@sentry/types@8.38.0":
version "8.38.0"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-8.38.0.tgz#9c48734a8b4055bfd553a0141efec78e9680ed09"
integrity sha512-fP5H9ZX01W4Z/EYctk3mkSHi7d06cLcX2/UWqwdWbyPWI+pL2QpUPICeO/C+8SnmYx//wFj3qWDhyPCh1PdFAA==
"@sentry/utils@8.38.0":
version "8.38.0"
resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-8.38.0.tgz#2f91ca7d044f6e17b993c866ca02a981c4c1bc25"
integrity sha512-3X7MgIKIx+2q5Al7QkhaRB4wV6DvzYsaeIwdqKUzGLuRjXmNgJrLoU87TAwQRmZ6Wr3IoEpThZZMNrzYPXxArw==
dependencies:
"@sentry/types" "8.38.0"
"@sentry/types@8.41.0":
version "8.41.0"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-8.41.0.tgz#db40c93bcedad26569c5dfe10a4b31253c349a10"
integrity sha512-eqdnGr9k9H++b9CjVUoTNUVahPVWeNnMy0YGkqS5+cjWWC+x43p56202oidGFmWo6702ub/xwUNH6M5PC4kq6A==
"@sinclair/typebox@^0.27.8":
version "0.27.8"