Commit acd5b040 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents b2375e52 3456b013
...@@ -101,3 +101,5 @@ apollo.config.js ...@@ -101,3 +101,5 @@ apollo.config.js
/tmp/matching_tests.txt /tmp/matching_tests.txt
ee/changelogs/unreleased-ee ee/changelogs/unreleased-ee
/sitespeed-result /sitespeed-result
tags.lock
tags.temp
...@@ -31,14 +31,16 @@ export const GROUP_VISIBILITY_TYPE = { ...@@ -31,14 +31,16 @@ export const GROUP_VISIBILITY_TYPE = {
'Public - The group and any public projects can be viewed without any authentication.', 'Public - The group and any public projects can be viewed without any authentication.',
), ),
internal: __( internal: __(
'Internal - The group and any internal projects can be viewed by any logged in user.', 'Internal - The group and any internal projects can be viewed by any logged in user except external users.',
), ),
private: __('Private - The group and its projects can only be viewed by members.'), private: __('Private - The group and its projects can only be viewed by members.'),
}; };
export const PROJECT_VISIBILITY_TYPE = { export const PROJECT_VISIBILITY_TYPE = {
public: __('Public - The project can be accessed without any authentication.'), public: __('Public - The project can be accessed without any authentication.'),
internal: __('Internal - The project can be accessed by any logged in user.'), internal: __(
'Internal - The project can be accessed by any logged in user except external users.',
),
private: __( private: __(
'Private - Project access must be granted explicitly to each user. If this project is part of a group, access will be granted to members of the group.', 'Private - Project access must be granted explicitly to each user. If this project is part of a group, access will be granted to members of the group.',
), ),
......
...@@ -120,7 +120,7 @@ export default { ...@@ -120,7 +120,7 @@ export default {
? __('The snippet is visible only to project members.') ? __('The snippet is visible only to project members.')
: __('The snippet is visible only to me.'); : __('The snippet is visible only to me.');
case 'internal': case 'internal':
return __('The snippet is visible to any logged in user.'); return __('The snippet is visible to any logged in user except external users.');
default: default:
return __('The snippet can be accessed without any authentication.'); return __('The snippet can be accessed without any authentication.');
} }
......
...@@ -14,7 +14,7 @@ export const SNIPPET_VISIBILITY = { ...@@ -14,7 +14,7 @@ export const SNIPPET_VISIBILITY = {
[SNIPPET_VISIBILITY_INTERNAL]: { [SNIPPET_VISIBILITY_INTERNAL]: {
label: __('Internal'), label: __('Internal'),
icon: 'shield', icon: 'shield',
description: __('The snippet is visible to any logged in user.'), description: __('The snippet is visible to any logged in user except external users.'),
}, },
[SNIPPET_VISIBILITY_PUBLIC]: { [SNIPPET_VISIBILITY_PUBLIC]: {
label: __('Public'), label: __('Public'),
......
...@@ -30,6 +30,8 @@ class GitlabSchema < GraphQL::Schema ...@@ -30,6 +30,8 @@ class GitlabSchema < GraphQL::Schema
default_max_page_size 100 default_max_page_size 100
lazy_resolve ::Gitlab::Graphql::Lazy, :force
class << self class << self
def multiplex(queries, **kwargs) def multiplex(queries, **kwargs)
kwargs[:max_complexity] ||= max_query_complexity(kwargs[:context]) kwargs[:max_complexity] ||= max_query_complexity(kwargs[:context])
......
...@@ -31,7 +31,7 @@ module VisibilityLevelHelper ...@@ -31,7 +31,7 @@ module VisibilityLevelHelper
when Gitlab::VisibilityLevel::PRIVATE when Gitlab::VisibilityLevel::PRIVATE
_("Project access must be granted explicitly to each user. If this project is part of a group, access will be granted to members of the group.") _("Project access must be granted explicitly to each user. If this project is part of a group, access will be granted to members of the group.")
when Gitlab::VisibilityLevel::INTERNAL when Gitlab::VisibilityLevel::INTERNAL
_("The project can be accessed by any logged in user.") _("The project can be accessed by any logged in user except external users.")
when Gitlab::VisibilityLevel::PUBLIC when Gitlab::VisibilityLevel::PUBLIC
_("The project can be accessed without any authentication.") _("The project can be accessed without any authentication.")
end end
...@@ -42,7 +42,7 @@ module VisibilityLevelHelper ...@@ -42,7 +42,7 @@ module VisibilityLevelHelper
when Gitlab::VisibilityLevel::PRIVATE when Gitlab::VisibilityLevel::PRIVATE
_("The group and its projects can only be viewed by members.") _("The group and its projects can only be viewed by members.")
when Gitlab::VisibilityLevel::INTERNAL when Gitlab::VisibilityLevel::INTERNAL
_("The group and any internal projects can be viewed by any logged in user.") _("The group and any internal projects can be viewed by any logged in user except external users.")
when Gitlab::VisibilityLevel::PUBLIC when Gitlab::VisibilityLevel::PUBLIC
_("The group and any public projects can be viewed without any authentication.") _("The group and any public projects can be viewed without any authentication.")
end end
......
...@@ -72,7 +72,7 @@ ...@@ -72,7 +72,7 @@
%li %li
= _("For public projects, anyone can view pipelines and access job details (output logs and artifacts)") = _("For public projects, anyone can view pipelines and access job details (output logs and artifacts)")
%li %li
= _("For internal projects, any logged in user can view pipelines and access job details (output logs and artifacts)") = _("For internal projects, any logged in user except external users can view pipelines and access job details (output logs and artifacts)")
%li %li
= _("For private projects, any member (guest or higher) can view pipelines and access job details (output logs and artifacts)") = _("For private projects, any member (guest or higher) can view pipelines and access job details (output logs and artifacts)")
%p %p
......
...@@ -104,7 +104,7 @@ class FeatureFlagOptionParser ...@@ -104,7 +104,7 @@ class FeatureFlagOptionParser
end end
# Name is a first name # Name is a first name
options.name = argv.first options.name = argv.first.downcase.gsub(/-/, '_')
options options
end end
......
---
title: Clarify that external users cannot access all internal projects, groups, and snippets
merge_request: 46087
author: Ben Bodenmiller (@bbodenmiller)
type: other
---
name: graphql_lazy_authorization
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/45263
rollout_issue_url:
type: development
group: group::plan
default_enabled: false
...@@ -17,7 +17,7 @@ Constants for snippet visibility levels are: ...@@ -17,7 +17,7 @@ Constants for snippet visibility levels are:
| visibility | Description | | visibility | Description |
| ---------- | ----------- | | ---------- | ----------- |
| `private` | The snippet is visible only the snippet creator | | `private` | The snippet is visible only the snippet creator |
| `internal` | The snippet is visible for any logged in user | | `internal` | The snippet is visible for any logged in user except [external users](../user/permissions.md#external-users) |
| `public` | The snippet can be accessed without any authentication | | `public` | The snippet can be accessed without any authentication |
NOTE: **Note:** NOTE: **Note:**
......
...@@ -16,7 +16,7 @@ Values for the project visibility level are: ...@@ -16,7 +16,7 @@ Values for the project visibility level are:
- `private`: - `private`:
Project access must be granted explicitly for each user. Project access must be granted explicitly for each user.
- `internal`: - `internal`:
The project can be cloned by any logged in user. The project can be cloned by any logged in user except [external users](../user/permissions.md#external-users).
- `public`: - `public`:
The project can be accessed without any authentication. The project can be accessed without any authentication.
......
...@@ -21,7 +21,7 @@ Valid values for snippet visibility levels are: ...@@ -21,7 +21,7 @@ Valid values for snippet visibility levels are:
| Visibility | Description | | Visibility | Description |
|:-----------|:----------------------------------------------------| |:-----------|:----------------------------------------------------|
| `private` | Snippet is visible only to the snippet creator. | | `private` | Snippet is visible only to the snippet creator. |
| `internal` | Snippet is visible for any logged in user. | | `internal` | Snippet is visible for any logged in user except [external users](../user/permissions.md#external-users). |
| `public` | Snippet can be accessed without any authentication. | | `public` | Snippet can be accessed without any authentication. |
## List all snippets for a user ## List all snippets for a user
......
...@@ -183,7 +183,7 @@ Job logs and artifacts are [not visible for guest users and non-project members] ...@@ -183,7 +183,7 @@ Job logs and artifacts are [not visible for guest users and non-project members]
If **Public pipelines** is enabled (default): If **Public pipelines** is enabled (default):
- For **public** projects, anyone can view the pipelines and related features. - For **public** projects, anyone can view the pipelines and related features.
- For **internal** projects, any logged in user can view the pipelines - For **internal** projects, any logged in user except [external users](../../user/permissions.md#external-users) can view the pipelines
and related features. and related features.
- For **private** projects, any project member (guest or higher) can view the pipelines - For **private** projects, any project member (guest or higher) can view the pipelines
and related features. and related features.
...@@ -192,7 +192,7 @@ If **Public pipelines** is disabled: ...@@ -192,7 +192,7 @@ If **Public pipelines** is disabled:
- For **public** projects, anyone can view the pipelines, but only members - For **public** projects, anyone can view the pipelines, but only members
(reporter or higher) can access the related features. (reporter or higher) can access the related features.
- For **internal** projects, any logged in user can view the pipelines. - For **internal** projects, any logged in user except [external users](../../user/permissions.md#external-users) can view the pipelines.
However, only members (reporter or higher) can access the job related features. However, only members (reporter or higher) can access the job related features.
- For **private** projects, only project members (reporter or higher) - For **private** projects, only project members (reporter or higher)
can view the pipelines or access the related features. can view the pipelines or access the related features.
......
...@@ -21,12 +21,12 @@ on the repository. ...@@ -21,12 +21,12 @@ on the repository.
### Internal projects ### Internal projects
Internal projects can be cloned by any logged in user. Internal projects can be cloned by any logged in user except [external users](../user/permissions.md#external-users).
They will also be listed in the public access directory (`/public`), but only for logged They will also be listed in the public access directory (`/public`), but only for logged
in users. in users.
Any logged in user will have [Guest permissions](../user/permissions.md) Any logged in user except [external users](../user/permissions.md#external-users) will have [Guest permissions](../user/permissions.md)
on the repository. on the repository.
NOTE: **Note:** NOTE: **Note:**
......
...@@ -314,6 +314,10 @@ External users: ...@@ -314,6 +314,10 @@ External users:
- Can only access public projects and projects to which they are explicitly granted access, - Can only access public projects and projects to which they are explicitly granted access,
thus hiding all other internal or private ones from them (like being thus hiding all other internal or private ones from them (like being
logged out). logged out).
- Can only access public groups and groups to which they are explicitly granted access,
thus hiding all other internal or private ones from them (like being
logged out).
- Can only access public snippets.
Access can be granted by adding the user as member to the project or group. Access can be granted by adding the user as member to the project or group.
Like usual users, they receive a role in the project or group with all Like usual users, they receive a role in the project or group with all
......
...@@ -9,9 +9,11 @@ module EE ...@@ -9,9 +9,11 @@ module EE
class_methods do class_methods do
extend ::Gitlab::Utils::Override extend ::Gitlab::Utils::Override
override :eventable_types override :feature_category_per_eventable_type
def eventable_types def feature_category_per_eventable_type
[::Epic, *super] super.merge!(
::Epic => :issue_tracking
)
end end
end end
end end
......
...@@ -10,7 +10,7 @@ module EE ...@@ -10,7 +10,7 @@ module EE
desc 'Restore a project' do desc 'Restore a project' do
success Entities::Project success Entities::Project
end end
post ':id/restore' do post ':id/restore', feature_category: :authentication_and_authorization do
authorize!(:remove_project, user_project) authorize!(:remove_project, user_project)
break not_found! unless user_project.feature_available?(:adjourned_deletion_for_projects_and_groups) break not_found! unless user_project.feature_available?(:adjourned_deletion_for_projects_and_groups)
...@@ -21,7 +21,7 @@ module EE ...@@ -21,7 +21,7 @@ module EE
render_api_error!(result[:message], 400) render_api_error!(result[:message], 400)
end end
end end
segment ':id/audit_events' do segment ':id/audit_events', feature_category: :audit_events do
before do before do
authorize! :admin_project, user_project authorize! :admin_project, user_project
check_audit_events_available!(user_project) check_audit_events_available!(user_project)
...@@ -37,7 +37,7 @@ module EE ...@@ -37,7 +37,7 @@ module EE
use :pagination use :pagination
end end
get '/' do get '/', feature_category: :audit_events do
level = ::Gitlab::Audit::Levels::Project.new(project: user_project) level = ::Gitlab::Audit::Levels::Project.new(project: user_project)
audit_events = AuditLogFinder.new( audit_events = AuditLogFinder.new(
level: level, level: level,
...@@ -53,7 +53,7 @@ module EE ...@@ -53,7 +53,7 @@ module EE
params do params do
requires :audit_event_id, type: Integer, desc: 'The ID of the audit event' requires :audit_event_id, type: Integer, desc: 'The ID of the audit event'
end end
get '/:audit_event_id' do get '/:audit_event_id', feature_category: :audit_events do
level = ::Gitlab::Audit::Levels::Project.new(project: user_project) level = ::Gitlab::Audit::Levels::Project.new(project: user_project)
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
# This is not `find_by!` from ActiveRecord # This is not `find_by!` from ActiveRecord
......
...@@ -41,18 +41,23 @@ module EE ...@@ -41,18 +41,23 @@ module EE
check_file_size! check_file_size!
end end
# Run the checks in separate threads for performance benefits # Run the checks in separate threads for performance benefits.
#
# The git hook environment is currently set in the current thread
# in lib/api/internal/base.rb. This needs to be passed into the
# child threads we spawn here.
# #
# @return [Nil] returns nil unless an error is raised # @return [Nil] returns nil unless an error is raised
# @raise [Gitlab::GitAccess::ForbiddenError] if any check fails # @raise [Gitlab::GitAccess::ForbiddenError] if any check fails
def run_checks_in_parallel! def run_checks_in_parallel!
git_env = ::Gitlab::Git::HookEnv.all(project.repository.gl_repository)
@threads = [] @threads = []
parallelize do parallelize(git_env) do
check_tag_or_branch! check_tag_or_branch!
end end
parallelize do parallelize(git_env) do
check_file_size! check_file_size!
end end
...@@ -73,8 +78,9 @@ module EE ...@@ -73,8 +78,9 @@ module EE
# Runs a block inside a new thread. This thread will # Runs a block inside a new thread. This thread will
# exit immediately upon an exception being raised. # exit immediately upon an exception being raised.
# #
# @param git_env [Hash] the current git environment
# @raise [Gitlab::GitAccess::ForbiddenError] # @raise [Gitlab::GitAccess::ForbiddenError]
def parallelize def parallelize(git_env)
@threads << Thread.new do @threads << Thread.new do
Thread.current.tap do |t| Thread.current.tap do |t|
t.name = "push_rule_check" t.name = "push_rule_check"
...@@ -82,7 +88,11 @@ module EE ...@@ -82,7 +88,11 @@ module EE
t.report_on_exception = false t.report_on_exception = false
end end
yield ::Gitlab::WithRequestStore.with_request_store do
::Gitlab::Git::HookEnv.set(project.repository.gl_repository, git_env)
yield
end
ensure # rubocop: disable Layout/RescueEnsureAlignment ensure # rubocop: disable Layout/RescueEnsureAlignment
ActiveRecord::Base.clear_active_connections! ActiveRecord::Base.clear_active_connections!
end end
......
...@@ -101,7 +101,7 @@ RSpec.describe EE::RegistrationsHelper do ...@@ -101,7 +101,7 @@ RSpec.describe EE::RegistrationsHelper do
it 'returns the desired mapping' do it 'returns the desired mapping' do
expect(helper.visibility_level_options).to eq [ expect(helper.visibility_level_options).to eq [
{ level: 0, label: 'Private', description: 'The group and its projects can only be viewed by members.' }, { level: 0, label: 'Private', description: 'The group and its projects can only be viewed by members.' },
{ level: 10, label: 'Internal', description: 'The group and any internal projects can be viewed by any logged in user.' }, { level: 10, label: 'Internal', description: 'The group and any internal projects can be viewed by any logged in user except external users.' },
{ level: 20, label: 'Public', description: 'The group and any public projects can be viewed without any authentication.' } { level: 20, label: 'Public', description: 'The group and any public projects can be viewed without any authentication.' }
] ]
end end
......
...@@ -59,7 +59,24 @@ RSpec.describe EE::Gitlab::Checks::PushRuleCheck do ...@@ -59,7 +59,24 @@ RSpec.describe EE::Gitlab::Checks::PushRuleCheck do
end end
describe '#validate!' do describe '#validate!' do
it_behaves_like "push checks" context "parallel push checks" do
it_behaves_like "push checks"
before do
::Gitlab::Git::HookEnv.set(project.repository.gl_repository,
"GIT_OBJECT_DIRECTORY_RELATIVE" => "objects",
"GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE" => [])
end
it "sets the git env correctly for all hooks", :request_store do
expect(Gitaly::Repository).to receive(:new)
.with(a_hash_including(git_object_directory: "objects"))
.and_call_original
# This push fails because of the commit message check
expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError)
end
end
context ":parallel_push_checks feature is disabled" do context ":parallel_push_checks feature is disabled" do
before do before do
......
...@@ -9,6 +9,8 @@ module API ...@@ -9,6 +9,8 @@ module API
include ::API::Helpers::Packages::BasicAuthHelpers::Constants include ::API::Helpers::Packages::BasicAuthHelpers::Constants
include ::Gitlab::Utils::StrongMemoize include ::Gitlab::Utils::StrongMemoize
feature_category :package_registry
content_type :json, 'application/json' content_type :json, 'application/json'
default_format :json default_format :json
......
...@@ -29,6 +29,8 @@ module API ...@@ -29,6 +29,8 @@ module API
CONAN_FILES = (Gitlab::Regex::Packages::CONAN_RECIPE_FILES + Gitlab::Regex::Packages::CONAN_PACKAGE_FILES).freeze CONAN_FILES = (Gitlab::Regex::Packages::CONAN_RECIPE_FILES + Gitlab::Regex::Packages::CONAN_PACKAGE_FILES).freeze
included do included do
feature_category :package_registry
helpers ::API::Helpers::PackagesManagerClientsHelpers helpers ::API::Helpers::PackagesManagerClientsHelpers
helpers ::API::Helpers::Packages::Conan::ApiHelpers helpers ::API::Helpers::Packages::Conan::ApiHelpers
helpers ::API::Helpers::RelatedResourcesHelpers helpers ::API::Helpers::RelatedResourcesHelpers
......
...@@ -26,6 +26,8 @@ module API ...@@ -26,6 +26,8 @@ module API
}.freeze }.freeze
included do included do
feature_category :package_registry
helpers ::API::Helpers::PackagesHelpers helpers ::API::Helpers::PackagesHelpers
helpers ::API::Helpers::Packages::BasicAuthHelpers helpers ::API::Helpers::Packages::BasicAuthHelpers
......
...@@ -7,6 +7,8 @@ module API ...@@ -7,6 +7,8 @@ module API
file_name: API::NO_SLASH_URL_PART_REGEX file_name: API::NO_SLASH_URL_PART_REGEX
}.freeze }.freeze
feature_category :package_registry
before do before do
require_packages_enabled! require_packages_enabled!
authenticate! authenticate!
......
...@@ -4,6 +4,8 @@ module API ...@@ -4,6 +4,8 @@ module API
helpers Gitlab::Golang helpers Gitlab::Golang
helpers ::API::Helpers::PackagesHelpers helpers ::API::Helpers::PackagesHelpers
feature_category :package_registry
# basic semver, except case encoded (A => !a) # basic semver, except case encoded (A => !a)
MODULE_VERSION_REGEX = /v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([-.!a-z0-9]+))?(?:\+([-.!a-z0-9]+))?/.freeze MODULE_VERSION_REGEX = /v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([-.!a-z0-9]+))?(?:\+([-.!a-z0-9]+))?/.freeze
......
...@@ -8,6 +8,8 @@ module API ...@@ -8,6 +8,8 @@ module API
authorize_packages_access!(user_group) authorize_packages_access!(user_group)
end end
feature_category :package_registry
helpers ::API::Helpers::PackagesHelpers helpers ::API::Helpers::PackagesHelpers
params do params do
......
...@@ -3,10 +3,13 @@ ...@@ -3,10 +3,13 @@
module API module API
module Helpers module Helpers
module ResourceLabelEventsHelpers module ResourceLabelEventsHelpers
def self.eventable_types def self.feature_category_per_eventable_type
# This is a method instead of a constant, allowing EE to more easily # This is a method instead of a constant, allowing EE to more easily
# extend it. # extend it.
[Issue, MergeRequest] {
Issue => :issue_tracking,
MergeRequest => :code_review
}
end end
end end
end end
......
...@@ -5,6 +5,8 @@ module API ...@@ -5,6 +5,8 @@ module API
file_name: API::NO_SLASH_URL_PART_REGEX file_name: API::NO_SLASH_URL_PART_REGEX
}.freeze }.freeze
feature_category :package_registry
content_type :md5, 'text/plain' content_type :md5, 'text/plain'
content_type :sha1, 'text/plain' content_type :sha1, 'text/plain'
content_type :binary, 'application/octet-stream' content_type :binary, 'application/octet-stream'
......
...@@ -5,6 +5,8 @@ module API ...@@ -5,6 +5,8 @@ module API
class NotificationSettings < ::API::Base class NotificationSettings < ::API::Base
before { authenticate! } before { authenticate! }
feature_category :users
helpers ::API::Helpers::MembersHelpers helpers ::API::Helpers::MembersHelpers
resource :notification_settings do resource :notification_settings do
......
...@@ -4,6 +4,8 @@ module API ...@@ -4,6 +4,8 @@ module API
helpers ::API::Helpers::PackagesHelpers helpers ::API::Helpers::PackagesHelpers
helpers ::API::Helpers::Packages::DependencyProxyHelpers helpers ::API::Helpers::Packages::DependencyProxyHelpers
feature_category :package_registry
NPM_ENDPOINT_REQUIREMENTS = { NPM_ENDPOINT_REQUIREMENTS = {
package_name: API::NO_SLASH_URL_PART_REGEX package_name: API::NO_SLASH_URL_PART_REGEX
}.freeze }.freeze
......
...@@ -10,6 +10,8 @@ module API ...@@ -10,6 +10,8 @@ module API
helpers ::API::Helpers::PackagesManagerClientsHelpers helpers ::API::Helpers::PackagesManagerClientsHelpers
helpers ::API::Helpers::Packages::BasicAuthHelpers helpers ::API::Helpers::Packages::BasicAuthHelpers
feature_category :package_registry
POSITIVE_INTEGER_REGEX = %r{\A[1-9]\d*\z}.freeze POSITIVE_INTEGER_REGEX = %r{\A[1-9]\d*\z}.freeze
NON_NEGATIVE_INTEGER_REGEX = %r{\A0|[1-9]\d*\z}.freeze NON_NEGATIVE_INTEGER_REGEX = %r{\A0|[1-9]\d*\z}.freeze
......
...@@ -8,6 +8,8 @@ module API ...@@ -8,6 +8,8 @@ module API
authorize_packages_access!(user_project) authorize_packages_access!(user_project)
end end
feature_category :package_registry
helpers ::API::Helpers::PackagesHelpers helpers ::API::Helpers::PackagesHelpers
params do params do
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
module API module API
class Pages < ::API::Base class Pages < ::API::Base
feature_category :pages
before do before do
require_pages_config_enabled! require_pages_config_enabled!
authenticated_with_can_read_all_resources! authenticated_with_can_read_all_resources!
......
...@@ -4,6 +4,8 @@ module API ...@@ -4,6 +4,8 @@ module API
class PagesDomains < ::API::Base class PagesDomains < ::API::Base
include PaginationParams include PaginationParams
feature_category :pages
PAGES_DOMAINS_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(domain: API::NO_SLASH_URL_PART_REGEX) PAGES_DOMAINS_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(domain: API::NO_SLASH_URL_PART_REGEX)
before do before do
......
...@@ -6,6 +6,8 @@ module API ...@@ -6,6 +6,8 @@ module API
before { authenticate! } before { authenticate! }
feature_category :kubernetes_management
params do params do
requires :id, type: String, desc: 'The ID of the project' requires :id, type: String, desc: 'The ID of the project'
end end
......
...@@ -10,6 +10,8 @@ module API ...@@ -10,6 +10,8 @@ module API
before { authorize_read_container_images! } before { authorize_read_container_images! }
feature_category :package_registry
params do params do
requires :id, type: String, desc: 'The ID of a project' requires :id, type: String, desc: 'The ID of a project'
end end
......
...@@ -6,6 +6,8 @@ module API ...@@ -6,6 +6,8 @@ module API
include APIGuard include APIGuard
helpers ::API::Helpers::EventsHelpers helpers ::API::Helpers::EventsHelpers
feature_category :users
params do params do
requires :id, type: String, desc: 'The ID of a project' requires :id, type: String, desc: 'The ID of a project'
end end
......
...@@ -4,6 +4,8 @@ module API ...@@ -4,6 +4,8 @@ module API
class ProjectExport < ::API::Base class ProjectExport < ::API::Base
helpers Helpers::RateLimiter helpers Helpers::RateLimiter
feature_category :importers
before do before do
not_found! unless Gitlab::CurrentSettings.project_export_enabled? not_found! unless Gitlab::CurrentSettings.project_export_enabled?
authorize_admin_project authorize_admin_project
......
...@@ -7,6 +7,8 @@ module API ...@@ -7,6 +7,8 @@ module API
before { authenticate! } before { authenticate! }
before { authorize_admin_project } before { authorize_admin_project }
feature_category :integrations
helpers do helpers do
params :project_hook_properties do params :project_hook_properties do
requires :url, type: String, desc: "The URL to send the request to" requires :url, type: String, desc: "The URL to send the request to"
......
...@@ -8,6 +8,8 @@ module API ...@@ -8,6 +8,8 @@ module API
helpers Helpers::FileUploadHelpers helpers Helpers::FileUploadHelpers
helpers Helpers::RateLimiter helpers Helpers::RateLimiter
feature_category :importers
helpers do helpers do
def import_params def import_params
declared_params(include_missing: false) declared_params(include_missing: false)
......
...@@ -7,6 +7,8 @@ module API ...@@ -7,6 +7,8 @@ module API
before { authenticate! } before { authenticate! }
feature_category :issue_tracking
params do params do
requires :id, type: String, desc: 'The ID of a project' requires :id, type: String, desc: 'The ID of a project'
end end
......
...@@ -8,6 +8,8 @@ module API ...@@ -8,6 +8,8 @@ module API
authorize_packages_access!(user_project) authorize_packages_access!(user_project)
end end
feature_category :package_registry
helpers ::API::Helpers::PackagesHelpers helpers ::API::Helpers::PackagesHelpers
params do params do
......
...@@ -6,6 +6,8 @@ module API ...@@ -6,6 +6,8 @@ module API
before { authenticated_as_admin! } before { authenticated_as_admin! }
feature_category :gitaly
resource :project_repository_storage_moves do resource :project_repository_storage_moves do
desc 'Get a list of all project repository storage moves' do desc 'Get a list of all project repository storage moves' do
detail 'This feature was introduced in GitLab 13.0.' detail 'This feature was introduced in GitLab 13.0.'
......
...@@ -11,6 +11,8 @@ module API ...@@ -11,6 +11,8 @@ module API
before { authenticate_non_get! } before { authenticate_non_get! }
feature_category :projects, ['/projects/:id/custom_attributes', '/projects/:id/custom_attributes/:key']
helpers do helpers do
# EE::API::Projects would override this method # EE::API::Projects would override this method
def apply_filters(projects) def apply_filters(projects)
...@@ -150,7 +152,7 @@ module API ...@@ -150,7 +152,7 @@ module API
use :statistics_params use :statistics_params
use :with_custom_attributes use :with_custom_attributes
end end
get ":user_id/projects" do get ":user_id/projects", feature_category: :projects do
user = find_user(params[:user_id]) user = find_user(params[:user_id])
not_found!('User') unless user not_found!('User') unless user
...@@ -167,7 +169,7 @@ module API ...@@ -167,7 +169,7 @@ module API
use :collection_params use :collection_params
use :statistics_params use :statistics_params
end end
get ":user_id/starred_projects" do get ":user_id/starred_projects", feature_category: :projects do
user = find_user(params[:user_id]) user = find_user(params[:user_id])
not_found!('User') unless user not_found!('User') unless user
...@@ -187,7 +189,7 @@ module API ...@@ -187,7 +189,7 @@ module API
use :statistics_params use :statistics_params
use :with_custom_attributes use :with_custom_attributes
end end
get do get feature_category: :projects do
present_projects load_projects present_projects load_projects
end end
...@@ -234,7 +236,7 @@ module API ...@@ -234,7 +236,7 @@ module API
use :create_params use :create_params
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
post "user/:user_id" do post "user/:user_id", feature_category: :projects do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab/issues/21139') Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab/issues/21139')
authenticated_as_admin! authenticated_as_admin!
user = User.find_by(id: params.delete(:user_id)) user = User.find_by(id: params.delete(:user_id))
...@@ -270,7 +272,7 @@ module API ...@@ -270,7 +272,7 @@ module API
optional :license, type: Boolean, default: false, optional :license, type: Boolean, default: false,
desc: 'Include project license data' desc: 'Include project license data'
end end
get ":id" do get ":id", feature_category: :projects do
options = { options = {
with: current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails, with: current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails,
current_user: current_user, current_user: current_user,
...@@ -294,7 +296,7 @@ module API ...@@ -294,7 +296,7 @@ module API
optional :path, type: String, desc: 'The path that will be assigned to the fork' optional :path, type: String, desc: 'The path that will be assigned to the fork'
optional :name, type: String, desc: 'The name that will be assigned to the fork' optional :name, type: String, desc: 'The name that will be assigned to the fork'
end end
post ':id/fork' do post ':id/fork', feature_category: :source_code_management do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/42284') Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/42284')
not_found! unless can?(current_user, :fork_project, user_project) not_found! unless can?(current_user, :fork_project, user_project)
...@@ -332,14 +334,14 @@ module API ...@@ -332,14 +334,14 @@ module API
use :collection_params use :collection_params
use :with_custom_attributes use :with_custom_attributes
end end
get ':id/forks' do get ':id/forks', feature_category: :source_code_management do
forks = ForkProjectsFinder.new(user_project, params: project_finder_params, current_user: current_user).execute forks = ForkProjectsFinder.new(user_project, params: project_finder_params, current_user: current_user).execute
present_projects forks, request_scope: user_project present_projects forks, request_scope: user_project
end end
desc 'Check pages access of this project' desc 'Check pages access of this project'
get ':id/pages_access' do get ':id/pages_access', feature_category: :pages do
authorize! :read_pages_content, user_project unless user_project.public_pages? authorize! :read_pages_content, user_project unless user_project.public_pages?
status 200 status 200
end end
...@@ -357,7 +359,7 @@ module API ...@@ -357,7 +359,7 @@ module API
at_least_one_of(*Helpers::ProjectsHelpers.update_params_at_least_one_of) at_least_one_of(*Helpers::ProjectsHelpers.update_params_at_least_one_of)
end end
put ':id' do put ':id', feature_category: :projects do
authorize_admin_project authorize_admin_project
attrs = declared_params(include_missing: false) attrs = declared_params(include_missing: false)
authorize! :rename_project, user_project if attrs[:name].present? authorize! :rename_project, user_project if attrs[:name].present?
...@@ -381,7 +383,7 @@ module API ...@@ -381,7 +383,7 @@ module API
desc 'Archive a project' do desc 'Archive a project' do
success Entities::Project success Entities::Project
end end
post ':id/archive' do post ':id/archive', feature_category: :projects do
authorize!(:archive_project, user_project) authorize!(:archive_project, user_project)
::Projects::UpdateService.new(user_project, current_user, archived: true).execute ::Projects::UpdateService.new(user_project, current_user, archived: true).execute
...@@ -392,7 +394,7 @@ module API ...@@ -392,7 +394,7 @@ module API
desc 'Unarchive a project' do desc 'Unarchive a project' do
success Entities::Project success Entities::Project
end end
post ':id/unarchive' do post ':id/unarchive', feature_category: :projects do
authorize!(:archive_project, user_project) authorize!(:archive_project, user_project)
::Projects::UpdateService.new(user_project, current_user, archived: false).execute ::Projects::UpdateService.new(user_project, current_user, archived: false).execute
...@@ -403,7 +405,7 @@ module API ...@@ -403,7 +405,7 @@ module API
desc 'Star a project' do desc 'Star a project' do
success Entities::Project success Entities::Project
end end
post ':id/star' do post ':id/star', feature_category: :projects do
if current_user.starred?(user_project) if current_user.starred?(user_project)
not_modified! not_modified!
else else
...@@ -417,7 +419,7 @@ module API ...@@ -417,7 +419,7 @@ module API
desc 'Unstar a project' do desc 'Unstar a project' do
success Entities::Project success Entities::Project
end end
post ':id/unstar' do post ':id/unstar', feature_category: :projects do
if current_user.starred?(user_project) if current_user.starred?(user_project)
current_user.toggle_star(user_project) current_user.toggle_star(user_project)
user_project.reset user_project.reset
...@@ -435,21 +437,21 @@ module API ...@@ -435,21 +437,21 @@ module API
optional :search, type: String, desc: 'Return list of users matching the search criteria' optional :search, type: String, desc: 'Return list of users matching the search criteria'
use :pagination use :pagination
end end
get ':id/starrers' do get ':id/starrers', feature_category: :projects do
starrers = UsersStarProjectsFinder.new(user_project, params, current_user: current_user).execute starrers = UsersStarProjectsFinder.new(user_project, params, current_user: current_user).execute
present paginate(starrers), with: Entities::UserStarsProject present paginate(starrers), with: Entities::UserStarsProject
end end
desc 'Get languages in project repository' desc 'Get languages in project repository'
get ':id/languages' do get ':id/languages', feature_category: :source_code_management do
::Projects::RepositoryLanguagesService ::Projects::RepositoryLanguagesService
.new(user_project, current_user) .new(user_project, current_user)
.execute.map { |lang| [lang.name, lang.share] }.to_h .execute.map { |lang| [lang.name, lang.share] }.to_h
end end
desc 'Delete a project' desc 'Delete a project'
delete ":id" do delete ":id", feature_category: :projects do
authorize! :remove_project, user_project authorize! :remove_project, user_project
delete_project(user_project) delete_project(user_project)
...@@ -459,7 +461,7 @@ module API ...@@ -459,7 +461,7 @@ module API
params do params do
requires :forked_from_id, type: String, desc: 'The ID of the project it was forked from' requires :forked_from_id, type: String, desc: 'The ID of the project it was forked from'
end end
post ":id/fork/:forked_from_id" do post ":id/fork/:forked_from_id", feature_category: :source_code_management do
authorize! :admin_project, user_project authorize! :admin_project, user_project
fork_from_project = find_project!(params[:forked_from_id]) fork_from_project = find_project!(params[:forked_from_id])
...@@ -478,7 +480,7 @@ module API ...@@ -478,7 +480,7 @@ module API
end end
desc 'Remove a forked_from relationship' desc 'Remove a forked_from relationship'
delete ":id/fork" do delete ":id/fork", feature_category: :source_code_management do
authorize! :remove_fork_project, user_project authorize! :remove_fork_project, user_project
result = destroy_conditionally!(user_project) do result = destroy_conditionally!(user_project) do
...@@ -496,7 +498,7 @@ module API ...@@ -496,7 +498,7 @@ module API
requires :group_access, type: Integer, values: Gitlab::Access.values, as: :link_group_access, desc: 'The group access level' requires :group_access, type: Integer, values: Gitlab::Access.values, as: :link_group_access, desc: 'The group access level'
optional :expires_at, type: Date, desc: 'Share expiration date' optional :expires_at, type: Date, desc: 'Share expiration date'
end end
post ":id/share" do post ":id/share", feature_category: :authentication_and_authorization do
authorize! :admin_project, user_project authorize! :admin_project, user_project
group = Group.find_by_id(params[:group_id]) group = Group.find_by_id(params[:group_id])
...@@ -518,7 +520,7 @@ module API ...@@ -518,7 +520,7 @@ module API
requires :group_id, type: Integer, desc: 'The ID of the group' requires :group_id, type: Integer, desc: 'The ID of the group'
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
delete ":id/share/:group_id" do delete ":id/share/:group_id", feature_category: :authentication_and_authorization do
authorize! :admin_project, user_project authorize! :admin_project, user_project
link = user_project.project_group_links.find_by(group_id: params[:group_id]) link = user_project.project_group_links.find_by(group_id: params[:group_id])
...@@ -535,7 +537,7 @@ module API ...@@ -535,7 +537,7 @@ module API
# TODO: remove rubocop disable - https://gitlab.com/gitlab-org/gitlab/issues/14960 # TODO: remove rubocop disable - https://gitlab.com/gitlab-org/gitlab/issues/14960
requires :file, type: File, desc: 'The file to be uploaded' # rubocop:disable Scalability/FileUploads requires :file, type: File, desc: 'The file to be uploaded' # rubocop:disable Scalability/FileUploads
end end
post ":id/uploads" do post ":id/uploads", feature_category: :not_owned do
upload = UploadService.new(user_project, params[:file]).execute upload = UploadService.new(user_project, params[:file]).execute
present upload, with: Entities::ProjectUpload present upload, with: Entities::ProjectUpload
...@@ -549,7 +551,7 @@ module API ...@@ -549,7 +551,7 @@ module API
optional :skip_users, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'Filter out users with the specified IDs' optional :skip_users, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'Filter out users with the specified IDs'
use :pagination use :pagination
end end
get ':id/users' do get ':id/users', feature_category: :authentication_and_authorization do
users = DeclarativePolicy.subject_scope { user_project.team.users } users = DeclarativePolicy.subject_scope { user_project.team.users }
users = users.search(params[:search]) if params[:search].present? users = users.search(params[:search]) if params[:search].present?
users = users.where_not_in(params[:skip_users]) if params[:skip_users].present? users = users.where_not_in(params[:skip_users]) if params[:skip_users].present?
...@@ -560,7 +562,7 @@ module API ...@@ -560,7 +562,7 @@ module API
desc 'Start the housekeeping task for a project' do desc 'Start the housekeeping task for a project' do
detail 'This feature was introduced in GitLab 9.0.' detail 'This feature was introduced in GitLab 9.0.'
end end
post ':id/housekeeping' do post ':id/housekeeping', feature_category: :source_code_management do
authorize_admin_project authorize_admin_project
begin begin
...@@ -574,7 +576,7 @@ module API ...@@ -574,7 +576,7 @@ module API
params do params do
requires :namespace, type: String, desc: 'The ID or path of the new namespace' requires :namespace, type: String, desc: 'The ID or path of the new namespace'
end end
put ":id/transfer" do put ":id/transfer", feature_category: :projects do
authorize! :change_namespace, user_project authorize! :change_namespace, user_project
namespace = find_namespace!(params[:namespace]) namespace = find_namespace!(params[:namespace])
......
...@@ -12,6 +12,8 @@ module API ...@@ -12,6 +12,8 @@ module API
helpers ::API::Helpers::Packages::BasicAuthHelpers helpers ::API::Helpers::Packages::BasicAuthHelpers
include ::API::Helpers::Packages::BasicAuthHelpers::Constants include ::API::Helpers::Packages::BasicAuthHelpers::Constants
feature_category :package_registry
default_format :json default_format :json
rescue_from ArgumentError do |e| rescue_from ArgumentError do |e|
......
...@@ -7,7 +7,7 @@ module API ...@@ -7,7 +7,7 @@ module API
before { authenticate! } before { authenticate! }
Helpers::ResourceLabelEventsHelpers.eventable_types.each do |eventable_type| Helpers::ResourceLabelEventsHelpers.feature_category_per_eventable_type.each do |eventable_type, feature_category|
parent_type = eventable_type.parent_class.to_s.underscore parent_type = eventable_type.parent_class.to_s.underscore
eventables_str = eventable_type.to_s.underscore.pluralize eventables_str = eventable_type.to_s.underscore.pluralize
...@@ -24,7 +24,7 @@ module API ...@@ -24,7 +24,7 @@ module API
use :pagination use :pagination
end end
get ":id/#{eventables_str}/:eventable_id/resource_label_events" do get ":id/#{eventables_str}/:eventable_id/resource_label_events", feature_category: feature_category do
eventable = find_noteable(eventable_type, params[:eventable_id]) eventable = find_noteable(eventable_type, params[:eventable_id])
events = eventable.resource_label_events.inc_relations events = eventable.resource_label_events.inc_relations
...@@ -40,7 +40,7 @@ module API ...@@ -40,7 +40,7 @@ module API
requires :event_id, type: String, desc: 'The ID of a resource label event' requires :event_id, type: String, desc: 'The ID of a resource label event'
requires :eventable_id, types: [Integer, String], desc: 'The ID of the eventable' requires :eventable_id, types: [Integer, String], desc: 'The ID of the eventable'
end end
get ":id/#{eventables_str}/:eventable_id/resource_label_events/:event_id" do get ":id/#{eventables_str}/:eventable_id/resource_label_events/:event_id", feature_category: feature_category do
eventable = find_noteable(eventable_type, params[:eventable_id]) eventable = find_noteable(eventable_type, params[:eventable_id])
event = eventable.resource_label_events.find(params[:event_id]) event = eventable.resource_label_events.find(params[:event_id])
......
...@@ -7,7 +7,10 @@ module API ...@@ -7,7 +7,10 @@ module API
before { authenticate! } before { authenticate! }
[Issue, MergeRequest].each do |eventable_type| {
Issue => :issue_tracking,
MergeRequest => :code_review
}.each do |eventable_type, feature_category|
parent_type = eventable_type.parent_class.to_s.underscore parent_type = eventable_type.parent_class.to_s.underscore
eventables_str = eventable_type.to_s.underscore.pluralize eventables_str = eventable_type.to_s.underscore.pluralize
...@@ -23,7 +26,7 @@ module API ...@@ -23,7 +26,7 @@ module API
use :pagination use :pagination
end end
get ":id/#{eventables_str}/:eventable_id/resource_milestone_events" do get ":id/#{eventables_str}/:eventable_id/resource_milestone_events", feature_category: feature_category do
eventable = find_noteable(eventable_type, params[:eventable_id]) eventable = find_noteable(eventable_type, params[:eventable_id])
events = ResourceMilestoneEventFinder.new(current_user, eventable).execute events = ResourceMilestoneEventFinder.new(current_user, eventable).execute
...@@ -38,7 +41,7 @@ module API ...@@ -38,7 +41,7 @@ module API
requires :event_id, type: String, desc: 'The ID of a resource milestone event' requires :event_id, type: String, desc: 'The ID of a resource milestone event'
requires :eventable_id, types: [Integer, String], desc: 'The ID of the eventable' requires :eventable_id, types: [Integer, String], desc: 'The ID of the eventable'
end end
get ":id/#{eventables_str}/:eventable_id/resource_milestone_events/:event_id" do get ":id/#{eventables_str}/:eventable_id/resource_milestone_events/:event_id", feature_category: feature_category do
eventable = find_noteable(eventable_type, params[:eventable_id]) eventable = find_noteable(eventable_type, params[:eventable_id])
event = eventable.resource_milestone_events.find(params[:event_id]) event = eventable.resource_milestone_events.find(params[:event_id])
......
...@@ -7,7 +7,10 @@ module API ...@@ -7,7 +7,10 @@ module API
before { authenticate! } before { authenticate! }
[Issue, MergeRequest].each do |eventable_class| {
Issue => :issue_tracking,
MergeRequest => :code_review
}.each do |eventable_class, feature_category|
eventable_name = eventable_class.to_s.underscore eventable_name = eventable_class.to_s.underscore
params do params do
...@@ -22,7 +25,7 @@ module API ...@@ -22,7 +25,7 @@ module API
use :pagination use :pagination
end end
get ":id/#{eventable_name.pluralize}/:eventable_iid/resource_state_events" do get ":id/#{eventable_name.pluralize}/:eventable_iid/resource_state_events", feature_category: feature_category do
eventable = find_noteable(eventable_class, params[:eventable_iid]) eventable = find_noteable(eventable_class, params[:eventable_iid])
events = ResourceStateEventFinder.new(current_user, eventable).execute events = ResourceStateEventFinder.new(current_user, eventable).execute
...@@ -37,7 +40,7 @@ module API ...@@ -37,7 +40,7 @@ module API
requires :eventable_iid, types: Integer, desc: "The IID of the #{eventable_name}" requires :eventable_iid, types: Integer, desc: "The IID of the #{eventable_name}"
requires :event_id, type: Integer, desc: 'The ID of a resource state event' requires :event_id, type: Integer, desc: 'The ID of a resource state event'
end end
get ":id/#{eventable_name.pluralize}/:eventable_iid/resource_state_events/:event_id" do get ":id/#{eventable_name.pluralize}/:eventable_iid/resource_state_events/:event_id", feature_category: feature_category do
eventable = find_noteable(eventable_class, params[:eventable_iid]) eventable = find_noteable(eventable_class, params[:eventable_iid])
event = ResourceStateEventFinder.new(current_user, eventable).find(params[:event_id]) event = ResourceStateEventFinder.new(current_user, eventable).find(params[:event_id])
......
...@@ -46,6 +46,8 @@ module Gitlab ...@@ -46,6 +46,8 @@ module Gitlab
# Returns any authorize metadata from @field # Returns any authorize metadata from @field
def field_authorizations def field_authorizations
return [] if @field.metadata[:authorize] == true
Array.wrap(@field.metadata[:authorize]) Array.wrap(@field.metadata[:authorize])
end end
...@@ -54,7 +56,7 @@ module Gitlab ...@@ -54,7 +56,7 @@ module Gitlab
# The field is a built-in/scalar type, or a list of scalars # The field is a built-in/scalar type, or a list of scalars
# authorize using the parent's object # authorize using the parent's object
parent_typed_object.object parent_typed_object.object
elsif @field.connection? || resolved_type.is_a?(Array) elsif @field.connection? || @field.type.list? || resolved_type.is_a?(Array)
# The field is a connection or a list of non-built-in types, we'll # The field is a connection or a list of non-built-in types, we'll
# authorize each element when rendering # authorize each element when rendering
nil nil
...@@ -75,16 +77,25 @@ module Gitlab ...@@ -75,16 +77,25 @@ module Gitlab
# no need to do anything # no need to do anything
elsif authorizing_object elsif authorizing_object
# Authorizing fields representing scalars, or a simple field with an object # Authorizing fields representing scalars, or a simple field with an object
resolved_type if allowed_access?(current_user, authorizing_object) ::Gitlab::Graphql::Lazy.with_value(authorizing_object) do |object|
resolved_type if allowed_access?(current_user, object)
end
elsif @field.connection? elsif @field.connection?
# A connection with pagination, modify the visible nodes on the ::Gitlab::Graphql::Lazy.with_value(resolved_type) do |type|
# connection type in place # A connection with pagination, modify the visible nodes on the
resolved_type.object.edge_nodes.to_a.keep_if { |node| allowed_access?(current_user, node) } # connection type in place
resolved_type nodes = to_nodes(type)
elsif resolved_type.is_a? Array nodes.keep_if { |node| allowed_access?(current_user, node) } if nodes
type
end
elsif @field.type.list? || resolved_type.is_a?(Array)
# A simple list of rendered types each object being an object to authorize # A simple list of rendered types each object being an object to authorize
resolved_type.select do |single_object_type| ::Gitlab::Graphql::Lazy.with_value(resolved_type) do |items|
allowed_access?(current_user, realized(single_object_type).object) items.select do |single_object_type|
object_type = realized(single_object_type)
object = object_type.try(:object) || object_type
allowed_access?(current_user, object)
end
end end
else else
raise "Can't authorize #{@field}" raise "Can't authorize #{@field}"
...@@ -93,18 +104,23 @@ module Gitlab ...@@ -93,18 +104,23 @@ module Gitlab
# Ensure that we are dealing with realized objects, not delayed promises # Ensure that we are dealing with realized objects, not delayed promises
def realized(thing) def realized(thing)
case thing ::Gitlab::Graphql::Lazy.force(thing)
when BatchLoader::GraphQL end
thing.sync
when GraphQL::Execution::Lazy # Try to get the connection
thing.value # part of the private api, but we need to unwrap it here. # can be at type.object or at type
def to_nodes(type)
if type.respond_to?(:nodes)
type.nodes
elsif type.respond_to?(:object)
to_nodes(type.object)
else else
thing nil
end end
end end
def allowed_access?(current_user, object) def allowed_access?(current_user, object)
object = object.sync if object.respond_to?(:sync) object = realized(object)
authorizations.all? do |ability| authorizations.all? do |ability|
Ability.allowed?(current_user, ability, object) Ability.allowed?(current_user, ability, object)
......
...@@ -3,17 +3,45 @@ ...@@ -3,17 +3,45 @@
module Gitlab module Gitlab
module Graphql module Graphql
class Lazy class Lazy
include Gitlab::Utils::StrongMemoize
def initialize(&block)
@proc = block
end
def force
strong_memoize(:force) { self.class.force(@proc.call) }
end
def then(&block)
self.class.new { yield force }
end
# Force evaluation of a (possibly) lazy value # Force evaluation of a (possibly) lazy value
def self.force(value) def self.force(value)
case value case value
when ::Gitlab::Graphql::Lazy
value.force
when ::BatchLoader::GraphQL when ::BatchLoader::GraphQL
value.sync value.sync
when ::GraphQL::Execution::Lazy
value.value # part of the private api, but we can force this as well
when ::Concurrent::Promise when ::Concurrent::Promise
value.execute.value value.execute if value.state == :unscheduled
value.value # value.value(10.seconds)
else else
value value
end end
end end
def self.with_value(unforced, &block)
if Feature.enabled?(:graphql_lazy_authorization)
self.new { unforced }.then(&block)
else
block.call(unforced)
end
end
end end
end end
end end
...@@ -11783,7 +11783,7 @@ msgstr "" ...@@ -11783,7 +11783,7 @@ msgstr ""
msgid "For help setting up the Service Desk for your instance, please contact an administrator." msgid "For help setting up the Service Desk for your instance, please contact an administrator."
msgstr "" msgstr ""
msgid "For internal projects, any logged in user can view pipelines and access job details (output logs and artifacts)" msgid "For internal projects, any logged in user except external users can view pipelines and access job details (output logs and artifacts)"
msgstr "" msgstr ""
msgid "For more info, read the documentation." msgid "For more info, read the documentation."
...@@ -14418,10 +14418,10 @@ msgstr "" ...@@ -14418,10 +14418,10 @@ msgstr ""
msgid "Internal" msgid "Internal"
msgstr "" msgstr ""
msgid "Internal - The group and any internal projects can be viewed by any logged in user." msgid "Internal - The group and any internal projects can be viewed by any logged in user except external users."
msgstr "" msgstr ""
msgid "Internal - The project can be accessed by any logged in user." msgid "Internal - The project can be accessed by any logged in user except external users."
msgstr "" msgstr ""
msgid "Internal URL (optional)" msgid "Internal URL (optional)"
...@@ -26477,7 +26477,7 @@ msgstr "" ...@@ -26477,7 +26477,7 @@ msgstr ""
msgid "The global settings require you to enable Two-Factor Authentication for your account." msgid "The global settings require you to enable Two-Factor Authentication for your account."
msgstr "" msgstr ""
msgid "The group and any internal projects can be viewed by any logged in user." msgid "The group and any internal projects can be viewed by any logged in user except external users."
msgstr "" msgstr ""
msgid "The group and any public projects can be viewed without any authentication." msgid "The group and any public projects can be viewed without any authentication."
...@@ -26588,7 +26588,7 @@ msgstr "" ...@@ -26588,7 +26588,7 @@ msgstr ""
msgid "The private key to use when a client certificate is provided. This value is encrypted at rest." msgid "The private key to use when a client certificate is provided. This value is encrypted at rest."
msgstr "" msgstr ""
msgid "The project can be accessed by any logged in user." msgid "The project can be accessed by any logged in user except external users."
msgstr "" msgstr ""
msgid "The project can be accessed by any user who is logged in." msgid "The project can be accessed by any user who is logged in."
...@@ -26663,7 +26663,7 @@ msgstr "" ...@@ -26663,7 +26663,7 @@ msgstr ""
msgid "The snippet is visible only to project members." msgid "The snippet is visible only to project members."
msgstr "" msgstr ""
msgid "The snippet is visible to any logged in user." msgid "The snippet is visible to any logged in user except external users."
msgstr "" msgstr ""
msgid "The specified tab is invalid, please select another" msgid "The specified tab is invalid, please select another"
......
...@@ -13,7 +13,7 @@ RSpec.describe 'bin/feature-flag' do ...@@ -13,7 +13,7 @@ RSpec.describe 'bin/feature-flag' do
let(:options) { FeatureFlagOptionParser.parse(argv) } let(:options) { FeatureFlagOptionParser.parse(argv) }
let(:creator) { described_class.new(options) } let(:creator) { described_class.new(options) }
let(:existing_flags) do let(:existing_flags) do
{ 'existing-feature-flag' => File.join('config', 'feature_flags', 'development', 'existing-feature-flag.yml') } { 'existing_feature_flag' => File.join('config', 'feature_flags', 'development', 'existing_feature_flag.yml') }
end end
before do before do
...@@ -32,12 +32,12 @@ RSpec.describe 'bin/feature-flag' do ...@@ -32,12 +32,12 @@ RSpec.describe 'bin/feature-flag' do
it 'properly creates a feature flag' do it 'properly creates a feature flag' do
expect(File).to receive(:write).with( expect(File).to receive(:write).with(
File.join('config', 'feature_flags', 'development', 'feature-flag-name.yml'), File.join('config', 'feature_flags', 'development', 'feature_flag_name.yml'),
anything) anything)
expect do expect do
subject subject
end.to output(/name: feature-flag-name/).to_stdout end.to output(/name: feature_flag_name/).to_stdout
end end
context 'when running on master' do context 'when running on master' do
......
...@@ -7,13 +7,14 @@ export const ITEM_TYPE = { ...@@ -7,13 +7,14 @@ export const ITEM_TYPE = {
export const GROUP_VISIBILITY_TYPE = { export const GROUP_VISIBILITY_TYPE = {
public: 'Public - The group and any public projects can be viewed without any authentication.', public: 'Public - The group and any public projects can be viewed without any authentication.',
internal: 'Internal - The group and any internal projects can be viewed by any logged in user.', internal:
'Internal - The group and any internal projects can be viewed by any logged in user except external users.',
private: 'Private - The group and its projects can only be viewed by members.', private: 'Private - The group and its projects can only be viewed by members.',
}; };
export const PROJECT_VISIBILITY_TYPE = { export const PROJECT_VISIBILITY_TYPE = {
public: 'Public - The project can be accessed without any authentication.', public: 'Public - The project can be accessed without any authentication.',
internal: 'Internal - The project can be accessed by any logged in user.', internal: 'Internal - The project can be accessed by any logged in user except external users.',
private: private:
'Private - Project access must be granted explicitly to each user. If this project is part of a group, access will be granted to members of the group.', 'Private - Project access must be granted explicitly to each user. If this project is part of a group, access will be granted to members of the group.',
}; };
......
...@@ -37,7 +37,15 @@ RSpec.describe 'Every API endpoint' do ...@@ -37,7 +37,15 @@ RSpec.describe 'Every API endpoint' do
::API::Lint, ::API::Markdown, ::API::Members, ::API::MergeRequestDiffs, ::API::Lint, ::API::Markdown, ::API::Members, ::API::MergeRequestDiffs,
::API::MergeRequests, ::API::MergeRequestApprovals, ::API::Metrics::Dashboard::Annotations, ::API::MergeRequests, ::API::MergeRequestApprovals, ::API::Metrics::Dashboard::Annotations,
::API::Metrics::UserStarredDashboards, ::API::Namespaces, ::API::Notes, ::API::Metrics::UserStarredDashboards, ::API::Namespaces, ::API::Notes,
::API::Discussions ::API::Discussions, ::API::ResourceLabelEvents, ::API::ResourceMilestoneEvents,
::API::ResourceStateEvents, ::API::NotificationSettings, ::API::ProjectPackages,
::API::GroupPackages, ::API::PackageFiles, ::API::NugetPackages, ::API::PypiPackages,
::API::ComposerPackages, ::API::ConanProjectPackages, ::API::ConanInstancePackages,
::API::DebianGroupPackages, ::API::DebianProjectPackages, ::API::MavenPackages,
::API::NpmPackages, ::API::GenericPackages, ::API::GoProxy, ::API::Pages,
::API::PagesDomains, ::API::ProjectClusters, ::API::ProjectContainerRepositories,
::API::ProjectEvents, ::API::ProjectExport, ::API::ProjectImport, ::API::ProjectHooks,
::API::ProjectMilestones, ::API::ProjectRepositoryStorageMoves, ::API::Projects
] ]
next unless completed_classes.include?(klass) next unless completed_classes.include?(klass)
......
...@@ -3,13 +3,22 @@ ...@@ -3,13 +3,22 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Gitlab::Badge::Coverage::Report do RSpec.describe Gitlab::Badge::Coverage::Report do
let(:project) { create(:project, :repository) } let_it_be(:project) { create(:project) }
let(:job_name) { nil } let_it_be(:pipeline) { create(:ci_pipeline, :success, project: project) }
let_it_be(:builds) do
[
create(:ci_build, :success, pipeline: pipeline, coverage: 40, name: 'first'),
create(:ci_build, :success, pipeline: pipeline, coverage: 60)
]
end
let(:badge) do let(:badge) do
described_class.new(project, 'master', opts: { job: job_name }) described_class.new(project, 'master', opts: { job: job_name })
end end
let(:job_name) { nil }
describe '#entity' do describe '#entity' do
it 'describes a coverage' do it 'describes a coverage' do
expect(badge.entity).to eq 'coverage' expect(badge.entity).to eq 'coverage'
...@@ -28,81 +37,47 @@ RSpec.describe Gitlab::Badge::Coverage::Report do ...@@ -28,81 +37,47 @@ RSpec.describe Gitlab::Badge::Coverage::Report do
end end
end end
shared_examples 'unknown coverage report' do describe '#status' do
context 'particular job specified' do before do
let(:job_name) { '' } allow(badge).to receive(:pipeline).and_return(pipeline)
it 'returns nil' do
expect(badge.status).to be_nil
end
end end
context 'particular job not specified' do context 'with no pipeline' do
let(:job_name) { nil } let(:pipeline) { nil }
it 'returns nil' do it 'returns nil' do
expect(badge.status).to be_nil expect(badge.status).to be_nil
end end
end end
end
context 'when latest successful pipeline exists' do
before do
create_pipeline do |pipeline|
create(:ci_build, :success, pipeline: pipeline, name: 'first', coverage: 40)
create(:ci_build, :success, pipeline: pipeline, coverage: 60)
end
create_pipeline do |pipeline|
create(:ci_build, :failed, pipeline: pipeline, coverage: 10)
end
end
context 'when particular job specified' do
let(:job_name) { 'first' }
it 'returns coverage for the particular job' do context 'with no job specified' do
expect(badge.status).to eq 40 it 'returns the pipeline coverage value' do
expect(badge.status).to eq(50.00)
end end
end end
context 'when particular job not specified' do context 'with a blank job name' do
let(:job_name) { '' } let(:job_name) { ' ' }
it 'returns arithemetic mean for the pipeline' do
expect(badge.status).to eq 50
end
end
end
context 'when only failed pipeline exists' do it 'returns the pipeline coverage value' do
before do expect(badge.status).to eq(50.00)
create_pipeline do |pipeline|
create(:ci_build, :failed, pipeline: pipeline, coverage: 10)
end end
end end
it_behaves_like 'unknown coverage report' context 'with an unmatching job name specified' do
let(:job_name) { 'incorrect name' }
context 'particular job specified' do it 'returns nil' do
let(:job_name) { 'nonexistent' }
it 'retruns nil' do
expect(badge.status).to be_nil expect(badge.status).to be_nil
end end
end end
end
context 'pipeline does not exist' do context 'with a matching job name specified' do
it_behaves_like 'unknown coverage report' let(:job_name) { 'first' }
end
def create_pipeline
opts = { project: project, sha: project.commit.id, ref: 'master' }
create(:ci_pipeline, opts).tap do |pipeline| it 'returns the pipeline coverage value' do
yield pipeline expect(badge.status).to eq(40.00)
::Ci::ProcessPipelineService.new(pipeline).execute end
end end
end end
end end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Graphql::Lazy do
def load(key)
BatchLoader.for(key).batch do |keys, loader|
keys.each { |x| loader.call(x, x * x) }
end
end
let(:value) { double(x: 1) }
describe '#force' do
subject { described_class.new { value.x } }
it 'can extract the value' do
expect(subject.force).to be 1
end
it 'can derive new lazy values' do
expect(subject.then { |x| x + 2 }.force).to be 3
end
it 'only evaluates once' do
expect(value).to receive(:x).once
expect(subject.force).to eq(subject.force)
end
it 'deals with nested laziness' do
expect(described_class.new { load(10) }.force).to eq(100)
expect(described_class.new { described_class.new { 5 } }.force).to eq 5
end
end
describe '.with_value' do
let(:inner) { described_class.new { value.x } }
subject { described_class.with_value(inner) { |x| x.to_s } }
it 'defers the application of a block to a value' do
expect(value).not_to receive(:x)
expect(subject).to be_an_instance_of(described_class)
end
it 'evaluates to the application of the block to the value' do
expect(value).to receive(:x).once
expect(subject.force).to eq(inner.force.to_s)
end
end
describe '.force' do
context 'when given a plain value' do
subject { described_class.force(1) }
it 'unwraps the value' do
expect(subject).to be 1
end
end
context 'when given a wrapped lazy value' do
subject { described_class.force(described_class.new { 2 }) }
it 'unwraps the value' do
expect(subject).to be 2
end
end
context 'when the value is from a batchloader' do
subject { described_class.force(load(3)) }
it 'syncs the value' do
expect(subject).to be 9
end
end
context 'when the value is a GraphQL lazy' do
subject { described_class.force(GitlabSchema.after_lazy(load(3)) { |x| x + 1 } ) }
it 'forces the evaluation' do
expect(subject).to be 10
end
end
context 'when the value is a promise' do
subject { described_class.force(::Concurrent::Promise.new { 4 }) }
it 'executes the promise and waits for the value' do
expect(subject).to be 4
end
end
end
end
...@@ -478,6 +478,8 @@ module GraphqlHelpers ...@@ -478,6 +478,8 @@ module GraphqlHelpers
use Gitlab::Graphql::Authorize use Gitlab::Graphql::Authorize
use Gitlab::Graphql::Pagination::Connections use Gitlab::Graphql::Pagination::Connections
lazy_resolve ::Gitlab::Graphql::Lazy, :force
query(query_type) query(query_type)
end end
......
...@@ -106,13 +106,11 @@ RSpec.shared_examples 'querying a GraphQL type with labels' do ...@@ -106,13 +106,11 @@ RSpec.shared_examples 'querying a GraphQL type with labels' do
end end
it 'batches queries for labels by title' do it 'batches queries for labels by title' do
pending('See: https://gitlab.com/gitlab-org/gitlab/-/issues/217767')
multi_selection = query_for(label_b, label_c) multi_selection = query_for(label_b, label_c)
single_selection = query_for(label_d) single_selection = query_for(label_d)
expect { run_query(multi_selection) } expect { run_query(multi_selection) }
.to issue_same_number_of_queries_as { run_query(single_selection) } .to issue_same_number_of_queries_as { run_query(single_selection) }.ignoring_cached_queries
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