diff --git a/doc/api/admin/token.md b/doc/api/admin/token.md index e9c109158839eef367575e9a26ef227bbaf430b5..74c8c31b2302ae75ab61e31e4762b62e975475f0 100644 --- a/doc/api/admin/token.md +++ b/doc/api/admin/token.md @@ -14,6 +14,7 @@ DETAILS: > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/165157) in GitLab 17.5 [with a flag](../../administration/feature_flags.md) named `admin_agnostic_token_finder`. Disabled by default. > - [Feed tokens added](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/169821) in GitLab 17.6. +> - [OAuth application secrets added](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/172985) in GitLab 17.7. FLAG: The availability of this feature is controlled by a feature flag. @@ -37,6 +38,7 @@ Supported tokens: - [Personal access tokens](../../user/profile/personal_access_tokens.md) - [Deploy tokens](../../user/project/deploy_tokens/index.md) - [Feed tokens](../../security/tokens/index.md#feed-token) +- [OAuth application secrets](../../integration/oauth_provider.md) ```plaintext POST /api/v4/admin/token diff --git a/lib/api/admin/token.rb b/lib/api/admin/token.rb index 9a54c95526f1540397a6e9c8d727af34fbaf7376..2100d2104c7a4d653ce1d1a41c598e99e9da68c0 100644 --- a/lib/api/admin/token.rb +++ b/lib/api/admin/token.rb @@ -11,7 +11,7 @@ def identify_token(plaintext) token = ::Authn::AgnosticTokenIdentifier.token_for(plaintext, AUDIT_SOURCE) raise ArgumentError, 'Token type not supported.' if token.blank? - token.revocable + token end end @@ -45,12 +45,11 @@ def identify_token(plaintext) end post 'token' do identified_token = identify_token(params[:token]) - - render_api_error!({ error: 'Not found' }, :not_found) if identified_token.nil? + render_api_error!({ error: 'Not found' }, :not_found) if identified_token.revocable.nil? status :ok - present identified_token, with: "API::Entities::#{identified_token.class.name}".constantize + present identified_token.revocable, with: identified_token.present_with end end end diff --git a/lib/authn/agnostic_token_identifier.rb b/lib/authn/agnostic_token_identifier.rb index 672f03d6eba43135f5ee799850f26abb9f53282c..7f04f75e1bcc727c8bced6654c72152506c4d5f4 100644 --- a/lib/authn/agnostic_token_identifier.rb +++ b/lib/authn/agnostic_token_identifier.rb @@ -3,10 +3,12 @@ module Authn class AgnosticTokenIdentifier NotFoundError = Class.new(StandardError) + UnsupportedTokenError = Class.new(StandardError) TOKEN_TYPES = [ ::Authn::Tokens::DeployToken, ::Authn::Tokens::FeedToken, - ::Authn::Tokens::PersonalAccessToken + ::Authn::Tokens::PersonalAccessToken, + ::Authn::Tokens::OauthApplicationSecret ].freeze def self.token_for(plaintext, source) diff --git a/lib/authn/tokens/deploy_token.rb b/lib/authn/tokens/deploy_token.rb index 43b694adf172f222ccfba11dcb0101fa8973ffd8..7ebaf1d98d482ebfe4f4f9c8bada2783071f025c 100644 --- a/lib/authn/tokens/deploy_token.rb +++ b/lib/authn/tokens/deploy_token.rb @@ -14,6 +14,10 @@ def initialize(plaintext, source) @source = source end + def present_with + ::API::Entities::DeployToken + end + def revoke!(current_user) raise ::Authn::AgnosticTokenIdentifier::NotFoundError, 'Not Found' if revocable.blank? diff --git a/lib/authn/tokens/feed_token.rb b/lib/authn/tokens/feed_token.rb index 1b425c764038679098ec36a2f2ae768aa7e3749a..b46c35632c95659c7588ed48d63f930e87087e22 100644 --- a/lib/authn/tokens/feed_token.rb +++ b/lib/authn/tokens/feed_token.rb @@ -14,6 +14,10 @@ def initialize(plaintext, source) @source = source end + def present_with + ::API::Entities::User + end + def revoke!(current_user) raise ::Authn::AgnosticTokenIdentifier::NotFoundError, 'Not Found' if revocable.blank? diff --git a/lib/authn/tokens/oauth_application_secret.rb b/lib/authn/tokens/oauth_application_secret.rb new file mode 100644 index 0000000000000000000000000000000000000000..84432afeaf735797fadddb197e6ebb380d54999c --- /dev/null +++ b/lib/authn/tokens/oauth_application_secret.rb @@ -0,0 +1,32 @@ +# frozen_string_literal:true + +module Authn + module Tokens + class OauthApplicationSecret + def self.prefix?(plaintext) + prefix = + ::Gitlab::DoorkeeperSecretStoring::Token::UniqueApplicationToken::OAUTH_APPLICATION_SECRET_PREFIX_FORMAT + .split('-').first + + plaintext.start_with?(prefix) + end + + attr_reader :revocable, :source + + def initialize(plaintext, source) + @revocable = ::Doorkeeper::Application.find_by_plaintext_token(:secret, plaintext) + @source = source + end + + def present_with + ::API::Entities::Application + end + + def revoke!(_current_user) + raise ::Authn::AgnosticTokenIdentifier::NotFoundError, 'Not Found' if revocable.blank? + + raise ::Authn::AgnosticTokenIdentifier::UnsupportedTokenError, 'Revocation not supported for this token type' + end + end + end +end diff --git a/lib/authn/tokens/personal_access_token.rb b/lib/authn/tokens/personal_access_token.rb index 669debaf2b47aa4ba9a7e798a27d3d47070f543d..67ef8681d7d164faccbedf3b8a9d353050308c6a 100644 --- a/lib/authn/tokens/personal_access_token.rb +++ b/lib/authn/tokens/personal_access_token.rb @@ -17,6 +17,10 @@ def initialize(plaintext, source) @source = source end + def present_with + ::API::Entities::PersonalAccessToken + end + def revoke!(current_user) raise ::Authn::AgnosticTokenIdentifier::NotFoundError, 'Not Found' if revocable.blank? diff --git a/spec/lib/authn/agnostic_token_identifier_spec.rb b/spec/lib/authn/agnostic_token_identifier_spec.rb index b3d96cb6a46535aae3bf21e1c0aaec9106a5b218..386b91894c2194a7ca984e26561f098e2ddc25dc 100644 --- a/spec/lib/authn/agnostic_token_identifier_spec.rb +++ b/spec/lib/authn/agnostic_token_identifier_spec.rb @@ -9,6 +9,7 @@ let_it_be(:deploy_token) { create(:deploy_token).token } let_it_be(:feed_token) { user.feed_token } let_it_be(:personal_access_token) { create(:personal_access_token, user: user).token } + let_it_be(:oauth_application_secret) { create(:oauth_application).plaintext_secret } subject(:token) { described_class.token_for(plaintext, :group_token_revocation_service) } @@ -17,6 +18,7 @@ ref(:personal_access_token) | ::Authn::Tokens::PersonalAccessToken ref(:feed_token) | ::Authn::Tokens::FeedToken ref(:deploy_token) | ::Authn::Tokens::DeployToken + ref(:oauth_application_secret) | ::Authn::Tokens::OauthApplicationSecret 'unsupported' | NilClass end diff --git a/spec/lib/authn/tokens/oauth_application_secret_spec.rb b/spec/lib/authn/tokens/oauth_application_secret_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..b0857f27088786c392b314b6e2cbf54a615a256c --- /dev/null +++ b/spec/lib/authn/tokens/oauth_application_secret_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Authn::Tokens::OauthApplicationSecret, feature_category: :system_access do + let_it_be(:user) { create(:user) } + + let(:oauth_application_secret) { create(:oauth_application) } + + subject(:token) { described_class.new(plaintext, :api_admin_token) } + + context 'with valid oauth application secret' do + let(:plaintext) { oauth_application_secret.plaintext_secret } + let(:valid_revocable) { oauth_application_secret } + + it_behaves_like 'finding the valid revocable' + + describe '#revoke!' do + it 'does not support revocation yet' do + expect do + token.revoke!(user) + end.to raise_error(::Authn::AgnosticTokenIdentifier::UnsupportedTokenError, + 'Revocation not supported for this token type') + end + end + end + + it_behaves_like 'token handling with unsupported token type' +end diff --git a/spec/requests/api/admin/token_spec.rb b/spec/requests/api/admin/token_spec.rb index 5cc09953497d7221fe1cea101026766796049375..2f411f6aab1a34e1a3d7f0bdf551850a4ce59504 100644 --- a/spec/requests/api/admin/token_spec.rb +++ b/spec/requests/api/admin/token_spec.rb @@ -10,6 +10,8 @@ let_it_be(:personal_access_token) { create(:personal_access_token, user: user) } let_it_be(:deploy_token) { create(:deploy_token) } + let_it_be(:oauth_application) { create(:oauth_application) } + let(:plaintext) { nil } let(:params) { { token: plaintext } } @@ -22,7 +24,8 @@ [ [ref(:personal_access_token), lazy { personal_access_token.token }], [ref(:deploy_token), lazy { deploy_token.token }], - [ref(:user), lazy { user.feed_token }] + [ref(:user), lazy { user.feed_token }], + [ref(:oauth_application), lazy { oauth_application.plaintext_secret }] ] end diff --git a/spec/support/shared_examples/authn/token_shared_examples.rb b/spec/support/shared_examples/authn/token_shared_examples.rb index a0ff50401de40e27112879e34029126efa5b8cd8..12c9741151f24f99586566dbeb91f5cef90d8480 100644 --- a/spec/support/shared_examples/authn/token_shared_examples.rb +++ b/spec/support/shared_examples/authn/token_shared_examples.rb @@ -27,4 +27,10 @@ expect(token.revocable).to eq(valid_revocable) end end + + describe '#present_with' do + it 'returns a constant that is a subclass of Grape::Entity' do + expect(token.present_with).to be <= Grape::Entity + end + end end