diff --git a/app/services/personal_access_tokens/create_service.rb b/app/services/personal_access_tokens/create_service.rb index be7d9b931e5d9b2c4a63cee7e671e6531e97d786..c31caaf864e4b1e7d342984c7c10ae2d6b33c961 100644 --- a/app/services/personal_access_tokens/create_service.rb +++ b/app/services/personal_access_tokens/create_service.rb @@ -35,6 +35,8 @@ def execute def personal_access_token_params { name: params[:name], + user_type: target_user.user_type, + group_id: group_id, impersonation: params[:impersonation] || false, scopes: params[:scopes], expires_at: pat_expiration, @@ -43,6 +45,10 @@ def personal_access_token_params } end + def group_id + # overridden in EE + end + def pat_expiration return params[:expires_at] if params[:expires_at].present? diff --git a/app/services/personal_access_tokens/rotate_service.rb b/app/services/personal_access_tokens/rotate_service.rb index 54597dc14d259684d05c6b02fa00da863e840de6..adbcfa3ba956fcabbccd493d8a3ad16bd1026cf7 100644 --- a/app/services/personal_access_tokens/rotate_service.rb +++ b/app/services/personal_access_tokens/rotate_service.rb @@ -110,6 +110,8 @@ def error_response(message) def create_token_params { name: token.name, + user_type: token.user_type, + group_id: token.group_id, description: token.description, previous_personal_access_token_id: token.id, impersonation: token.impersonation, diff --git a/db/docs/batched_background_migrations/backfill_group_id_and_user_type_for_human_users_personal_access_tokens.yml b/db/docs/batched_background_migrations/backfill_group_id_and_user_type_for_human_users_personal_access_tokens.yml new file mode 100644 index 0000000000000000000000000000000000000000..678f37f78987fb9e9c6daf97292e42e333bc159a --- /dev/null +++ b/db/docs/batched_background_migrations/backfill_group_id_and_user_type_for_human_users_personal_access_tokens.yml @@ -0,0 +1,9 @@ +--- +migration_job_name: BackfillGroupIdAndUserTypeForHumanUsersPersonalAccessTokens +description: Backfills `group_id` based on `user_details.enterprise_group_id` and + `user_type` based on `users.user_type` for existing human users' personal_access_tokens. +feature_category: system_access +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/201921 +milestone: '18.4' +queued_migration_version: 20250828111740 +finalized_by: # version of the migration that finalized this BBM diff --git a/db/post_migrate/20250828111740_queue_backfill_group_id_and_user_type_for_human_users_personal_access_tokens.rb b/db/post_migrate/20250828111740_queue_backfill_group_id_and_user_type_for_human_users_personal_access_tokens.rb new file mode 100644 index 0000000000000000000000000000000000000000..ce4569008966da7904cbe108261cc3ef6fc0dc02 --- /dev/null +++ b/db/post_migrate/20250828111740_queue_backfill_group_id_and_user_type_for_human_users_personal_access_tokens.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +class QueueBackfillGroupIdAndUserTypeForHumanUsersPersonalAccessTokens < Gitlab::Database::Migration[2.3] + milestone '18.4' + + restrict_gitlab_migration gitlab_schema: :gitlab_main + + MIGRATION = "BackfillGroupIdAndUserTypeForHumanUsersPersonalAccessTokens" + DELAY_INTERVAL = 2.minutes + BATCH_SIZE = 1000 + SUB_BATCH_SIZE = 100 + + def up + queue_batched_background_migration( + MIGRATION, + :personal_access_tokens, + :id, + job_interval: DELAY_INTERVAL, + batch_size: BATCH_SIZE, + sub_batch_size: SUB_BATCH_SIZE + ) + end + + def down + delete_batched_background_migration(MIGRATION, :personal_access_tokens, :id, []) + end +end diff --git a/db/schema_migrations/20250828111740 b/db/schema_migrations/20250828111740 new file mode 100644 index 0000000000000000000000000000000000000000..a3dd67f58acba523fbf6db9a91314d19701b4772 --- /dev/null +++ b/db/schema_migrations/20250828111740 @@ -0,0 +1 @@ +9eedf1bfcb5c6c72cee92fb23f45a1073fdc5f0e096e7289d5cfa782245a280e \ No newline at end of file diff --git a/ee/app/services/ee/personal_access_tokens/create_service.rb b/ee/app/services/ee/personal_access_tokens/create_service.rb index ffa759762fe479fcf193bd1ea2d654bd2b98b0bc..8bb255207aadf963adf18a069e9154d22de05dd8 100644 --- a/ee/app/services/ee/personal_access_tokens/create_service.rb +++ b/ee/app/services/ee/personal_access_tokens/create_service.rb @@ -13,6 +13,12 @@ def execute private + def group_id + return target_user.enterprise_group_id if target_user.enterprise_user? + + super + end + def send_audit_event(response) message = if response.success? "Created personal access token with id #{response.payload[:personal_access_token].id}" diff --git a/ee/app/services/groups/enterprise_users/associate_service.rb b/ee/app/services/groups/enterprise_users/associate_service.rb index 071b9b565b644fe33ee16ad7eb2375d9d6657f28..f4a4fcfd07eadf43a264de18d0a3dd5db70c2c4c 100644 --- a/ee/app/services/groups/enterprise_users/associate_service.rb +++ b/ee/app/services/groups/enterprise_users/associate_service.rb @@ -21,6 +21,7 @@ def execute # Allows the raising of persistent failure and enables it to be retried when called from inside sidekiq. # see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/130735#note_1550114699 + associate_user_personal_access_tokens_with_group @user.update!(user_attributes) Notify.user_associated_with_enterprise_group_email(user.id).deliver_later @@ -32,6 +33,12 @@ def execute private + def associate_user_personal_access_tokens_with_group + user.personal_access_tokens.each_batch(of: 100) do |personal_access_tokens_batch| + personal_access_tokens_batch.update_all(group_id: group.id) + end + end + def user_attributes enterprise_user_attributes.merge(::Onboarding::FinishService.new(user).onboarding_attributes) end diff --git a/ee/app/services/groups/enterprise_users/disassociate_service.rb b/ee/app/services/groups/enterprise_users/disassociate_service.rb index 4399bf8171ccabfe4d6b6e829f5e2e542fc71733..dd4cb2d18d76e3d439aee9de26ad3dc17eea2a9b 100644 --- a/ee/app/services/groups/enterprise_users/disassociate_service.rb +++ b/ee/app/services/groups/enterprise_users/disassociate_service.rb @@ -17,12 +17,23 @@ def execute return error('The user matches the "Enterprise User" definition for the group') end + # Allows the raising of persistent failure and enables it to be retried when called from inside sidekiq. + # see https://gitlab.com/gitlab-org/gitlab/-/merge_requests/130735#note_1550114699 + disassociate_user_personal_access_tokens_from_group @user.user_detail.update!(enterprise_group_id: nil, enterprise_group_associated_at: nil) log_info(message: 'Disassociated the user from the enterprise group') success end + + private + + def disassociate_user_personal_access_tokens_from_group + user.personal_access_tokens.each_batch(of: 100) do |personal_access_tokens_batch| + personal_access_tokens_batch.update_all(group_id: nil) + end + end end end end diff --git a/ee/spec/services/ee/personal_access_tokens/create_service_spec.rb b/ee/spec/services/ee/personal_access_tokens/create_service_spec.rb index cbc1b7b297c345d38df54940311aa9891b38e105..8809fdb2e394facf2f830a845e8844ce00d19a89 100644 --- a/ee/spec/services/ee/personal_access_tokens/create_service_spec.rb +++ b/ee/spec/services/ee/personal_access_tokens/create_service_spec.rb @@ -66,6 +66,7 @@ subject(:create_token) { service.execute } let(:target_user) { create(:user) } + let(:current_user) { target_user } let(:organization) { create(:organization) } let(:service) do described_class.new(current_user: current_user, target_user: target_user, @@ -291,5 +292,29 @@ it_behaves_like 'an unsuccessfully created token' end end + + context 'for group_id' do + let(:params) { valid_params } + + context 'when the user is an enterprise user' do + let_it_be(:target_user) { create(:enterprise_user) } + + it "creates personal access token record with group_id set to the user's enterprise_group_id" do + expect(target_user.enterprise_group_id).not_to be_nil + + expect(create_token.success?).to be true + expect(token.group_id).to eq(target_user.enterprise_group_id) + end + end + + context 'when the user is a regular user' do + let_it_be(:target_user) { create(:user) } + + it "creates personal access token record with group_id set to nil" do + expect(create_token.success?).to be true + expect(token.group_id).to be_nil + end + end + end end end diff --git a/ee/spec/services/ee/personal_access_tokens/rotate_service_spec.rb b/ee/spec/services/ee/personal_access_tokens/rotate_service_spec.rb index 0337d9a4765d00bbf8878490a23cdaeba52324eb..5ec3c0facaa8f94446b27dbef2aa835fa5dc61b5 100644 --- a/ee/spec/services/ee/personal_access_tokens/rotate_service_spec.rb +++ b/ee/spec/services/ee/personal_access_tokens/rotate_service_spec.rb @@ -88,4 +88,47 @@ expect(new_token.user).to eq(token.user) end end + + context "for enterprise_user's token" do + let_it_be(:current_user) { create(:enterprise_user) } + let_it_be(:token, reload: true) do + create(:personal_access_token, user: current_user, group: current_user.enterprise_group) + end + + it "rotates user's own token" do + expect(response).to be_success + + new_token = response.payload[:personal_access_token] + + expect(new_token.token).not_to eq(token.token) + expect(new_token.user).to eq(token.user) + expect(new_token.user_type).to eq(token.user_type) + expect(new_token.group_id).to eq(token.group_id) + expect(new_token.user.namespace).to eq(token.user.namespace) + expect(new_token.organization).to eq(token.organization) + expect(new_token.description).to eq(token.description) + end + end + + context "for group service account's token" do + let_it_be(:group) { create(:group) } + let_it_be(:current_user) { create(:user, :service_account, provisioned_by_group: group) } + let_it_be(:token, reload: true) do + create(:personal_access_token, user: current_user, group: group) + end + + it "rotates user's own token" do + expect(response).to be_success + + new_token = response.payload[:personal_access_token] + + expect(new_token.token).not_to eq(token.token) + expect(new_token.user).to eq(token.user) + expect(new_token.user_type).to eq(token.user_type) + expect(new_token.group_id).to eq(token.group_id) + expect(new_token.user.namespace).to eq(token.user.namespace) + expect(new_token.organization).to eq(token.organization) + expect(new_token.description).to eq(token.description) + end + end end diff --git a/ee/spec/services/groups/enterprise_users/associate_service_spec.rb b/ee/spec/services/groups/enterprise_users/associate_service_spec.rb index a97864ba7d7c10d0d8543daf1b7eff74fe80994a..6d45b6b201a00b98a2ecdee9b58a3ad4acccd769 100644 --- a/ee/spec/services/groups/enterprise_users/associate_service_spec.rb +++ b/ee/spec/services/groups/enterprise_users/associate_service_spec.rb @@ -22,6 +22,18 @@ let(:user) { create(:user, created_at: user_created_at) } + let(:user_personal_access_token1) { create(:personal_access_token, user: user, group_id: user.enterprise_group_id) } + + let(:user_personal_access_token2) do + create(:personal_access_token, :expired, user: user, group_id: user.enterprise_group_id) + end + + let(:user_personal_access_token3) do + create(:personal_access_token, :revoked, user: user, group_id: user.enterprise_group_id) + end + + let(:another_user_personal_access_token) { create(:personal_access_token) } + subject(:service) { described_class.new(group: group, user: user) } describe '#execute' do @@ -56,6 +68,25 @@ expect(user.user_detail.enterprise_group_associated_at).to eq(Time.current) end + it 'sets group_id for user.personal_access_tokens', :aggregate_failures do + previous_enterprise_group_id = user.user_detail.enterprise_group_id + expect(previous_enterprise_group_id).not_to eq(group.id) + + expect(user_personal_access_token1.group_id).to eq(previous_enterprise_group_id) + expect(user_personal_access_token2.group_id).to eq(previous_enterprise_group_id) + expect(user_personal_access_token3.group_id).to eq(previous_enterprise_group_id) + + expect(another_user_personal_access_token.group_id).to be_nil + + service.execute + + expect(user_personal_access_token1.reload.group_id).to eq(group.id) + expect(user_personal_access_token2.reload.group_id).to eq(group.id) + expect(user_personal_access_token3.reload.group_id).to eq(group.id) + + expect(another_user_personal_access_token.reload.group_id).to be_nil + end + it 'enqueues user_associated_with_enterprise_group_email email for later delivery to the user' do expect do service.execute @@ -164,6 +195,24 @@ expect(user.user_detail.enterprise_group_associated_at).to eq(previous_enterprise_group_associated_at) end + it 'does not update group_id for user.personal_access_tokens', :aggregate_failures do + previous_enterprise_group_id = user.user_detail.enterprise_group_id + + expect(user_personal_access_token1.group_id).to eq(previous_enterprise_group_id) + expect(user_personal_access_token2.group_id).to eq(previous_enterprise_group_id) + expect(user_personal_access_token3.group_id).to eq(previous_enterprise_group_id) + + expect(another_user_personal_access_token.group_id).to be_nil + + service.execute + + expect(user_personal_access_token1.reload.group_id).to eq(previous_enterprise_group_id) + expect(user_personal_access_token2.reload.group_id).to eq(previous_enterprise_group_id) + expect(user_personal_access_token3.reload.group_id).to eq(previous_enterprise_group_id) + + expect(another_user_personal_access_token.reload.group_id).to be_nil + end + it 'does not enqueue any email for later delivery' do expect do service.execute diff --git a/ee/spec/services/groups/enterprise_users/disassociate_service_spec.rb b/ee/spec/services/groups/enterprise_users/disassociate_service_spec.rb index 876c9745089e01d7fee12755f695e0b0bb7d0693..8e81173d1cb453b7f72cd3b9c05aa4d48af8499d 100644 --- a/ee/spec/services/groups/enterprise_users/disassociate_service_spec.rb +++ b/ee/spec/services/groups/enterprise_users/disassociate_service_spec.rb @@ -5,6 +5,22 @@ RSpec.describe Groups::EnterpriseUsers::DisassociateService, :saas, feature_category: :user_management do subject(:service) { described_class.new(user: user) } + let(:user_personal_access_token1) do + create(:personal_access_token, user: user, group_id: user.enterprise_group_id) + end + + let(:user_personal_access_token2) do + create(:personal_access_token, :expired, user: user, group_id: user.enterprise_group_id) + end + + let(:user_personal_access_token3) do + create(:personal_access_token, :revoked, user: user, group_id: user.enterprise_group_id) + end + + let(:another_user_personal_access_token) do + create(:personal_access_token, group_id: create(:group).id) + end + describe '#execute' do shared_examples 'disassociates the user from the enterprise group' do it 'returns a successful response', :aggregate_failures do @@ -35,6 +51,25 @@ expect(user.user_detail.enterprise_group_associated_at).to eq(nil) end + it 'sets group_id for user.personal_access_tokens to nil', :aggregate_failures do + previous_enterprise_group_id = user.user_detail.enterprise_group_id + expect(previous_enterprise_group_id).not_to be_nil + + expect(user_personal_access_token1.group_id).to eq(previous_enterprise_group_id) + expect(user_personal_access_token2.group_id).to eq(previous_enterprise_group_id) + expect(user_personal_access_token3.group_id).to eq(previous_enterprise_group_id) + + expect(another_user_personal_access_token.group_id).not_to be_nil + + service.execute + + expect(user_personal_access_token1.reload.group_id).to be_nil + expect(user_personal_access_token2.reload.group_id).to be_nil + expect(user_personal_access_token3.reload.group_id).to be_nil + + expect(another_user_personal_access_token.reload.group_id).not_to be_nil + end + it 'logs message with info level about disassociating the user from the enterprise group' do allow(Gitlab::AppLogger).to receive(:info) @@ -92,6 +127,24 @@ expect(user.user_detail.enterprise_group_associated_at).to eq(previous_enterprise_group_associated_at) end + it 'does not update group_id for user.personal_access_tokens to nil', :aggregate_failures do + previous_enterprise_group_id = user.user_detail.enterprise_group_id + + expect(user_personal_access_token1.group_id).to eq(previous_enterprise_group_id) + expect(user_personal_access_token2.group_id).to eq(previous_enterprise_group_id) + expect(user_personal_access_token3.group_id).to eq(previous_enterprise_group_id) + + expect(another_user_personal_access_token.group_id).not_to be_nil + + service.execute + + expect(user_personal_access_token1.reload.group_id).to eq(previous_enterprise_group_id) + expect(user_personal_access_token2.reload.group_id).to eq(previous_enterprise_group_id) + expect(user_personal_access_token3.reload.group_id).to eq(previous_enterprise_group_id) + + expect(another_user_personal_access_token.reload.group_id).not_to be_nil + end + it 'does not log any message with info level' do expect(Gitlab::AppLogger).not_to receive(:info).with( message: 'Disassociated the user from the enterprise group' diff --git a/lib/gitlab/background_migration/backfill_group_id_and_user_type_for_human_users_personal_access_tokens.rb b/lib/gitlab/background_migration/backfill_group_id_and_user_type_for_human_users_personal_access_tokens.rb new file mode 100644 index 0000000000000000000000000000000000000000..db1c8aa931a8adc11578b669a0962ba9973aa8f3 --- /dev/null +++ b/lib/gitlab/background_migration/backfill_group_id_and_user_type_for_human_users_personal_access_tokens.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module Gitlab + module BackgroundMigration + class BackfillGroupIdAndUserTypeForHumanUsersPersonalAccessTokens < BatchedMigrationJob + operation_name :backfill_group_id_and_user_type_for_human_users_personal_access_tokens + feature_category :system_access + + def perform + each_sub_batch do |sub_batch| + connection.execute( + <<~SQL + UPDATE personal_access_tokens + SET user_type=users.user_type, group_id=user_details.enterprise_group_id + FROM + users + LEFT JOIN user_details ON user_details.user_id=users.id + WHERE + personal_access_tokens.id IN (#{sub_batch.select(:id).limit(sub_batch_size).to_sql}) + AND personal_access_tokens.user_type IS NULL + AND personal_access_tokens.user_id=users.id + AND users.user_type = 0 + SQL + ) + end + end + end + end +end diff --git a/spec/lib/gitlab/background_migration/backfill_group_id_and_user_type_for_human_users_personal_access_tokens_spec.rb b/spec/lib/gitlab/background_migration/backfill_group_id_and_user_type_for_human_users_personal_access_tokens_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..a54068a4b560f332b974f31bc7e044898f37bcea --- /dev/null +++ b/spec/lib/gitlab/background_migration/backfill_group_id_and_user_type_for_human_users_personal_access_tokens_spec.rb @@ -0,0 +1,295 @@ +# frozen_string_literal: true + +require 'spec_helper' + +# rubocop:disable RSpec/MultipleMemoizedHelpers -- We need this many for this background migration +RSpec.describe( + Gitlab::BackgroundMigration::BackfillGroupIdAndUserTypeForHumanUsersPersonalAccessTokens, + feature_category: :system_access +) do + subject(:migration) do + described_class.new( + batch_table: :personal_access_tokens, + batch_column: :id, + sub_batch_size: 100, + pause_ms: 100, + connection: ApplicationRecord.connection + ) + end + + let(:organizations) { table(:organizations) } + let(:namespaces) { table(:namespaces) } + let(:users) { table(:users) } + let(:user_details) { table(:user_details) } + let(:personal_access_tokens) { table(:personal_access_tokens) } + + let!(:organization) { organizations.create!(name: 'organization', path: 'organization') } + + let(:group1) { namespaces.create!(name: 'group1', path: 'group1', type: 'Group1', organization_id: organization.id) } + let(:group2) { namespaces.create!(name: 'group2', path: 'group2', type: 'Group2', organization_id: organization.id) } + + let!(:human_user) do + users.create!( + username: 'human_user', + email: 'human_user@example.com', + user_type: 0, + projects_limit: 10, + organization_id: organization.id + ) + end + + let!(:human_user_details) do + user_details.create!( + user_id: human_user.id + ) + end + + let!(:human_user_personal_access_token1) do + personal_access_tokens.create!( + name: 'human_user_personal_access_token1', + user_id: human_user.id, + organization_id: human_user.organization_id + ) + end + + let!(:human_user_personal_access_token2) do + personal_access_tokens.create!( + name: 'human_user_personal_access_token2', + user_id: human_user.id, + organization_id: human_user.organization_id + ) + end + + let!(:human_user_without_user_details) do + users.create!( + username: 'human_user_without_user_details', + email: 'human_user_without_user_details@example.com', + user_type: 0, + projects_limit: 10, + organization_id: organization.id + ) + end + + let!(:human_user_without_user_details_personal_access_token1) do + personal_access_tokens.create!( + name: 'human_user_without_user_details_personal_access_token1', + user_id: human_user_without_user_details.id, + organization_id: human_user_without_user_details.organization_id + ) + end + + let!(:human_user_without_user_details_personal_access_token2) do + personal_access_tokens.create!( + name: 'human_user_without_user_details_personal_access_token2', + user_id: human_user_without_user_details.id, + organization_id: human_user_without_user_details.organization_id + ) + end + + let!(:enterprise_user1) do + users.create!( + username: 'enterprise_user1', + email: 'enterprise_user1@example.com', + user_type: 0, + projects_limit: 10, + organization_id: organization.id + ) + end + + let!(:enterprise_user1_details) do + user_details.create!( + user_id: enterprise_user1.id, + enterprise_group_id: group1.id + ) + end + + let!(:enterprise_user1_personal_access_token1) do + personal_access_tokens.create!( + name: 'enterprise_user1_personal_access_token1', + user_id: enterprise_user1.id, + organization_id: enterprise_user1.organization_id + ) + end + + let!(:enterprise_user1_personal_access_token2) do + personal_access_tokens.create!( + name: 'enterprise_user1_personal_access_token2', + user_id: enterprise_user1.id, + organization_id: enterprise_user1.organization_id + ) + end + + let!(:enterprise_user2) do + users.create!( + username: 'enterprise_user2', + email: 'enterprise_user2@example.com', + user_type: 0, + projects_limit: 10, + organization_id: organization.id + ) + end + + let!(:enterprise_user2_details) do + user_details.create!( + user_id: enterprise_user2.id, + enterprise_group_id: group2.id + ) + end + + let!(:enterprise_user2_personal_access_token1) do + personal_access_tokens.create!( + name: 'enterprise_user2_personal_access_token1', + user_id: enterprise_user2.id, + organization_id: enterprise_user2.organization_id + ) + end + + let!(:enterprise_user2_personal_access_token2) do + personal_access_tokens.create!( + name: 'enterprise_user2_personal_access_token2', + user_id: enterprise_user2.id, + organization_id: enterprise_user2.organization_id + ) + end + + let!(:project_bot_user) do + users.create!( + username: 'project_bot_user', + email: 'project_bot_user@example.com', + user_type: 6, + projects_limit: 10, + organization_id: organization.id + ) + end + + let!(:project_bot_user_details) do + user_details.create!( + user_id: project_bot_user.id + ) + end + + let!(:project_bot_user_personal_access_token1) do + personal_access_tokens.create!( + name: 'project_bot_user_personal_access_token1', + user_id: project_bot_user.id, + organization_id: project_bot_user.organization_id + ) + end + + let!(:project_bot_user_personal_access_token2) do + personal_access_tokens.create!( + name: 'project_bot_user_personal_access_token2', + user_id: project_bot_user.id, + organization_id: project_bot_user.organization_id + ) + end + + let!(:service_account_user) do + users.create!( + username: 'service_account_user', + email: 'service_account_user@example.com', + user_type: 13, + projects_limit: 10, + organization_id: organization.id + ) + end + + let!(:service_account_user_details) do + user_details.create!( + user_id: service_account_user.id + ) + end + + let!(:service_account_user_personal_access_token1) do + personal_access_tokens.create!( + name: 'service_account_user_personal_access_token1', + user_id: service_account_user.id, + organization_id: service_account_user.organization_id + ) + end + + let!(:service_account_user_personal_access_token2) do + personal_access_tokens.create!( + name: 'service_account_user_personal_access_token2', + user_id: service_account_user.id, + organization_id: service_account_user.organization_id + ) + end + + it "backfills group_id and user_type for human users' personal_access_tokens", :aggregate_failures do + expect(human_user_personal_access_token1.group_id).to be_nil + expect(human_user_personal_access_token1.user_type).to be_nil + + expect(human_user_personal_access_token2.group_id).to be_nil + expect(human_user_personal_access_token2.user_type).to be_nil + + expect(human_user_without_user_details_personal_access_token1.group_id).to be_nil + expect(human_user_without_user_details_personal_access_token1.user_type).to be_nil + + expect(human_user_without_user_details_personal_access_token2.group_id).to be_nil + expect(human_user_without_user_details_personal_access_token2.user_type).to be_nil + + expect(enterprise_user1_personal_access_token1.group_id).to be_nil + expect(enterprise_user1_personal_access_token1.user_type).to be_nil + + expect(enterprise_user1_personal_access_token2.group_id).to be_nil + expect(enterprise_user1_personal_access_token2.user_type).to be_nil + + expect(enterprise_user2_personal_access_token1.group_id).to be_nil + expect(enterprise_user2_personal_access_token1.user_type).to be_nil + + expect(enterprise_user2_personal_access_token2.group_id).to be_nil + expect(enterprise_user2_personal_access_token2.user_type).to be_nil + + expect(project_bot_user_personal_access_token1.group_id).to be_nil + expect(project_bot_user_personal_access_token1.user_type).to be_nil + + expect(project_bot_user_personal_access_token2.group_id).to be_nil + expect(project_bot_user_personal_access_token2.user_type).to be_nil + + expect(service_account_user_personal_access_token1.group_id).to be_nil + expect(service_account_user_personal_access_token1.user_type).to be_nil + + expect(service_account_user_personal_access_token2.group_id).to be_nil + expect(service_account_user_personal_access_token2.user_type).to be_nil + + migration.perform + + expect(human_user_personal_access_token1.reload.group_id).to be_nil + expect(human_user_personal_access_token1.reload.user_type).to eq(0) + + expect(human_user_personal_access_token2.reload.group_id).to be_nil + expect(human_user_personal_access_token2.reload.user_type).to eq(0) + + expect(human_user_without_user_details_personal_access_token1.reload.group_id).to be_nil + expect(human_user_without_user_details_personal_access_token1.reload.user_type).to eq(0) + + expect(human_user_without_user_details_personal_access_token2.reload.group_id).to be_nil + expect(human_user_without_user_details_personal_access_token2.reload.user_type).to eq(0) + + expect(enterprise_user1_personal_access_token1.reload.group_id).to eq(group1.id) + expect(enterprise_user1_personal_access_token1.reload.user_type).to eq(0) + + expect(enterprise_user1_personal_access_token2.reload.group_id).to eq(group1.id) + expect(enterprise_user1_personal_access_token2.reload.user_type).to eq(0) + + expect(enterprise_user2_personal_access_token1.reload.group_id).to eq(group2.id) + expect(enterprise_user2_personal_access_token1.reload.user_type).to eq(0) + + expect(enterprise_user2_personal_access_token2.reload.group_id).to eq(group2.id) + expect(enterprise_user2_personal_access_token2.reload.user_type).to eq(0) + + expect(project_bot_user_personal_access_token1.reload.group_id).to be_nil + expect(project_bot_user_personal_access_token1.reload.user_type).to be_nil + + expect(project_bot_user_personal_access_token2.reload.group_id).to be_nil + expect(project_bot_user_personal_access_token2.reload.user_type).to be_nil + + expect(service_account_user_personal_access_token1.reload.group_id).to be_nil + expect(service_account_user_personal_access_token1.reload.user_type).to be_nil + + expect(service_account_user_personal_access_token2.reload.group_id).to be_nil + expect(service_account_user_personal_access_token2.reload.user_type).to be_nil + end +end +# rubocop:enable RSpec/MultipleMemoizedHelpers diff --git a/spec/migrations/20250828111740_queue_backfill_group_id_and_user_type_for_human_users_personal_access_tokens_spec.rb b/spec/migrations/20250828111740_queue_backfill_group_id_and_user_type_for_human_users_personal_access_tokens_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..9b5eea599f4492976304fcc865b8005daefc4bad --- /dev/null +++ b/spec/migrations/20250828111740_queue_backfill_group_id_and_user_type_for_human_users_personal_access_tokens_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe QueueBackfillGroupIdAndUserTypeForHumanUsersPersonalAccessTokens, migration: :gitlab_main, feature_category: :system_access 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: :personal_access_tokens, + column_name: :id, + interval: described_class::DELAY_INTERVAL, + batch_size: described_class::BATCH_SIZE, + sub_batch_size: described_class::SUB_BATCH_SIZE + ) + } + end + end +end diff --git a/spec/services/personal_access_tokens/create_service_spec.rb b/spec/services/personal_access_tokens/create_service_spec.rb index 014b610e05c1d89bcf7246bd11b28ac1e1107bff..f2caf3bfbe408c30a14fd3754536c6347facf3cb 100644 --- a/spec/services/personal_access_tokens/create_service_spec.rb +++ b/spec/services/personal_access_tokens/create_service_spec.rb @@ -13,6 +13,7 @@ expect(token.expires_at).to eq(params[:expires_at]) expect(token.organization).to eq(organization) expect(token.user).to eq(user) + expect(token.user_type).to eq(user.user_type) end it 'logs the event' do diff --git a/spec/services/personal_access_tokens/rotate_service_spec.rb b/spec/services/personal_access_tokens/rotate_service_spec.rb index d6684c98fdd664120c3dcae62b984d9fd637823f..61a9d927dd3829539b90a67ba7b8c5c5a174f940 100644 --- a/spec/services/personal_access_tokens/rotate_service_spec.rb +++ b/spec/services/personal_access_tokens/rotate_service_spec.rb @@ -22,6 +22,8 @@ expect(new_token.token).not_to eq(token.token) expect(new_token.expires_at).to eq(Time.zone.today + 1.week) expect(new_token.user).to eq(token.user) + expect(new_token.user_type).to eq(token.user_type) + expect(new_token.group_id).to eq(token.group_id) expect(new_token.user.namespace).to eq(token.user.namespace) expect(new_token.organization).to eq(token.organization) expect(new_token.description).to eq(token.description)