Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2025-01-20 06:29:07 +00:00
parent 51b2a7d57c
commit b16f62a9de
26 changed files with 354 additions and 40 deletions

View File

@ -1 +1 @@
a6ce958a34a266e110385201ccab8529dec47282
75f33270bda9bf257949abfc1fcbe6ca90c7a479

View File

@ -37,6 +37,7 @@ const ICON_COLORS = {
'issue-close': 'system-note-icon-info',
issues: 'system-note-icon-success',
error: 'system-note-icon-danger',
'review-warning': 'system-note-icon-warning',
};
export default {

View File

@ -47,6 +47,7 @@ a {
max-width: 65ch;
margin: auto;
padding: $gl-spacing-scale-5 0;
text-align: center;
}
.action-container {

View File

@ -67,6 +67,11 @@
--system-note-icon-background-color: var(--gl-status-info-background-color);
}
&.system-note-icon-warning {
--system-note-icon-color: var(--gl-status-warning-text-color);
--system-note-icon-background-color: var(--gl-status-warning-background-color);
}
&:not(.mr-system-note-empty) {
&::before {
@include activity-line-gradient(transparent, var(--system-note-icon-background-color));

View File

@ -48,7 +48,8 @@ module SystemNoteHelper
'unrelate_from_child' => 'link',
'relate_to_parent' => 'link',
'unrelate_from_parent' => 'link',
'requested_changes' => 'error'
'requested_changes' => 'error',
'override' => 'review-warning'
}.freeze
def system_note_icon_name(note)

View File

@ -245,7 +245,8 @@ class ActiveSession
# Explanation of why this Marshal.load call is OK:
# https://gitlab.com/gitlab-com/gl-security/product-security/appsec/appsec-reviews/-/issues/124#note_744576714
# rubocop:disable Security/MarshalLoad
Marshal.load(raw_session)
session_data = Marshal.load(raw_session)
session_data.is_a?(ActiveSupport::Cache::Entry) ? session_data.value : session_data
# rubocop:enable Security/MarshalLoad
end
end

View File

@ -5,6 +5,9 @@
%title= yield(:title)
- if vite_enabled?
= universal_stylesheet_link_tag 'errors'
= universal_stylesheet_link_tag 'application'
= universal_stylesheet_link_tag 'fonts'
= universal_stylesheet_link_tag 'tailwind'
- else
%style
= error_css

View File

@ -40,20 +40,11 @@ cookie_key = if Rails.env.development?
::Redis::Store::Factory.prepend(Gitlab::Patch::RedisStoreFactory)
Rails.application.configure do
config.session_store(
Gitlab::Sessions::RedisStore, # Using the cookie_store would enable session replay attacks
redis_server: Gitlab::Redis::Sessions.params.merge(
namespace: Gitlab::Redis::Sessions::SESSION_NAMESPACE,
serializer: Gitlab::Sessions::RedisStoreSerializer
),
key: cookie_key,
secure: Gitlab.config.gitlab.https,
httponly: true,
expires_in: Settings.gitlab['session_expire_delay'] * 60,
path: Rails.application.config.relative_url_root.presence || '/',
session_cookie_token_prefix: session_cookie_token_prefix
)
ENV['USE_REDIS_CACHE_STORE_AS_SESSION_STORE'] = 'true' if Rails.env.test? || Rails.env.development?
config.middleware.insert_after Gitlab::Sessions::RedisStore, Gitlab::Middleware::UnauthenticatedSessionExpiry
session_store_class, options = Gitlab::Sessions::StoreBuilder.new(cookie_key, session_cookie_token_prefix).prepare
Rails.application.configure do
config.session_store(session_store_class, **options)
config.middleware.insert_after session_store_class, Gitlab::Middleware::UnauthenticatedSessionExpiry
end

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
class AddSbomOccurrencesVulnerabilitiesProjectIdNotNullConstraint < Gitlab::Database::Migration[2.2]
disable_ddl_transaction!
milestone '17.9'
def up
add_not_null_constraint :sbom_occurrences_vulnerabilities, :project_id, validate: false
end
def down
remove_not_null_constraint :sbom_occurrences_vulnerabilities, :project_id
end
end

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
class PrepareSbomOccurencesVulnerabilitiesProjectIdNotNullValidation < Gitlab::Database::Migration[2.2]
disable_ddl_transaction!
milestone '17.9'
CONSTRAINT_NAME = :check_a02e48df9c
def up
prepare_async_check_constraint_validation :sbom_occurrences_vulnerabilities, name: CONSTRAINT_NAME
end
def down
unprepare_async_check_constraint_validation :sbom_occurrences_vulnerabilities, name: CONSTRAINT_NAME
end
end

View File

@ -0,0 +1 @@
320152a9c25bbafa131c4e4db83ddd03836386c81acda27ed4267232710b3ae5

View File

@ -0,0 +1 @@
0d1a6d9361a9e08a0777e69452ecb32627a0a97bb700db3490e013e3da9554f3

View File

@ -26309,6 +26309,9 @@ ALTER TABLE vulnerability_scanners
ALTER TABLE p_ci_pipeline_variables
ADD CONSTRAINT check_6e932dbabf CHECK ((project_id IS NOT NULL)) NOT VALID;
ALTER TABLE sbom_occurrences_vulnerabilities
ADD CONSTRAINT check_a02e48df9c CHECK ((project_id IS NOT NULL)) NOT VALID;
ALTER TABLE sprints
ADD CONSTRAINT check_ccd8a1eae0 CHECK ((start_date IS NOT NULL)) NOT VALID;

View File

@ -36,7 +36,7 @@ GitLab offers the following machine type for hosted runners on Linux Arm64.
| Runner Tag | vCPUs | Memory | Storage |
|-------------------------------------------------------|-------|--------|---------|
| `saas-linux-small-arm64`. | 2 | 8 GB | 30 GB |
| `saas-linux-small-arm64` | 2 | 8 GB | 30 GB |
| `saas-linux-medium-arm64` (Premium and Ultimate only) | 4 | 16 GB | 50 GB |
| `saas-linux-large-arm64` (Premium and Ultimate only) | 8 | 32 GB | 100 GB |

View File

@ -0,0 +1,31 @@
# frozen_string_literal: true
module Gitlab
module Sessions
class CacheStore < ActionDispatch::Session::CacheStore
DELIMITER = '-'
attr_reader :session_cookie_token_prefix
def initialize(app, options = {})
super
@session_cookie_token_prefix = options.fetch(:session_cookie_token_prefix, "") || ""
end
def generate_sid
delimiter = session_cookie_token_prefix.empty? ? '' : DELIMITER
Rack::Session::SessionId.new(session_cookie_token_prefix + delimiter + super.public_id)
end
private
# ActionDispatch::Session::CacheStore (superclass) prepends
# hardcoded "_session_id:" to the cache key which doesn't match
# the previous implementation of Gitlab::Sessions::RedisStore.
def cache_key(id)
id
end
end
end
end

View File

@ -0,0 +1,22 @@
# frozen_string_literal: true
module Gitlab
module Sessions
module CacheStoreCoder
extend self
include ActiveSupport::Cache::Coders::Rails61Coder
def load(payload)
unmarshalled = super
return unmarshalled if unmarshalled.is_a?(ActiveSupport::Cache::Entry)
# The session payload coming from the old `RedisStore` is a hash,
# whereas payload from `CacheStore` expects the hash to be wrapped in `ActiveSupport::Cache::Entry`.
# The payload here is re-wrapped to make old sessions compatible when read by `CacheStore`.
# https://gitlab.com/gitlab-com/gl-infra/data-access/durability/team/-/issues/35#note_2278902354
ActiveSupport::Cache::Entry.new(unmarshalled)
end
end
end
end

View File

@ -0,0 +1,55 @@
# frozen_string_literal: true
module Gitlab
module Sessions
class StoreBuilder
attr_reader :cookie_key, :session_cookie_token_prefix
def initialize(cookie_key, session_cookie_token_prefix)
@cookie_key = cookie_key
@session_cookie_token_prefix = session_cookie_token_prefix
end
def prepare
if Gitlab::Utils.to_boolean(ENV.fetch('USE_REDIS_CACHE_STORE_AS_SESSION_STORE', 'false'))
# Set expiry to very large number (practically permanent) instead of the default 1 week
# as some specs rely on time travel to a distant past or future.
Settings.gitlab['session_expire_delay'] = ::Gitlab::Database::MAX_INT_VALUE if Rails.env.test?
[
::Gitlab::Sessions::CacheStore, # Using the cookie_store would enable session replay attacks
{
cache: ActiveSupport::Cache::RedisCacheStore.new(
namespace: Gitlab::Redis::Sessions::SESSION_NAMESPACE,
redis: Gitlab::Redis::Sessions,
expires_in: Settings.gitlab['session_expire_delay'] * 60,
coder: Gitlab::Sessions::CacheStoreCoder
),
key: cookie_key,
secure: Gitlab.config.gitlab.https,
httponly: true,
path: Rails.application.config.relative_url_root.presence || '/',
session_cookie_token_prefix: session_cookie_token_prefix
}
]
else
[
Gitlab::Sessions::RedisStore, # Using the cookie_store would enable session replay attacks
{
redis_server: Gitlab::Redis::Sessions.params.merge(
namespace: Gitlab::Redis::Sessions::SESSION_NAMESPACE,
serializer: Gitlab::Sessions::RedisStoreSerializer
),
key: cookie_key,
secure: Gitlab.config.gitlab.https,
httponly: true,
expires_in: Settings.gitlab['session_expire_delay'] * 60,
path: Rails.application.config.relative_url_root.presence || '/',
session_cookie_token_prefix: session_cookie_token_prefix
}
]
end
end
end
end
end

View File

@ -16557,7 +16557,7 @@ msgstr ""
msgid "Could not retrieve the list of branches. Use the YAML editor mode, or refresh this page later. To view the list of branches, go to %{boldStart}Code - Branches%{boldEnd}"
msgstr ""
msgid "Could not retrieve the list of protected branches. Use the YAML editor mode, or refresh this page later. To view the list of protected branches, go to %{boldStart}Settings - Branches%{boldEnd} and expand %{boldStart}Protected branches%{boldEnd}."
msgid "Could not retrieve the list of protected branches. Use the YAML editor mode, or refresh this page later. To view the list of protected branches, go to %{boldStart}Settings - Repository%{boldEnd} and expand %{boldStart}Protected branches%{boldEnd}."
msgstr ""
msgid "Could not revoke access token %{access_token_name}."

View File

@ -62,7 +62,15 @@ describe('system note component', () => {
});
it('should render svg icon only for certain icons', () => {
const ALLOWED_ICONS = ['check', 'merge', 'merge-request-close', 'issue-close'];
const ALLOWED_ICONS = [
'check',
'merge',
'merge-request-close',
'issue-close',
'issues',
'error',
'review-warning',
];
createComponent(props);
expect(vm.find('[data-testid="timeline-icon"]').exists()).toBe(false);

View File

@ -14,6 +14,17 @@ RSpec.describe 'Session initializer for GitLab' do
end
describe 'config#session_store' do
it 'initialized as a redis_store with Gitlab::Sessions::CacheStore' do
expect(subject).to receive(:session_store).with(
::Gitlab::Sessions::CacheStore,
a_hash_including(
cache: ActiveSupport::Cache::RedisCacheStore
)
)
load_session_store
end
context 'when cell.id is configured' do
before do
stub_config(cell: { id: 1 })
@ -21,12 +32,9 @@ RSpec.describe 'Session initializer for GitLab' do
it 'initialized as a `redis_store` with session cookies prefix that includes cell id' do
expect(subject).to receive(:session_store).with(
Gitlab::Sessions::RedisStore,
::Gitlab::Sessions::CacheStore,
a_hash_including(
redis_server: Gitlab::Redis::Sessions.params.merge(
namespace: Gitlab::Redis::Sessions::SESSION_NAMESPACE,
serializer: Gitlab::Sessions::RedisStoreSerializer
),
cache: ActiveSupport::Cache::RedisCacheStore,
session_cookie_token_prefix: 'cell-1'
)
)
@ -42,12 +50,9 @@ RSpec.describe 'Session initializer for GitLab' do
it 'initialized as a `redis_store` with empty session cookie prefix' do
expect(subject).to receive(:session_store).with(
Gitlab::Sessions::RedisStore,
::Gitlab::Sessions::CacheStore,
a_hash_including(
redis_server: Gitlab::Redis::Sessions.params.merge(
namespace: Gitlab::Redis::Sessions::SESSION_NAMESPACE,
serializer: Gitlab::Sessions::RedisStoreSerializer
),
cache: ActiveSupport::Cache::RedisCacheStore,
session_cookie_token_prefix: ''
)
)

View File

@ -0,0 +1,41 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Sessions::CacheStoreCoder, feature_category: :system_access do
let(:hash) { { a: 1, b: 2 } }
shared_examples 'unmarshal' do
it 'returns ActiveSupport::Cache::Entry object' do
ret = load
expect(ret).to be_an_instance_of(ActiveSupport::Cache::Entry)
expect(ret.value).to eq(expected)
end
end
describe '.load' do
let(:expected) { hash }
subject(:load) { described_class.load(serialized) }
context 'with ActiveSupport::Cache::Entry value' do
let(:serialized) { Marshal.dump(ActiveSupport::Cache::Entry.new(hash)) }
it_behaves_like 'unmarshal'
end
context 'with hash value' do
let(:serialized) { Marshal.dump(hash) }
it_behaves_like 'unmarshal'
end
context 'with nil value' do
let(:serialized) { Marshal.dump(nil) }
let(:expected) { nil }
it_behaves_like 'unmarshal'
end
end
end

View File

@ -0,0 +1,42 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Sessions::CacheStore, feature_category: :cell do
using RSpec::Parameterized::TableSyntax
describe '#generate_sid' do
let(:redis_store) do
described_class.new(Rails.application, { session_cookie_token_prefix: session_cookie_token_prefix })
end
context 'when passing `session_cookie_token_prefix` in options' do
where(:prefix, :calculated_prefix) do
nil | ''
'' | ''
'random_prefix_' | 'random_prefix_-'
'_random_prefix' | '_random_prefix-'
end
with_them do
let(:session_cookie_token_prefix) { prefix }
it 'generates sid that is prefixed with the configured prefix' do
generated_sid = redis_store.generate_sid
expect(generated_sid).to be_a Rack::Session::SessionId
expect(generated_sid.public_id).to match(/^#{calculated_prefix}[a-z0-9]{32}$/)
end
end
end
context 'when not passing `session_cookie_token_prefix` in options' do
let(:redis_store) { described_class.new(Rails.application) }
it 'generates sid that is not prefixed' do
generated_sid = redis_store.generate_sid
expect(generated_sid).to be_a Rack::Session::SessionId
expect(generated_sid.public_id).to match(/^[a-z0-9]{32}$/)
end
end
end
end

View File

@ -0,0 +1,47 @@
# frozen_string_literal: true
require 'rspec'
RSpec.describe Gitlab::Sessions::StoreBuilder, feature_category: :system_access do
let(:cookie_key) { 'cookie' }
let(:session_cookie_token_prefix) { 'token_prefix' }
subject(:prepare) { described_class.new(cookie_key, session_cookie_token_prefix).prepare }
context 'when env var USE_REDIS_CACHE_STORE_AS_SESSION_STORE=true' do
before do
stub_env('USE_REDIS_CACHE_STORE_AS_SESSION_STORE', 'true')
end
it 'returns Gitlab::Sessions::CacheStore' do
expect(prepare).to match([
::Gitlab::Sessions::CacheStore,
a_hash_including(
cache: ActiveSupport::Cache::RedisCacheStore,
key: cookie_key,
session_cookie_token_prefix: session_cookie_token_prefix
)
])
end
end
context 'when env var USE_REDIS_CACHE_STORE_AS_SESSION_STORE=false' do
before do
stub_env('USE_REDIS_CACHE_STORE_AS_SESSION_STORE', 'false')
end
it 'returns Gitlab::Sessions::RedisStore' do
expect(prepare).to match([
Gitlab::Sessions::RedisStore,
a_hash_including(
redis_server: Gitlab::Redis::Sessions.params.merge(
namespace: Gitlab::Redis::Sessions::SESSION_NAMESPACE,
serializer: Gitlab::Sessions::RedisStoreSerializer
),
key: cookie_key,
session_cookie_token_prefix: session_cookie_token_prefix
)
])
end
end
end

View File

@ -181,13 +181,31 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_sessions, feature_category: :s
end
describe '.sessions_from_ids' do
it 'uses the ActiveSession lookup to return original sessions' do
Gitlab::Redis::Sessions.with do |redis|
# Emulate redis-rack: https://github.com/redis-store/redis-rack/blob/c75f7f1a6016ee224e2615017fbfee964f23a837/lib/rack/session/redis.rb#L88
redis.set("session:gitlab:#{rack_session.private_id}", Marshal.dump({ _csrf_token: 'abcd' }))
context 'with new session format from Gitlab::Sessions::CacheStore' do
before do
store = ActiveSupport::Cache::RedisCacheStore.new(
namespace: Gitlab::Redis::Sessions::SESSION_NAMESPACE,
redis: Gitlab::Redis::Sessions
)
# ActiveSupport::Cache::RedisCacheStore wraps the data in ActiveSupport::Cache::Entry
# https://github.com/rails/rails/blob/v7.0.8.6/activesupport/lib/active_support/cache.rb#L506
store.write(rack_session.private_id, { _csrf_token: 'abcd' })
end
expect(described_class.sessions_from_ids([rack_session.private_id])).to eq [{ _csrf_token: 'abcd' }]
it 'uses the ActiveSession lookup to return original sessions' do
expect(described_class.sessions_from_ids([rack_session.private_id])).to eq [{ _csrf_token: 'abcd' }]
end
end
context 'with old session format from Gitlab::Sessions::RedisStore' do
it 'uses the ActiveSession lookup to return original sessions' do
Gitlab::Redis::Sessions.with do |redis|
# Emulate redis-rack: https://github.com/redis-store/redis-rack/blob/c75f7f1a6016ee224e2615017fbfee964f23a837/lib/rack/session/redis.rb#L88
redis.set("session:gitlab:#{rack_session.private_id}", Marshal.dump({ _csrf_token: 'abcd' }))
end
expect(described_class.sessions_from_ids([rack_session.private_id])).to eq [{ _csrf_token: 'abcd' }]
end
end
it 'avoids a redis lookup for an empty array' do

View File

@ -23,17 +23,19 @@ RSpec.describe ApplicationController, type: :request, feature_category: :shared
describe 'session expiration' do
context 'when user is authenticated' do
it 'does not set the expire_after option' do
it 'sets the expire_after option from session_expire_delay setting' do
sign_in(user)
get root_path
expect(request.env['rack.session.options'][:expire_after]).to be_nil
expect(request.env['rack.session.options'][:expire_after]).to eq(
Settings.gitlab['session_expire_delay'] * 60
)
end
end
context 'when user is unauthenticated' do
it 'sets the expire_after option' do
it 'sets the expire_after option from unauthenticated_session_expire_delay' do
get root_path
expect(request.env['rack.session.options'][:expire_after]).to eq(

View File

@ -10,8 +10,13 @@ module SessionHelpers
session_id = Rack::Session::SessionId.new(SecureRandom.hex)
store = ActiveSupport::Cache::RedisCacheStore.new(
namespace: Gitlab::Redis::Sessions::SESSION_NAMESPACE,
redis: Gitlab::Redis::Sessions
)
store.write(session_id.private_id, session_data)
Gitlab::Redis::Sessions.with do |redis|
redis.set("session:gitlab:#{session_id.private_id}", Marshal.dump(session_data))
redis.sadd("session:lookup:user:gitlab:#{user_id}", [session_id.private_id]) if user_id
end