From 3c206b1fc887c162d48358a500e96f3ef14a3400 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 13 Nov 2025 22:07:17 +0000 Subject: [PATCH 1/6] Initial plan From 0fe7def240780955fa781b3a62fb7776bcbe8195 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 13 Nov 2025 22:18:22 +0000 Subject: [PATCH 2/6] Restrict users query to admin users and add comprehensive tests Co-authored-by: Knerio <96529060+Knerio@users.noreply.github.com> --- app/graphql/types/query_type.rb | 4 + app/policies/global_policy.rb | 1 + .../graphql/query/user_node_query_spec.rb | 67 +++++++++++++++++ .../graphql/query/users_query_spec.rb | 75 +++++++++++++++++++ 4 files changed, 147 insertions(+) create mode 100644 spec/requests/graphql/query/user_node_query_spec.rb create mode 100644 spec/requests/graphql/query/users_query_spec.rb diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb index a7f0d24f..f5f30056 100644 --- a/app/graphql/types/query_type.rb +++ b/app/graphql/types/query_type.rb @@ -84,6 +84,10 @@ def namespace(id:) end def users + unless Ability.allowed?(current_authentication, :list_users, :global) + raise GraphQL::ExecutionError, 'You do not have permission to list all users' + end + User.all end diff --git a/app/policies/global_policy.rb b/app/policies/global_policy.rb index 84e90fd8..770fd1c1 100644 --- a/app/policies/global_policy.rb +++ b/app/policies/global_policy.rb @@ -20,5 +20,6 @@ class GlobalPolicy < BasePolicy enable :update_runtime enable :delete_runtime enable :rotate_runtime_token + enable :list_users end end diff --git a/spec/requests/graphql/query/user_node_query_spec.rb b/spec/requests/graphql/query/user_node_query_spec.rb new file mode 100644 index 00000000..90b03a8e --- /dev/null +++ b/spec/requests/graphql/query/user_node_query_spec.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'user node Query' do + include GraphqlHelpers + + subject(:query!) { post_graphql query, current_user: current_user } + + let(:target_user) { create(:user) } + let(:query) do + <<~QUERY + query { + node(id: "#{target_user.to_global_id}") { + ... on User { + id + username + } + } + } + QUERY + end + + context 'when anonymous' do + let(:current_user) { nil } + + it 'returns nil due to read_user authorization on UserType' do + query! + + expect(graphql_data_at(:node)).to be_nil + end + end + + context 'when logged in as regular user' do + let(:current_user) { create(:user) } + + it 'returns the user' do + query! + + expect(graphql_data_at(:node, :id)).to eq(target_user.to_global_id.to_s) + expect(graphql_data_at(:node, :username)).to eq(target_user.username) + end + end + + context 'when logged in as admin user' do + let(:current_user) { create(:user, :admin) } + + it 'returns the user' do + query! + + expect(graphql_data_at(:node, :id)).to eq(target_user.to_global_id.to_s) + expect(graphql_data_at(:node, :username)).to eq(target_user.username) + end + end + + context 'when querying self' do + let(:current_user) { create(:user) } + let(:target_user) { current_user } + + it 'returns the user' do + query! + + expect(graphql_data_at(:node, :id)).to eq(current_user.to_global_id.to_s) + expect(graphql_data_at(:node, :username)).to eq(current_user.username) + end + end +end diff --git a/spec/requests/graphql/query/users_query_spec.rb b/spec/requests/graphql/query/users_query_spec.rb new file mode 100644 index 00000000..186311c9 --- /dev/null +++ b/spec/requests/graphql/query/users_query_spec.rb @@ -0,0 +1,75 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'users Query' do + include GraphqlHelpers + + subject(:query!) { post_graphql query, current_user: current_user } + + let(:query) do + <<~QUERY + query { + users { + nodes { + id + username + } + } + } + QUERY + end + + let!(:user1) { create(:user) } + let!(:user2) { create(:user) } + let!(:user3) { create(:user) } + + context 'when anonymous' do + let(:current_user) { nil } + + it 'returns an error' do + query! + + expect(graphql_errors).to include( + a_hash_including( + 'message' => 'You do not have permission to list all users' + ) + ) + end + end + + context 'when logged in as regular user' do + let(:current_user) { create(:user) } + + it 'returns an error' do + query! + + expect(graphql_errors).to include( + a_hash_including( + 'message' => 'You do not have permission to list all users' + ) + ) + end + end + + context 'when logged in as admin user' do + let(:current_user) { create(:user, :admin) } + + it 'returns all users' do + query! + + expect(graphql_data_at(:users, :nodes)).to contain_exactly( + a_graphql_entity_for(user1), + a_graphql_entity_for(user2), + a_graphql_entity_for(user3), + a_graphql_entity_for(current_user) + ) + end + + it 'does not return errors' do + query! + + expect(graphql_errors).to be_nil + end + end +end From 522c8cd2d2e0aeed318a12331002d1d2f455e479 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 13 Nov 2025 22:29:23 +0000 Subject: [PATCH 3/6] Use GraphQL::UnauthorizedError for consistent error structure Co-authored-by: Knerio <96529060+Knerio@users.noreply.github.com> --- app/graphql/types/query_type.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb index f5f30056..0c9cf4cc 100644 --- a/app/graphql/types/query_type.rb +++ b/app/graphql/types/query_type.rb @@ -85,7 +85,7 @@ def namespace(id:) def users unless Ability.allowed?(current_authentication, :list_users, :global) - raise GraphQL::ExecutionError, 'You do not have permission to list all users' + raise GraphQL::UnauthorizedError, 'You do not have permission to list all users' end User.all From 262873044a03560943d1dbec8679c060ce7c7f7e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 13 Nov 2025 22:38:34 +0000 Subject: [PATCH 4/6] Add dedicated user query field for querying users by ID Co-authored-by: Knerio <96529060+Knerio@users.noreply.github.com> --- app/graphql/types/query_type.rb | 8 +++++++ ..._node_query_spec.rb => user_query_spec.rb} | 24 +++++++++---------- 2 files changed, 19 insertions(+), 13 deletions(-) rename spec/requests/graphql/query/{user_node_query_spec.rb => user_query_spec.rb} (62%) diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb index 0c9cf4cc..cda10586 100644 --- a/app/graphql/types/query_type.rb +++ b/app/graphql/types/query_type.rb @@ -40,6 +40,10 @@ class QueryType < Types::BaseObject argument :id, Types::GlobalIdType[::Namespace], required: true, description: 'GlobalID of the target namespace' end + field :user, Types::UserType, null: true, description: 'Find a user' do + argument :id, Types::GlobalIdType[::User], required: true, description: 'GlobalID of the target user' + end + field :users, Types::UserType.connection_type, null: false, description: 'Find users' field :global_runtimes, Types::RuntimeType.connection_type, null: false, description: 'Find runtimes' @@ -83,6 +87,10 @@ def namespace(id:) SagittariusSchema.object_from_id(id) end + def user(id:) + SagittariusSchema.object_from_id(id) + end + def users unless Ability.allowed?(current_authentication, :list_users, :global) raise GraphQL::UnauthorizedError, 'You do not have permission to list all users' diff --git a/spec/requests/graphql/query/user_node_query_spec.rb b/spec/requests/graphql/query/user_query_spec.rb similarity index 62% rename from spec/requests/graphql/query/user_node_query_spec.rb rename to spec/requests/graphql/query/user_query_spec.rb index 90b03a8e..87bd4c8a 100644 --- a/spec/requests/graphql/query/user_node_query_spec.rb +++ b/spec/requests/graphql/query/user_query_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' -RSpec.describe 'user node Query' do +RSpec.describe 'user Query' do include GraphqlHelpers subject(:query!) { post_graphql query, current_user: current_user } @@ -11,11 +11,9 @@ let(:query) do <<~QUERY query { - node(id: "#{target_user.to_global_id}") { - ... on User { - id - username - } + user(id: "#{target_user.to_global_id}") { + id + username } } QUERY @@ -27,7 +25,7 @@ it 'returns nil due to read_user authorization on UserType' do query! - expect(graphql_data_at(:node)).to be_nil + expect(graphql_data_at(:user)).to be_nil end end @@ -37,8 +35,8 @@ it 'returns the user' do query! - expect(graphql_data_at(:node, :id)).to eq(target_user.to_global_id.to_s) - expect(graphql_data_at(:node, :username)).to eq(target_user.username) + expect(graphql_data_at(:user, :id)).to eq(target_user.to_global_id.to_s) + expect(graphql_data_at(:user, :username)).to eq(target_user.username) end end @@ -48,8 +46,8 @@ it 'returns the user' do query! - expect(graphql_data_at(:node, :id)).to eq(target_user.to_global_id.to_s) - expect(graphql_data_at(:node, :username)).to eq(target_user.username) + expect(graphql_data_at(:user, :id)).to eq(target_user.to_global_id.to_s) + expect(graphql_data_at(:user, :username)).to eq(target_user.username) end end @@ -60,8 +58,8 @@ it 'returns the user' do query! - expect(graphql_data_at(:node, :id)).to eq(current_user.to_global_id.to_s) - expect(graphql_data_at(:node, :username)).to eq(current_user.username) + expect(graphql_data_at(:user, :id)).to eq(current_user.to_global_id.to_s) + expect(graphql_data_at(:user, :username)).to eq(current_user.username) end end end From 391f0c779d73f5dd57c8172f8ee645428cb82068 Mon Sep 17 00:00:00 2001 From: Dario Pranjic Date: Sat, 15 Nov 2025 17:19:20 +0100 Subject: [PATCH 5/6] Fix field test --- spec/graphql/types/query_type_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/graphql/types/query_type_spec.rb b/spec/graphql/types/query_type_spec.rb index 179b49ba..56bae912 100644 --- a/spec/graphql/types/query_type_spec.rb +++ b/spec/graphql/types/query_type_spec.rb @@ -12,6 +12,7 @@ organization organizations users + user global_runtimes namespace userAbilities From 367a282b0cfe5f0877430df98837aa6361addd8f Mon Sep 17 00:00:00 2001 From: Dario Pranjic Date: Sat, 15 Nov 2025 21:26:00 +0100 Subject: [PATCH 6/6] Fix graphql permission issues --- app/graphql/types/query_type.rb | 4 +- docs/graphql/object/query.md | 10 +++++ .../graphql/query/users_query_spec.rb | 43 +++++-------------- 3 files changed, 21 insertions(+), 36 deletions(-) diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb index cda10586..332f6ba4 100644 --- a/app/graphql/types/query_type.rb +++ b/app/graphql/types/query_type.rb @@ -92,9 +92,7 @@ def user(id:) end def users - unless Ability.allowed?(current_authentication, :list_users, :global) - raise GraphQL::UnauthorizedError, 'You do not have permission to list all users' - end + return User.none unless Ability.allowed?(context[:current_authentication], :list_users, :global) User.all end diff --git a/docs/graphql/object/query.md b/docs/graphql/object/query.md index f7046c80..9d8a171c 100644 --- a/docs/graphql/object/query.md +++ b/docs/graphql/object/query.md @@ -68,3 +68,13 @@ Returns [`Organization`](../object/organization.md). |------|------|-------------| | `id` | [`OrganizationID`](../scalar/organizationid.md) | GlobalID of the target organization | | `name` | [`String`](../scalar/string.md) | Name of the target organization | + +### user + +Find a user + +Returns [`User`](../object/user.md). + +| Name | Type | Description | +|------|------|-------------| +| `id` | [`UserID!`](../scalar/userid.md) | GlobalID of the target user | diff --git a/spec/requests/graphql/query/users_query_spec.rb b/spec/requests/graphql/query/users_query_spec.rb index 186311c9..0db57b59 100644 --- a/spec/requests/graphql/query/users_query_spec.rb +++ b/spec/requests/graphql/query/users_query_spec.rb @@ -5,8 +5,6 @@ RSpec.describe 'users Query' do include GraphqlHelpers - subject(:query!) { post_graphql query, current_user: current_user } - let(:query) do <<~QUERY query { @@ -20,21 +18,19 @@ QUERY end - let!(:user1) { create(:user) } - let!(:user2) { create(:user) } - let!(:user3) { create(:user) } + before do + create(:user) + create(:user) + create(:user) + + post_graphql query, current_user: current_user + end context 'when anonymous' do let(:current_user) { nil } it 'returns an error' do - query! - - expect(graphql_errors).to include( - a_hash_including( - 'message' => 'You do not have permission to list all users' - ) - ) + expect(graphql_data_at(:users, :nodes)).to be_empty end end @@ -42,13 +38,7 @@ let(:current_user) { create(:user) } it 'returns an error' do - query! - - expect(graphql_errors).to include( - a_hash_including( - 'message' => 'You do not have permission to list all users' - ) - ) + expect(graphql_data_at(:users, :nodes)).to be_empty end end @@ -56,20 +46,7 @@ let(:current_user) { create(:user, :admin) } it 'returns all users' do - query! - - expect(graphql_data_at(:users, :nodes)).to contain_exactly( - a_graphql_entity_for(user1), - a_graphql_entity_for(user2), - a_graphql_entity_for(user3), - a_graphql_entity_for(current_user) - ) - end - - it 'does not return errors' do - query! - - expect(graphql_errors).to be_nil + expect(graphql_data_at(:users, :nodes)).to have_attributes(length: 4) end end end