diff --git a/app/serializers/feature_flags/client_configuration_entity.rb b/app/serializers/feature_flags/client_configuration_entity.rb new file mode 100644 index 0000000000000000000000000000000000000000..5a4c3e4cd554765688e5a93bab8d6c0efde0b4a5 --- /dev/null +++ b/app/serializers/feature_flags/client_configuration_entity.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module FeatureFlags + class ClientConfigurationEntity < Grape::Entity + include RequestAwareEntity + + expose :id + expose :project_id + end +end diff --git a/doc/api/admin/token.md b/doc/api/admin/token.md index 7ec50c8daa40960d07f2b247ad299730863d5046..ad703fde91fe8bebfae76bec33e9e9b29c9f6070 100644 --- a/doc/api/admin/token.md +++ b/doc/api/admin/token.md @@ -32,6 +32,7 @@ Prerequisites: > - [Runner authentication tokens added](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/173987) in GitLab 17.7. > - [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. Gets information for a given token. This endpoint supports the following tokens: @@ -44,6 +45,7 @@ Gets information for a given token. This endpoint supports the following tokens: - [Runner authentication tokens](../../security/tokens/index.md#runner-authentication-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) ```plaintext POST /api/v4/admin/token diff --git a/lib/authn/agnostic_token_identifier.rb b/lib/authn/agnostic_token_identifier.rb index 6d8c7cda707315ecff0bb819e2ea9fbc94925e5e..e83661cbd356c997cd3916117f2c6d96d4998e36 100644 --- a/lib/authn/agnostic_token_identifier.rb +++ b/lib/authn/agnostic_token_identifier.rb @@ -12,7 +12,8 @@ class AgnosticTokenIdentifier ::Authn::Tokens::ClusterAgentToken, ::Authn::Tokens::RunnerAuthenticationToken, ::Authn::Tokens::CiTriggerToken, - ::Authn::Tokens::CiJobToken + ::Authn::Tokens::CiJobToken, + ::Authn::Tokens::FeatureFlagsClientToken ].freeze def self.token_for(plaintext, source) diff --git a/lib/authn/tokens/feature_flags_client_token.rb b/lib/authn/tokens/feature_flags_client_token.rb new file mode 100644 index 0000000000000000000000000000000000000000..f6c6c64a4f9daac33a854df09ebd324b1f8a5387 --- /dev/null +++ b/lib/authn/tokens/feature_flags_client_token.rb @@ -0,0 +1,28 @@ +# frozen_string_literal:true + +module Authn + module Tokens + class FeatureFlagsClientToken + def self.prefix?(plaintext) + plaintext.start_with?(::Operations::FeatureFlagsClient::FEATURE_FLAGS_CLIENT_TOKEN_PREFIX) + end + + attr_reader :revocable, :source + + def initialize(plaintext, source) + @revocable = ::Operations::FeatureFlagsClient.find_by_token(plaintext) + @source = source + end + + def present_with + ::FeatureFlags::ClientConfigurationEntity + end + + def revoke!(_current_user) + raise ::Authn::AgnosticTokenIdentifier::NotFoundError, 'Not Found' if revocable.blank? + + raise ::Authn::AgnosticTokenIdentifier::UnsupportedTokenError, 'Unsupported token type' + end + end + end +end diff --git a/spec/fixtures/api/schemas/feature_flags/client_configuration.json b/spec/fixtures/api/schemas/feature_flags/client_configuration.json new file mode 100644 index 0000000000000000000000000000000000000000..05ffc9a3f85b1747028f739f88f8c44c5f29d12d --- /dev/null +++ b/spec/fixtures/api/schemas/feature_flags/client_configuration.json @@ -0,0 +1,20 @@ +{ + "type": "object", + "required": [ + "id", + "project_id" + ], + "properties": { + "id": { + "type": [ + "integer" + ] + }, + "project_id": { + "type": [ + "integer" + ] + } + }, + "additionalProperties": false +} diff --git a/spec/lib/authn/agnostic_token_identifier_spec.rb b/spec/lib/authn/agnostic_token_identifier_spec.rb index 9f28c4931534cf990ede4d0c32beee70d4703e4d..3a5d98abe418e08606f0653bda17b3a70923c112 100644 --- a/spec/lib/authn/agnostic_token_identifier_spec.rb +++ b/spec/lib/authn/agnostic_token_identifier_spec.rb @@ -22,6 +22,7 @@ let_it_be(:cluster_agent_token) { create(:cluster_agent_token, token_encrypted: nil).token } 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 } subject(:token) { described_class.token_for(plaintext, :group_token_revocation_service) } @@ -35,6 +36,7 @@ ref(:cluster_agent_token) | ::Authn::Tokens::ClusterAgentToken ref(:runner_authentication_token) | ::Authn::Tokens::RunnerAuthenticationToken ref(:ci_trigger_token) | ::Authn::Tokens::CiTriggerToken + ref(:feature_flags_client_token) | ::Authn::Tokens::FeatureFlagsClientToken 'unsupported' | NilClass end diff --git a/spec/lib/authn/tokens/feature_flags_client_token_spec.rb b/spec/lib/authn/tokens/feature_flags_client_token_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..d7b9527200011f1808e2b94c0f718bb96482c4ba --- /dev/null +++ b/spec/lib/authn/tokens/feature_flags_client_token_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Authn::Tokens::FeatureFlagsClientToken, feature_category: :system_access do + let_it_be(:user) { create(:user) } + + let(:feature_flags_client) { create(:operations_feature_flags_client) } + + subject(:token) { described_class.new(plaintext, :api_admin_token) } + + context 'with valid Feature Flags Client token' do + let(:plaintext) { feature_flags_client.token } + let(:valid_revocable) { feature_flags_client } + + 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, 'Unsupported 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 f3f464b5c8999f5001051501e727d73eccb707e5..32eab9a622b50b47fd34efe2865b6b87221fd5b1 100644 --- a/spec/requests/api/admin/token_spec.rb +++ b/spec/requests/api/admin/token_spec.rb @@ -54,6 +54,7 @@ let(:impersonation_token) { create(:personal_access_token, :impersonation, user: user) } let(:ci_trigger) { create(:ci_trigger) } let(:ci_build) { create(:ci_build, status: :running) } + let(:feature_flags_client) { create(:operations_feature_flags_client) } let(:plaintext) { nil } let(:params) { { token: plaintext } } @@ -73,7 +74,8 @@ [ref(:cluster_agent_token), lazy { cluster_agent_token.token }], [ref(:runner_authentication_token), lazy { runner_authentication_token.token }], [ref(:impersonation_token), lazy { impersonation_token.token }], - [ref(:ci_trigger), lazy { ci_trigger.token }] + [ref(:ci_trigger), lazy { ci_trigger.token }], + [ref(:feature_flags_client), lazy { feature_flags_client.token }] ] end diff --git a/spec/serializers/feature_flags/client_configuration_entity_spec.rb b/spec/serializers/feature_flags/client_configuration_entity_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..0b7de6c28c9045d3492f8ac2151713ed995652e1 --- /dev/null +++ b/spec/serializers/feature_flags/client_configuration_entity_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe FeatureFlags::ClientConfigurationEntity, factory_default: :keep, feature_category: :feature_flags do + let_it_be(:project) { create_default(:project) } + let(:feature_flags_client) { project.create_operations_feature_flags_client! } + let(:entity) { described_class.new(feature_flags_client) } + + describe '#to_json' do + subject(:json) { entity.to_json } + + it 'matches schema' do + expect(json).to match_schema('feature_flags/client_configuration') + end + end +end