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
/tmp/matching_tests.txt
ee/changelogs/unreleased-ee
/sitespeed-result
tags.lock
tags.temp
......@@ -31,14 +31,16 @@ export const GROUP_VISIBILITY_TYPE = {
'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 - 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.'),
};
export const PROJECT_VISIBILITY_TYPE = {
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 - 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 {
? __('The snippet is visible only to project members.')
: __('The snippet is visible only to me.');
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:
return __('The snippet can be accessed without any authentication.');
}
......
......@@ -14,7 +14,7 @@ export const SNIPPET_VISIBILITY = {
[SNIPPET_VISIBILITY_INTERNAL]: {
label: __('Internal'),
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]: {
label: __('Public'),
......
......@@ -30,6 +30,8 @@ class GitlabSchema < GraphQL::Schema
default_max_page_size 100
lazy_resolve ::Gitlab::Graphql::Lazy, :force
class << self
def multiplex(queries, **kwargs)
kwargs[:max_complexity] ||= max_query_complexity(kwargs[:context])
......
......@@ -31,7 +31,7 @@ module VisibilityLevelHelper
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.")
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
_("The project can be accessed without any authentication.")
end
......@@ -42,7 +42,7 @@ module VisibilityLevelHelper
when Gitlab::VisibilityLevel::PRIVATE
_("The group and its projects can only be viewed by members.")
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
_("The group and any public projects can be viewed without any authentication.")
end
......
......@@ -72,7 +72,7 @@
%li
= _("For public projects, anyone can view pipelines and access job details (output logs and artifacts)")
%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
= _("For private projects, any member (guest or higher) can view pipelines and access job details (output logs and artifacts)")
%p
......
......@@ -104,7 +104,7 @@ class FeatureFlagOptionParser
end
# Name is a first name
options.name = argv.first
options.name = argv.first.downcase.gsub(/-/, '_')
options
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:
| visibility | Description |
| ---------- | ----------- |
| `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 |
NOTE: **Note:**
......
......@@ -16,7 +16,7 @@ Values for the project visibility level are:
- `private`:
Project access must be granted explicitly for each user.
- `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`:
The project can be accessed without any authentication.
......
......@@ -21,7 +21,7 @@ Valid values for snippet visibility levels are:
| Visibility | Description |
|:-----------|:----------------------------------------------------|
| `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. |
## List all snippets for a user
......
......@@ -183,7 +183,7 @@ Job logs and artifacts are [not visible for guest users and non-project members]
If **Public pipelines** is enabled (default):
- 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.
- For **private** projects, any project member (guest or higher) can view the pipelines
and related features.
......@@ -192,7 +192,7 @@ If **Public pipelines** is disabled:
- For **public** projects, anyone can view the pipelines, but only members
(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.
- For **private** projects, only project members (reporter or higher)
can view the pipelines or access the related features.
......
......@@ -21,12 +21,12 @@ on the repository.
### 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
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.
NOTE: **Note:**
......
......@@ -314,6 +314,10 @@ External users:
- 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
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.
Like usual users, they receive a role in the project or group with all
......
......@@ -9,9 +9,11 @@ module EE
class_methods do
extend ::Gitlab::Utils::Override
override :eventable_types
def eventable_types
[::Epic, *super]
override :feature_category_per_eventable_type
def feature_category_per_eventable_type
super.merge!(
::Epic => :issue_tracking
)
end
end
end
......
......@@ -10,7 +10,7 @@ module EE
desc 'Restore a project' do
success Entities::Project
end
post ':id/restore' do
post ':id/restore', feature_category: :authentication_and_authorization do
authorize!(:remove_project, user_project)
break not_found! unless user_project.feature_available?(:adjourned_deletion_for_projects_and_groups)
......@@ -21,7 +21,7 @@ module EE
render_api_error!(result[:message], 400)
end
end
segment ':id/audit_events' do
segment ':id/audit_events', feature_category: :audit_events do
before do
authorize! :admin_project, user_project
check_audit_events_available!(user_project)
......@@ -37,7 +37,7 @@ module EE
use :pagination
end
get '/' do
get '/', feature_category: :audit_events do
level = ::Gitlab::Audit::Levels::Project.new(project: user_project)
audit_events = AuditLogFinder.new(
level: level,
......@@ -53,7 +53,7 @@ module EE
params do
requires :audit_event_id, type: Integer, desc: 'The ID of the audit event'
end
get '/:audit_event_id' do
get '/:audit_event_id', feature_category: :audit_events do
level = ::Gitlab::Audit::Levels::Project.new(project: user_project)
# rubocop: disable CodeReuse/ActiveRecord
# This is not `find_by!` from ActiveRecord
......
......@@ -41,18 +41,23 @@ module EE
check_file_size!
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
# @raise [Gitlab::GitAccess::ForbiddenError] if any check fails
def run_checks_in_parallel!
git_env = ::Gitlab::Git::HookEnv.all(project.repository.gl_repository)
@threads = []
parallelize do
parallelize(git_env) do
check_tag_or_branch!
end
parallelize do
parallelize(git_env) do
check_file_size!
end
......@@ -73,8 +78,9 @@ module EE
# Runs a block inside a new thread. This thread will
# exit immediately upon an exception being raised.
#
# @param git_env [Hash] the current git environment
# @raise [Gitlab::GitAccess::ForbiddenError]
def parallelize
def parallelize(git_env)
@threads << Thread.new do
Thread.current.tap do |t|
t.name = "push_rule_check"
......@@ -82,7 +88,11 @@ module EE
t.report_on_exception = false
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
ActiveRecord::Base.clear_active_connections!
end
......
......@@ -101,7 +101,7 @@ RSpec.describe EE::RegistrationsHelper do
it 'returns the desired mapping' do
expect(helper.visibility_level_options).to eq [
{ 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.' }
]
end
......
......@@ -59,7 +59,24 @@ RSpec.describe EE::Gitlab::Checks::PushRuleCheck do
end
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
before do
......
......@@ -9,6 +9,8 @@ module API
include ::API::Helpers::Packages::BasicAuthHelpers::Constants
include ::Gitlab::Utils::StrongMemoize
feature_category :package_registry
content_type :json, 'application/json'
default_format :json
......
......@@ -29,6 +29,8 @@ module API
CONAN_FILES = (Gitlab::Regex::Packages::CONAN_RECIPE_FILES + Gitlab::Regex::Packages::CONAN_PACKAGE_FILES).freeze
included do
feature_category :package_registry
helpers ::API::Helpers::PackagesManagerClientsHelpers
helpers ::API::Helpers::Packages::Conan::ApiHelpers
helpers ::API::Helpers::RelatedResourcesHelpers
......
......@@ -26,6 +26,8 @@ module API
}.freeze
included do
feature_category :package_registry
helpers ::API::Helpers::PackagesHelpers
helpers ::API::Helpers::Packages::BasicAuthHelpers
......
......@@ -7,6 +7,8 @@ module API
file_name: API::NO_SLASH_URL_PART_REGEX
}.freeze
feature_category :package_registry
before do
require_packages_enabled!
authenticate!
......
......@@ -4,6 +4,8 @@ module API
helpers Gitlab::Golang
helpers ::API::Helpers::PackagesHelpers
feature_category :package_registry
# 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
......
......@@ -8,6 +8,8 @@ module API
authorize_packages_access!(user_group)
end
feature_category :package_registry
helpers ::API::Helpers::PackagesHelpers
params do
......
......@@ -3,10 +3,13 @@
module API
module Helpers
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
# extend it.
[Issue, MergeRequest]
{
Issue => :issue_tracking,
MergeRequest => :code_review
}
end
end
end
......
......@@ -5,6 +5,8 @@ module API
file_name: API::NO_SLASH_URL_PART_REGEX
}.freeze
feature_category :package_registry
content_type :md5, 'text/plain'
content_type :sha1, 'text/plain'
content_type :binary, 'application/octet-stream'
......
......@@ -5,6 +5,8 @@ module API
class NotificationSettings < ::API::Base
before { authenticate! }
feature_category :users
helpers ::API::Helpers::MembersHelpers
resource :notification_settings do
......
......@@ -4,6 +4,8 @@ module API
helpers ::API::Helpers::PackagesHelpers
helpers ::API::Helpers::Packages::DependencyProxyHelpers
feature_category :package_registry
NPM_ENDPOINT_REQUIREMENTS = {
package_name: API::NO_SLASH_URL_PART_REGEX
}.freeze
......
......@@ -10,6 +10,8 @@ module API
helpers ::API::Helpers::PackagesManagerClientsHelpers
helpers ::API::Helpers::Packages::BasicAuthHelpers
feature_category :package_registry
POSITIVE_INTEGER_REGEX = %r{\A[1-9]\d*\z}.freeze
NON_NEGATIVE_INTEGER_REGEX = %r{\A0|[1-9]\d*\z}.freeze
......
......@@ -8,6 +8,8 @@ module API
authorize_packages_access!(user_project)
end
feature_category :package_registry
helpers ::API::Helpers::PackagesHelpers
params do
......
......@@ -2,6 +2,8 @@
module API
class Pages < ::API::Base
feature_category :pages
before do
require_pages_config_enabled!
authenticated_with_can_read_all_resources!
......
......@@ -4,6 +4,8 @@ module API
class PagesDomains < ::API::Base
include PaginationParams
feature_category :pages
PAGES_DOMAINS_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(domain: API::NO_SLASH_URL_PART_REGEX)
before do
......
......@@ -6,6 +6,8 @@ module API
before { authenticate! }
feature_category :kubernetes_management
params do
requires :id, type: String, desc: 'The ID of the project'
end
......
......@@ -10,6 +10,8 @@ module API
before { authorize_read_container_images! }
feature_category :package_registry
params do
requires :id, type: String, desc: 'The ID of a project'
end
......
......@@ -6,6 +6,8 @@ module API
include APIGuard
helpers ::API::Helpers::EventsHelpers
feature_category :users
params do
requires :id, type: String, desc: 'The ID of a project'
end
......
......@@ -4,6 +4,8 @@ module API
class ProjectExport < ::API::Base
helpers Helpers::RateLimiter
feature_category :importers
before do
not_found! unless Gitlab::CurrentSettings.project_export_enabled?
authorize_admin_project
......
......@@ -7,6 +7,8 @@ module API
before { authenticate! }
before { authorize_admin_project }
feature_category :integrations
helpers do
params :project_hook_properties do
requires :url, type: String, desc: "The URL to send the request to"
......
......@@ -8,6 +8,8 @@ module API
helpers Helpers::FileUploadHelpers
helpers Helpers::RateLimiter
feature_category :importers
helpers do
def import_params
declared_params(include_missing: false)
......
......@@ -7,6 +7,8 @@ module API
before { authenticate! }
feature_category :issue_tracking
params do
requires :id, type: String, desc: 'The ID of a project'
end
......
......@@ -8,6 +8,8 @@ module API
authorize_packages_access!(user_project)
end
feature_category :package_registry
helpers ::API::Helpers::PackagesHelpers
params do
......
......@@ -6,6 +6,8 @@ module API
before { authenticated_as_admin! }
feature_category :gitaly
resource :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.'
......
......@@ -11,6 +11,8 @@ module API
before { authenticate_non_get! }
feature_category :projects, ['/projects/:id/custom_attributes', '/projects/:id/custom_attributes/:key']
helpers do
# EE::API::Projects would override this method
def apply_filters(projects)
......@@ -150,7 +152,7 @@ module API
use :statistics_params
use :with_custom_attributes
end
get ":user_id/projects" do
get ":user_id/projects", feature_category: :projects do
user = find_user(params[:user_id])
not_found!('User') unless user
......@@ -167,7 +169,7 @@ module API
use :collection_params
use :statistics_params
end
get ":user_id/starred_projects" do
get ":user_id/starred_projects", feature_category: :projects do
user = find_user(params[:user_id])
not_found!('User') unless user
......@@ -187,7 +189,7 @@ module API
use :statistics_params
use :with_custom_attributes
end
get do
get feature_category: :projects do
present_projects load_projects
end
......@@ -234,7 +236,7 @@ module API
use :create_params
end
# 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')
authenticated_as_admin!
user = User.find_by(id: params.delete(:user_id))
......@@ -270,7 +272,7 @@ module API
optional :license, type: Boolean, default: false,
desc: 'Include project license data'
end
get ":id" do
get ":id", feature_category: :projects do
options = {
with: current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails,
current_user: current_user,
......@@ -294,7 +296,7 @@ module API
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'
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')
not_found! unless can?(current_user, :fork_project, user_project)
......@@ -332,14 +334,14 @@ module API
use :collection_params
use :with_custom_attributes
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
present_projects forks, request_scope: user_project
end
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?
status 200
end
......@@ -357,7 +359,7 @@ module API
at_least_one_of(*Helpers::ProjectsHelpers.update_params_at_least_one_of)
end
put ':id' do
put ':id', feature_category: :projects do
authorize_admin_project
attrs = declared_params(include_missing: false)
authorize! :rename_project, user_project if attrs[:name].present?
......@@ -381,7 +383,7 @@ module API
desc 'Archive a project' do
success Entities::Project
end
post ':id/archive' do
post ':id/archive', feature_category: :projects do
authorize!(:archive_project, user_project)
::Projects::UpdateService.new(user_project, current_user, archived: true).execute
......@@ -392,7 +394,7 @@ module API
desc 'Unarchive a project' do
success Entities::Project
end
post ':id/unarchive' do
post ':id/unarchive', feature_category: :projects do
authorize!(:archive_project, user_project)
::Projects::UpdateService.new(user_project, current_user, archived: false).execute
......@@ -403,7 +405,7 @@ module API
desc 'Star a project' do
success Entities::Project
end
post ':id/star' do
post ':id/star', feature_category: :projects do
if current_user.starred?(user_project)
not_modified!
else
......@@ -417,7 +419,7 @@ module API
desc 'Unstar a project' do
success Entities::Project
end
post ':id/unstar' do
post ':id/unstar', feature_category: :projects do
if current_user.starred?(user_project)
current_user.toggle_star(user_project)
user_project.reset
......@@ -435,21 +437,21 @@ module API
optional :search, type: String, desc: 'Return list of users matching the search criteria'
use :pagination
end
get ':id/starrers' do
get ':id/starrers', feature_category: :projects do
starrers = UsersStarProjectsFinder.new(user_project, params, current_user: current_user).execute
present paginate(starrers), with: Entities::UserStarsProject
end
desc 'Get languages in project repository'
get ':id/languages' do
get ':id/languages', feature_category: :source_code_management do
::Projects::RepositoryLanguagesService
.new(user_project, current_user)
.execute.map { |lang| [lang.name, lang.share] }.to_h
end
desc 'Delete a project'
delete ":id" do
delete ":id", feature_category: :projects do
authorize! :remove_project, user_project
delete_project(user_project)
......@@ -459,7 +461,7 @@ module API
params do
requires :forked_from_id, type: String, desc: 'The ID of the project it was forked from'
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
fork_from_project = find_project!(params[:forked_from_id])
......@@ -478,7 +480,7 @@ module API
end
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
result = destroy_conditionally!(user_project) do
......@@ -496,7 +498,7 @@ module API
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'
end
post ":id/share" do
post ":id/share", feature_category: :authentication_and_authorization do
authorize! :admin_project, user_project
group = Group.find_by_id(params[:group_id])
......@@ -518,7 +520,7 @@ module API
requires :group_id, type: Integer, desc: 'The ID of the group'
end
# 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
link = user_project.project_group_links.find_by(group_id: params[:group_id])
......@@ -535,7 +537,7 @@ module API
# 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
end
post ":id/uploads" do
post ":id/uploads", feature_category: :not_owned do
upload = UploadService.new(user_project, params[:file]).execute
present upload, with: Entities::ProjectUpload
......@@ -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'
use :pagination
end
get ':id/users' do
get ':id/users', feature_category: :authentication_and_authorization do
users = DeclarativePolicy.subject_scope { user_project.team.users }
users = users.search(params[:search]) if params[:search].present?
users = users.where_not_in(params[:skip_users]) if params[:skip_users].present?
......@@ -560,7 +562,7 @@ module API
desc 'Start the housekeeping task for a project' do
detail 'This feature was introduced in GitLab 9.0.'
end
post ':id/housekeeping' do
post ':id/housekeeping', feature_category: :source_code_management do
authorize_admin_project
begin
......@@ -574,7 +576,7 @@ module API
params do
requires :namespace, type: String, desc: 'The ID or path of the new namespace'
end
put ":id/transfer" do
put ":id/transfer", feature_category: :projects do
authorize! :change_namespace, user_project
namespace = find_namespace!(params[:namespace])
......
......@@ -12,6 +12,8 @@ module API
helpers ::API::Helpers::Packages::BasicAuthHelpers
include ::API::Helpers::Packages::BasicAuthHelpers::Constants
feature_category :package_registry
default_format :json
rescue_from ArgumentError do |e|
......
......@@ -7,7 +7,7 @@ module API
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
eventables_str = eventable_type.to_s.underscore.pluralize
......@@ -24,7 +24,7 @@ module API
use :pagination
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])
events = eventable.resource_label_events.inc_relations
......@@ -40,7 +40,7 @@ module API
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'
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])
event = eventable.resource_label_events.find(params[:event_id])
......
......@@ -7,7 +7,10 @@ module API
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
eventables_str = eventable_type.to_s.underscore.pluralize
......@@ -23,7 +26,7 @@ module API
use :pagination
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])
events = ResourceMilestoneEventFinder.new(current_user, eventable).execute
......@@ -38,7 +41,7 @@ module API
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'
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])
event = eventable.resource_milestone_events.find(params[:event_id])
......
......@@ -7,7 +7,10 @@ module API
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
params do
......@@ -22,7 +25,7 @@ module API
use :pagination
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])
events = ResourceStateEventFinder.new(current_user, eventable).execute
......@@ -37,7 +40,7 @@ module API
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'
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])
event = ResourceStateEventFinder.new(current_user, eventable).find(params[:event_id])
......
......@@ -46,6 +46,8 @@ module Gitlab
# Returns any authorize metadata from @field
def field_authorizations
return [] if @field.metadata[:authorize] == true
Array.wrap(@field.metadata[:authorize])
end
......@@ -54,7 +56,7 @@ module Gitlab
# The field is a built-in/scalar type, or a list of scalars
# authorize using the parent's 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
# authorize each element when rendering
nil
......@@ -75,16 +77,25 @@ module Gitlab
# no need to do anything
elsif authorizing_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?
# A connection with pagination, modify the visible nodes on the
# connection type in place
resolved_type.object.edge_nodes.to_a.keep_if { |node| allowed_access?(current_user, node) }
resolved_type
elsif resolved_type.is_a? Array
::Gitlab::Graphql::Lazy.with_value(resolved_type) do |type|
# A connection with pagination, modify the visible nodes on the
# connection type in place
nodes = to_nodes(type)
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
resolved_type.select do |single_object_type|
allowed_access?(current_user, realized(single_object_type).object)
::Gitlab::Graphql::Lazy.with_value(resolved_type) do |items|
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
else
raise "Can't authorize #{@field}"
......@@ -93,18 +104,23 @@ module Gitlab
# Ensure that we are dealing with realized objects, not delayed promises
def realized(thing)
case thing
when BatchLoader::GraphQL
thing.sync
when GraphQL::Execution::Lazy
thing.value # part of the private api, but we need to unwrap it here.
::Gitlab::Graphql::Lazy.force(thing)
end
# Try to get the connection
# 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
thing
nil
end
end
def allowed_access?(current_user, object)
object = object.sync if object.respond_to?(:sync)
object = realized(object)
authorizations.all? do |ability|
Ability.allowed?(current_user, ability, object)
......
......@@ -3,17 +3,45 @@
module Gitlab
module Graphql
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
def self.force(value)
case value
when ::Gitlab::Graphql::Lazy
value.force
when ::BatchLoader::GraphQL
value.sync
when ::GraphQL::Execution::Lazy
value.value # part of the private api, but we can force this as well
when ::Concurrent::Promise
value.execute.value
value.execute if value.state == :unscheduled
value.value # value.value(10.seconds)
else
value
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
......@@ -11783,7 +11783,7 @@ msgstr ""
msgid "For help setting up the Service Desk for your instance, please contact an administrator."
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 ""
msgid "For more info, read the documentation."
......@@ -14418,10 +14418,10 @@ msgstr ""
msgid "Internal"
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 ""
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 ""
msgid "Internal URL (optional)"
......@@ -26477,7 +26477,7 @@ msgstr ""
msgid "The global settings require you to enable Two-Factor Authentication for your account."
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 ""
msgid "The group and any public projects can be viewed without any authentication."
......@@ -26588,7 +26588,7 @@ msgstr ""
msgid "The private key to use when a client certificate is provided. This value is encrypted at rest."
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 ""
msgid "The project can be accessed by any user who is logged in."
......@@ -26663,7 +26663,7 @@ msgstr ""
msgid "The snippet is visible only to project members."
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 ""
msgid "The specified tab is invalid, please select another"
......
......@@ -13,7 +13,7 @@ RSpec.describe 'bin/feature-flag' do
let(:options) { FeatureFlagOptionParser.parse(argv) }
let(:creator) { described_class.new(options) }
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
before do
......@@ -32,12 +32,12 @@ RSpec.describe 'bin/feature-flag' do
it 'properly creates a feature flag' do
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)
expect do
subject
end.to output(/name: feature-flag-name/).to_stdout
end.to output(/name: feature_flag_name/).to_stdout
end
context 'when running on master' do
......
......@@ -7,13 +7,14 @@ export const ITEM_TYPE = {
export const GROUP_VISIBILITY_TYPE = {
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.',
};
export const PROJECT_VISIBILITY_TYPE = {
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 - 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
::API::Lint, ::API::Markdown, ::API::Members, ::API::MergeRequestDiffs,
::API::MergeRequests, ::API::MergeRequestApprovals, ::API::Metrics::Dashboard::Annotations,
::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)
......
......@@ -3,13 +3,22 @@
require 'spec_helper'
RSpec.describe Gitlab::Badge::Coverage::Report do
let(:project) { create(:project, :repository) }
let(:job_name) { nil }
let_it_be(:project) { create(:project) }
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
described_class.new(project, 'master', opts: { job: job_name })
end
let(:job_name) { nil }
describe '#entity' do
it 'describes a coverage' do
expect(badge.entity).to eq 'coverage'
......@@ -28,81 +37,47 @@ RSpec.describe Gitlab::Badge::Coverage::Report do
end
end
shared_examples 'unknown coverage report' do
context 'particular job specified' do
let(:job_name) { '' }
it 'returns nil' do
expect(badge.status).to be_nil
end
describe '#status' do
before do
allow(badge).to receive(:pipeline).and_return(pipeline)
end
context 'particular job not specified' do
let(:job_name) { nil }
context 'with no pipeline' do
let(:pipeline) { nil }
it 'returns nil' do
expect(badge.status).to be_nil
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
expect(badge.status).to eq 40
context 'with no job specified' do
it 'returns the pipeline coverage value' do
expect(badge.status).to eq(50.00)
end
end
context 'when particular job not specified' do
let(:job_name) { '' }
it 'returns arithemetic mean for the pipeline' do
expect(badge.status).to eq 50
end
end
end
context 'with a blank job name' do
let(:job_name) { ' ' }
context 'when only failed pipeline exists' do
before do
create_pipeline do |pipeline|
create(:ci_build, :failed, pipeline: pipeline, coverage: 10)
it 'returns the pipeline coverage value' do
expect(badge.status).to eq(50.00)
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
let(:job_name) { 'nonexistent' }
it 'retruns nil' do
it 'returns nil' do
expect(badge.status).to be_nil
end
end
end
context 'pipeline does not exist' do
it_behaves_like 'unknown coverage report'
end
def create_pipeline
opts = { project: project, sha: project.commit.id, ref: 'master' }
context 'with a matching job name specified' do
let(:job_name) { 'first' }
create(:ci_pipeline, opts).tap do |pipeline|
yield pipeline
::Ci::ProcessPipelineService.new(pipeline).execute
it 'returns the pipeline coverage value' do
expect(badge.status).to eq(40.00)
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
use Gitlab::Graphql::Authorize
use Gitlab::Graphql::Pagination::Connections
lazy_resolve ::Gitlab::Graphql::Lazy, :force
query(query_type)
end
......
......@@ -106,13 +106,11 @@ RSpec.shared_examples 'querying a GraphQL type with labels' do
end
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)
single_selection = query_for(label_d)
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
......
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