Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2025-07-08 21:11:05 +00:00
parent ba0514ff4e
commit 26510a4da3
73 changed files with 558 additions and 145 deletions

View File

@ -92,7 +92,7 @@ class ProfilesController < Profiles::ApplicationController
:location,
:mastodon,
:name,
:organization,
:user_detail_organization,
:private_profile,
:pronouns,
:pronunciation,

View File

@ -57,7 +57,7 @@ module UserSettings
:mastodon,
:name,
:orcid,
:organization,
:user_detail_organization,
:private_profile,
:pronouns,
:pronunciation,

View File

@ -220,7 +220,8 @@ module Types
field :organization,
type: ::GraphQL::Types::String,
null: true,
description: 'Who the user represents or works for.'
description: 'Who the user represents or works for.',
method: :user_detail_organization
field :job_title,
type: ::GraphQL::Types::String,

View File

@ -84,7 +84,7 @@ module ProfilesHelper
pronunciation: user.pronunciation,
website_url: user.website_url,
job_title: user.job_title,
organization: user.organization,
organization: user.user_detail_organization,
bio: user.bio,
include_private_contributions: user.include_private_contributions?.to_s,
achievements_enabled: user.achievements_enabled.to_s,

View File

@ -148,7 +148,7 @@ module UsersHelper
def work_information(user, with_schema_markup: false)
return unless user
organization = user.organization
organization = user.user_detail_organization
job_title = user.job_title
if organization.present? && job_title.present?

View File

@ -169,6 +169,7 @@ module Ci
)
end
# TODO: Remove this scope when FF `ci_stop_using_has_exposed_artifacts_metadata_col` is removed
scope :with_exposed_artifacts, -> do
joins(:metadata).merge(Ci::BuildMetadata.with_exposed_artifacts)
.includes(:metadata, :job_artifacts_metadata)

View File

@ -39,6 +39,7 @@ module Ci
end
scope :with_interruptible, -> { where(interruptible: true) }
# TODO: Remove this scope when FF `ci_stop_using_has_exposed_artifacts_metadata_col` is removed
scope :with_exposed_artifacts, -> { where(has_exposed_artifacts: true) }
enum :timeout_source, {

View File

@ -1280,7 +1280,11 @@ module Ci
end
def has_exposed_artifacts?
complete? && builds.latest.with_exposed_artifacts.exists?
if Feature.enabled?(:ci_stop_using_has_exposed_artifacts_metadata_col, project)
complete? && builds.latest.any_with_exposed_artifacts?
else
complete? && builds.latest.with_exposed_artifacts.exists?
end
end
def has_erasable_artifacts?

View File

@ -29,10 +29,41 @@ module Ci
scope :with_project_and_metadata, -> do
joins(:metadata).includes(:metadata).preload(:project)
end
def self.any_with_exposed_artifacts?
found_exposed_artifacts = false
# TODO: Remove :project preload when FF `ci_stop_using_has_exposed_artifacts_metadata_col` is removed
includes(:project).each_batch do |batch|
# We only load what we need for `has_exposed_artifacts?`
records = batch.select(:id, :partition_id, :project_id, :options).to_a
ActiveRecord::Associations::Preloader.new(
records: records,
associations: :metadata,
scope: Ci::BuildMetadata.select(:build_id, :partition_id, :config_options)
).call
next unless records.any?(&:has_exposed_artifacts?)
found_exposed_artifacts = true
break
end
found_exposed_artifacts
end
def self.select_with_exposed_artifacts
includes(:metadata, :job_artifacts_metadata, :project).select(&:has_exposed_artifacts?)
end
end
def has_exposed_artifacts?
!!metadata&.has_exposed_artifacts?
if Feature.enabled?(:ci_stop_using_has_exposed_artifacts_metadata_col, project)
options.dig(:artifacts, :expose_as).present?
else
!!metadata&.has_exposed_artifacts?
end
end
def ensure_metadata
@ -63,6 +94,7 @@ module Ci
def options=(value)
write_metadata_attribute(:options, :config_options, value)
# TODO: Remove code below when FF `ci_stop_using_has_exposed_artifacts_metadata_col` is removed
ensure_metadata.tap do |metadata|
# Store presence of exposed artifacts in build metadata to make it easier to query
metadata.has_exposed_artifacts = value&.dig(:artifacts, :expose_as).present?

View File

@ -471,7 +471,7 @@ class User < ApplicationRecord
delegate :twitter, :twitter=, to: :user_detail, allow_nil: true
delegate :website_url, :website_url=, to: :user_detail, allow_nil: true
delegate :location, :location=, to: :user_detail, allow_nil: true
delegate :organization, :organization=, to: :user_detail, allow_nil: true
delegate :organization, :organization=, to: :user_detail, prefix: true, allow_nil: true
delegate :discord, :discord=, to: :user_detail, allow_nil: true
delegate :github, :github=, to: :user_detail, allow_nil: true
delegate :email_reset_offered_at, :email_reset_offered_at=, to: :user_detail, allow_nil: true

View File

@ -15,12 +15,25 @@ module Ci
def for_pipeline(pipeline, limit: MAX_EXPOSED_ARTIFACTS)
results = []
pipeline.builds.latest.with_exposed_artifacts.find_each do |job|
if job_exposed_artifacts = for_job(job)
results << job_exposed_artifacts
end
if Feature.enabled?(:ci_stop_using_has_exposed_artifacts_metadata_col, project)
pipeline.builds.latest.each_batch do |job_batch|
job_batch.select_with_exposed_artifacts.each do |job|
exposed_artifacts = for_job(job)
results << exposed_artifacts if exposed_artifacts
break if results.size >= limit
break if results.size >= limit
end
break if results.size >= limit
end
else
pipeline.builds.latest.with_exposed_artifacts.find_each do |job|
if job_exposed_artifacts = for_job(job)
results << job_exposed_artifacts
end
break if results.size >= limit
end
end
results

View File

@ -195,7 +195,7 @@ module Users
:location,
:name,
:note,
:organization,
:user_detail_organization,
:password,
:password_automatically_set,
:password_expires_at,

View File

@ -107,10 +107,10 @@
.form-text.gl-text-subtle
= s_("Profiles|Your job title was automatically set based on your %{provider_label} account") % { provider_label: attribute_provider_label(:job_title) }
.form-group.gl-form-group
= f.label :organization, s_('Profiles|Organization')
= f.text_field :organization, class: 'gl-form-input form-control gl-md-form-input-lg', readonly: @user.read_only_attribute?(:organization)
= f.label :user_detail_organization, s_('Profiles|Organization')
= f.text_field :user_detail_organization, class: 'gl-form-input form-control gl-md-form-input-lg', readonly: @user.read_only_attribute?(:user_detail_organization)
.form-text.gl-text-subtle
- if @user.read_only_attribute?(:organization)
- if @user.read_only_attribute?(:user_detail_organization)
= s_("Profiles|Your organization was automatically set based on your %{provider_label} account") % { provider_label: attribute_provider_label(:organization) }
- else
= s_("Profiles|Who you represent or work for.")

View File

@ -0,0 +1,10 @@
---
name: policy_yaml_invalidated
description: The policy YAML is invalidated in security policy project
introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/work_items/550892
introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/196721
feature_category: security_policy_management
milestone: '18.2'
saved_to_database: true
streamed: true
scope: [Project]

View File

@ -0,0 +1,13 @@
---
name: ci_stop_using_has_exposed_artifacts_metadata_col
description: >
Updates code to stop using p_ci_builds_metadata.has_exposed_artifacts. This means that
finding exposed artifacts requires iterating through all latest builds of a pipeline,
not just ones with has_exposed_artifacts = true.
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/work_items/550223
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/195693
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/551510
milestone: '18.2'
group: group::ci platform
type: gitlab_com_derisk
default_enabled: false

View File

@ -0,0 +1,8 @@
---
migration_job_name: BackfillAnalyzerProjectStatusesFromProjectSecuritySettings
description: Backfills AnalyzerProjectStatuses from ProjectSecuritySettings based on the container_scanning_for_registry_enabled and secret_push_protection_enabled columns
feature_category: security_asset_inventories
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/195569
milestone: '18.2'
queued_migration_version: 20250625184737
finalized_by:

View File

@ -5,4 +5,4 @@ feature_category: source_code_management
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/183124
milestone: '17.10'
queued_migration_version: 20250301123510
finalized_by: # version of the migration that finalized this BBM
finalized_by: '20250707162531'

View File

@ -0,0 +1,35 @@
# frozen_string_literal: true
# See https://docs.gitlab.com/ee/development/database/batched_background_migrations.html
# for more information on when/how to queue batched background migrations
# Update below commented lines with appropriate values.
class QueueBackfillAnalyzerProjectStatusesFromProjectSecuritySettings < Gitlab::Database::Migration[2.3]
milestone '18.2'
# Select the applicable gitlab schema for your batched background migration
# the project_security_settings are on gitlab_main
# but analyzer_project_statuses is on gitlab_sec
restrict_gitlab_migration gitlab_schema: :gitlab_main
MIGRATION = "BackfillAnalyzerProjectStatusesFromProjectSecuritySettings"
DELAY_INTERVAL = 2.minutes
BATCH_SIZE = 1000
SUB_BATCH_SIZE = 100
def up
queue_batched_background_migration(
MIGRATION,
:project_security_settings,
:project_id,
job_interval: DELAY_INTERVAL,
batch_size: BATCH_SIZE,
sub_batch_size: SUB_BATCH_SIZE
)
end
def down
delete_batched_background_migration(MIGRATION, :project_security_settings, :project_id, [])
end
end

View File

@ -0,0 +1,20 @@
# frozen_string_literal: true
class FinalizeBackfillSnippetStatisticsSnippetOrganizationId < Gitlab::Database::Migration[2.3]
milestone '18.2'
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_main_cell
def up
ensure_batched_background_migration_is_finished(
job_class_name: 'BackfillSnippetStatisticsSnippetOrganizationId',
table_name: :snippet_statistics,
column_name: :snippet_id,
job_arguments: [:snippet_organization_id, :snippets, :organization_id, :snippet_id],
finalize: true
)
end
def down; end
end

View File

@ -0,0 +1 @@
aba9fc447811624de352d725b46c1e065ca6eb00fa55e7933cff07f27703647b

View File

@ -0,0 +1 @@
43d2f3d57a2b26945bf37508b98e14f135acd68dace467d4756e4bfe31034d93

View File

@ -52,16 +52,34 @@ GitLab Dedicated leverages GitLab Geo for [disaster recovery](../../subscription
Geo does not use an active-active failover configuration. For more information, see [Geo](../geo/_index.md).
### AWS PrivateLink connection (optional)
### AWS PrivateLink connection
>Required for Geo migrations to Dedicated. Otherwise, optional
Optionally, private connectivity is available for your GitLab Dedicated instance, using [AWS PrivateLink](https://aws.amazon.com/privatelink/) as a connection gateway.
Both [inbound](configure_instance/network_security.md#inbound-private-link) and [outbound](configure_instance/network_security.md#outbound-private-link) private links are supported.
![Diagram of a GitLab-managed AWS VPC using AWS PrivateLink to connect with a customer-managed AWS VPC.](img/privatelink_diagram_v17_1.png)
#### Inbound
![Diagram of a GitLab-managed AWS VPC using inbound AWS PrivateLink to connect with a customer-managed AWS VPC.](img/privatelink_inbound_v18_0.png)
GitLab team members with edit access can update the [source](https://lucid.app/lucidchart/933b958b-bfad-4898-a8ae-182815f159ca/edit?invitationId=inv_38b9a265-dff2-4db6-abdb-369ea1e92f5f) files for the diagram in Lucidchart.
#### Outbound
![Diagram of a GitLab-managed AWS VPC using outbound AWS PrivateLink to connect with a customer-managed AWS VPC.](img/privatelink_outbound_v18_0.png)
GitLab team members with edit access can update the [source](https://lucid.app/lucidchart/5aeae97e-a3c4-43e3-8b9d-27900d944147/edit?invitationId=inv_0e4fee9f-cf63-439c-9bf9-71ecbfbd8979&page=F5pcfQybsAYU8#) files for the diagram in Lucidchart.
#### AWS PrivateLink for migration
Additionally, AWS PrivateLink is also used for migration purposes. The customer's Dedicated GitLab instance can use AWS PrivateLink to pull data for a migration to GitLab Dedicated.
![Diagram of a simplified Dedicated Geo setup.](img/dedicated_geo_simplified_v18_0.png)
GitLab team members with edit access can update the [source](https://lucid.app/lucidchart/1e83e102-37b3-48a9-885d-e72122683bce/edit?view_items=AzvnMfovRJe3p&invitationId=inv_c02140dd-416b-41b5-b14a-7288b54bb9b5) files for the diagram in Lucidchart.
## Hosted runners for GitLab Dedicated
The following diagram illustrates a GitLab-managed AWS account that contains GitLab runners, which are interconnected to a GitLab Dedicated instance, the public internet, and optionally a customer AWS account that uses AWS PrivateLink.

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

View File

@ -38995,6 +38995,7 @@ Compliance violation for a project.
| <a id="projectcomplianceviolationcreatedat"></a>`createdAt` | [`Time!`](#time) | Timestamp when the violation was detected. |
| <a id="projectcomplianceviolationdiscussions"></a>`discussions` | [`DiscussionConnection!`](#discussionconnection) | All discussions on the noteable. (see [Connections](#connections)) |
| <a id="projectcomplianceviolationid"></a>`id` | [`ID!`](#id) | Compliance violation ID. |
| <a id="projectcomplianceviolationissues"></a>`issues` | [`IssueConnection`](#issueconnection) | Project issues linked to the violation. (see [Connections](#connections)) |
| <a id="projectcomplianceviolationproject"></a>`project` | [`Project!`](#project) | Project of the compliance violation. |
| <a id="projectcomplianceviolationstatus"></a>`status` | [`ComplianceViolationStatus!`](#complianceviolationstatus) | Compliance violation status of the project. |

View File

@ -45813,12 +45813,12 @@ definitions:
type: string
github:
type: string
organization:
type: string
job_title:
type: string
pronouns:
type: string
organization:
type: string
bot:
type: string
work_information:
@ -50218,12 +50218,12 @@ definitions:
type: string
github:
type: string
organization:
type: string
job_title:
type: string
pronouns:
type: string
organization:
type: string
bot:
type: string
work_information:
@ -65529,12 +65529,12 @@ definitions:
type: string
github:
type: string
organization:
type: string
job_title:
type: string
pronouns:
type: string
organization:
type: string
bot:
type: string
work_information:

View File

@ -159,7 +159,7 @@ Instead of:
Use lowercase for **advanced search** to refer to the faster, more efficient search across the entire GitLab instance.
## agent
## agent for Kubernetes
Use lowercase to refer to the [GitLab agent for Kubernetes](https://gitlab.com/gitlab-org/cluster-integration/gitlab-agent).
For example:
@ -198,6 +198,17 @@ Instead of **agnostic**, use **platform-independent** or **vendor-neutral**.
Use **AI**. Do not spell out **artificial intelligence**.
## AI agent
When writing about AI, the **AI agent** is an entity that performs actions for the user.
After first use, you can use **agent** without **AI**.
When you're interacting with an AI agent, a [**session**](#session) is running.
The user can stop a session.
One or more AI agents can be part of a [**flow**](#flows), where they are orchestrated to work together on a problem.
## AI gateway
Use lowercase for **AI gateway** and do not hyphenate.
@ -1052,6 +1063,13 @@ the available attributes. For example, you might filter by assignee or reviewer.
Filtering is different from [searching](#search).
## flows
GitLab provides multiple **flows** that are run by [AI agents](#ai-agent).
Both **flow** and **agent flow** are acceptable.
You choose a flow. You start a [**session**](#session).
## foo
Do not use **foo** in product documentation. You can use it in our API and contributor documentation, but try to use a clearer and more meaningful example instead.
@ -2311,6 +2329,11 @@ See [GitLab Self-Managed](#gitlab-self-managed).
Use title case for **Service Desk**.
## session
When an [AI agent](#ai-agent) is working on a [**flow**](#flows), a **session** is running.
The session can start and stop.
## setup, set up
Use **setup** as a noun, and **set up** as a verb. For example:

View File

@ -5,6 +5,12 @@ info: To determine the technical writer assigned to the Stage/Group associated w
title: Configure GitLab as an OAuth 2.0 authentication identity provider
---
{{< history >}}
- Group SAML SSO support for OAuth applications [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/461212) in GitLab 18.2 [with a flag](../administration/feature_flags/_index.md) named `ff_oauth_redirect_to_sso_login`. Disabled by default.
{{< /history >}}
[OAuth 2.0](https://oauth.net/2/) provides secure delegated server resource
access to client applications on behalf of a resource owner. OAuth 2 allows
authorization servers to issue access tokens to third-party clients with the approval
@ -24,9 +30,10 @@ You can use a non-SSL URL instead, but you should use an SSL URL.
After adding an OAuth 2 application to an instance, you can use OAuth 2 to:
- Enable users to sign in to your application with their GitLab.com account.
- Enable users to sign in to your application using [SAML SSO](../user/group/saml_sso/_index.md)
when SAML is configured for the associated group.
- Set up GitLab.com for authentication to your GitLab instance. For more information,
see [integrating your server with GitLab.com](gitlab.md).
- After an application is created, external services can manage access tokens using the
[OAuth 2 API](../api/oauth2.md).

View File

@ -211,7 +211,7 @@ This section lists the features that are not available for GitLab Dedicated.
The following GitLab application features are not available:
- LDAP, smart card, or Kerberos authentication
- LDAP (Git clone with username / password), smart card, or Kerberos authentication
- Multiple login providers
- FortiAuthenticator or FortiToken 2FA
- Reply by email
@ -222,6 +222,7 @@ The following GitLab application features are not available:
- Features other than [available features](#available-features) that must be configured outside of the GitLab user interface
- Any functionality or feature behind a feature flag that is turned `off` by default
- [Sigstore for keyless signing and verification](../../ci/yaml/signing_examples.md)
- OAuth providers outside of standard SAML
The following features are not supported:

View File

@ -76,7 +76,7 @@ The **Metric trends** table displays metrics for the last six months, with month
### AI usage metrics
**Code Suggestions usage**: Monthly user engagement with AI Code Suggestions.
**Code Suggestions: Usage**: Monthly user engagement with AI Code Suggestions.
On GitLab.com, data updates every fives minutes.
GitLab counts Code Suggestions usage only if the user has pushed code to the project in the current month.

View File

@ -154,8 +154,8 @@ This method gives information about dependencies which are direct.
To enable the CI/CD component on a Gradle project:
1. Edit the `build.gradle` or `build.gradle.kts` to use the
[gradle-dependency-lock-plugin](https://github.com/nebula-plugins/gradle-dependency-lock-plugin/wiki/Usage#example).
1. Configure the `.gitlab-ci.yml` file to generate the `dependencies.lock` artifacts, and pass them
[gradle-dependency-lock-plugin](https://github.com/nebula-plugins/gradle-dependency-lock-plugin/wiki/Usage#example) or use an init script.
1. Configure the `.gitlab-ci.yml` file to generate the `dependencies.lock` and `dependencies.direct.lock` artifacts, and pass them
to the `dependency-scanning` job.
The following example demonstrates how to configure the component
@ -166,30 +166,44 @@ stages:
- build
- test
# Define the image that contains Java and Gradle
image: gradle:8.0-jdk11
include:
- component: $CI_SERVER_FQDN/components/dependency-scanning/main@0
build:
generate nebula lockfile:
# Running in the build stage ensures that the dependency-scanning job
# receives the dependencies.lock artifacts.
# receives the scannable artifacts.
stage: build
script:
- gradle generateLock saveLock
- gradle assemble
# generateLock saves the lock file in the build/ directory of a project
# and saveLock copies it into the root of a project. To avoid duplicates
# and get an accurate location of the dependency, use find to remove the
# lock files in the build/ directory only.
- |
cat << EOF > nebula.gradle
initscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.netflix.nebula:gradle-dependency-lock-plugin:12.7.1'
}
}
allprojects {
apply plugin: nebula.plugin.dependencylock.DependencyLockPlugin
}
EOF
./gradlew --init-script nebula.gradle -PdependencyLock.includeTransitives=true -PdependencyLock.lockFile=dependencies.lock generateLock saveLock
./gradlew --init-script nebula.gradle -PdependencyLock.includeTransitives=false -PdependencyLock.lockFile=dependencies.direct.lock generateLock saveLock
# generateLock saves the lock file in the build/ directory of a project
# and saveLock copies it into the root of a project. To avoid duplicates
# and get an accurate location of the dependency, use find to remove the
# lock files in the build/ directory only.
after_script:
- find . -path '*/build/dependencies.lock' -print -delete
# Collect all dependencies.lock artifacts and pass them onto jobs
# in sequential stages.
- find . -path '*/build/dependencies*.lock' -print -delete
# Collect all generated artifacts and pass them onto jobs in sequential stages.
artifacts:
paths:
- "**/dependencies.lock"
- '**/dependencies*.lock'
- '**/dependencies*.lock'
```
###### HtmlDependencyReportTask

View File

@ -45,7 +45,7 @@ the top of the vulnerability's page.
- Tier: Ultimate
- Add-on: GitLab Duo Enterprise, GitLab Duo with Amazon Q
- Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated
- LLM: Anthropic [Claude 3 Haiku](https://docs.anthropic.com/en/docs/about-claude/models#claude-3-a-new-generation-of-ai)
- LLM: Anthropic [Claude 3.7 Sonnet](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-7-sonnet)
- LLM for Amazon Q: Amazon Q Developer
{{< /details >}}

View File

@ -542,6 +542,7 @@ Audit event types belong to the following product categories.
| [`merge_request_merged_with_policy_violations`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/195775) | A merge request merged with security policy violations | {{< icon name="check-circle" >}} Yes | GitLab [18.2](https://gitlab.com/gitlab-org/gitlab/-/work_items/549813) | Project |
| [`policy_violations_detected`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/193482) | Security policy violation is detected in the merge request | {{< icon name="dotted-circle" >}} No | GitLab [18.2](https://gitlab.com/gitlab-org/gitlab/-/work_items/549811) | Project |
| [`policy_violations_resolved`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/193482) | Security policy violations are resolved in the merge request | {{< icon name="dotted-circle" >}} No | GitLab [18.2](https://gitlab.com/gitlab-org/gitlab/-/issues/549812) | Project |
| [`policy_yaml_invalidated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/196721) | The policy YAML is invalidated in security policy project | {{< icon name="check-circle" >}} Yes | GitLab [18.2](https://gitlab.com/gitlab-org/gitlab/-/work_items/550892) | Project |
### Security testing configuration

View File

@ -675,10 +675,11 @@ To troubleshoot a failed CI/CD job from the job log:
{{< details >}}
- Tier: Ultimate
- Add-on: GitLab Duo Enterprise
- Add-on: GitLab Duo Enterprise, GitLab Duo with Amazon Q
- Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated
- Editors: GitLab UI
- LLM: Anthropic [Claude 3.7 Sonnet](https://console.cloud.google.com/vertex-ai/publishers/anthropic/model-garden/claude-3-7-sonnet)
- LLM for Amazon Q: Amazon Q Developer
{{< /details >}}

View File

@ -8,7 +8,12 @@ module API
include Gitlab::Utils::StrongMemoize
expose :created_at, if: ->(user, opts) { Ability.allowed?(opts[:current_user], :read_user_profile, user) }
expose :bio, :location, :linkedin, :twitter, :discord, :website_url, :github, :organization, :job_title, :pronouns
expose :bio, :location, :linkedin, :twitter, :discord, :website_url, :github, :job_title, :pronouns
# rubocop:disable Style/SymbolProc -- we're not able to pass &:user_detail_organization as this tries to pass Grape::Entity::Options to said method
expose :organization do |user|
user.user_detail_organization
end
# rubocop:enable Style/SymbolProc
expose :bot?, as: :bot
expose :work_information do |user|
work_information(user)

View File

@ -438,6 +438,8 @@ module API
user_params[:password_expires_at] = Time.current if admin_making_changes_for_another_user
end
user_params[:user_detail_organization] = user_params.delete(:organization) if user_params[:organization]
result = ::Users::UpdateService.new(current_user, user_params.merge(user: user)).execute do |user|
user.send_only_admin_changed_your_password_notification! if admin_making_changes_for_another_user
end

View File

@ -79,7 +79,7 @@ module Banzai
# If sanitization times out, we can not return partial un-sanitized results.
# It's ok to allow any following filters to run since this is safe HTML.
def returned_timeout_value
HTML::Pipeline.parse(COMPLEX_MARKDOWN_MESSAGE)
Banzai::PipelineBase.parse(COMPLEX_MARKDOWN_MESSAGE)
end
class << self

View File

@ -47,7 +47,7 @@ module Banzai
private
def render_documents(objects, attribute)
pipeline = HTML::Pipeline.new([])
pipeline = Banzai::PipelineBase.new([])
objects.map do |object|
document = pipeline.to_document(Banzai.render_field(object, attribute))

View File

@ -12,7 +12,7 @@ module Banzai
end
def self.html_pipeline
@html_pipeline ||= HTML::Pipeline.new(filters)
@html_pipeline ||= Banzai::PipelineBase.new(filters)
@html_pipeline.setup_instrumentation(name)
@html_pipeline

View File

@ -0,0 +1,26 @@
# frozen_string_literal: true
module Banzai
# Custom pipeline class that extends HTML::Pipeline to allow GitLab-specific
# customizations and overrides of the html-pipeline gem behavior.
class PipelineBase < ::HTML::Pipeline
extend ::Gitlab::Utils::Override
HTML_PIPELINE_SUBSCRIPTION = 'call_filter.html_pipeline'
# Use thread ID to make subscription unique per thread
def self.filter_subscription_name
"#{HTML_PIPELINE_SUBSCRIPTION}_#{Thread.current.object_id}"
end
# overridden to use our own filter subscription name
override :perform_filter
def perform_filter(filter, doc, context, result)
payload = default_payload(filter: filter.name, context: context, result: result)
instrument Banzai::PipelineBase.filter_subscription_name, payload do
filter.call(doc, context, result)
end
end
end
end

View File

@ -3,7 +3,6 @@
module Banzai
module Renderer
USER_CONTENT_ID_PREFIX = 'user-content-'
HTML_PIPELINE_SUBSCRIPTION = 'call_filter.html_pipeline'
# Convert a Markdown String into an HTML-safe String of HTML
#
@ -193,15 +192,19 @@ module Banzai
Rails.cache.__send__(:expanded_key, full_cache_key(cache_key, pipeline_name)) # rubocop:disable GitlabSecurity/PublicSend
end
# this is built specifically for outputting debug timing/information for the Banzai pipeline.
# Example usage:
# Banzai.render(markdown, project: nil, debug_timing: true)
# Banzai.render(markdown, project: Project.first, debug: true)
# Instrumentation is used for a couple purposes
# - tracking timing of filters and the pipeline to manage performance and security.
# See `PipelineTimingCheck`
# - outputting debug timing/information for the pipeline.
# Example usage:
# Banzai.render(markdown, project: nil, debug_timing: true)
# Banzai.render(markdown, project: Project.first, debug: true)
def self.instrument_filters
service = ActiveSupport::Notifications
HTML::Pipeline.default_instrumentation_service = service
Banzai::PipelineBase.default_instrumentation_service = service
subscription_name = Banzai::PipelineBase.filter_subscription_name
service.monotonic_subscribe(HTML_PIPELINE_SUBSCRIPTION) do |_event, start, ending, _transaction_id, payload|
service.monotonic_subscribe(subscription_name) do |_event, start, ending, _transaction_id, payload|
duration = ending - start
payload[:result][:pipeline_timing] = payload[:result][:pipeline_timing].to_f + duration
@ -222,7 +225,7 @@ module Banzai
yield
ensure
service.unsubscribe(HTML_PIPELINE_SUBSCRIPTION) if service
service.unsubscribe(subscription_name) if service
end
def self.formatted_duration(duration)

View File

@ -26,6 +26,7 @@ module Gitlab
SignupDisabledError = Class.new(StandardError)
SigninDisabledForProviderError = Class.new(StandardError)
IdentityWithUntrustedExternUidError = Class.new(StandardError)
UnknownAttributeMappingError = Class.new(StandardError)
attr_reader :auth_hash
@ -271,8 +272,7 @@ module Gitlab
if sync_profile_from_provider?
UserSyncedAttributesMetadata.syncable_attributes(auth_hash.provider).each do |key|
if auth_hash.has_attribute?(key) && gl_user.sync_attribute?(key)
gl_user.public_send("#{key}=".to_sym, auth_hash.public_send(key)) # rubocop:disable GitlabSecurity/PublicSend
if assign_value_to_user(gl_user, auth_hash, key)
metadata.set_attribute_synced(key, true)
else
metadata.set_attribute_synced(key, false)
@ -323,6 +323,23 @@ module Gitlab
gl_user.errors.add(attr, error)
end
end
private
def assign_value_to_user(user, auth_hash, key)
return unless auth_hash.has_attribute?(key) && user.sync_attribute?(key)
value = auth_hash.public_send(key) # rubocop:disable GitlabSecurity/PublicSend -- we validate that `key` is a supported value by calling `auth_hash.has_attribute?(key)` on L330
if key.to_sym == :organization
user.user_detail_organization = value
elsif user.respond_to?(:"#{key}=")
user.public_send("#{key}=".to_sym, value) # rubocop:disable GitlabSecurity/PublicSend -- we validate that `key` is a supported value by calling `gl_user.sync_attribute?(key)` on L330
else
raise UnknownAttributeMappingError
end
true
end
end
end
end

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
class BackfillAnalyzerProjectStatusesFromProjectSecuritySettings < BatchedMigrationJob
feature_category :security_asset_inventories
operation_name :backfill_analyzer_project_statuses_from_project_security_settings
def perform; end
end
end
end
Gitlab::BackgroundMigration::BackfillAnalyzerProjectStatusesFromProjectSecuritySettings.prepend_mod

View File

@ -76,7 +76,7 @@ module Gitlab
user_pb = ::Spamcheck::User.new
user_pb.username = user.username
user_pb.id = user.id
user_pb.org = user.organization || ''
user_pb.org = user.user_detail_organization || ''
user_pb.created_at = convert_to_pb_timestamp(user.created_at)
user_pb.abuse_metadata = Google::Protobuf::Map.new(:string, :float, user.abuse_metadata)

View File

@ -5856,19 +5856,19 @@ msgstr ""
msgid "AiAnalytics|the ClickHouse data store is not available"
msgstr ""
msgid "AiImpactAnalytics|Code Suggestions acceptance rate"
msgid "AiImpactAnalytics|Assigned Duo seat engagement"
msgstr ""
msgid "AiImpactAnalytics|Code Suggestions usage"
msgid "AiImpactAnalytics|Code Suggestions: Acceptance rate"
msgstr ""
msgid "AiImpactAnalytics|Duo Chat: Unique users"
msgid "AiImpactAnalytics|Code Suggestions: Usage"
msgstr ""
msgid "AiImpactAnalytics|Duo RCA: Unique users"
msgid "AiImpactAnalytics|Duo Chat: Usage"
msgstr ""
msgid "AiImpactAnalytics|Duo seats: Assigned and used"
msgid "AiImpactAnalytics|Duo RCA: Usage"
msgstr ""
msgid "AiImpactAnalytics|Monthly GitLab Duo Code Suggestions accepted / total Code Suggestions generated."
@ -9067,12 +9067,6 @@ msgstr ""
msgid "Attribute 'pod-overrides' is not yet supported"
msgstr ""
msgid "Audit Event Author Placeholder"
msgstr ""
msgid "Audit Event Details placeholder"
msgstr ""
msgid "Audit events"
msgstr ""
@ -16109,6 +16103,9 @@ msgstr ""
msgid "ComplianceReport|Avoid creating new compliance pipelines and use pipeline execution policies instead. %{linkStart}Pipeline execution policies%{linkEnd} provide the ability to enforce CI/CD jobs, execute security scans, and better manage compliance enforcement in pipelines."
msgstr ""
msgid "ComplianceReport|By %{name}"
msgstr ""
msgid "ComplianceReport|Change status"
msgstr ""
@ -16169,6 +16166,9 @@ msgstr ""
msgid "ComplianceReport|Full target branch name"
msgstr ""
msgid "ComplianceReport|Generic Audit event"
msgstr ""
msgid "ComplianceReport|Have questions or thoughts on the new improvements? %{linkStart}Please provide feedback on your experience%{linkEnd}."
msgstr ""
@ -16181,6 +16181,9 @@ msgstr ""
msgid "ComplianceReport|Migrate pipeline to a policy"
msgstr ""
msgid "ComplianceReport|No audit event available"
msgstr ""
msgid "ComplianceReport|No frameworks"
msgstr ""
@ -16235,6 +16238,9 @@ msgstr ""
msgid "ComplianceReport|Unable to load the compliance violations report. Refresh the page and try again."
msgstr ""
msgid "ComplianceReport|Unknown author"
msgstr ""
msgid "ComplianceReport|Update filtered results?"
msgstr ""
@ -62607,6 +62613,12 @@ msgstr ""
msgid "The file has been successfully deleted."
msgstr ""
msgid "The file is locked."
msgstr ""
msgid "The file is unlocked."
msgstr ""
msgid "The file you're about to delete is tracked by LFS"
msgstr ""

View File

@ -11,7 +11,7 @@ gem 'capybara', '~> 3.40.0'
gem 'capybara-screenshot', '~> 1.0.26'
gem 'rake', '~> 13', '>= 13.3.0'
gem 'rspec', '~> 3.13', '>= 3.13.1'
gem 'selenium-webdriver', '= 4.33.0'
gem 'selenium-webdriver', '= 4.34.0'
gem 'rest-client', '~> 2.1.0'
gem 'rspec_junit_formatter', '~> 0.6.0'
gem 'faker', '~> 3.5', '>= 3.5.2'

View File

@ -323,7 +323,7 @@ GEM
addressable (>= 2.3.5)
faraday (>= 0.17.3, < 3)
securerandom (0.4.1)
selenium-webdriver (4.33.0)
selenium-webdriver (4.34.0)
base64 (~> 0.2)
logger (~> 1.4)
rexml (~> 3.2, >= 3.2.5)
@ -405,7 +405,7 @@ DEPENDENCIES
rspec-parameterized (~> 2.0.0)
rspec_junit_formatter (~> 0.6.0)
ruby-debug-ide (~> 0.7.5)
selenium-webdriver (= 4.33.0)
selenium-webdriver (= 4.34.0)
slack-notifier (~> 2.4)
terminal-table (~> 4.0.0)
warning (~> 1.5)

View File

@ -17,7 +17,6 @@ ee/spec/frontend/geo_sites/index_spec.js
ee/spec/frontend/groups/settings/components/comma_separated_list_token_selector_spec.js
ee/spec/frontend/issues_analytics/components/issues_analytics_table_spec.js
ee/spec/frontend/iterations/components/iteration_form_spec.js
ee/spec/frontend/iterations/components/iteration_report_issues_spec.js
ee/spec/frontend/members/components/action_dropdowns/user_action_dropdown_spec.js
ee/spec/frontend/members/components/modals/ldap_override_confirmation_modal_spec.js
ee/spec/frontend/members/components/table/members_table_spec.js

View File

@ -34,7 +34,7 @@ RSpec.describe 'User edit profile', feature_category: :user_profile do
fill_in 'user_location', with: 'Ukraine'
fill_in 'user_bio', with: 'I <3 GitLab :tada:'
fill_in 'user_job_title', with: 'Frontend Engineer'
fill_in 'user_organization', with: 'GitLab'
fill_in 'user_user_detail_organization', with: 'GitLab'
submit_settings
expect(user.reload).to have_attributes(
@ -43,7 +43,7 @@ RSpec.describe 'User edit profile', feature_category: :user_profile do
website_url: 'http://testurl.com',
bio: 'I <3 GitLab :tada:',
job_title: 'Frontend Engineer',
organization: 'GitLab'
user_detail_organization: 'GitLab'
)
expect(find('#user_location').value).to eq 'Ukraine'
@ -51,7 +51,7 @@ RSpec.describe 'User edit profile', feature_category: :user_profile do
end
it 'does not set secondary emails without user input' do
fill_in 'user_organization', with: 'GitLab'
fill_in 'user_user_detail_organization', with: 'GitLab'
submit_settings
user.reload
@ -559,7 +559,7 @@ RSpec.describe 'User edit profile', feature_category: :user_profile do
context 'when job title and organziation are entered' do
it "shows job title and organzation on user's profile" do
fill_in 'user_job_title', with: 'Frontend Engineer'
fill_in 'user_organization', with: 'GitLab - work info test'
fill_in 'user_user_detail_organization', with: 'GitLab - work info test'
submit_settings
visit_user
@ -581,7 +581,7 @@ RSpec.describe 'User edit profile', feature_category: :user_profile do
context 'when only organization is entered' do
it "shows only organization on user's profile" do
fill_in 'user_organization', with: 'GitLab - work info test'
fill_in 'user_user_detail_organization', with: 'GitLab - work info test'
submit_settings
visit_user

View File

@ -36,7 +36,7 @@ RSpec.describe 'User page', feature_category: :user_profile do
context 'work information' do
it 'shows job title and organization details' do
user.update!(organization: 'GitLab - work info test', job_title: 'Frontend Engineer')
user.update!(user_detail_organization: 'GitLab - work info test', job_title: 'Frontend Engineer')
subject
@ -44,7 +44,7 @@ RSpec.describe 'User page', feature_category: :user_profile do
end
it 'shows job title' do
user.update!(organization: nil, job_title: 'Frontend Engineer - work info test')
user.update!(user_detail_organization: nil, job_title: 'Frontend Engineer - work info test')
subject
@ -52,7 +52,7 @@ RSpec.describe 'User page', feature_category: :user_profile do
end
it 'shows organization details' do
user.update!(organization: 'GitLab - work info test', job_title: '')
user.update!(user_detail_organization: 'GitLab - work info test', job_title: '')
subject
@ -210,7 +210,7 @@ RSpec.describe 'User page', feature_category: :user_profile do
create(
:user,
state: :blocked,
organization: 'GitLab - work info test',
user_detail_organization: 'GitLab - work info test',
job_title: 'Frontend Engineer',
pronunciation: 'pruh-nuhn-see-ay-shn',
bio: 'My personal bio'
@ -260,7 +260,7 @@ RSpec.describe 'User page', feature_category: :user_profile do
create(
:user,
:unconfirmed,
organization: 'GitLab - work info test',
user_detail_organization: 'GitLab - work info test',
job_title: 'Frontend Engineer',
pronunciation: 'pruh-nuhn-see-ay-shn',
bio: 'My personal bio'
@ -389,7 +389,7 @@ RSpec.describe 'User page', feature_category: :user_profile do
end
context 'structured markup' do
let_it_be(:user) { create(:user, website_url: 'https://gitlab.com', organization: 'GitLab', job_title: 'Frontend Engineer', email: 'public@example.com', public_email: 'public@example.com', location: 'Country', created_at: Time.zone.now, updated_at: Time.zone.now) }
let_it_be(:user) { create(:user, website_url: 'https://gitlab.com', user_detail_organization: 'GitLab', job_title: 'Frontend Engineer', email: 'public@example.com', public_email: 'public@example.com', location: 'Country', created_at: Time.zone.now, updated_at: Time.zone.now) }
it 'shows Person structured markup' do
subject

View File

@ -130,7 +130,7 @@ RSpec.describe ProfilesHelper, feature_category: :user_profile do
pronouns: 'they/them',
pronunciation: 'test-user',
job_title: 'Developer',
organization: 'GitLab',
user_detail_organization: 'GitLab',
location: 'Remote',
website_url: 'https://example.com',
bio: 'Test bio')

View File

@ -412,7 +412,7 @@ RSpec.describe UsersHelper, feature_category: :user_management do
context 'without schema markup' do
context 'when both job_title and organization are present' do
let(:user) { build(:user, organization: 'GitLab', job_title: 'Frontend Engineer') }
let(:user) { build(:user, user_detail_organization: 'GitLab', job_title: 'Frontend Engineer') }
it 'returns job title concatenated with organization' do
is_expected.to eq('Frontend Engineer at GitLab')
@ -420,7 +420,7 @@ RSpec.describe UsersHelper, feature_category: :user_management do
end
context 'when only organization is present' do
let(:user) { build(:user, organization: 'GitLab') }
let(:user) { build(:user, user_detail_organization: 'GitLab') }
it "returns organization" do
is_expected.to eq('GitLab')
@ -440,7 +440,7 @@ RSpec.describe UsersHelper, feature_category: :user_management do
let(:with_schema_markup) { true }
context 'when both job_title and organization are present' do
let(:user) { build(:user, organization: 'GitLab', job_title: 'Frontend Engineer') }
let(:user) { build(:user, user_detail_organization: 'GitLab', job_title: 'Frontend Engineer') }
it 'returns job title concatenated with organization' do
is_expected.to eq('<span itemprop="jobTitle">Frontend Engineer</span> at <span itemprop="worksFor">GitLab</span>')
@ -448,7 +448,7 @@ RSpec.describe UsersHelper, feature_category: :user_management do
end
context 'when only organization is present' do
let(:user) { build(:user, organization: 'GitLab') }
let(:user) { build(:user, user_detail_organization: 'GitLab') }
it "returns organization" do
is_expected.to eq('<span itemprop="worksFor">GitLab</span>')

View File

@ -98,7 +98,7 @@ RSpec.describe Banzai::Filter::References::ExternalIssueReferenceFilter, feature
end
context 'with RequestStore enabled', :request_store do
let(:reference_filter) { HTML::Pipeline.new([described_class]) }
let(:reference_filter) { Banzai::PipelineBase.new([described_class]) }
it 'queries the collection on the first call' do
expect_any_instance_of(Project).to receive(:default_issues_tracker?).once.and_call_original

View File

@ -94,7 +94,7 @@ RSpec.describe Banzai::Filter::TableOfContentsTagFilter, feature_category: :mark
end
def result(html)
HTML::Pipeline.new([Banzai::Filter::MarkdownFilter, described_class]).call(html)
Banzai::PipelineBase.new([Banzai::Filter::MarkdownFilter, described_class]).call(html)
end
let(:results) { result("[toc]\n\n#{header(1, 'Header 1')}#{header(2, 'Header 2')}") }

View File

@ -35,7 +35,7 @@ RSpec.describe Banzai::Pipeline::PostProcessPipeline, feature_category: :markdow
HTML
end
let(:doc) { HTML::Pipeline.parse(html) }
let(:doc) { Banzai::PipelineBase.parse(html) }
let(:non_related_xpath_calls) { 1 }
it 'searches for attributes only once' do

View File

@ -0,0 +1,24 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Banzai::PipelineBase, feature_category: :markdown do
it 'subclasses HTML::Pipeline' do
expect(described_class <= ::HTML::Pipeline).to be_truthy
end
describe '#filter_subscription_name' do
it 'adds thread id to subscription name' do
expect(described_class.filter_subscription_name)
.to eq "call_filter.html_pipeline_#{Thread.current.object_id}"
end
end
describe '.perform_filter' do
it 'overrides the perform_filter method' do
expect(described_class).to receive(:filter_subscription_name).and_call_original.at_least(:once)
Banzai::Pipeline::QuickActionPipeline.call('foo', project: nil)
end
end
end

View File

@ -172,8 +172,10 @@ RSpec.describe Banzai::Renderer, feature_category: :markdown do
end
describe 'instrumentation in render_result' do
let(:subscription_name) { "call_filter.html_pipeline_#{Thread.current.object_id}" }
it 'calculates pipeline timing' do
expect(ActiveSupport::Notifications).to receive(:monotonic_subscribe).with('call_filter.html_pipeline')
expect(ActiveSupport::Notifications).to receive(:monotonic_subscribe).with(subscription_name)
.and_call_original.at_least(:once)
expect(ActiveSupport::Notifications).to receive(:unsubscribe).and_call_original.at_least(:once)
expect(Rainbow).not_to receive(:new)
@ -184,7 +186,7 @@ RSpec.describe Banzai::Renderer, feature_category: :markdown do
end
it 'enables debug output' do
expect(ActiveSupport::Notifications).to receive(:monotonic_subscribe).with('call_filter.html_pipeline')
expect(ActiveSupport::Notifications).to receive(:monotonic_subscribe).with(subscription_name)
.and_call_original.at_least(:once)
expect(ActiveSupport::Notifications).to receive(:unsubscribe).and_call_original.at_least(:once)
expect(Rainbow).to receive(:new).and_call_original.at_least(:once)
@ -194,7 +196,7 @@ RSpec.describe Banzai::Renderer, feature_category: :markdown do
end
it 'enables debug_timing output' do
expect(ActiveSupport::Notifications).to receive(:monotonic_subscribe).with('call_filter.html_pipeline')
expect(ActiveSupport::Notifications).to receive(:monotonic_subscribe).with(subscription_name)
.and_call_original.at_least(:once)
expect(ActiveSupport::Notifications).to receive(:unsubscribe).and_call_original.at_least(:once)
expect(Rainbow).to receive(:new).and_call_original.at_least(:once)

View File

@ -1150,11 +1150,25 @@ RSpec.describe Gitlab::Auth::OAuth::User, :aggregate_failures, feature_category:
end
it "updates the user organization and job title" do
expect(gl_user.organization).to eq(info_hash[:organization])
expect(gl_user.user_detail_organization).to eq(info_hash[:organization])
expect(gl_user.job_title).to eq(info_hash[:job_title])
expect(gl_user.user_synced_attributes_metadata.organization_synced).to be(true)
expect(gl_user.user_synced_attributes_metadata.job_title_synced).to be(true)
end
context "when there is a mismatch with what attributes can be synced" do
before do
allow(UserSyncedAttributesMetadata).to receive(:syncable_attributes).and_return([:random_key])
info_hash[:random_key] = "random value"
allow_next_instance_of(Gitlab::Auth::OAuth::AuthHash) do |instance|
allow(instance).to receive(:random_key).and_return(info_hash[:random_key])
end
end
it "raises an error" do
expect { oauth_user }.to raise_error Gitlab::Auth::OAuth::User::UnknownAttributeMappingError
end
end
end
context "update only requested info" do
@ -1178,7 +1192,7 @@ RSpec.describe Gitlab::Auth::OAuth::User, :aggregate_failures, feature_category:
end
it "updates the user organization and job title" do
expect(gl_user.organization).to eq(info_hash[:organization])
expect(gl_user.user_detail_organization).to eq(info_hash[:organization])
expect(gl_user.job_title).to eq(info_hash[:job_title])
expect(gl_user.user_synced_attributes_metadata.organization_synced).to be(true)
expect(gl_user.user_synced_attributes_metadata.job_title_synced).to be(true)

View File

@ -6,7 +6,7 @@ RSpec.describe Gitlab::Spamcheck::Client, feature_category: :instance_resiliency
include_context 'includes Spam constants'
let(:endpoint) { 'grpc://grpc.test.url' }
let_it_be(:user) { create(:user, organization: 'GitLab') }
let_it_be(:user) { create(:user, user_detail_organization: 'GitLab') }
let(:verdict_value) { ::Spamcheck::SpamVerdict::Verdict::ALLOW }
let(:verdict_score) { 0.01 }
let(:verdict_evaluated) { true }
@ -169,7 +169,7 @@ RSpec.describe Gitlab::Spamcheck::Client, feature_category: :instance_resiliency
user_pb = described_class.new.send(:build_user_protobuf, user)
expect(user_pb.username).to eq user.username
expect(user_pb.id).to eq user.id
expect(user_pb.org).to eq user.organization
expect(user_pb.org).to eq user.user_detail_organization
expect(user_pb.created_at).to eq timestamp_to_protobuf_timestamp(user.created_at)
expect(user_pb.emails.count).to be 1
expect(user_pb.emails.first.email).to eq user.email

View File

@ -46,22 +46,22 @@ RSpec.describe Gitlab::Utils::SanitizeNodeLink do
context "with the scheme: #{scheme}" do
tags = {
a: {
doc: HTML::Pipeline.parse("<a href='#{scheme}alert(1);'>foo</a>"),
doc: Banzai::PipelineBase.parse("<a href='#{scheme}alert(1);'>foo</a>"),
attr: "href",
node_to_check: ->(doc) { doc.children.first }
},
img: {
doc: HTML::Pipeline.parse("<img src='#{scheme}alert(1);'>"),
doc: Banzai::PipelineBase.parse("<img src='#{scheme}alert(1);'>"),
attr: "src",
node_to_check: ->(doc) { doc.children.first }
},
video: {
doc: HTML::Pipeline.parse("<video><source src='#{scheme}alert(1);'></video>"),
doc: Banzai::PipelineBase.parse("<video><source src='#{scheme}alert(1);'></video>"),
attr: "src",
node_to_check: ->(doc) { doc.children.first.children.filter("source").first }
},
audio: {
doc: HTML::Pipeline.parse("<audio><source src='#{scheme}alert(1);'></audio>"),
doc: Banzai::PipelineBase.parse("<audio><source src='#{scheme}alert(1);'></audio>"),
attr: "src",
node_to_check: ->(doc) { doc.children.first.children.filter("source").first }
}
@ -82,7 +82,7 @@ RSpec.describe Gitlab::Utils::SanitizeNodeLink do
end
context 'handling child nodes' do
let(:doc) { HTML::Pipeline.parse(nodes_with_children) }
let(:doc) { Banzai::PipelineBase.parse(nodes_with_children) }
let(:node) { doc.children.first }
it 'santizes child nodes' do
@ -101,7 +101,7 @@ RSpec.describe Gitlab::Utils::SanitizeNodeLink do
end
context 'when URI is valid' do
let(:doc) { HTML::Pipeline.parse("<a href='http://example.com'>foo</a>") }
let(:doc) { Banzai::PipelineBase.parse("<a href='http://example.com'>foo</a>") }
let(:node) { doc.children.first }
it 'does not remove it' do
@ -112,7 +112,7 @@ RSpec.describe Gitlab::Utils::SanitizeNodeLink do
end
context 'when URI is invalid' do
let(:doc) { HTML::Pipeline.parse("<a href='http://example:wrong_port.com'>foo</a>") }
let(:doc) { Banzai::PipelineBase.parse("<a href='http://example:wrong_port.com'>foo</a>") }
let(:node) { doc.children.first }
it 'removes the link' do
@ -123,7 +123,7 @@ RSpec.describe Gitlab::Utils::SanitizeNodeLink do
end
context 'when URI is encoded but still invalid' do
let(:doc) { HTML::Pipeline.parse("<a href='http://example%EF%BC%9A%E7%BD%91'>foo</a>") }
let(:doc) { Banzai::PipelineBase.parse("<a href='http://example%EF%BC%9A%E7%BD%91'>foo</a>") }
let(:node) { doc.children.first }
it 'removes the link' do
@ -137,7 +137,7 @@ RSpec.describe Gitlab::Utils::SanitizeNodeLink do
describe "#safe_protocol?" do
invalid_schemes.each do |scheme|
context "with the scheme: #{scheme}" do
let(:doc) { HTML::Pipeline.parse("<a href='#{scheme}alert(1);'>foo</a>") }
let(:doc) { Banzai::PipelineBase.parse("<a href='#{scheme}alert(1);'>foo</a>") }
let(:node) { doc.children.first }
let(:uri) { Addressable::URI.parse(node['href']) }
@ -150,7 +150,7 @@ RSpec.describe Gitlab::Utils::SanitizeNodeLink do
describe '#sanitize_unsafe_links' do
it 'sanitizes all nodes without specifically recursing children' do
doc = HTML::Pipeline.parse(nodes_with_children)
doc = Banzai::PipelineBase.parse(nodes_with_children)
allowlist = { elements: %w[details summary a], attributes: { 'a' => ['href'] },
transformers: [object.method(:sanitize_unsafe_links)] }

View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe QueueBackfillAnalyzerProjectStatusesFromProjectSecuritySettings, migration: :gitlab_main, feature_category: :security_asset_inventories do
let!(:batched_migration) { described_class::MIGRATION }
it 'schedules a new batched migration' do
reversible_migration do |migration|
migration.before -> {
expect(batched_migration).not_to have_scheduled_batched_migration
}
migration.after -> {
expect(batched_migration).to have_scheduled_batched_migration(
gitlab_schema: :gitlab_main,
table_name: :project_security_settings,
column_name: :project_id,
interval: described_class::DELAY_INTERVAL,
batch_size: described_class::BATCH_SIZE,
sub_batch_size: described_class::SUB_BATCH_SIZE
)
}
end
end
end

View File

@ -2682,6 +2682,38 @@ RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep, feature_category:
end
end
describe '#has_exposed_artifacts?' do
let(:pipeline) { create(:ci_pipeline, :success) }
let(:options) { nil }
subject { pipeline.has_exposed_artifacts? }
before do
create(:ci_build, pipeline: pipeline)
create(:ci_build, options: options, pipeline: pipeline)
end
it { is_expected.to eq(false) }
context 'with unexposed artifacts' do
let(:options) { { artifacts: { paths: ['test'] } } }
it { is_expected.to eq(false) }
end
context 'with exposed artifacts' do
let(:options) { { artifacts: { expose_as: 'test', paths: ['test'] } } }
it { is_expected.to eq(true) }
context 'when the pipeline is not complete' do
let(:pipeline) { create(:ci_pipeline, :running) }
it { is_expected.to eq(false) }
end
end
end
describe '#manual_actions' do
subject { pipeline.manual_actions }

View File

@ -477,7 +477,7 @@ RSpec.describe UserDetail, feature_category: :system_access do
bluesky: 'did:plc:ewvi7nxzyoun6zhxrhs64oiz',
orcid: '1234-1234-1234-1234',
mastodon: '@robin@example.com',
organization: 'organization',
user_detail_organization: 'organization',
twitter: 'twitter',
website_url: 'https://example.com'
}

View File

@ -196,8 +196,8 @@ RSpec.describe User, feature_category: :user_profile do
it { is_expected.to delegate_method(:location).to(:user_detail).allow_nil }
it { is_expected.to delegate_method(:location=).to(:user_detail).with_arguments(:args).allow_nil }
it { is_expected.to delegate_method(:organization).to(:user_detail).allow_nil }
it { is_expected.to delegate_method(:organization=).to(:user_detail).with_arguments(:args).allow_nil }
it { is_expected.to delegate_method(:organization).to(:user_detail).with_prefix.allow_nil }
it { is_expected.to delegate_method(:organization=).to(:user_detail).with_arguments(:args).with_prefix.allow_nil }
it { is_expected.to delegate_method(:email_reset_offered_at).to(:user_detail).allow_nil }
it { is_expected.to delegate_method(:email_reset_offered_at=).to(:user_detail).with_arguments(:args).allow_nil }
@ -354,26 +354,27 @@ RSpec.describe User, feature_category: :user_profile do
expect(create(:user).user_detail).to be_persisted
end
shared_examples 'delegated field' do |field|
shared_examples 'delegated field' do |field, prefix: nil|
field_name = prefix ? :"#{prefix}_#{field}" : field
it 'correctly stores the `user_detail` attribute when the field is given on user creation' do
user = create(:user, field => 'my field')
user = create(:user, field_name => 'my field')
expect(user.user_detail).to be_persisted
expect(user.user_detail[field]).to eq('my field')
end
it 'delegates to `user_detail`' do
user = create(:user, field => 'my field')
user = create(:user, field_name => 'my field')
expect(user.public_send(field)).to eq(user.user_detail[field])
expect(user.public_send(field_name)).to eq(user.user_detail[field])
end
end
it_behaves_like 'delegated field', :organization, prefix: 'user_detail'
it_behaves_like 'delegated field', :bio
it_behaves_like 'delegated field', :linkedin
it_behaves_like 'delegated field', :twitter
it_behaves_like 'delegated field', :location
it_behaves_like 'delegated field', :organization
it 'creates `user_detail` when `website_url` is given' do
user = create(:user, website_url: 'https://example.com')
@ -5656,10 +5657,12 @@ RSpec.describe User, feature_category: :user_profile do
describe '#ci_owned_runners', feature_category: :runner do
let_it_be_with_refind(:user) { create(:user) }
subject(:ci_owned_runners) { user.ci_owned_runners }
shared_examples 'nested groups owner' do
context 'when the user is the owner of a multi-level group' do
it 'loads all the runners in the tree of groups' do
expect(user.ci_owned_runners).to contain_exactly(runner, group_runner)
is_expected.to contain_exactly(runner, group_runner)
end
it 'returns true for owns_runner?' do
@ -5670,6 +5673,8 @@ RSpec.describe User, feature_category: :user_profile do
end
shared_examples 'project member' do
let_it_be(:another_project_runner) { create(:ci_runner, :project, projects: [another_project, project]) }
before do
add_user # this method has to be defined in the caller block
end
@ -5678,11 +5683,12 @@ RSpec.describe User, feature_category: :user_profile do
let(:access) { :maintainer }
it 'loads the runners of the project' do
expect(user.ci_owned_runners).to contain_exactly(project_runner)
is_expected.to contain_exactly(project_runner, another_project_runner)
end
it 'returns true for owns_runner?' do
expect(user.owns_runner?(project_runner)).to eq(true)
expect(user.owns_runner?(another_project_runner)).to eq(true)
end
end
@ -5690,11 +5696,12 @@ RSpec.describe User, feature_category: :user_profile do
let(:access) { :developer }
it 'does not load any runner' do
expect(user.ci_owned_runners).to be_empty
is_expected.to be_empty
end
it 'returns false for owns_runner?' do
expect(user.owns_runner?(project_runner)).to eq(false)
expect(user.owns_runner?(another_project_runner)).to eq(false)
end
end
@ -5702,11 +5709,12 @@ RSpec.describe User, feature_category: :user_profile do
let(:access) { :reporter }
it 'does not load any runner' do
expect(user.ci_owned_runners).to be_empty
is_expected.to be_empty
end
it 'returns false for owns_runner?' do
expect(user.owns_runner?(project_runner)).to eq(false)
expect(user.owns_runner?(another_project_runner)).to eq(false)
end
end
@ -5714,11 +5722,12 @@ RSpec.describe User, feature_category: :user_profile do
let(:access) { :guest }
it 'does not load any runner' do
expect(user.ci_owned_runners).to be_empty
is_expected.to be_empty
end
it 'returns false for owns_runner?' do
expect(user.owns_runner?(project_runner)).to eq(false)
expect(user.owns_runner?(another_project_runner)).to eq(false)
end
end
end
@ -5730,7 +5739,7 @@ RSpec.describe User, feature_category: :user_profile do
end
it 'does not load the runners of the group' do
expect(user.ci_owned_runners).to be_empty
is_expected.to be_empty
end
it 'returns false for owns_runner?' do
@ -5744,7 +5753,7 @@ RSpec.describe User, feature_category: :user_profile do
end
it 'does not load any runner' do
expect(user.ci_owned_runners).to be_empty
is_expected.to be_empty
end
it 'returns false for owns_runner?' do
@ -5880,6 +5889,7 @@ RSpec.describe User, feature_category: :user_profile do
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, group: group) }
let_it_be(:runner) { create(:ci_runner, :project, projects: [project]) }
let_it_be(:another_project) { create(:project) }
it 'does not load any runner' do
expect(user.ci_owned_runners).to be_empty
@ -5902,6 +5912,17 @@ RSpec.describe User, feature_category: :user_profile do
it_behaves_like 'nested groups owner'
end
context 'with a project runner owned by inaccessible project' do
let_it_be(:another_project_runner) { create(:ci_runner, :project, projects: [another_project, project]) }
it 'returns runner shared from inaccessible project' do
expect(ci_owned_runners).to contain_exactly(runner, another_project_runner)
expect(user.owns_runner?(another_project_runner)).to be true
expect(user.owns_runner?(runner)).to be true
end
end
end
context 'when user is member of project' do
@ -5944,7 +5965,21 @@ RSpec.describe User, feature_category: :user_profile do
)
expect(user).not_to receive(:ci_owned_project_runners)
user.ci_owned_runners
ci_owned_runners
end
context 'with a project runner owned by inaccessible project' do
let_it_be(:project) { create(:project, owners: user) }
let_it_be(:runner) { create(:ci_runner, :project, projects: [project]) }
let_it_be(:another_project) { create(:project) }
let_it_be(:another_project_runner) { create(:ci_runner, :project, projects: [another_project, project]) }
it 'returns runner shared from inaccessible project' do
expect(ci_owned_runners).to contain_exactly(runner, another_project_runner)
expect(user.owns_runner?(another_project_runner)).to be true
expect(user.owns_runner?(runner)).to be true
end
end
end
@ -5961,7 +5996,7 @@ RSpec.describe User, feature_category: :user_profile do
expect(user).not_to receive(:ci_owned_project_runners_from_project_members)
expect(user).not_to receive(:ci_owned_project_runners_from_group_members)
user.ci_owned_runners
ci_owned_runners
end
context 'when feature flag is only enabled for other user' do
@ -5972,7 +6007,7 @@ RSpec.describe User, feature_category: :user_profile do
expect(user).to receive(:ci_owned_project_runners_from_group_members).and_return([])
expect(user).to receive(:ci_owned_group_runners).and_return([])
user.ci_owned_runners
ci_owned_runners
end
end
end

View File

@ -443,7 +443,7 @@ RSpec.describe 'Query.runner(id)', :freeze_time, feature_category: :fleet_visibi
end
let_it_be(:owned_project_owner) { create(:user) }
let_it_be(:owned_project) { create(:project) }
let_it_be(:owned_project) { create(:project, owners: owned_project_owner) }
let_it_be(:other_project) { create(:project) }
let_it_be(:project_runner) { create(:ci_runner, :project_type, projects: [other_project, owned_project]) }
let_it_be(:owned_project_pipeline) { create(:ci_pipeline, project: owned_project) }
@ -458,10 +458,6 @@ RSpec.describe 'Query.runner(id)', :freeze_time, feature_category: :fleet_visibi
tag_list: %i[d e f], created_at: 30.minutes.ago, started_at: 19.minutes.ago, finished_at: 1.minute.ago)
end
before_all do
owned_project.add_owner(owned_project_owner)
end
it 'returns empty values for sensitive fields in non-owned jobs' do
post_graphql(query, current_user: owned_project_owner)
@ -825,11 +821,7 @@ RSpec.describe 'Query.runner(id)', :freeze_time, feature_category: :fleet_visibi
end
describe 'by non-admin user' do
let(:user) { create(:user) }
before do
group.add_member(user, Gitlab::Access::OWNER)
end
let(:user) { create(:user, owner_of: group) }
it_behaves_like 'retrieval with no admin url' do
let(:runner) { active_group_runner }

View File

@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe API::GroupMilestones, feature_category: :team_planning do
let_it_be(:organization) { create(:organization) }
let_it_be(:user) { create(:user, organization: organization) }
let_it_be(:user) { create(:user, user_detail_organization: organization) }
let_it_be_with_refind(:group) { create(:group, :private) }
let_it_be(:project) { create(:project, namespace: group, organization: organization) }
let_it_be(:group_member) { create(:group_member, group: group, user: user) }

View File

@ -1857,7 +1857,7 @@ RSpec.describe API::Users, :with_current_organization, :aggregate_failures, feat
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['organization']).to eq('GitLab')
expect(user.reload.organization).to eq('GitLab')
expect(user.reload.user_detail_organization).to eq('GitLab')
end
it 'updates user with avatar' do

View File

@ -137,7 +137,7 @@ RSpec.describe Users::BuildService, feature_category: :user_management do
username: 1,
website_url: 1,
private_profile: 1,
organization: 1,
user_detail_organization: 1,
location: 1,
public_email: 1,
user_type: 'project_bot',

View File

@ -52,7 +52,7 @@ module FilterSpecHelper
def pipeline_result(body, context = {})
context.reverse_merge!(project: project) if defined?(project)
pipeline = HTML::Pipeline.new([described_class], context)
pipeline = Banzai::PipelineBase.new([described_class], context)
pipeline.call(body)
end

View File

@ -317,6 +317,7 @@ type GitAuditEventRequest struct {
Repo string `json:"gl_repository"`
Username string `json:"username"`
PackfileStats *gitalypb.PackfileNegotiationStatistics `json:"packfile_stats,omitempty"`
Changes string `json:"changes"`
}
// SendGitAuditEvent sends a Git audit event using the API client.

View File

@ -222,6 +222,7 @@ func TestSendGitAuditEvent(t *testing.T) {
Protocol: "http",
Repo: "project-1",
Username: "GitLab-Shell",
Changes: "_any",
PackfileStats: &gitalypb.PackfileNegotiationStatistics{
Wants: 3,
Haves: 23,

View File

@ -109,6 +109,7 @@ func sendGitAuditEvent(action string) func(*api.API, *http.Request, *api.Respons
Repo: response.GL_REPOSITORY,
Username: response.GL_USERNAME,
PackfileStats: stats,
Changes: "_any",
})
if err != nil {
log.WithContextFields(ctx, log.Fields{