diff --git a/doc/api/admin/token.md b/doc/api/admin/token.md index 3613754199abe892a40765f022aaf2263d6b8c23..e3bf9dea4c91b7ba1a57e8412b59c16463cc14c2 100644 --- a/doc/api/admin/token.md +++ b/doc/api/admin/token.md @@ -33,6 +33,7 @@ Prerequisites: > - [Pipeline trigger tokens added](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/174030) in GitLab 17.7. > - [CI/CD Job Tokens added](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/175234) in GitLab 17.9. > - [Feature flags client tokens added](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/177431) in GitLab 17.9. +> - [GitLab session cookies added](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/178022) in GitLab 17.9. Gets information for a given token. This endpoint supports the following tokens: @@ -46,6 +47,7 @@ Gets information for a given token. This endpoint supports the following tokens: - [Pipeline trigger tokens](../../ci/triggers/index.md#create-a-pipeline-trigger-token) - [CI/CD Job Tokens](../../security/tokens/index.md#cicd-job-tokens) - [Feature flags client tokens](../../operations/feature_flags.md#get-access-credentials) +- [GitLab session cookies](../../user/profile/active_sessions.md) ```plaintext POST /api/v4/admin/token diff --git a/lib/authn/agnostic_token_identifier.rb b/lib/authn/agnostic_token_identifier.rb index e83661cbd356c997cd3916117f2c6d96d4998e36..e727b9763e16cb7b439a609a4055ed9ed98b3a11 100644 --- a/lib/authn/agnostic_token_identifier.rb +++ b/lib/authn/agnostic_token_identifier.rb @@ -13,7 +13,8 @@ class AgnosticTokenIdentifier ::Authn::Tokens::RunnerAuthenticationToken, ::Authn::Tokens::CiTriggerToken, ::Authn::Tokens::CiJobToken, - ::Authn::Tokens::FeatureFlagsClientToken + ::Authn::Tokens::FeatureFlagsClientToken, + ::Authn::Tokens::GitlabSession ].freeze def self.token_for(plaintext, source) diff --git a/lib/authn/tokens/gitlab_session.rb b/lib/authn/tokens/gitlab_session.rb new file mode 100644 index 0000000000000000000000000000000000000000..026ed75d35bcdd62c09c008bac4323a93419b9d2 --- /dev/null +++ b/lib/authn/tokens/gitlab_session.rb @@ -0,0 +1,46 @@ +# frozen_string_literal:true + +module Authn + module Tokens + class GitlabSession + def self.prefix?(plaintext) + plaintext.start_with?(session_cookie_key_prefix) + end + + def self.session_cookie_key_prefix + "#{Gitlab::Application.config.session_options[:key]}=" + end + + attr_reader :revocable, :source + + def initialize(plaintext, source) + session = find_session(plaintext) + + @revocable = Warden::SessionSerializer.new('rack.session' => session).fetch(:user) if session + @source = source + end + + def present_with + ::API::Entities::User + 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 + + private + + def find_session(plaintext) + public_session_id = extract_session(plaintext) + session_id = Rack::Session::SessionId.new(public_session_id) + ActiveSession.sessions_from_ids([session_id.private_id]).first + end + + def extract_session(plaintext) + plaintext.delete_prefix(self.class.session_cookie_key_prefix) + end + end + end +end diff --git a/spec/lib/authn/agnostic_token_identifier_spec.rb b/spec/lib/authn/agnostic_token_identifier_spec.rb index 3a5d98abe418e08606f0653bda17b3a70923c112..09d3b4a90c8594680a117b119c9500583abcf2b8 100644 --- a/spec/lib/authn/agnostic_token_identifier_spec.rb +++ b/spec/lib/authn/agnostic_token_identifier_spec.rb @@ -23,6 +23,7 @@ let_it_be(:runner_authentication_token) { create(:ci_runner, registration_type: :authenticated_user).token } let_it_be(:ci_trigger_token) { create(:ci_trigger).token } let_it_be(:feature_flags_client_token) { create(:operations_feature_flags_client).token } + let_it_be(:gitlab_session) { '_gitlab_session=session_id' } subject(:token) { described_class.token_for(plaintext, :group_token_revocation_service) } @@ -37,6 +38,7 @@ ref(:runner_authentication_token) | ::Authn::Tokens::RunnerAuthenticationToken ref(:ci_trigger_token) | ::Authn::Tokens::CiTriggerToken ref(:feature_flags_client_token) | ::Authn::Tokens::FeatureFlagsClientToken + ref(:gitlab_session) | ::Authn::Tokens::GitlabSession 'unsupported' | NilClass end diff --git a/spec/lib/authn/tokens/gitlab_session_spec.rb b/spec/lib/authn/tokens/gitlab_session_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..c4df261b62650e542244a4a9184523ca96e14453 --- /dev/null +++ b/spec/lib/authn/tokens/gitlab_session_spec.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Authn::Tokens::GitlabSession, feature_category: :system_access do + let_it_be(:user) { create(:user) } + + subject(:token) { described_class.new(plaintext, :api_admin_token) } + + context 'with a valid gitlab session in ActiveSession' do + let(:valid_revocable) { user } + let(:session_id) { 'session_id' } + let(:plaintext) { "_gitlab_session=#{session_id}" } + let(:rack_session) { Rack::Session::SessionId.new(session_id) } + let(:session_hash) { { 'warden.user.user.key' => [[user.id], user.authenticatable_salt] } } + + before do + allow(ActiveSession).to receive(:sessions_from_ids).with([rack_session.private_id]).and_return([session_hash]) + end + + 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 32eab9a622b50b47fd34efe2865b6b87221fd5b1..34cc1d0d019208f4467b07870cb427ad4cd205af 100644 --- a/spec/requests/api/admin/token_spec.rb +++ b/spec/requests/api/admin/token_spec.rb @@ -101,6 +101,39 @@ end end + context 'with _gitlab_session' do + let(:session_id) { 'session_id' } + let(:plaintext) { "_gitlab_session=#{session_id}" } + + context 'with a valid session in ActiveSession' do + before do + rack_session = Rack::Session::SessionId.new(session_id) + allow(ActiveSession).to receive(:sessions_from_ids) + .with([rack_session.private_id]).and_return([{ 'warden.user.user.key' => [[user.id], + user.authenticatable_salt] }]) + end + + it 'returns info about the token' do + post_token + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['id']).to eq(user.id) + end + end + + context 'with an unknown session' do + let(:session_id) { '_gitlab_session=unknown' } + + it_behaves_like 'returning response status', :not_found + end + + context 'with an empty session' do + let(:plaintext) { "_gitlab_session=" } + + it_behaves_like 'returning response status', :not_found + end + end + it_behaves_like 'rejecting invalid requests with admin' end