Commit 5d0a3d83 authored by charlie ablett's avatar charlie ablett

Merge branch 'nfriend-adjust-release-graphql-permissions' into 'master'

Adjust permissions for Release data in GraphQL endpoint

See merge request gitlab-org/gitlab!33538
parents 1441fb1a da1e555b
...@@ -251,7 +251,8 @@ module Types ...@@ -251,7 +251,8 @@ module Types
null: true, null: true,
description: 'A single release of the project', description: 'A single release of the project',
resolver: Resolvers::ReleasesResolver.single, resolver: Resolvers::ReleasesResolver.single,
feature_flag: :graphql_release_data feature_flag: :graphql_release_data,
authorize: :download_code
field :container_expiration_policy, field :container_expiration_policy,
Types::ContainerExpirationPolicyType, Types::ContainerExpirationPolicyType,
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
module Types module Types
class ReleaseAssetsType < BaseObject class ReleaseAssetsType < BaseObject
graphql_name 'ReleaseAssets' graphql_name 'ReleaseAssets'
description 'A container for all assets associated with a release'
authorize :read_release authorize :read_release
...@@ -10,7 +11,7 @@ module Types ...@@ -10,7 +11,7 @@ module Types
present_using ReleasePresenter present_using ReleasePresenter
field :assets_count, GraphQL::INT_TYPE, null: true, field :count, GraphQL::INT_TYPE, null: true, method: :assets_count,
description: 'Number of assets of the release' description: 'Number of assets of the release'
field :links, Types::ReleaseLinkType.connection_type, null: true, field :links, Types::ReleaseLinkType.connection_type, null: true,
description: 'Asset links of the release' description: 'Asset links of the release'
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
module Types module Types
class ReleaseLinkType < BaseObject class ReleaseLinkType < BaseObject
graphql_name 'ReleaseLink' graphql_name 'ReleaseLink'
description 'Represents an asset link associated with a release'
authorize :read_release authorize :read_release
......
...@@ -3,8 +3,9 @@ ...@@ -3,8 +3,9 @@
module Types module Types
class ReleaseSourceType < BaseObject class ReleaseSourceType < BaseObject
graphql_name 'ReleaseSource' graphql_name 'ReleaseSource'
description 'Represents the source code attached to a release in a particular format'
authorize :read_release_sources authorize :download_code
field :format, GraphQL::STRING_TYPE, null: true, field :format, GraphQL::STRING_TYPE, null: true,
description: 'Format of the source' description: 'Format of the source'
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
module Types module Types
class ReleaseType < BaseObject class ReleaseType < BaseObject
graphql_name 'Release' graphql_name 'Release'
description 'Represents a release'
authorize :read_release authorize :read_release
...@@ -10,10 +11,12 @@ module Types ...@@ -10,10 +11,12 @@ module Types
present_using ReleasePresenter present_using ReleasePresenter
field :tag_name, GraphQL::STRING_TYPE, null: false, method: :tag, field :tag_name, GraphQL::STRING_TYPE, null: true, method: :tag,
description: 'Name of the tag associated with the release' description: 'Name of the tag associated with the release',
authorize: :download_code
field :tag_path, GraphQL::STRING_TYPE, null: true, field :tag_path, GraphQL::STRING_TYPE, null: true,
description: 'Relative web path to the tag associated with the release' description: 'Relative web path to the tag associated with the release',
authorize: :download_code
field :description, GraphQL::STRING_TYPE, null: true, field :description, GraphQL::STRING_TYPE, null: true,
description: 'Description (also known as "release notes") of the release' description: 'Description (also known as "release notes") of the release'
markdown_field :description_html, null: true markdown_field :description_html, null: true
...@@ -39,8 +42,7 @@ module Types ...@@ -39,8 +42,7 @@ module Types
field :commit, Types::CommitType, null: true, field :commit, Types::CommitType, null: true,
complexity: 10, calls_gitaly: true, complexity: 10, calls_gitaly: true,
description: 'The commit associated with the release', description: 'The commit associated with the release'
authorize: :reporter_access
def commit def commit
return if release.sha.nil? return if release.sha.nil?
......
...@@ -3,11 +3,5 @@ ...@@ -3,11 +3,5 @@
module Releases module Releases
class SourcePolicy < BasePolicy class SourcePolicy < BasePolicy
delegate { @subject.project } delegate { @subject.project }
rule { can?(:public_access) | can?(:reporter_access) }.policy do
enable :read_release_sources
end
rule { ~can?(:read_release) }.prevent :read_release_sources
end end
end end
...@@ -5,7 +5,7 @@ class ReleasePresenter < Gitlab::View::Presenter::Delegated ...@@ -5,7 +5,7 @@ class ReleasePresenter < Gitlab::View::Presenter::Delegated
presents :release presents :release
delegate :project, :tag, :assets_count, to: :release delegate :project, :tag, to: :release
def commit_path def commit_path
return unless release.commit && can_download_code? return unless release.commit && can_download_code?
...@@ -43,6 +43,18 @@ class ReleasePresenter < Gitlab::View::Presenter::Delegated ...@@ -43,6 +43,18 @@ class ReleasePresenter < Gitlab::View::Presenter::Delegated
edit_project_release_url(project, release) edit_project_release_url(project, release)
end end
def assets_count
if can_download_code?
release.assets_count
else
release.assets_count(except: [:sources])
end
end
def name
can_download_code? ? release.name : "Release-#{release.id}"
end
private private
def can_download_code? def can_download_code?
......
...@@ -10102,6 +10102,9 @@ enum RegistryState { ...@@ -10102,6 +10102,9 @@ enum RegistryState {
SYNCED SYNCED
} }
"""
Represents a release
"""
type Release { type Release {
""" """
Assets of the release Assets of the release
...@@ -10196,7 +10199,7 @@ type Release { ...@@ -10196,7 +10199,7 @@ type Release {
""" """
Name of the tag associated with the release Name of the tag associated with the release
""" """
tagName: String! tagName: String
""" """
Relative web path to the tag associated with the release Relative web path to the tag associated with the release
...@@ -10204,11 +10207,14 @@ type Release { ...@@ -10204,11 +10207,14 @@ type Release {
tagPath: String tagPath: String
} }
"""
A container for all assets associated with a release
"""
type ReleaseAssets { type ReleaseAssets {
""" """
Number of assets of the release Number of assets of the release
""" """
assetsCount: Int count: Int
""" """
Asset links of the release Asset links of the release
...@@ -10356,6 +10362,9 @@ type ReleaseEvidenceEdge { ...@@ -10356,6 +10362,9 @@ type ReleaseEvidenceEdge {
node: ReleaseEvidence node: ReleaseEvidence
} }
"""
Represents an asset link associated with a release
"""
type ReleaseLink { type ReleaseLink {
""" """
Indicates the link points to an external resource Indicates the link points to an external resource
...@@ -10443,6 +10452,9 @@ enum ReleaseLinkType { ...@@ -10443,6 +10452,9 @@ enum ReleaseLinkType {
RUNBOOK RUNBOOK
} }
"""
Represents the source code attached to a release in a particular format
"""
type ReleaseSource { type ReleaseSource {
""" """
Format of the source Format of the source
......
...@@ -29588,7 +29588,7 @@ ...@@ -29588,7 +29588,7 @@
{ {
"kind": "OBJECT", "kind": "OBJECT",
"name": "Release", "name": "Release",
"description": null, "description": "Represents a release",
"fields": [ "fields": [
{ {
"name": "assets", "name": "assets",
...@@ -29815,13 +29815,9 @@ ...@@ -29815,13 +29815,9 @@
], ],
"type": { "type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR", "kind": "SCALAR",
"name": "String", "name": "String",
"ofType": null "ofType": null
}
}, },
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
...@@ -29851,10 +29847,10 @@ ...@@ -29851,10 +29847,10 @@
{ {
"kind": "OBJECT", "kind": "OBJECT",
"name": "ReleaseAssets", "name": "ReleaseAssets",
"description": null, "description": "A container for all assets associated with a release",
"fields": [ "fields": [
{ {
"name": "assetsCount", "name": "count",
"description": "Number of assets of the release", "description": "Number of assets of the release",
"args": [ "args": [
...@@ -30281,7 +30277,7 @@ ...@@ -30281,7 +30277,7 @@
{ {
"kind": "OBJECT", "kind": "OBJECT",
"name": "ReleaseLink", "name": "ReleaseLink",
"description": null, "description": "Represents an asset link associated with a release",
"fields": [ "fields": [
{ {
"name": "external", "name": "external",
...@@ -30515,7 +30511,7 @@ ...@@ -30515,7 +30511,7 @@
{ {
"kind": "OBJECT", "kind": "OBJECT",
"name": "ReleaseSource", "name": "ReleaseSource",
"description": null, "description": "Represents the source code attached to a release in a particular format",
"fields": [ "fields": [
{ {
"name": "format", "name": "format",
...@@ -1400,6 +1400,8 @@ Represents a Project Member ...@@ -1400,6 +1400,8 @@ Represents a Project Member
## Release ## Release
Represents a release
| Name | Type | Description | | Name | Type | Description |
| --- | ---- | ---------- | | --- | ---- | ---------- |
| `assets` | ReleaseAssets | Assets of the release | | `assets` | ReleaseAssets | Assets of the release |
...@@ -1410,14 +1412,16 @@ Represents a Project Member ...@@ -1410,14 +1412,16 @@ Represents a Project Member
| `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` | | `descriptionHtml` | String | The GitLab Flavored Markdown rendering of `description` |
| `name` | String | Name of the release | | `name` | String | Name of the release |
| `releasedAt` | Time | Timestamp of when the release was released | | `releasedAt` | Time | Timestamp of when the release was released |
| `tagName` | String! | Name of the tag associated with the release | | `tagName` | String | Name of the tag associated with the release |
| `tagPath` | String | Relative web path to the tag associated with the release | | `tagPath` | String | Relative web path to the tag associated with the release |
## ReleaseAssets ## ReleaseAssets
A container for all assets associated with a release
| Name | Type | Description | | Name | Type | Description |
| --- | ---- | ---------- | | --- | ---- | ---------- |
| `assetsCount` | Int | Number of assets of the release | | `count` | Int | Number of assets of the release |
## ReleaseEvidence ## ReleaseEvidence
...@@ -1432,6 +1436,8 @@ Evidence for a release ...@@ -1432,6 +1436,8 @@ Evidence for a release
## ReleaseLink ## ReleaseLink
Represents an asset link associated with a release
| Name | Type | Description | | Name | Type | Description |
| --- | ---- | ---------- | | --- | ---- | ---------- |
| `external` | Boolean | Indicates the link points to an external resource | | `external` | Boolean | Indicates the link points to an external resource |
...@@ -1442,6 +1448,8 @@ Evidence for a release ...@@ -1442,6 +1448,8 @@ Evidence for a release
## ReleaseSource ## ReleaseSource
Represents the source code attached to a release in a particular format
| Name | Type | Description | | Name | Type | Description |
| --- | ---- | ---------- | | --- | ---- | ---------- |
| `format` | String | Format of the source | | `format` | String | Format of the source |
......
...@@ -5,9 +5,7 @@ module API ...@@ -5,9 +5,7 @@ module API
class Release < Grape::Entity class Release < Grape::Entity
include ::API::Helpers::Presentable include ::API::Helpers::Presentable
expose :name do |release, _| expose :name
can_download_code? ? release.name : "Release-#{release.id}"
end
expose :tag, as: :tag_name, if: ->(_, _) { can_download_code? } expose :tag, as: :tag_name, if: ->(_, _) { can_download_code? }
expose :description expose :description
expose :description_html do |entity| expose :description_html do |entity|
...@@ -23,10 +21,7 @@ module API ...@@ -23,10 +21,7 @@ module API
expose :tag_path, expose_nil: false expose :tag_path, expose_nil: false
expose :assets do expose :assets do
expose :assets_count, as: :count do |release, _| expose :assets_count, as: :count
assets_to_exclude = can_download_code? ? [] : [:sources]
release.assets_count(except: assets_to_exclude)
end
expose :sources, using: Entities::Releases::Source, if: ->(_, _) { can_download_code? } expose :sources, using: Entities::Releases::Source, if: ->(_, _) { can_download_code? }
expose :links, using: Entities::Releases::Link do |release, options| expose :links, using: Entities::Releases::Link do |release, options|
release.links.sorted release.links.sorted
......
...@@ -7,7 +7,7 @@ describe GitlabSchema.types['ReleaseAssets'] do ...@@ -7,7 +7,7 @@ describe GitlabSchema.types['ReleaseAssets'] do
it 'has the expected fields' do it 'has the expected fields' do
expected_fields = %w[ expected_fields = %w[
assets_count links sources count links sources
] ]
expect(described_class).to include_graphql_fields(*expected_fields) expect(described_class).to include_graphql_fields(*expected_fields)
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
require 'spec_helper' require 'spec_helper'
describe GitlabSchema.types['ReleaseSource'] do describe GitlabSchema.types['ReleaseSource'] do
it { expect(described_class).to require_graphql_authorizations(:read_release_sources) } it { expect(described_class).to require_graphql_authorizations(:download_code) }
it 'has the expected fields' do it 'has the expected fields' do
expected_fields = %w[ expected_fields = %w[
......
...@@ -44,6 +44,5 @@ describe GitlabSchema.types['Release'] do ...@@ -44,6 +44,5 @@ describe GitlabSchema.types['Release'] do
subject { described_class.fields['commit'] } subject { described_class.fields['commit'] }
it { is_expected.to have_graphql_type(Types::CommitType) } it { is_expected.to have_graphql_type(Types::CommitType) }
it { is_expected.to require_graphql_authorizations(:reporter_access) }
end end
end end
# frozen_string_literal: true
require 'spec_helper'
describe Releases::SourcePolicy do
using RSpec::Parameterized::TableSyntax
let(:policy) { described_class.new(user, source) }
let_it_be(:public_user) { create(:user) }
let_it_be(:guest) { create(:user) }
let_it_be(:reporter) { create(:user) }
let(:release) { create(:release, project: project) }
let(:source) { release.sources.first }
shared_examples 'source code access' do
it "allows access a release's source code" do
expect(policy).to be_allowed(:read_release_sources)
end
end
shared_examples 'no source code access' do
it "does not allow access a release's source code" do
expect(policy).to be_disallowed(:read_release_sources)
end
end
context 'a private project' do
let_it_be(:project) { create(:project, :private) }
context 'accessed by a public user' do
let(:user) { public_user }
it_behaves_like 'no source code access'
end
context 'accessed by a user with Guest permissions' do
let(:user) { guest }
before do
project.add_guest(user)
end
it_behaves_like 'no source code access'
end
context 'accessed by a user with Reporter permissions' do
let(:user) { reporter }
before do
project.add_reporter(user)
end
it_behaves_like 'source code access'
end
end
context 'a public project' do
let_it_be(:project) { create(:project, :public) }
context 'accessed by a public user' do
let(:user) { public_user }
it_behaves_like 'source code access'
end
context 'accessed by a user with Guest permissions' do
let(:user) { guest }
before do
project.add_guest(user)
end
it_behaves_like 'source code access'
end
context 'accessed by a user with Reporter permissions' do
let(:user) { reporter }
before do
project.add_reporter(user)
end
it_behaves_like 'source code access'
end
end
end
...@@ -112,4 +112,36 @@ describe ReleasePresenter do ...@@ -112,4 +112,36 @@ describe ReleasePresenter do
it { is_expected.to be_nil } it { is_expected.to be_nil }
end end
end end
describe '#assets_count' do
subject { presenter.assets_count }
it 'returns the number of assets associated to the release' do
is_expected.to be release.assets_count
end
context 'when a user is not allowed to download release sources' do
let(:presenter) { described_class.new(release, current_user: guest) }
it 'returns the number of all non-source assets associated to the release' do
is_expected.to be release.assets_count(except: [:sources])
end
end
end
describe '#name' do
subject { presenter.name }
it 'returns the release name' do
is_expected.to eq release.name
end
context "when a user is not allowed to access any repository information" do
let(:presenter) { described_class.new(release, current_user: guest) }
it 'returns a replacement name to avoid potentially leaking tag information' do
is_expected.to eq "Release-#{release.id}"
end
end
end
end end
...@@ -7,15 +7,10 @@ describe 'Query.project(fullPath).release(tagName)' do ...@@ -7,15 +7,10 @@ describe 'Query.project(fullPath).release(tagName)' do
include GraphqlHelpers include GraphqlHelpers
include Presentable include Presentable
let_it_be(:project) { create(:project, :repository) }
let_it_be(:milestone_1) { create(:milestone, project: project) }
let_it_be(:milestone_2) { create(:milestone, project: project) }
let_it_be(:release) { create(:release, :with_evidence, project: project, milestones: [milestone_1, milestone_2]) }
let_it_be(:release_link_1) { create(:release_link, release: release) }
let_it_be(:release_link_2) { create(:release_link, release: release) }
let_it_be(:developer) { create(:user) } let_it_be(:developer) { create(:user) }
let_it_be(:guest) { create(:user) }
let(:current_user) { developer } let_it_be(:reporter) { create(:user) }
let_it_be(:stranger) { create(:user) }
def query(rq = release_fields) def query(rq = release_fields)
graphql_query_for(:project, { fullPath: project.full_path }, graphql_query_for(:project, { fullPath: project.full_path },
...@@ -27,12 +22,10 @@ describe 'Query.project(fullPath).release(tagName)' do ...@@ -27,12 +22,10 @@ describe 'Query.project(fullPath).release(tagName)' do
let(:data) { graphql_data.dig(*path) } let(:data) { graphql_data.dig(*path) }
before do shared_examples 'full access to the release field' do
project.add_developer(developer)
end
describe 'scalar fields' do describe 'scalar fields' do
let(:path) { path_prefix } let(:path) { path_prefix }
let(:release_fields) do let(:release_fields) do
query_graphql_field(%{ query_graphql_field(%{
tagName tagName
...@@ -64,6 +57,7 @@ describe 'Query.project(fullPath).release(tagName)' do ...@@ -64,6 +57,7 @@ describe 'Query.project(fullPath).release(tagName)' do
describe 'milestones' do describe 'milestones' do
let(:path) { path_prefix + %w[milestones nodes] } let(:path) { path_prefix + %w[milestones nodes] }
let(:release_fields) do let(:release_fields) do
query_graphql_field(:milestones, nil, 'nodes { id title }') query_graphql_field(:milestones, nil, 'nodes { id title }')
end end
...@@ -81,6 +75,7 @@ describe 'Query.project(fullPath).release(tagName)' do ...@@ -81,6 +75,7 @@ describe 'Query.project(fullPath).release(tagName)' do
describe 'author' do describe 'author' do
let(:path) { path_prefix + %w[author] } let(:path) { path_prefix + %w[author] }
let(:release_fields) do let(:release_fields) do
query_graphql_field(:author, nil, 'id username') query_graphql_field(:author, nil, 'id username')
end end
...@@ -97,6 +92,7 @@ describe 'Query.project(fullPath).release(tagName)' do ...@@ -97,6 +92,7 @@ describe 'Query.project(fullPath).release(tagName)' do
describe 'commit' do describe 'commit' do
let(:path) { path_prefix + %w[commit] } let(:path) { path_prefix + %w[commit] }
let(:release_fields) do let(:release_fields) do
query_graphql_field(:commit, nil, 'sha') query_graphql_field(:commit, nil, 'sha')
end end
...@@ -109,21 +105,23 @@ describe 'Query.project(fullPath).release(tagName)' do ...@@ -109,21 +105,23 @@ describe 'Query.project(fullPath).release(tagName)' do
end end
describe 'assets' do describe 'assets' do
describe 'assetsCount' do describe 'count' do
let(:path) { path_prefix + %w[assets] } let(:path) { path_prefix + %w[assets] }
let(:release_fields) do let(:release_fields) do
query_graphql_field(:assets, nil, 'assetsCount') query_graphql_field(:assets, nil, 'count')
end end
it 'returns the number of assets associated to the release' do it 'returns the number of assets associated to the release' do
post_query post_query
expect(data).to eq({ 'assetsCount' => release.sources.size + release.links.size }) expect(data).to eq({ 'count' => release.sources.size + release.links.size })
end end
end end
describe 'links' do describe 'links' do
let(:path) { path_prefix + %w[assets links nodes] } let(:path) { path_prefix + %w[assets links nodes] }
let(:release_fields) do let(:release_fields) do
query_graphql_field(:assets, nil, query_graphql_field(:assets, nil,
query_graphql_field(:links, nil, 'nodes { id name url external }')) query_graphql_field(:links, nil, 'nodes { id name url external }'))
...@@ -147,6 +145,7 @@ describe 'Query.project(fullPath).release(tagName)' do ...@@ -147,6 +145,7 @@ describe 'Query.project(fullPath).release(tagName)' do
describe 'sources' do describe 'sources' do
let(:path) { path_prefix + %w[assets sources nodes] } let(:path) { path_prefix + %w[assets sources nodes] }
let(:release_fields) do let(:release_fields) do
query_graphql_field(:assets, nil, query_graphql_field(:assets, nil,
query_graphql_field(:sources, nil, 'nodes { format url }')) query_graphql_field(:sources, nil, 'nodes { format url }'))
...@@ -165,14 +164,15 @@ describe 'Query.project(fullPath).release(tagName)' do ...@@ -165,14 +164,15 @@ describe 'Query.project(fullPath).release(tagName)' do
expect(data).to match_array(expected) expect(data).to match_array(expected)
end end
end end
end
describe 'evidences' do describe 'evidences' do
let(:path) { path_prefix + %w[evidences] } let(:path) { path_prefix + %w[evidences] }
let(:release_fields) do let(:release_fields) do
query_graphql_field(:evidences, nil, 'nodes { id sha filepath collectedAt }') query_graphql_field(:evidences, nil, 'nodes { id sha filepath collectedAt }')
end end
context 'for a developer' do
it 'finds all evidence fields' do it 'finds all evidence fields' do
post_query post_query
...@@ -187,19 +187,102 @@ describe 'Query.project(fullPath).release(tagName)' do ...@@ -187,19 +187,102 @@ describe 'Query.project(fullPath).release(tagName)' do
expect(data["nodes"].first).to eq(expected) expect(data["nodes"].first).to eq(expected)
end end
end end
end
context 'for a guest' do shared_examples 'no access to the release field' do
let(:current_user) { create :user } describe 'repository-related fields' do
let(:path) { path_prefix }
before do let(:release_fields) do
project.add_guest(current_user) query_graphql_field('description')
end end
it 'denies access' do before do
post_query post_query
end
it 'returns nil' do
expect(data).to eq(nil)
end
end
end
describe "ensures that the correct data is returned based on the project's visibility and the user's access level" do
context 'when the project is private' do
let_it_be(:project) { create(:project, :repository, :private) }
let_it_be(:milestone_1) { create(:milestone, project: project) }
let_it_be(:milestone_2) { create(:milestone, project: project) }
let_it_be(:release) { create(:release, :with_evidence, project: project, milestones: [milestone_1, milestone_2]) }
let_it_be(:release_link_1) { create(:release_link, release: release) }
let_it_be(:release_link_2) { create(:release_link, release: release) }
expect(data['node']).to be_nil before_all do
project.add_developer(developer)
project.add_guest(guest)
project.add_reporter(reporter)
end
context 'when the user is not logged in' do
let(:current_user) { stranger }
it_behaves_like 'no access to the release field'
end
context 'when the user has Guest permissions' do
let(:current_user) { guest }
it_behaves_like 'no access to the release field'
end
context 'when the user has Reporter permissions' do
let(:current_user) { reporter }
it_behaves_like 'full access to the release field'
end
context 'when the user has Developer permissions' do
let(:current_user) { developer }
it_behaves_like 'full access to the release field'
end
end
context 'when the project is public' do
let_it_be(:project) { create(:project, :repository, :public) }
let_it_be(:milestone_1) { create(:milestone, project: project) }
let_it_be(:milestone_2) { create(:milestone, project: project) }
let_it_be(:release) { create(:release, :with_evidence, project: project, milestones: [milestone_1, milestone_2]) }
let_it_be(:release_link_1) { create(:release_link, release: release) }
let_it_be(:release_link_2) { create(:release_link, release: release) }
before_all do
project.add_developer(developer)
project.add_guest(guest)
project.add_reporter(reporter)
end
context 'when the user is not logged in' do
let(:current_user) { stranger }
it_behaves_like 'full access to the release field'
end end
context 'when the user has Guest permissions' do
let(:current_user) { guest }
it_behaves_like 'full access to the release field'
end
context 'when the user has Reporter permissions' do
let(:current_user) { reporter }
it_behaves_like 'full access to the release field'
end
context 'when the user has Developer permissions' do
let(:current_user) { developer }
it_behaves_like 'full access to the release field'
end end
end end
end end
......
# frozen_string_literal: true
require 'spec_helper'
describe 'Query.project(fullPath).releases()' do
include GraphqlHelpers
let_it_be(:guest) { create(:user) }
let_it_be(:reporter) { create(:user) }
let_it_be(:stranger) { create(:user) }
let(:query) do
graphql_query_for(:project, { fullPath: project.full_path },
%{
releases {
nodes {
tagName
tagPath
name
commit {
sha
}
assets {
count
sources {
nodes {
url
}
}
}
evidences {
nodes {
sha
}
}
}
}
})
end
let(:post_query) { post_graphql(query, current_user: current_user) }
let(:data) { graphql_data.dig('project', 'releases', 'nodes', 0) }
shared_examples 'full access to all repository-related fields' do
describe 'repository-related fields' do
before do
post_query
end
it 'returns data for fields that are protected in private projects' do
expected_sources = release.sources.map do |s|
{ 'url' => s.url }
end
expected_evidences = release.evidences.map do |e|
{ 'sha' => e.sha }
end
expect(data).to eq({
'tagName' => release.tag,
'tagPath' => project_tag_path(project, release.tag),
'name' => release.name,
'commit' => {
'sha' => release.commit.sha
},
'assets' => {
'count' => release.assets_count,
'sources' => {
'nodes' => expected_sources
}
},
'evidences' => {
'nodes' => expected_evidences
}
})
end
end
end
shared_examples 'no access to any repository-related fields' do
describe 'repository-related fields' do
before do
post_query
end
it 'does not return data for fields that expose repository information' do
expect(data).to eq({
'tagName' => nil,
'tagPath' => nil,
'name' => "Release-#{release.id}",
'commit' => nil,
'assets' => {
'count' => release.assets_count(except: [:sources]),
'sources' => {
'nodes' => []
}
},
'evidences' => {
'nodes' => []
}
})
end
end
end
describe "ensures that the correct data is returned based on the project's visibility and the user's access level" do
context 'when the project is private' do
let_it_be(:project) { create(:project, :repository, :private) }
let_it_be(:release) { create(:release, :with_evidence, project: project) }
before_all do
project.add_guest(guest)
project.add_reporter(reporter)
end
context 'when the user has Guest permissions' do
let(:current_user) { guest }
it_behaves_like 'no access to any repository-related fields'
end
context 'when the user has Reporter permissions' do
let(:current_user) { reporter }
it_behaves_like 'full access to all repository-related fields'
end
end
context 'when the project is public' do
let_it_be(:project) { create(:project, :repository, :public) }
let_it_be(:release) { create(:release, :with_evidence, project: project) }
before_all do
project.add_guest(guest)
project.add_reporter(reporter)
end
context 'when the user is not logged in' do
let(:current_user) { stranger }
it_behaves_like 'full access to all repository-related fields'
end
context 'when the user has Guest permissions' do
let(:current_user) { guest }
it_behaves_like 'full access to all repository-related fields'
end
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment