From 7f9981baf29b2e04dae8fbed9e4885526d6c3f3e Mon Sep 17 00:00:00 2001 From: Aryan Dutt Date: Wed, 9 Jul 2025 05:58:46 +0000 Subject: [PATCH 01/11] Add OAuth access token to validity checks This MR enhances the Secret Detection Validity Checks feature so that gitlab_oauth_access_token are now assigned a status when detected by a Secret Detection scan Changelog: added MR: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/197089 EE: true --- .../security/secret_detection/access_token.rb | 19 +++++++++++++++++++ .../secret_detection/token_lookup_service.rb | 7 ++++++- .../update_token_status_service_spec.rb | 4 ++++ 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 ee/app/models/security/secret_detection/access_token.rb diff --git a/ee/app/models/security/secret_detection/access_token.rb b/ee/app/models/security/secret_detection/access_token.rb new file mode 100644 index 00000000000000..d829e40ebc6859 --- /dev/null +++ b/ee/app/models/security/secret_detection/access_token.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Security + module SecretDetection + class AccessToken < ::Doorkeeper::AccessToken + # Used to find multiple tokens (by their hashed value) + scope :with_token_digests, ->(hashed_tokens) do + return none if hashed_tokens.blank? + + where(token: hashed_tokens) + end + + # Hashes raw token + def self.encode(raw_token_value) + ::Gitlab::DoorkeeperSecretStoring::Token::Pbkdf2Sha512.transform_secret(raw_token_value) + end + end + end +end diff --git a/ee/app/services/security/secret_detection/token_lookup_service.rb b/ee/app/services/security/secret_detection/token_lookup_service.rb index 28bc521c693ac1..c829ba489e938a 100644 --- a/ee/app/services/security/secret_detection/token_lookup_service.rb +++ b/ee/app/services/security/secret_detection/token_lookup_service.rb @@ -76,7 +76,12 @@ class TokenLookupService 'gitlab_ci_build_token' => CI_BUILD_TOKEN_CONFIG, 'gitlab_incoming_email_token' => INCOMING_EMAIL_TOKEN_CONFIG, 'gitlab_feed_token_v2' => FEED_TOKEN_CONFIG, - 'gitlab_pipeline_trigger_token' => PIPELINE_TRIGGER_CONFIG + 'gitlab_pipeline_trigger_token' => PIPELINE_TRIGGER_CONFIG, + 'gitlab_oauth_access_token' => { + model: Security::SecretDetection::AccessToken, + lookup_method: :with_token_digests, + token_method: :token + } }.freeze # Checks if a given token type is supported by this service diff --git a/ee/spec/services/security/secret_detection/update_token_status_service_spec.rb b/ee/spec/services/security/secret_detection/update_token_status_service_spec.rb index 33b71c56f9cc0d..af3e0665e151c3 100644 --- a/ee/spec/services/security/secret_detection/update_token_status_service_spec.rb +++ b/ee/spec/services/security/secret_detection/update_token_status_service_spec.rb @@ -269,6 +269,10 @@ { factory: [:ci_trigger, { project: project }], identifier: 'gitlab_pipeline_trigger_token' + }, + { + factory: [:oauth_access_token], + identifier: 'gitlab_oauth_access_token' } ] end -- GitLab From 1db8c878a971f3408a10a82006fcb802cfacef98 Mon Sep 17 00:00:00 2001 From: Aryan Dutt Date: Fri, 11 Jul 2025 19:19:47 -0500 Subject: [PATCH 02/11] Adds model_token_method for the test case --- .../secret_detection/update_token_status_service_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ee/spec/services/security/secret_detection/update_token_status_service_spec.rb b/ee/spec/services/security/secret_detection/update_token_status_service_spec.rb index af3e0665e151c3..57e8e8cb956560 100644 --- a/ee/spec/services/security/secret_detection/update_token_status_service_spec.rb +++ b/ee/spec/services/security/secret_detection/update_token_status_service_spec.rb @@ -272,7 +272,8 @@ }, { factory: [:oauth_access_token], - identifier: 'gitlab_oauth_access_token' + identifier: 'gitlab_oauth_access_token', + model_token_method: 'plaintext_token' } ] end -- GitLab From b31c48174dd6ce48369fc24a8d79a4fc2f0c46db Mon Sep 17 00:00:00 2001 From: Aryan Dutt Date: Tue, 15 Jul 2025 22:25:43 +0000 Subject: [PATCH 03/11] Refactor OAuth access token to use existing OauthAccessToken model --- app/models/oauth_access_token.rb | 27 +++++++++++++++++++ .../security/secret_detection/access_token.rb | 19 ------------- .../secret_detection/token_lookup_service.rb | 2 +- 3 files changed, 28 insertions(+), 20 deletions(-) delete mode 100644 ee/app/models/security/secret_detection/access_token.rb diff --git a/app/models/oauth_access_token.rb b/app/models/oauth_access_token.rb index ed687048d64479..b21f25cd954aa2 100644 --- a/app/models/oauth_access_token.rb +++ b/app/models/oauth_access_token.rb @@ -26,6 +26,33 @@ def scopes=(value) end end + scope :with_token_digests, ->(hashed_tokens) do + return none if hashed_tokens.blank? + + where(token: hashed_tokens) + end + + def self.encode(raw_token_value) + ::Gitlab::DoorkeeperSecretStoring::Token::Pbkdf2Sha512.transform_secret(raw_token_value) + end + + # this method overrides a shortcoming upstream, more context: + # https://gitlab.com/gitlab-org/gitlab/-/issues/367888 + def self.find_by_fallback_token(attr, plain_secret) + return unless fallback_secret_strategy && fallback_secret_strategy == Doorkeeper::SecretStoring::Plain + # token is hashed, don't allow plaintext comparison + return if plain_secret.starts_with?("$") + + super + end + + # Override Doorkeeper::AccessToken.matching_token_for since we + # have `reuse_access_tokens` disabled and we also hash tokens. + # This ensures we don't accidentally return a hashed token value. + def self.matching_token_for(application, resource_owner, scopes) + # no-op + end + def scope_user user_id = extract_user_id_from_scopes return unless user_id diff --git a/ee/app/models/security/secret_detection/access_token.rb b/ee/app/models/security/secret_detection/access_token.rb deleted file mode 100644 index d829e40ebc6859..00000000000000 --- a/ee/app/models/security/secret_detection/access_token.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -module Security - module SecretDetection - class AccessToken < ::Doorkeeper::AccessToken - # Used to find multiple tokens (by their hashed value) - scope :with_token_digests, ->(hashed_tokens) do - return none if hashed_tokens.blank? - - where(token: hashed_tokens) - end - - # Hashes raw token - def self.encode(raw_token_value) - ::Gitlab::DoorkeeperSecretStoring::Token::Pbkdf2Sha512.transform_secret(raw_token_value) - end - end - end -end diff --git a/ee/app/services/security/secret_detection/token_lookup_service.rb b/ee/app/services/security/secret_detection/token_lookup_service.rb index c829ba489e938a..53509f139cd6ed 100644 --- a/ee/app/services/security/secret_detection/token_lookup_service.rb +++ b/ee/app/services/security/secret_detection/token_lookup_service.rb @@ -78,7 +78,7 @@ class TokenLookupService 'gitlab_feed_token_v2' => FEED_TOKEN_CONFIG, 'gitlab_pipeline_trigger_token' => PIPELINE_TRIGGER_CONFIG, 'gitlab_oauth_access_token' => { - model: Security::SecretDetection::AccessToken, + model: OauthAccessToken, lookup_method: :with_token_digests, token_method: :token } -- GitLab From efc50bb1df24474f0453ede21a814a8c3ae569b1 Mon Sep 17 00:00:00 2001 From: Aryan Dutt Date: Thu, 17 Jul 2025 04:50:06 +0000 Subject: [PATCH 04/11] Use Doorkeeper::Application wrapper for OAuth token secret detection --- app/models/oauth_access_token.rb | 10 ---------- .../secret_detection/oauth_application.rb | 19 +++++++++++++++++++ db/docs/oauth_applications.yml | 1 + .../secret_detection/token_lookup_service.rb | 4 ++-- .../update_token_status_service_spec.rb | 4 ++-- 5 files changed, 24 insertions(+), 14 deletions(-) create mode 100644 app/models/security/secret_detection/oauth_application.rb diff --git a/app/models/oauth_access_token.rb b/app/models/oauth_access_token.rb index b21f25cd954aa2..44d46414241183 100644 --- a/app/models/oauth_access_token.rb +++ b/app/models/oauth_access_token.rb @@ -26,16 +26,6 @@ def scopes=(value) end end - scope :with_token_digests, ->(hashed_tokens) do - return none if hashed_tokens.blank? - - where(token: hashed_tokens) - end - - def self.encode(raw_token_value) - ::Gitlab::DoorkeeperSecretStoring::Token::Pbkdf2Sha512.transform_secret(raw_token_value) - end - # this method overrides a shortcoming upstream, more context: # https://gitlab.com/gitlab-org/gitlab/-/issues/367888 def self.find_by_fallback_token(attr, plain_secret) diff --git a/app/models/security/secret_detection/oauth_application.rb b/app/models/security/secret_detection/oauth_application.rb new file mode 100644 index 00000000000000..bec5a5d15d69db --- /dev/null +++ b/app/models/security/secret_detection/oauth_application.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Security + module SecretDetection + class OauthApplication < ::Doorkeeper::Application + # Used to find multiple tokens (by their hashed value) + scope :with_token_digests, ->(hashed_tokens) do + return none if hashed_tokens.blank? + + where(uid: hashed_tokens) + end + + # Hashes raw token + def self.encode(raw_token_value) + ::Gitlab::DoorkeeperSecretStoring::Token::Pbkdf2Sha512.transform_secret(raw_token_value) + end + end + end +end diff --git a/db/docs/oauth_applications.yml b/db/docs/oauth_applications.yml index 7e41cb3a3ac359..ea76e69f2f1028 100644 --- a/db/docs/oauth_applications.yml +++ b/db/docs/oauth_applications.yml @@ -3,6 +3,7 @@ table_name: oauth_applications classes: - Doorkeeper::Application - Authn::OauthApplication +- Security::SecretDetection::OauthApplication feature_categories: - system_access description: TODO diff --git a/ee/app/services/security/secret_detection/token_lookup_service.rb b/ee/app/services/security/secret_detection/token_lookup_service.rb index 53509f139cd6ed..7fe2031fd99165 100644 --- a/ee/app/services/security/secret_detection/token_lookup_service.rb +++ b/ee/app/services/security/secret_detection/token_lookup_service.rb @@ -78,9 +78,9 @@ class TokenLookupService 'gitlab_feed_token_v2' => FEED_TOKEN_CONFIG, 'gitlab_pipeline_trigger_token' => PIPELINE_TRIGGER_CONFIG, 'gitlab_oauth_access_token' => { - model: OauthAccessToken, + model: Security::SecretDetection::OauthApplication, lookup_method: :with_token_digests, - token_method: :token + token_method: :uid } }.freeze diff --git a/ee/spec/services/security/secret_detection/update_token_status_service_spec.rb b/ee/spec/services/security/secret_detection/update_token_status_service_spec.rb index 57e8e8cb956560..c109662c4a987b 100644 --- a/ee/spec/services/security/secret_detection/update_token_status_service_spec.rb +++ b/ee/spec/services/security/secret_detection/update_token_status_service_spec.rb @@ -271,9 +271,9 @@ identifier: 'gitlab_pipeline_trigger_token' }, { - factory: [:oauth_access_token], + factory: [:oauth_application], identifier: 'gitlab_oauth_access_token', - model_token_method: 'plaintext_token' + model_token_method: 'uid' } ] end -- GitLab From 64efacbbe423e0977b4231489eb8dccc59178268 Mon Sep 17 00:00:00 2001 From: Aryan Dutt Date: Wed, 23 Jul 2025 05:00:50 +0000 Subject: [PATCH 05/11] Updates token_method --- app/models/security/secret_detection/oauth_application.rb | 2 +- .../services/security/secret_detection/token_lookup_service.rb | 2 +- .../secret_detection/update_token_status_service_spec.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/security/secret_detection/oauth_application.rb b/app/models/security/secret_detection/oauth_application.rb index bec5a5d15d69db..bbdc76527c3ece 100644 --- a/app/models/security/secret_detection/oauth_application.rb +++ b/app/models/security/secret_detection/oauth_application.rb @@ -7,7 +7,7 @@ class OauthApplication < ::Doorkeeper::Application scope :with_token_digests, ->(hashed_tokens) do return none if hashed_tokens.blank? - where(uid: hashed_tokens) + where(secret: hashed_tokens) end # Hashes raw token diff --git a/ee/app/services/security/secret_detection/token_lookup_service.rb b/ee/app/services/security/secret_detection/token_lookup_service.rb index 7fe2031fd99165..d7079b21826bfe 100644 --- a/ee/app/services/security/secret_detection/token_lookup_service.rb +++ b/ee/app/services/security/secret_detection/token_lookup_service.rb @@ -80,7 +80,7 @@ class TokenLookupService 'gitlab_oauth_access_token' => { model: Security::SecretDetection::OauthApplication, lookup_method: :with_token_digests, - token_method: :uid + token_method: :secret } }.freeze diff --git a/ee/spec/services/security/secret_detection/update_token_status_service_spec.rb b/ee/spec/services/security/secret_detection/update_token_status_service_spec.rb index c109662c4a987b..340229278232c6 100644 --- a/ee/spec/services/security/secret_detection/update_token_status_service_spec.rb +++ b/ee/spec/services/security/secret_detection/update_token_status_service_spec.rb @@ -273,7 +273,7 @@ { factory: [:oauth_application], identifier: 'gitlab_oauth_access_token', - model_token_method: 'uid' + model_token_method: 'plaintext_secret' } ] end -- GitLab From fd1f059a04a26c091ea3ea6b170c78b6329d2deb Mon Sep 17 00:00:00 2001 From: Aryan Dutt Date: Sat, 26 Jul 2025 20:35:11 -0500 Subject: [PATCH 06/11] Apply 1 suggestion(s) to 1 file(s) Co-authored-by: Aditya Tiwari --- .../services/security/secret_detection/token_lookup_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ee/app/services/security/secret_detection/token_lookup_service.rb b/ee/app/services/security/secret_detection/token_lookup_service.rb index d7079b21826bfe..41e61a66d61f3d 100644 --- a/ee/app/services/security/secret_detection/token_lookup_service.rb +++ b/ee/app/services/security/secret_detection/token_lookup_service.rb @@ -77,7 +77,7 @@ class TokenLookupService 'gitlab_incoming_email_token' => INCOMING_EMAIL_TOKEN_CONFIG, 'gitlab_feed_token_v2' => FEED_TOKEN_CONFIG, 'gitlab_pipeline_trigger_token' => PIPELINE_TRIGGER_CONFIG, - 'gitlab_oauth_access_token' => { + 'gitlab_oauth_app_secret' => { model: Security::SecretDetection::OauthApplication, lookup_method: :with_token_digests, token_method: :secret -- GitLab From a1518da6d0ec9d21aaec5486bc26f8a6f225eb7c Mon Sep 17 00:00:00 2001 From: Aryan Dutt Date: Fri, 8 Aug 2025 06:21:20 +0000 Subject: [PATCH 07/11] adds oauth_appliation_spec --- .../oauth_application_spec.rb | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 spec/models/security/secret_detection/oauth_application_spec.rb diff --git a/spec/models/security/secret_detection/oauth_application_spec.rb b/spec/models/security/secret_detection/oauth_application_spec.rb new file mode 100644 index 00000000000000..d2ffcbe90c4f7d --- /dev/null +++ b/spec/models/security/secret_detection/oauth_application_spec.rb @@ -0,0 +1,100 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Security::SecretDetection::OauthApplication, type: :model, feature_category: :secret_detection do + describe '.with_token_digests' do + let!(:app1) { create(:oauth_application, secret: 'hashed_token_1') } + let!(:app2) { create(:oauth_application, secret: 'hashed_token_2') } + let!(:app3) { create(:oauth_application, secret: 'different_token') } + + context 'when hashed_tokens is provided' do + it 'returns applications with matching secret digests' do + hashed_tokens = %w[hashed_token_1 hashed_token_2] + + result = described_class.with_token_digests(hashed_tokens) + + expect(result).to contain_exactly(app1, app2) + end + + it 'returns empty relation when no matches found' do + hashed_tokens = ['non_existent_token'] + + result = described_class.with_token_digests(hashed_tokens) + + expect(result).to be_empty + end + + it 'handles single token in array' do + hashed_tokens = ['hashed_token_1'] + + result = described_class.with_token_digests(hashed_tokens) + + expect(result).to contain_exactly(app1) + end + end + + context 'when hashed_tokens is blank' do + it 'returns none scope for nil' do + result = described_class.with_token_digests(nil) + + expect(result).to eq(described_class.none) + end + + it 'returns none scope for empty array' do + result = described_class.with_token_digests([]) + + expect(result).to eq(described_class.none) + end + + it 'returns none scope for empty string' do + result = described_class.with_token_digests('') + + expect(result).to eq(described_class.none) + end + end + end + + describe '.encode' do + let(:raw_token) { 'my_secret_token_123' } + + it 'encodes raw token using Pbkdf2Sha512 transformer' do + expect(::Gitlab::DoorkeeperSecretStoring::Token::Pbkdf2Sha512) + .to receive(:transform_secret) + .with(raw_token) + .and_return('encoded_token_hash') + + result = described_class.encode(raw_token) + + expect(result).to eq('encoded_token_hash') + end + + it 'handles empty token' do + expect(::Gitlab::DoorkeeperSecretStoring::Token::Pbkdf2Sha512) + .to receive(:transform_secret) + .with('') + .and_return('encoded_empty_hash') + + result = described_class.encode('') + + expect(result).to eq('encoded_empty_hash') + end + + it 'handles nil token' do + expect(::Gitlab::DoorkeeperSecretStoring::Token::Pbkdf2Sha512) + .to receive(:transform_secret) + .with(nil) + .and_return('encoded_nil_hash') + + result = described_class.encode(nil) + + expect(result).to eq('encoded_nil_hash') + end + end + + describe 'inheritance' do + it 'inherits from Doorkeeper::Application' do + expect(described_class.superclass).to eq(::Doorkeeper::Application) + end + end +end -- GitLab From 7f05a5f8398635fe1c272204c72e14ffeb823e07 Mon Sep 17 00:00:00 2001 From: Craig Smith <5344211-craigmsmith@users.noreply.gitlab.com> Date: Wed, 27 Aug 2025 13:45:40 +1000 Subject: [PATCH 08/11] Remove functions added in rebase --- app/models/oauth_access_token.rb | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/app/models/oauth_access_token.rb b/app/models/oauth_access_token.rb index 44d46414241183..ed687048d64479 100644 --- a/app/models/oauth_access_token.rb +++ b/app/models/oauth_access_token.rb @@ -26,23 +26,6 @@ def scopes=(value) end end - # this method overrides a shortcoming upstream, more context: - # https://gitlab.com/gitlab-org/gitlab/-/issues/367888 - def self.find_by_fallback_token(attr, plain_secret) - return unless fallback_secret_strategy && fallback_secret_strategy == Doorkeeper::SecretStoring::Plain - # token is hashed, don't allow plaintext comparison - return if plain_secret.starts_with?("$") - - super - end - - # Override Doorkeeper::AccessToken.matching_token_for since we - # have `reuse_access_tokens` disabled and we also hash tokens. - # This ensures we don't accidentally return a hashed token value. - def self.matching_token_for(application, resource_owner, scopes) - # no-op - end - def scope_user user_id = extract_user_id_from_scopes return unless user_id -- GitLab From 4303cccb5ff397483937c9182959570229528625 Mon Sep 17 00:00:00 2001 From: Craig Smith <5344211-craigmsmith@users.noreply.gitlab.com> Date: Wed, 27 Aug 2025 13:52:17 +1000 Subject: [PATCH 09/11] Remove unnecessary tag --- spec/models/security/secret_detection/oauth_application_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/models/security/secret_detection/oauth_application_spec.rb b/spec/models/security/secret_detection/oauth_application_spec.rb index d2ffcbe90c4f7d..b38bb931a40c05 100644 --- a/spec/models/security/secret_detection/oauth_application_spec.rb +++ b/spec/models/security/secret_detection/oauth_application_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Security::SecretDetection::OauthApplication, type: :model, feature_category: :secret_detection do +RSpec.describe Security::SecretDetection::OauthApplication, feature_category: :secret_detection do describe '.with_token_digests' do let!(:app1) { create(:oauth_application, secret: 'hashed_token_1') } let!(:app2) { create(:oauth_application, secret: 'hashed_token_2') } -- GitLab From 90a813235ffdcfc833599f0f4b202afeb54c3cf0 Mon Sep 17 00:00:00 2001 From: Craig Smith <5344211-craigmsmith@users.noreply.gitlab.com> Date: Wed, 27 Aug 2025 15:07:13 +1000 Subject: [PATCH 10/11] Remove Secret Detection Oauth --- app/models/authn/oauth_application.rb | 12 +++ .../secret_detection/oauth_application.rb | 19 ---- db/docs/oauth_applications.yml | 1 - .../secret_detection/token_lookup_service.rb | 2 +- spec/models/authn/oauth_application_spec.rb | 89 ++++++++++++++++ .../oauth_application_spec.rb | 100 ------------------ 6 files changed, 102 insertions(+), 121 deletions(-) delete mode 100644 app/models/security/secret_detection/oauth_application.rb delete mode 100644 spec/models/security/secret_detection/oauth_application_spec.rb diff --git a/app/models/authn/oauth_application.rb b/app/models/authn/oauth_application.rb index 57a11543457351..7dc2ecf0076640 100644 --- a/app/models/authn/oauth_application.rb +++ b/app/models/authn/oauth_application.rb @@ -4,6 +4,18 @@ module Authn class OauthApplication < Doorkeeper::Application include Doorkeeper::Concerns::TokenFallback + # Used to find multiple tokens (by their hashed value) + scope :with_token_digests, ->(hashed_tokens) do + return none if hashed_tokens.blank? + + where(secret: hashed_tokens) + end + + # Hashes raw token + def self.encode(raw_token_value) + ::Gitlab::DoorkeeperSecretStoring::Pbkdf2Sha512.transform_secret(raw_token_value) + end + # Check whether the given plain text secret matches our stored secret # # @param input [#to_s] Plain secret provided by user diff --git a/app/models/security/secret_detection/oauth_application.rb b/app/models/security/secret_detection/oauth_application.rb deleted file mode 100644 index bbdc76527c3ece..00000000000000 --- a/app/models/security/secret_detection/oauth_application.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -module Security - module SecretDetection - class OauthApplication < ::Doorkeeper::Application - # Used to find multiple tokens (by their hashed value) - scope :with_token_digests, ->(hashed_tokens) do - return none if hashed_tokens.blank? - - where(secret: hashed_tokens) - end - - # Hashes raw token - def self.encode(raw_token_value) - ::Gitlab::DoorkeeperSecretStoring::Token::Pbkdf2Sha512.transform_secret(raw_token_value) - end - end - end -end diff --git a/db/docs/oauth_applications.yml b/db/docs/oauth_applications.yml index ea76e69f2f1028..7e41cb3a3ac359 100644 --- a/db/docs/oauth_applications.yml +++ b/db/docs/oauth_applications.yml @@ -3,7 +3,6 @@ table_name: oauth_applications classes: - Doorkeeper::Application - Authn::OauthApplication -- Security::SecretDetection::OauthApplication feature_categories: - system_access description: TODO diff --git a/ee/app/services/security/secret_detection/token_lookup_service.rb b/ee/app/services/security/secret_detection/token_lookup_service.rb index 41e61a66d61f3d..c4b167b114ff25 100644 --- a/ee/app/services/security/secret_detection/token_lookup_service.rb +++ b/ee/app/services/security/secret_detection/token_lookup_service.rb @@ -78,7 +78,7 @@ class TokenLookupService 'gitlab_feed_token_v2' => FEED_TOKEN_CONFIG, 'gitlab_pipeline_trigger_token' => PIPELINE_TRIGGER_CONFIG, 'gitlab_oauth_app_secret' => { - model: Security::SecretDetection::OauthApplication, + model: Authn::OauthApplication, lookup_method: :with_token_digests, token_method: :secret } diff --git a/spec/models/authn/oauth_application_spec.rb b/spec/models/authn/oauth_application_spec.rb index 7ebe16490631f4..1a42d4330ad3f1 100644 --- a/spec/models/authn/oauth_application_spec.rb +++ b/spec/models/authn/oauth_application_spec.rb @@ -119,4 +119,93 @@ end end end + + describe '.with_token_digests' do + let!(:app1) { create(:oauth_application, secret: 'hashed_token_1') } + let!(:app2) { create(:oauth_application, secret: 'hashed_token_2') } + let!(:app3) { create(:oauth_application, secret: 'different_token') } + + context 'when hashed_tokens is provided' do + it 'returns applications with matching secret digests' do + hashed_tokens = %w[hashed_token_1 hashed_token_2] + + result = described_class.with_token_digests(hashed_tokens) + + expect(result).to contain_exactly(app1, app2) + end + + it 'returns empty relation when no matches found' do + hashed_tokens = ['non_existent_token'] + + result = described_class.with_token_digests(hashed_tokens) + + expect(result).to be_empty + end + + it 'handles single token in array' do + hashed_tokens = ['hashed_token_1'] + + result = described_class.with_token_digests(hashed_tokens) + + expect(result).to contain_exactly(app1) + end + end + + context 'when hashed_tokens is blank' do + it 'returns none scope for nil' do + result = described_class.with_token_digests(nil) + + expect(result).to eq(described_class.none) + end + + it 'returns none scope for empty array' do + result = described_class.with_token_digests([]) + + expect(result).to eq(described_class.none) + end + + it 'returns none scope for empty string' do + result = described_class.with_token_digests('') + + expect(result).to eq(described_class.none) + end + end + end + + describe '.encode' do + let(:raw_token) { 'my_secret_token_123' } + + it 'encodes raw token using Pbkdf2Sha512 transformer' do + expect(::Gitlab::DoorkeeperSecretStoring::Pbkdf2Sha512) + .to receive(:transform_secret) + .with(raw_token) + .and_return('encoded_token_hash') + + result = described_class.encode(raw_token) + + expect(result).to eq('encoded_token_hash') + end + + it 'handles empty token' do + expect(::Gitlab::DoorkeeperSecretStoring::Pbkdf2Sha512) + .to receive(:transform_secret) + .with('') + .and_return('encoded_empty_hash') + + result = described_class.encode('') + + expect(result).to eq('encoded_empty_hash') + end + + it 'handles nil token' do + expect(::Gitlab::DoorkeeperSecretStoring::Pbkdf2Sha512) + .to receive(:transform_secret) + .with(nil) + .and_return('encoded_nil_hash') + + result = described_class.encode(nil) + + expect(result).to eq('encoded_nil_hash') + end + end end diff --git a/spec/models/security/secret_detection/oauth_application_spec.rb b/spec/models/security/secret_detection/oauth_application_spec.rb deleted file mode 100644 index b38bb931a40c05..00000000000000 --- a/spec/models/security/secret_detection/oauth_application_spec.rb +++ /dev/null @@ -1,100 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Security::SecretDetection::OauthApplication, feature_category: :secret_detection do - describe '.with_token_digests' do - let!(:app1) { create(:oauth_application, secret: 'hashed_token_1') } - let!(:app2) { create(:oauth_application, secret: 'hashed_token_2') } - let!(:app3) { create(:oauth_application, secret: 'different_token') } - - context 'when hashed_tokens is provided' do - it 'returns applications with matching secret digests' do - hashed_tokens = %w[hashed_token_1 hashed_token_2] - - result = described_class.with_token_digests(hashed_tokens) - - expect(result).to contain_exactly(app1, app2) - end - - it 'returns empty relation when no matches found' do - hashed_tokens = ['non_existent_token'] - - result = described_class.with_token_digests(hashed_tokens) - - expect(result).to be_empty - end - - it 'handles single token in array' do - hashed_tokens = ['hashed_token_1'] - - result = described_class.with_token_digests(hashed_tokens) - - expect(result).to contain_exactly(app1) - end - end - - context 'when hashed_tokens is blank' do - it 'returns none scope for nil' do - result = described_class.with_token_digests(nil) - - expect(result).to eq(described_class.none) - end - - it 'returns none scope for empty array' do - result = described_class.with_token_digests([]) - - expect(result).to eq(described_class.none) - end - - it 'returns none scope for empty string' do - result = described_class.with_token_digests('') - - expect(result).to eq(described_class.none) - end - end - end - - describe '.encode' do - let(:raw_token) { 'my_secret_token_123' } - - it 'encodes raw token using Pbkdf2Sha512 transformer' do - expect(::Gitlab::DoorkeeperSecretStoring::Token::Pbkdf2Sha512) - .to receive(:transform_secret) - .with(raw_token) - .and_return('encoded_token_hash') - - result = described_class.encode(raw_token) - - expect(result).to eq('encoded_token_hash') - end - - it 'handles empty token' do - expect(::Gitlab::DoorkeeperSecretStoring::Token::Pbkdf2Sha512) - .to receive(:transform_secret) - .with('') - .and_return('encoded_empty_hash') - - result = described_class.encode('') - - expect(result).to eq('encoded_empty_hash') - end - - it 'handles nil token' do - expect(::Gitlab::DoorkeeperSecretStoring::Token::Pbkdf2Sha512) - .to receive(:transform_secret) - .with(nil) - .and_return('encoded_nil_hash') - - result = described_class.encode(nil) - - expect(result).to eq('encoded_nil_hash') - end - end - - describe 'inheritance' do - it 'inherits from Doorkeeper::Application' do - expect(described_class.superclass).to eq(::Doorkeeper::Application) - end - end -end -- GitLab From 8861c5527a48248593003abc14a33f930525a78b Mon Sep 17 00:00:00 2001 From: Craig Smith <5344211-craigmsmith@users.noreply.gitlab.com> Date: Wed, 27 Aug 2025 19:01:07 +1000 Subject: [PATCH 11/11] fix tests --- .../secret_detection/update_token_status_service_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ee/spec/services/security/secret_detection/update_token_status_service_spec.rb b/ee/spec/services/security/secret_detection/update_token_status_service_spec.rb index 340229278232c6..cbde8d5888714d 100644 --- a/ee/spec/services/security/secret_detection/update_token_status_service_spec.rb +++ b/ee/spec/services/security/secret_detection/update_token_status_service_spec.rb @@ -272,7 +272,7 @@ }, { factory: [:oauth_application], - identifier: 'gitlab_oauth_access_token', + identifier: 'gitlab_oauth_app_secret', model_token_method: 'plaintext_secret' } ] -- GitLab