From 063d558240da1371f039eb10c943bac4edb746e6 Mon Sep 17 00:00:00 2001 From: Drew Blessing <drew@gitlab.com> Date: Fri, 21 Feb 2025 07:55:37 +0000 Subject: [PATCH] Expose resource access token resource type and id --- lib/api/entities/resource_access_token.rb | 34 ++++++++++++++++--- .../public_api/v4/resource_access_token.json | 10 +++++- .../self_rotation_spec.rb | 9 +++-- .../api/resource_access_tokens_spec.rb | 34 ++++++++++++++----- 4 files changed, 69 insertions(+), 18 deletions(-) diff --git a/lib/api/entities/resource_access_token.rb b/lib/api/entities/resource_access_token.rb index 7e2a1e0075a3..fe6387dfc807 100644 --- a/lib/api/entities/resource_access_token.rb +++ b/lib/api/entities/resource_access_token.rb @@ -4,12 +4,36 @@ module API module Entities class ResourceAccessToken < Entities::PersonalAccessToken expose :access_level, - documentation: { type: 'integer', - example: 40, - description: 'Access level. Valid values are 10 (Guest), 20 (Reporter), 30 (Developer) \ + documentation: { + type: 'integer', + example: 40, + description: 'Access level. Valid values are 10 (Guest), 20 (Reporter), 30 (Developer) \ , 40 (Maintainer), and 50 (Owner). Defaults to 40.', - values: [10, 20, 30, 40, 50] } do |token, options| - options[:resource].member(token.user).access_level + values: [10, 20, 30, 40, 50] + } do |token, _options| + token.user.members.first.access_level + end + + expose :resource_type, + documentation: { + type: 'string', + example: 'project', + description: 'Whether a token belongs to a project or group', + values: %w[project group] + } do |token, _options| + token.user.bot_namespace && token.user.bot_namespace.is_a?(::Namespaces::ProjectNamespace) ? 'project' : 'group' + end + + expose :resource_id, + documentation: { + type: 'integer', + example: 1234, + description: 'The ID of the project or group' + } do |token, _options| + bot_namespace = token.user.bot_namespace + next unless bot_namespace + + bot_namespace.is_a?(::Namespaces::ProjectNamespace) ? bot_namespace.project.id : bot_namespace.id end end end diff --git a/spec/fixtures/api/schemas/public_api/v4/resource_access_token.json b/spec/fixtures/api/schemas/public_api/v4/resource_access_token.json index 61a73ef89ce2..819668f02f97 100644 --- a/spec/fixtures/api/schemas/public_api/v4/resource_access_token.json +++ b/spec/fixtures/api/schemas/public_api/v4/resource_access_token.json @@ -11,7 +11,9 @@ "revoked", "access_level", "scopes", - "last_used_at" + "last_used_at", + "resource_type", + "resource_id" ], "properties": { "id": { @@ -61,6 +63,12 @@ "null" ], "format": "date-time" + }, + "resource_type": { + "type": "string" + }, + "resource_id": { + "type": "integer" } }, "additionalProperties": false diff --git a/spec/requests/api/resource_access_tokens/self_rotation_spec.rb b/spec/requests/api/resource_access_tokens/self_rotation_spec.rb index 62f3dadb679f..d95da2c750ee 100644 --- a/spec/requests/api/resource_access_tokens/self_rotation_spec.rb +++ b/spec/requests/api/resource_access_tokens/self_rotation_spec.rb @@ -7,9 +7,6 @@ let(:expiry_date) { Time.zone.today + 1.week } let(:params) { {} } - let_it_be(:current_user) { create(:user, :project_bot) } - let_it_be(:other_user) { create(:user, :project_bot) } - subject(:rotate_token) { post(api(path, personal_access_token: token), params: params) } shared_examples 'rotating token succeeds' do @@ -173,6 +170,9 @@ context 'when the resource is a project' do let_it_be(:resource) { create(:project) } + let_it_be(:namespace) { resource.project_namespace } + let_it_be(:current_user) { create(:user, :project_bot, bot_namespace: namespace) } + let_it_be(:other_user) { create(:user, :project_bot, bot_namespace: namespace) } before_all { resource.add_guest(current_user) } @@ -181,6 +181,9 @@ context 'when the resource is a group' do let_it_be(:resource) { create(:group) } + let_it_be(:namespace) { resource } + let_it_be(:current_user) { create(:user, :project_bot, bot_namespace: namespace) } + let_it_be(:other_user) { create(:user, :project_bot, bot_namespace: namespace) } before_all { resource.add_guest(current_user) } diff --git a/spec/requests/api/resource_access_tokens_spec.rb b/spec/requests/api/resource_access_tokens_spec.rb index fb5e839a3a90..dd0d5fe61c9c 100644 --- a/spec/requests/api/resource_access_tokens_spec.rb +++ b/spec/requests/api/resource_access_tokens_spec.rb @@ -11,7 +11,7 @@ subject(:get_tokens) { get api("/#{source_type}s/#{resource_id}/access_tokens", user) } context "when the user has valid permissions" do - let_it_be(:project_bot) { create(:user, :project_bot) } + let_it_be(:project_bot) { create(:user, :project_bot, bot_namespace: namespace) } let_it_be(:active_access_tokens) { create_list(:personal_access_token, 5, user: project_bot) } let_it_be(:expired_token) { create(:personal_access_token, :expired, user: project_bot) } let_it_be(:revoked_token) { create(:personal_access_token, :revoked, user: project_bot) } @@ -49,8 +49,12 @@ if source_type == 'project' expect(api_get_token["access_level"]).to eq(resource.team.max_member_access(token.user.id)) + expect(api_get_token["resource_type"]).to eq('project') + expect(api_get_token["resource_id"]).to eq(namespace.project.id) else expect(api_get_token["access_level"]).to eq(resource.max_member_access_for_user(token.user)) + expect(api_get_token["resource_type"]).to eq('group') + expect(api_get_token["resource_id"]).to eq(namespace.id) end expect(api_get_token["expires_at"]).to eq(token.expires_at.to_date.iso8601) @@ -71,7 +75,7 @@ end context "when tokens belong to a different #{source_type}" do - let_it_be(:bot) { create(:user, :project_bot) } + let_it_be(:bot) { create(:user, :project_bot, bot_namespace: other_resource_namespace) } let_it_be(:token) { create(:personal_access_token, user: bot) } before do @@ -151,7 +155,7 @@ context "when the user does not have valid permissions" do let_it_be(:user) { user_non_priviledged } - let_it_be(:project_bot) { create(:user, :project_bot) } + let_it_be(:project_bot) { create(:user, :project_bot, bot_namespace: namespace) } let_it_be(:access_tokens) { create_list(:personal_access_token, 3, user: project_bot) } let_it_be(:resource_id) { resource.id } @@ -170,7 +174,7 @@ context "GET #{source_type}s/:id/access_tokens/:token_id" do subject(:get_token) { get api("/#{source_type}s/#{resource_id}/access_tokens/#{token_id}", user) } - let_it_be(:project_bot) { create(:user, :project_bot) } + let_it_be(:project_bot) { create(:user, :project_bot, bot_namespace: namespace) } let_it_be(:token) { create(:personal_access_token, user: project_bot) } let_it_be(:resource_id) { resource.id } let_it_be(:token_id) { token.id } @@ -195,15 +199,19 @@ if source_type == 'project' expect(json_response["access_level"]).to eq(resource.team.max_member_access(token.user.id)) + expect(json_response["resource_type"]).to eq('project') + expect(json_response["resource_id"]).to eq(namespace.project.id) else expect(json_response["access_level"]).to eq(resource.max_member_access_for_user(token.user)) + expect(json_response["resource_type"]).to eq('group') + expect(json_response["resource_id"]).to eq(namespace.id) end expect(json_response["expires_at"]).to eq(token.expires_at.to_date.iso8601) end context "when using #{source_type} access token to GET other #{source_type} access token" do - let_it_be(:other_project_bot) { create(:user, :project_bot) } + let_it_be(:other_project_bot) { create(:user, :project_bot, bot_namespace: namespace) } let_it_be(:other_token) { create(:personal_access_token, user: other_project_bot) } let_it_be(:token_id) { other_token.id } @@ -222,8 +230,12 @@ if source_type == 'project' expect(json_response["access_level"]).to eq(resource.team.max_member_access(other_token.user.id)) + expect(json_response["resource_type"]).to eq('project') + expect(json_response["resource_id"]).to eq(namespace.project.id) else expect(json_response["access_level"]).to eq(resource.max_member_access_for_user(other_token.user)) + expect(json_response["resource_type"]).to eq('group') + expect(json_response["resource_id"]).to eq(namespace.id) end expect(json_response["expires_at"]).to eq(other_token.expires_at.to_date.iso8601) @@ -267,7 +279,7 @@ context "DELETE #{source_type}s/:id/access_tokens/:token_id", :sidekiq_inline do subject(:delete_token) { delete api("/#{source_type}s/#{resource_id}/access_tokens/#{token_id}", user) } - let_it_be(:project_bot) { create(:user, :project_bot) } + let_it_be(:project_bot) { create(:user, :project_bot, bot_namespace: namespace) } let_it_be(:token) { create(:personal_access_token, user: project_bot) } let_it_be(:resource_id) { resource.id } let_it_be(:token_id) { token.id } @@ -286,7 +298,7 @@ end context "when using #{source_type} access token to DELETE other #{source_type} access token" do - let_it_be(:other_project_bot) { create(:user, :project_bot) } + let_it_be(:other_project_bot) { create(:user, :project_bot, bot_namespace: namespace) } let_it_be(:other_token) { create(:personal_access_token, user: other_project_bot) } let_it_be(:token_id) { other_token.id } @@ -482,7 +494,7 @@ end context "when a #{source_type} access token tries to create another #{source_type} access token" do - let_it_be(:project_bot) { create(:user, :project_bot) } + let_it_be(:project_bot) { create(:user, :project_bot, bot_namespace: namespace) } let_it_be(:user) { project_bot } before do @@ -504,7 +516,7 @@ end context "POST #{source_type}s/:id/access_tokens/:token_id/rotate" do - let_it_be(:project_bot) { create(:user, :project_bot) } + let_it_be(:project_bot) { create(:user, :project_bot, bot_namespace: namespace) } let_it_be(:token) { create(:personal_access_token, user: project_bot) } let_it_be(:resource_id) { resource.id } let_it_be(:token_id) { token.id } @@ -667,7 +679,9 @@ context 'when the resource is a project' do let_it_be(:resource) { create(:project, group: create(:group)) } + let_it_be(:namespace) { resource.project_namespace } let_it_be(:other_resource) { create(:project) } + let_it_be(:other_resource_namespace) { other_resource.project_namespace } let_it_be(:unknown_resource) { create(:project) } before_all do @@ -681,7 +695,9 @@ context 'when the resource is a group' do let_it_be(:resource) { create(:group) } + let_it_be(:namespace) { resource } let_it_be(:other_resource) { create(:group) } + let_it_be(:other_resource_namespace) { other_resource } let_it_be(:unknown_resource) { create(:project) } before_all do -- GitLab