New upstream version 14.4.4+ds1
This commit is contained in:
parent
9deebb8264
commit
a1c8598022
104 changed files with 1287 additions and 176 deletions
|
@ -19,6 +19,9 @@
|
|||
.if-default-branch-refs: &if-default-branch-refs
|
||||
if: '$CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'
|
||||
|
||||
.if-stable-branch-refs: &if-stable-branch-refs
|
||||
if: '$CI_COMMIT_REF_NAME =~ /^[\d-]+-stable(-ee)?$/'
|
||||
|
||||
.if-default-branch-push: &if-default-branch-push
|
||||
if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE == "push"'
|
||||
|
||||
|
@ -40,6 +43,9 @@
|
|||
.if-automated-merge-request: &if-automated-merge-request
|
||||
if: '$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME == "release-tools/update-gitaly" || $CI_MERGE_REQUEST_TARGET_BRANCH_NAME =~ /stable-ee$/'
|
||||
|
||||
.if-merge-request-targeting-stable-branch: &if-merge-request-targeting-stable-branch
|
||||
if: '$CI_MERGE_REQUEST_IID && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME =~ /^[\d-]+-stable(-ee)?$/'
|
||||
|
||||
.if-merge-request-labels-as-if-foss: &if-merge-request-labels-as-if-foss
|
||||
if: '$CI_MERGE_REQUEST_LABELS =~ /pipeline:run-as-if-foss/'
|
||||
|
||||
|
@ -518,6 +524,12 @@
|
|||
when: never
|
||||
- <<: *if-jh
|
||||
when: never
|
||||
- <<: *if-security-merge-request
|
||||
when: never
|
||||
- <<: *if-merge-request-targeting-stable-branch
|
||||
when: never
|
||||
- <<: *if-stable-branch-refs
|
||||
when: never
|
||||
- <<: *if-merge-request-labels-as-if-jh
|
||||
- <<: *if-merge-request-labels-run-all-rspec
|
||||
- changes: *code-backstage-qa-patterns
|
||||
|
@ -550,7 +562,11 @@
|
|||
- <<: *if-jh
|
||||
when: never
|
||||
- <<: *if-security-merge-request
|
||||
changes: *code-backstage-patterns
|
||||
when: never
|
||||
- <<: *if-merge-request-targeting-stable-branch
|
||||
when: never
|
||||
- <<: *if-stable-branch-refs
|
||||
when: never
|
||||
- <<: *if-merge-request-labels-as-if-jh
|
||||
- <<: *if-merge-request-labels-run-all-rspec
|
||||
- <<: *if-merge-request
|
||||
|
@ -1163,6 +1179,24 @@
|
|||
- <<: *if-merge-request-labels-as-if-foss
|
||||
changes: *code-backstage-patterns
|
||||
|
||||
.rails:rules:as-if-jh-rspec:
|
||||
rules:
|
||||
- <<: *if-not-ee
|
||||
when: never
|
||||
- <<: *if-jh
|
||||
when: never
|
||||
- <<: *if-security-merge-request
|
||||
when: never
|
||||
- <<: *if-merge-request-targeting-stable-branch
|
||||
when: never
|
||||
- <<: *if-stable-branch-refs
|
||||
when: never
|
||||
- <<: *if-merge-request-labels-as-if-jh
|
||||
allow_failure: true
|
||||
- <<: *if-merge-request
|
||||
changes: *ci-patterns
|
||||
allow_failure: true
|
||||
|
||||
.rails:rules:ee-and-foss-db-library-code:
|
||||
rules:
|
||||
- changes: *db-library-patterns
|
||||
|
@ -1638,6 +1672,12 @@
|
|||
when: never
|
||||
- <<: *if-jh
|
||||
when: never
|
||||
- <<: *if-security-merge-request
|
||||
when: never
|
||||
- <<: *if-merge-request-targeting-stable-branch
|
||||
when: never
|
||||
- <<: *if-stable-branch-refs
|
||||
when: never
|
||||
- <<: *if-merge-request-labels-as-if-jh
|
||||
- <<: *if-merge-request-labels-run-all-rspec
|
||||
- changes: *code-backstage-qa-patterns
|
||||
|
|
|
@ -365,6 +365,10 @@ That's all of the required database changes.
|
|||
::Gitlab::GitAccessCoolWidget
|
||||
end
|
||||
|
||||
def self.no_repo_message
|
||||
git_access_class.error_message(:no_repo)
|
||||
end
|
||||
|
||||
# The feature flag follows the format `geo_#{replicable_name}_replication`,
|
||||
# so here it would be `geo_cool_widget_replication`
|
||||
def self.replication_enabled_by_default?
|
||||
|
@ -403,6 +407,9 @@ That's all of the required database changes.
|
|||
```
|
||||
|
||||
- [ ] Make sure a Geo secondary site can request and download Cool Widgets on the Geo primary site. You may need to make some changes to `Gitlab::GitAccessCoolWidget`. For example, see [this change for Group-level Wikis](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/54914/diffs?commit_id=0f2b36f66697b4addbc69bd377ee2818f648dd33).
|
||||
|
||||
- [ ] Make sure a Geo secondary site can replicate Cool Widgets where repository does not exist on the Geo primary site. The only way to know about this is to parse the error text. You may need to make some changes to `Gitlab::CoolWidgetReplicator.no_repo_message` to return the proper error message. For example, see [this change for Group-level Wikis](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/74133).
|
||||
|
||||
- [ ] Generate the feature flag definition file by running the feature flag command and following the command prompts:
|
||||
|
||||
```shell
|
||||
|
|
15
CHANGELOG.md
15
CHANGELOG.md
|
@ -2,6 +2,21 @@
|
|||
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||
entry.
|
||||
|
||||
## 14.4.4 (2021-12-03)
|
||||
|
||||
No changes.
|
||||
|
||||
## 14.4.3 (2021-12-01)
|
||||
|
||||
### Fixed (6 changes)
|
||||
|
||||
- [Check validation only if new record of license](gitlab-org/gitlab@5e0834a921dad1b1e07119de629ea44eb0ad5733) ([merge request](gitlab-org/gitlab!75421)) **GitLab Enterprise Edition**
|
||||
- [Fix for hexadecimal branch deletion](gitlab-org/gitlab@fc3c2f211d5a2f190032c4d0109e2bcb31050b4d) ([merge request](gitlab-org/gitlab!75421))
|
||||
- [Geo - Fix no repo error message for group-level wikis](gitlab-org/gitlab@bdf3a712a4bfe245dfa7e7a90c24f2fdb482e309) ([merge request](gitlab-org/gitlab!75421)) **GitLab Enterprise Edition**
|
||||
- [Prevent Git operations from checking replication lag on non-Geo-secondary sites](gitlab-org/gitlab@c158c01027f61aadd1c72f0817731d368d0d58cc) ([merge request](gitlab-org/gitlab!75421)) **GitLab Enterprise Edition**
|
||||
- [Allow SSO callbacks through maintenance mode](gitlab-org/gitlab@1acae9807b1808ac360a4be098a50c547c9540b9) by @dzaporozhets ([merge request](gitlab-org/gitlab!75421)) **GitLab Enterprise Edition**
|
||||
- [Fix 2FA setup for LDAP users](gitlab-org/gitlab@9b9a7230aed3ffeef3e8f608dd1a569397c71684) ([merge request](gitlab-org/gitlab!75421))
|
||||
|
||||
## 14.4.2 (2021-11-08)
|
||||
|
||||
### Fixed (3 changes)
|
||||
|
|
|
@ -1 +1 @@
|
|||
14.4.2
|
||||
14.4.4
|
2
Gemfile
2
Gemfile
|
@ -96,7 +96,7 @@ gem 'grape-entity', '~> 0.10.0'
|
|||
gem 'rack-cors', '~> 1.0.6', require: 'rack/cors'
|
||||
|
||||
# GraphQL API
|
||||
gem 'graphql', '~> 1.11.8'
|
||||
gem 'graphql', '~> 1.11.10'
|
||||
# NOTE: graphiql-rails v1.5+ doesn't work: https://gitlab.com/gitlab-org/gitlab/issues/31771
|
||||
# TODO: remove app/views/graphiql/rails/editors/show.html.erb when https://github.com/rmosolgo/graphiql-rails/pull/71 is released:
|
||||
# https://gitlab.com/gitlab-org/gitlab/issues/31747
|
||||
|
|
|
@ -560,7 +560,7 @@ GEM
|
|||
faraday (>= 1.0)
|
||||
faraday_middleware
|
||||
graphql-client
|
||||
graphql (1.11.8)
|
||||
graphql (1.11.10)
|
||||
graphql-client (0.16.0)
|
||||
activesupport (>= 3.0)
|
||||
graphql (~> 1.8)
|
||||
|
@ -1484,7 +1484,7 @@ DEPENDENCIES
|
|||
grape_logging (~> 1.7)
|
||||
graphiql-rails (~> 1.4.10)
|
||||
graphlient (~> 0.4.0)
|
||||
graphql (~> 1.11.8)
|
||||
graphql (~> 1.11.10)
|
||||
graphql-docs (~> 1.6.0)
|
||||
grpc (~> 1.30.2)
|
||||
gssapi
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
14.4.2
|
||||
14.4.4
|
|
@ -75,7 +75,7 @@ export function initMermaid(mermaid) {
|
|||
|
||||
function importMermaidModule() {
|
||||
return import(/* webpackChunkName: 'mermaid' */ 'mermaid')
|
||||
.then((mermaid) => {
|
||||
.then(({ default: mermaid }) => {
|
||||
mermaidModule = initMermaid(mermaid);
|
||||
})
|
||||
.catch((err) => {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { SwaggerUIBundle } from 'swagger-ui-dist';
|
||||
import createFlash from '~/flash';
|
||||
import { removeParams, updateHistory } from '~/lib/utils/url_utility';
|
||||
import { __ } from '~/locale';
|
||||
|
||||
export default () => {
|
||||
|
@ -7,9 +8,14 @@ export default () => {
|
|||
|
||||
Promise.all([import(/* webpackChunkName: 'openapi' */ 'swagger-ui-dist/swagger-ui.css')])
|
||||
.then(() => {
|
||||
// Temporary fix to prevent an XSS attack due to "useUnsafeMarkdown"
|
||||
// Once we upgrade Swagger to "4.0.0", we can safely remove this as it will be deprecated
|
||||
// Follow-up issue: https://gitlab.com/gitlab-org/gitlab/-/issues/339696
|
||||
updateHistory({ url: removeParams(['useUnsafeMarkdown']), replace: true });
|
||||
SwaggerUIBundle({
|
||||
url: el.dataset.endpoint,
|
||||
dom_id: '#js-openapi-viewer',
|
||||
useUnsafeMarkdown: false,
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
|
|
|
@ -9,6 +9,9 @@ class GraphqlController < ApplicationController
|
|||
# Header can be passed by tests to disable SQL query limits.
|
||||
DISABLE_SQL_QUERY_LIMIT_HEADER = 'HTTP_X_GITLAB_DISABLE_SQL_QUERY_LIMIT'
|
||||
|
||||
# Max size of the query text in characters
|
||||
MAX_QUERY_SIZE = 10_000
|
||||
|
||||
# If a user is using their session to access GraphQL, we need to have session
|
||||
# storage, since the admin-mode check is session wide.
|
||||
# We can't enable this for anonymous users because that would cause users using
|
||||
|
@ -29,6 +32,7 @@ class GraphqlController < ApplicationController
|
|||
before_action :set_user_last_activity
|
||||
before_action :track_vs_code_usage
|
||||
before_action :disable_query_limiting
|
||||
before_action :limit_query_size
|
||||
|
||||
before_action :disallow_mutations_for_get
|
||||
|
||||
|
@ -81,6 +85,16 @@ class GraphqlController < ApplicationController
|
|||
raise ::Gitlab::Graphql::Errors::ArgumentError, "Mutations are forbidden in #{request.request_method} requests"
|
||||
end
|
||||
|
||||
def limit_query_size
|
||||
total_size = if multiplex?
|
||||
params[:_json].sum { _1[:query].size }
|
||||
else
|
||||
query.size
|
||||
end
|
||||
|
||||
raise ::Gitlab::Graphql::Errors::ArgumentError, "Query too large" if total_size > MAX_QUERY_SIZE
|
||||
end
|
||||
|
||||
def any_mutating_query?
|
||||
if multiplex?
|
||||
multiplex_queries.any? { |q| mutation?(q[:query], q[:operation_name]) }
|
||||
|
@ -126,7 +140,7 @@ class GraphqlController < ApplicationController
|
|||
end
|
||||
|
||||
def query
|
||||
params[:query]
|
||||
params.fetch(:query, '')
|
||||
end
|
||||
|
||||
def multiplex_queries
|
||||
|
|
|
@ -147,7 +147,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
|
|||
end
|
||||
|
||||
def current_password_required?
|
||||
!current_user.password_automatically_set?
|
||||
!current_user.password_automatically_set? && current_user.allow_password_authentication_for_web?
|
||||
end
|
||||
|
||||
def build_qr_code
|
||||
|
|
|
@ -26,6 +26,9 @@ class GitlabSchema < GraphQL::Schema
|
|||
|
||||
default_max_page_size 100
|
||||
|
||||
validate_max_errors 5
|
||||
validate_timeout 0.2.seconds
|
||||
|
||||
lazy_resolve ::Gitlab::Graphql::Lazy, :force
|
||||
|
||||
class << self
|
||||
|
|
|
@ -29,7 +29,10 @@ module Types
|
|||
field :name,
|
||||
type: GraphQL::Types::String,
|
||||
null: false,
|
||||
description: 'Human-readable name of the user.'
|
||||
resolver_method: :redacted_name,
|
||||
description: 'Human-readable name of the user. ' \
|
||||
'Will return `****` if the user is a project bot and the requester does not have permission to read resource access tokens.'
|
||||
|
||||
field :state,
|
||||
type: Types::UserStateEnum,
|
||||
null: false,
|
||||
|
@ -121,5 +124,16 @@ module Types
|
|||
::Types::UserType
|
||||
end
|
||||
end
|
||||
|
||||
def redacted_name
|
||||
return object.name unless object.project_bot?
|
||||
|
||||
return object.name if context[:current_user]&.can?(:read_resource_access_tokens, object.projects.first)
|
||||
|
||||
# If the requester does not have permission to read the project bot name,
|
||||
# the API returns an arbitrary string. UI changes will be addressed in a follow up issue:
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/346058
|
||||
'****'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -201,18 +201,30 @@ module SearchHelper
|
|||
if @project && @project.repository.root_ref
|
||||
ref = @ref || @project.repository.root_ref
|
||||
|
||||
result = [
|
||||
{ category: "In this project", label: _("Files"), url: project_tree_path(@project, ref) },
|
||||
{ category: "In this project", label: _("Commits"), url: project_commits_path(@project, ref) },
|
||||
{ category: "In this project", label: _("Network"), url: project_network_path(@project, ref) },
|
||||
{ category: "In this project", label: _("Graph"), url: project_graph_path(@project, ref) },
|
||||
result = []
|
||||
|
||||
if can?(current_user, :download_code, @project)
|
||||
result.concat([
|
||||
{ category: "In this project", label: _("Files"), url: project_tree_path(@project, ref) },
|
||||
{ category: "In this project", label: _("Commits"), url: project_commits_path(@project, ref) }
|
||||
])
|
||||
end
|
||||
|
||||
if can?(current_user, :read_repository_graphs, @project)
|
||||
result.concat([
|
||||
{ category: "In this project", label: _("Network"), url: project_network_path(@project, ref) },
|
||||
{ category: "In this project", label: _("Graph"), url: project_graph_path(@project, ref) }
|
||||
])
|
||||
end
|
||||
|
||||
result.concat([
|
||||
{ category: "In this project", label: _("Issues"), url: project_issues_path(@project) },
|
||||
{ category: "In this project", label: _("Merge requests"), url: project_merge_requests_path(@project) },
|
||||
{ category: "In this project", label: _("Milestones"), url: project_milestones_path(@project) },
|
||||
{ category: "In this project", label: _("Snippets"), url: project_snippets_path(@project) },
|
||||
{ category: "In this project", label: _("Members"), url: project_project_members_path(@project) },
|
||||
{ category: "In this project", label: _("Wiki"), url: project_wikis_path(@project) }
|
||||
]
|
||||
])
|
||||
|
||||
if can?(current_user, :read_feature_flag, @project)
|
||||
result << { category: "In this project", label: _("Feature Flags"), url: project_feature_flags_path(@project) }
|
||||
|
|
|
@ -9,11 +9,15 @@ module BulkMemberAccessLoad
|
|||
# Determine the maximum access level for a group of resources in bulk.
|
||||
#
|
||||
# Returns a Hash mapping resource ID -> maximum access level.
|
||||
def max_member_access_for_resource_ids(resource_klass, resource_ids, memoization_index = self.id, &block)
|
||||
def max_member_access_for_resource_ids(resource_klass, resource_ids, &block)
|
||||
raise 'Block is mandatory' unless block_given?
|
||||
|
||||
memoization_index = self.id
|
||||
memoization_class = self.class
|
||||
|
||||
resource_ids = resource_ids.uniq
|
||||
access = load_access_hash(resource_klass, memoization_index)
|
||||
memo_id = "#{memoization_class}:#{memoization_index}"
|
||||
access = load_access_hash(resource_klass, memo_id)
|
||||
|
||||
# Look up only the IDs we need
|
||||
resource_ids -= access.keys
|
||||
|
@ -33,8 +37,8 @@ module BulkMemberAccessLoad
|
|||
access
|
||||
end
|
||||
|
||||
def merge_value_to_request_store(resource_klass, resource_id, memoization_index, value)
|
||||
max_member_access_for_resource_ids(resource_klass, [resource_id], memoization_index) do
|
||||
def merge_value_to_request_store(resource_klass, resource_id, value)
|
||||
max_member_access_for_resource_ids(resource_klass, [resource_id]) do
|
||||
{ resource_id => value }
|
||||
end
|
||||
end
|
||||
|
@ -45,16 +49,13 @@ module BulkMemberAccessLoad
|
|||
"max_member_access_for_#{klass.name.underscore.pluralize}:#{memoization_index}"
|
||||
end
|
||||
|
||||
def load_access_hash(resource_klass, memoization_index)
|
||||
key = max_member_access_for_resource_key(resource_klass, memoization_index)
|
||||
def load_access_hash(resource_klass, memo_id)
|
||||
return {} unless Gitlab::SafeRequestStore.active?
|
||||
|
||||
access = {}
|
||||
if Gitlab::SafeRequestStore.active?
|
||||
Gitlab::SafeRequestStore[key] ||= {}
|
||||
access = Gitlab::SafeRequestStore[key]
|
||||
end
|
||||
key = max_member_access_for_resource_key(resource_klass, memo_id)
|
||||
Gitlab::SafeRequestStore[key] ||= {}
|
||||
|
||||
access
|
||||
Gitlab::SafeRequestStore[key]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -12,6 +12,7 @@ module DiffPositionableNote
|
|||
serialize :change_position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize
|
||||
|
||||
validate :diff_refs_match_commit, if: :for_commit?
|
||||
validates :position, json_schema: { filename: "position", hash_conversion: true }
|
||||
end
|
||||
|
||||
%i(original_position position change_position).each do |meth|
|
||||
|
|
|
@ -5,8 +5,6 @@ module Preloaders
|
|||
# stores the values in requests store.
|
||||
# Will only be able to preload max access level for groups where the user is a direct member
|
||||
class UserMaxAccessLevelInGroupsPreloader
|
||||
include BulkMemberAccessLoad
|
||||
|
||||
def initialize(groups, user)
|
||||
@groups = groups
|
||||
@user = user
|
||||
|
@ -19,8 +17,9 @@ module Preloaders
|
|||
.group(:source_id)
|
||||
.maximum(:access_level)
|
||||
|
||||
group_memberships.each do |group_id, max_access_level|
|
||||
merge_value_to_request_store(User, @user.id, group_id, max_access_level)
|
||||
@groups.each do |group|
|
||||
access_level = group_memberships[group.id]
|
||||
group.merge_value_to_request_store(User, @user.id, access_level) if access_level.present?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -37,6 +37,7 @@ class Project < ApplicationRecord
|
|||
include Repositories::CanHousekeepRepository
|
||||
include EachBatch
|
||||
include GitlabRoutingHelper
|
||||
include BulkMemberAccessLoad
|
||||
|
||||
extend Gitlab::Cache::RequestCache
|
||||
extend Gitlab::Utils::Override
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ProjectTeam
|
||||
include BulkMemberAccessLoad
|
||||
|
||||
attr_accessor :project
|
||||
|
||||
def initialize(project)
|
||||
|
@ -169,7 +167,7 @@ class ProjectTeam
|
|||
#
|
||||
# Returns a Hash mapping user ID -> maximum access level.
|
||||
def max_member_access_for_user_ids(user_ids)
|
||||
max_member_access_for_resource_ids(User, user_ids, project.id) do |user_ids|
|
||||
project.max_member_access_for_resource_ids(User, user_ids) do |user_ids|
|
||||
project.project_authorizations
|
||||
.where(user: user_ids)
|
||||
.group(:user_id)
|
||||
|
@ -178,7 +176,7 @@ class ProjectTeam
|
|||
end
|
||||
|
||||
def write_member_access_for_user_id(user_id, project_access_level)
|
||||
merge_value_to_request_store(User, user_id, project.id, project_access_level)
|
||||
project.merge_value_to_request_store(User, user_id, project_access_level)
|
||||
end
|
||||
|
||||
def max_member_access(user_id)
|
||||
|
|
|
@ -67,7 +67,7 @@ class Todo < ApplicationRecord
|
|||
scope :for_type, -> (type) { where(target_type: type) }
|
||||
scope :for_target, -> (id) { where(target_id: id) }
|
||||
scope :for_commit, -> (id) { where(commit_id: id) }
|
||||
scope :with_entity_associations, -> { preload(:target, :author, :note, group: :route, project: [:route, { namespace: :route }]) }
|
||||
scope :with_entity_associations, -> { preload(:target, :author, :note, group: :route, project: [:route, { namespace: [:route, :owner] }]) }
|
||||
scope :joins_issue_and_assignees, -> { left_joins(issue: :assignees) }
|
||||
|
||||
enum resolved_by_action: { system_done: 0, api_all_done: 1, api_done: 2, mark_all_done: 3, mark_done: 4 }, _prefix: :resolved_by
|
||||
|
|
|
@ -17,7 +17,9 @@ class IssuablePolicy < BasePolicy
|
|||
enable :read_issue
|
||||
enable :update_issue
|
||||
enable :reopen_issue
|
||||
enable :read_merge_request
|
||||
end
|
||||
|
||||
rule { can?(:read_merge_request) & assignee_or_author }.policy do
|
||||
enable :update_merge_request
|
||||
enable :reopen_merge_request
|
||||
end
|
||||
|
|
|
@ -90,6 +90,11 @@ class ProjectPolicy < BasePolicy
|
|||
user.is_a?(DeployToken) && user.has_access_to?(project) && user.write_package_registry
|
||||
end
|
||||
|
||||
desc "Deploy token with read access"
|
||||
condition(:download_code_deploy_token) do
|
||||
user.is_a?(DeployToken) && user.has_access_to?(project)
|
||||
end
|
||||
|
||||
desc "If user is authenticated via CI job token then the target project should be in scope"
|
||||
condition(:project_allowed_for_job_token) do
|
||||
!@user&.from_ci_job_token? || @user.ci_job_token_scope.includes?(project)
|
||||
|
@ -497,6 +502,10 @@ class ProjectPolicy < BasePolicy
|
|||
prevent(:download_wiki_code)
|
||||
end
|
||||
|
||||
rule { download_code_deploy_token }.policy do
|
||||
enable :download_wiki_code
|
||||
end
|
||||
|
||||
rule { builds_disabled | repository_disabled }.policy do
|
||||
prevent(*create_read_update_admin_destroy(:build))
|
||||
prevent(*create_read_update_admin_destroy(:pipeline_schedule))
|
||||
|
@ -678,12 +687,14 @@ class ProjectPolicy < BasePolicy
|
|||
|
||||
rule { project_bot }.enable :project_bot_access
|
||||
|
||||
rule { can?(:read_all_resources) }.enable :read_resource_access_tokens
|
||||
|
||||
rule { can?(:admin_project) & resource_access_token_feature_available }.policy do
|
||||
enable :read_resource_access_tokens
|
||||
enable :destroy_resource_access_tokens
|
||||
end
|
||||
|
||||
rule { can?(:read_resource_access_tokens) & resource_access_token_creation_allowed }.policy do
|
||||
rule { can?(:admin_project) & resource_access_token_feature_available & resource_access_token_creation_allowed }.policy do
|
||||
enable :create_resource_access_tokens
|
||||
end
|
||||
|
||||
|
|
|
@ -13,5 +13,23 @@ module ProtectedBranches
|
|||
def after_execute(*)
|
||||
# overridden in EE::ProtectedBranches module
|
||||
end
|
||||
|
||||
def filtered_params
|
||||
return unless params
|
||||
|
||||
params[:name] = sanitize_branch_name(params[:name]) if params[:name].present?
|
||||
params
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def sanitize_branch_name(name)
|
||||
name = CGI.unescapeHTML(name)
|
||||
name = Sanitize.fragment(name)
|
||||
|
||||
# Sanitize.fragment escapes HTML chars, so unescape again to allow names
|
||||
# like `feature->master`
|
||||
CGI.unescapeHTML(name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -21,7 +21,7 @@ module ProtectedBranches
|
|||
end
|
||||
|
||||
def protected_branch
|
||||
@protected_branch ||= project.protected_branches.new(params)
|
||||
@protected_branch ||= project.protected_branches.new(filtered_params)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,7 +8,7 @@ module ProtectedBranches
|
|||
old_merge_access_levels = protected_branch.merge_access_levels.map(&:clone)
|
||||
old_push_access_levels = protected_branch.push_access_levels.map(&:clone)
|
||||
|
||||
if protected_branch.update(params)
|
||||
if protected_branch.update(filtered_params)
|
||||
after_execute(protected_branch: protected_branch, old_merge_access_levels: old_merge_access_levels, old_push_access_levels: old_push_access_levels)
|
||||
end
|
||||
|
||||
|
|
|
@ -24,8 +24,10 @@ class JsonSchemaValidator < ActiveModel::EachValidator
|
|||
end
|
||||
|
||||
def validate_each(record, attribute, value)
|
||||
value = value.to_h.stringify_keys if options[:hash_conversion] == true
|
||||
|
||||
unless valid_schema?(value)
|
||||
record.errors.add(attribute, "must be a valid json schema")
|
||||
record.errors.add(attribute, _("must be a valid json schema"))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
151
app/validators/json_schemas/position.json
Normal file
151
app/validators/json_schemas/position.json
Normal file
|
@ -0,0 +1,151 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"description": "Gitlab::Diff::Position",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"base_sha": {
|
||||
"oneOf": [
|
||||
{ "type": "null" },
|
||||
{ "type": "string", "maxLength": 40 }
|
||||
]
|
||||
},
|
||||
"start_sha": {
|
||||
"oneOf": [
|
||||
{ "type": "null" },
|
||||
{ "type": "string", "maxLength": 40 }
|
||||
]
|
||||
},
|
||||
"head_sha": {
|
||||
"oneOf": [
|
||||
{ "type": "null" },
|
||||
{ "type": "string", "maxLength": 40 }
|
||||
]
|
||||
},
|
||||
"file_identifier_hash": {
|
||||
"oneOf": [
|
||||
{ "type": "null" },
|
||||
{ "type": "string", "maxLength": 40 }
|
||||
]
|
||||
},
|
||||
"old_path": {
|
||||
"oneOf": [
|
||||
{ "type": "null" },
|
||||
{ "type": "string", "maxLength": 1000 }
|
||||
]
|
||||
},
|
||||
"new_path": {
|
||||
"oneOf": [
|
||||
{ "type": "null" },
|
||||
{ "type": "string", "maxLength": 1000 }
|
||||
]
|
||||
},
|
||||
"position_type": {
|
||||
"oneOf": [
|
||||
{ "type": "null" },
|
||||
{ "type": "string", "maxLength": 10 }
|
||||
]
|
||||
},
|
||||
"old_line": {
|
||||
"oneOf": [
|
||||
{ "type": "null" },
|
||||
{ "type": "integer" }
|
||||
]
|
||||
},
|
||||
"new_line": {
|
||||
"oneOf": [
|
||||
{ "type": "null" },
|
||||
{ "type": "integer" }
|
||||
]
|
||||
},
|
||||
"line_range": {
|
||||
"oneOf": [
|
||||
{ "type": "null" },
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"start": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"line_code": { "type": "string", "maxLength": 100 },
|
||||
"type": {
|
||||
"oneOf": [
|
||||
{ "type": "null" },
|
||||
{ "type": "string", "maxLength": 100 }
|
||||
]
|
||||
},
|
||||
"old_line": {
|
||||
"oneOf": [
|
||||
{ "type": "null" },
|
||||
{ "type": "integer" }
|
||||
]
|
||||
},
|
||||
"new_line": {
|
||||
"oneOf": [
|
||||
{ "type": "null" },
|
||||
{ "type": "integer" }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"end": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"line_code": { "type": "string", "maxLength": 100 },
|
||||
"type": {
|
||||
"oneOf": [
|
||||
{ "type": "null" },
|
||||
{ "type": "string", "maxLength": 100 }
|
||||
]
|
||||
},
|
||||
"old_line": {
|
||||
"oneOf": [
|
||||
{ "type": "null" },
|
||||
{ "type": "integer" }
|
||||
]
|
||||
},
|
||||
"new_line": {
|
||||
"oneOf": [
|
||||
{ "type": "null" },
|
||||
{ "type": "integer" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"width": {
|
||||
"oneOf": [
|
||||
{ "type": "null" },
|
||||
{ "type": "integer" },
|
||||
{ "type": "string", "maxLength": 10 }
|
||||
]
|
||||
},
|
||||
"height": {
|
||||
"oneOf": [
|
||||
{ "type": "null" },
|
||||
{ "type": "integer" },
|
||||
{ "type": "string", "maxLength": 10 }
|
||||
]
|
||||
},
|
||||
"x": {
|
||||
"oneOf": [
|
||||
{ "type": "null" },
|
||||
{ "type": "integer" },
|
||||
{ "type": "string", "maxLength": 10 }
|
||||
]
|
||||
},
|
||||
"y": {
|
||||
"oneOf": [
|
||||
{ "type": "null" },
|
||||
{ "type": "integer" },
|
||||
{ "type": "string", "maxLength": 10 }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -29,8 +29,9 @@
|
|||
= hidden_field_tag :scope, search_context.scope
|
||||
= hidden_field_tag :search_code, search_context.code_search?
|
||||
|
||||
- ref = search_context.ref if can?(current_user, :download_code, search_context.project)
|
||||
= hidden_field_tag :snippets, search_context.for_snippets?
|
||||
= hidden_field_tag :repository_ref, search_context.ref
|
||||
= hidden_field_tag :repository_ref, ref
|
||||
= hidden_field_tag :nav_source, 'navbar'
|
||||
|
||||
-# workaround for non-JS feature specs, see spec/support/helpers/search_helpers.rb
|
||||
|
@ -38,4 +39,4 @@
|
|||
%noscript= button_tag 'Search'
|
||||
.search-autocomplete-opts.hide{ :'data-autocomplete-path' => search_autocomplete_path,
|
||||
:'data-autocomplete-project-id' => search_context.project.try(:id),
|
||||
:'data-autocomplete-project-ref' => search_context.ref }
|
||||
:'data-autocomplete-project-ref' => ref }
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
- current_route_path = request.fullpath.match(%r{-/tree/[^/]+/(.+$)}).to_a[1]
|
||||
- add_page_startup_graphql_call('repository/path_last_commit', { projectPath: @project.full_path, ref: current_ref, path: current_route_path || "" })
|
||||
- @content_class = "limit-container-width" unless fluid_layout
|
||||
- @skip_current_level_breadcrumb = true
|
||||
- add_page_specific_style 'page_bundles/project'
|
||||
|
@ -14,6 +13,7 @@
|
|||
= render "home_panel"
|
||||
|
||||
- if can?(current_user, :download_code, @project) && @project.repository_languages.present?
|
||||
- add_page_startup_graphql_call('repository/path_last_commit', { projectPath: @project.full_path, ref: current_ref, path: current_route_path || "" })
|
||||
= repository_languages_bar(@project.repository_languages)
|
||||
|
||||
= render "archived_notice", project: @project
|
||||
|
|
|
@ -376,6 +376,7 @@ module Gitlab
|
|||
config.cache_store = :redis_cache_store, Gitlab::Redis::Cache.active_support_config
|
||||
|
||||
config.active_job.queue_adapter = :sidekiq
|
||||
config.active_job.logger = nil
|
||||
|
||||
# This is needed for gitlab-shell
|
||||
ENV['GITLAB_PATH_OUTSIDE_HOOK'] = ENV['PATH']
|
||||
|
|
|
@ -114,3 +114,5 @@ Sidekiq.configure_client do |config|
|
|||
|
||||
config.client_middleware(&Gitlab::SidekiqMiddleware.client_configurator)
|
||||
end
|
||||
|
||||
Sidekiq::Cron::Poller.prepend Gitlab::Patch::SidekiqCronPoller
|
||||
|
|
|
@ -11466,7 +11466,7 @@ A user assigned to a merge request.
|
|||
| <a id="mergerequestassigneeid"></a>`id` | [`ID!`](#id) | ID of the user. |
|
||||
| <a id="mergerequestassigneelocation"></a>`location` | [`String`](#string) | Location of the user. |
|
||||
| <a id="mergerequestassigneemergerequestinteraction"></a>`mergeRequestInteraction` | [`UserMergeRequestInteraction`](#usermergerequestinteraction) | Details of this user's interactions with the merge request. |
|
||||
| <a id="mergerequestassigneename"></a>`name` | [`String!`](#string) | Human-readable name of the user. |
|
||||
| <a id="mergerequestassigneename"></a>`name` | [`String!`](#string) | Human-readable name of the user. Will return `****` if the user is a project bot and the requester does not have permission to read resource access tokens. |
|
||||
| <a id="mergerequestassigneenamespace"></a>`namespace` | [`Namespace`](#namespace) | Personal namespace of the user. |
|
||||
| <a id="mergerequestassigneeprojectmemberships"></a>`projectMemberships` | [`ProjectMemberConnection`](#projectmemberconnection) | Project memberships of the user. (see [Connections](#connections)) |
|
||||
| <a id="mergerequestassigneepublicemail"></a>`publicEmail` | [`String`](#string) | User's public email. |
|
||||
|
@ -11712,7 +11712,7 @@ A user assigned to a merge request as a reviewer.
|
|||
| <a id="mergerequestreviewerid"></a>`id` | [`ID!`](#id) | ID of the user. |
|
||||
| <a id="mergerequestreviewerlocation"></a>`location` | [`String`](#string) | Location of the user. |
|
||||
| <a id="mergerequestreviewermergerequestinteraction"></a>`mergeRequestInteraction` | [`UserMergeRequestInteraction`](#usermergerequestinteraction) | Details of this user's interactions with the merge request. |
|
||||
| <a id="mergerequestreviewername"></a>`name` | [`String!`](#string) | Human-readable name of the user. |
|
||||
| <a id="mergerequestreviewername"></a>`name` | [`String!`](#string) | Human-readable name of the user. Will return `****` if the user is a project bot and the requester does not have permission to read resource access tokens. |
|
||||
| <a id="mergerequestreviewernamespace"></a>`namespace` | [`Namespace`](#namespace) | Personal namespace of the user. |
|
||||
| <a id="mergerequestreviewerprojectmemberships"></a>`projectMemberships` | [`ProjectMemberConnection`](#projectmemberconnection) | Project memberships of the user. (see [Connections](#connections)) |
|
||||
| <a id="mergerequestreviewerpublicemail"></a>`publicEmail` | [`String`](#string) | User's public email. |
|
||||
|
@ -14660,7 +14660,7 @@ Core represention of a GitLab user.
|
|||
| <a id="usercoregroupmemberships"></a>`groupMemberships` | [`GroupMemberConnection`](#groupmemberconnection) | Group memberships of the user. (see [Connections](#connections)) |
|
||||
| <a id="usercoreid"></a>`id` | [`ID!`](#id) | ID of the user. |
|
||||
| <a id="usercorelocation"></a>`location` | [`String`](#string) | Location of the user. |
|
||||
| <a id="usercorename"></a>`name` | [`String!`](#string) | Human-readable name of the user. |
|
||||
| <a id="usercorename"></a>`name` | [`String!`](#string) | Human-readable name of the user. Will return `****` if the user is a project bot and the requester does not have permission to read resource access tokens. |
|
||||
| <a id="usercorenamespace"></a>`namespace` | [`Namespace`](#namespace) | Personal namespace of the user. |
|
||||
| <a id="usercoreprojectmemberships"></a>`projectMemberships` | [`ProjectMemberConnection`](#projectmemberconnection) | Project memberships of the user. (see [Connections](#connections)) |
|
||||
| <a id="usercorepublicemail"></a>`publicEmail` | [`String`](#string) | User's public email. |
|
||||
|
@ -17705,7 +17705,7 @@ Implementations:
|
|||
| <a id="usergroupmemberships"></a>`groupMemberships` | [`GroupMemberConnection`](#groupmemberconnection) | Group memberships of the user. (see [Connections](#connections)) |
|
||||
| <a id="userid"></a>`id` | [`ID!`](#id) | ID of the user. |
|
||||
| <a id="userlocation"></a>`location` | [`String`](#string) | Location of the user. |
|
||||
| <a id="username"></a>`name` | [`String!`](#string) | Human-readable name of the user. |
|
||||
| <a id="username"></a>`name` | [`String!`](#string) | Human-readable name of the user. Will return `****` if the user is a project bot and the requester does not have permission to read resource access tokens. |
|
||||
| <a id="usernamespace"></a>`namespace` | [`Namespace`](#namespace) | Personal namespace of the user. |
|
||||
| <a id="userprojectmemberships"></a>`projectMemberships` | [`ProjectMemberConnection`](#projectmemberconnection) | Project memberships of the user. (see [Connections](#connections)) |
|
||||
| <a id="userpublicemail"></a>`publicEmail` | [`String`](#string) | User's public email. |
|
||||
|
|
|
@ -806,7 +806,7 @@ When the pipeline is successful, the package is created.
|
|||
The version string is validated by using the following regex.
|
||||
|
||||
```ruby
|
||||
\A(\.?[\w\+-]+\.?)+\z
|
||||
\A(?!.*\.\.)[\w+.-]+\z
|
||||
```
|
||||
|
||||
You can play around with the regex and try your version strings on [this regular expression editor](https://rubular.com/r/rrLQqUXjfKEoL6).
|
||||
|
|
|
@ -55,7 +55,9 @@ module API
|
|||
expose(:snippets_enabled) { |project, options| project.feature_available?(:snippets, options[:current_user]) }
|
||||
expose(:container_registry_enabled) { |project, options| project.feature_available?(:container_registry, options[:current_user]) }
|
||||
expose :service_desk_enabled
|
||||
expose :service_desk_address
|
||||
expose :service_desk_address, if: -> (project, options) do
|
||||
Ability.allowed?(options[:current_user], :admin_issue, project)
|
||||
end
|
||||
|
||||
expose(:can_create_merge_request_in) do |project, options|
|
||||
Ability.allowed?(options[:current_user], :create_merge_request_in, project)
|
||||
|
|
|
@ -3,7 +3,17 @@
|
|||
module API
|
||||
module Entities
|
||||
class UserSafe < Grape::Entity
|
||||
expose :id, :name, :username
|
||||
expose :id, :username
|
||||
expose :name do |user|
|
||||
next user.name unless user.project_bot?
|
||||
|
||||
next user.name if options[:current_user]&.can?(:read_resource_access_tokens, user.projects.first)
|
||||
|
||||
# If the requester does not have permission to read the project bot name,
|
||||
# the API returns an arbitrary string. UI changes will be addressed in a follow up issue:
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/346058
|
||||
'****'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,6 +4,16 @@ module API
|
|||
class Lint < ::API::Base
|
||||
feature_category :pipeline_authoring
|
||||
|
||||
helpers do
|
||||
def can_lint_ci?
|
||||
signup_unrestricted = Gitlab::CurrentSettings.signup_enabled? && !Gitlab::CurrentSettings.signup_limited?
|
||||
internal_user = current_user.present? && !current_user.external?
|
||||
is_developer = current_user.present? && current_user.projects.any? { |p| p.team.member?(current_user, Gitlab::Access::DEVELOPER) }
|
||||
|
||||
signup_unrestricted || internal_user || is_developer
|
||||
end
|
||||
end
|
||||
|
||||
namespace :ci do
|
||||
desc 'Validation of .gitlab-ci.yml content'
|
||||
params do
|
||||
|
@ -11,7 +21,7 @@ module API
|
|||
optional :include_merged_yaml, type: Boolean, desc: 'Whether or not to include merged CI config yaml in the response'
|
||||
end
|
||||
post '/lint' do
|
||||
unauthorized! if (Gitlab::CurrentSettings.signup_disabled? || Gitlab::CurrentSettings.signup_limited?) && current_user.nil?
|
||||
unauthorized! unless can_lint_ci?
|
||||
|
||||
result = Gitlab::Ci::Lint.new(project: nil, current_user: current_user)
|
||||
.validate(params[:content], dry_run: false)
|
||||
|
|
|
@ -25,9 +25,7 @@ module API
|
|||
# Examples:
|
||||
# GET /projects/:id/merge_requests/:merge_request_iid/approvals
|
||||
desc 'List approvals for merge request'
|
||||
get 'approvals' do
|
||||
not_found!("Merge Request") unless can?(current_user, :read_merge_request, user_project)
|
||||
|
||||
get 'approvals', urgency: :low do
|
||||
merge_request = find_merge_request_with_access(params[:merge_request_iid])
|
||||
|
||||
present_approval(merge_request)
|
||||
|
|
|
@ -23,8 +23,6 @@ module API
|
|||
use :pagination
|
||||
end
|
||||
get ":id/merge_requests/:merge_request_iid/versions" do
|
||||
not_found!("Merge Request") unless can?(current_user, :read_merge_request, user_project)
|
||||
|
||||
merge_request = find_merge_request_with_access(params[:merge_request_iid])
|
||||
|
||||
present paginate(merge_request.merge_request_diffs.order_id_desc), with: Entities::MergeRequestDiff
|
||||
|
@ -41,8 +39,6 @@ module API
|
|||
end
|
||||
|
||||
get ":id/merge_requests/:merge_request_iid/versions/:version_id" do
|
||||
not_found!("Merge Request") unless can?(current_user, :read_merge_request, user_project)
|
||||
|
||||
merge_request = find_merge_request_with_access(params[:merge_request_iid])
|
||||
|
||||
present_cached merge_request.merge_request_diffs.find(params[:version_id]), with: Entities::MergeRequestDiffFull, cache_context: nil
|
||||
|
|
|
@ -264,8 +264,6 @@ module API
|
|||
success Entities::MergeRequest
|
||||
end
|
||||
get ':id/merge_requests/:merge_request_iid', feature_category: :code_review do
|
||||
not_found!("Merge Request") unless can?(current_user, :read_merge_request, user_project)
|
||||
|
||||
merge_request = find_merge_request_with_access(params[:merge_request_iid])
|
||||
|
||||
present merge_request,
|
||||
|
@ -282,8 +280,6 @@ module API
|
|||
success Entities::UserBasic
|
||||
end
|
||||
get ':id/merge_requests/:merge_request_iid/participants', feature_category: :code_review do
|
||||
not_found!("Merge Request") unless can?(current_user, :read_merge_request, user_project)
|
||||
|
||||
merge_request = find_merge_request_with_access(params[:merge_request_iid])
|
||||
|
||||
participants = ::Kaminari.paginate_array(merge_request.participants)
|
||||
|
@ -295,8 +291,6 @@ module API
|
|||
success Entities::Commit
|
||||
end
|
||||
get ':id/merge_requests/:merge_request_iid/commits', feature_category: :code_review do
|
||||
not_found!("Merge Request") unless can?(current_user, :read_merge_request, user_project)
|
||||
|
||||
merge_request = find_merge_request_with_access(params[:merge_request_iid])
|
||||
|
||||
commits =
|
||||
|
@ -378,8 +372,6 @@ module API
|
|||
success Entities::MergeRequestChanges
|
||||
end
|
||||
get ':id/merge_requests/:merge_request_iid/changes', feature_category: :code_review do
|
||||
not_found!("Merge Request") unless can?(current_user, :read_merge_request, user_project)
|
||||
|
||||
merge_request = find_merge_request_with_access(params[:merge_request_iid])
|
||||
|
||||
present merge_request,
|
||||
|
@ -395,8 +387,6 @@ module API
|
|||
get ':id/merge_requests/:merge_request_iid/pipelines', feature_category: :continuous_integration do
|
||||
pipelines = merge_request_pipelines_with_access
|
||||
|
||||
not_found!("Merge Request") unless can?(current_user, :read_merge_request, user_project)
|
||||
|
||||
present paginate(pipelines), with: Entities::Ci::PipelineBasic
|
||||
end
|
||||
|
||||
|
|
|
@ -29,10 +29,6 @@ module API
|
|||
post ":id/#{type}/:#{type_id_str}/todo" do
|
||||
issuable = instance_exec(params[type_id_str], &finder)
|
||||
|
||||
unless can?(current_user, :read_merge_request, issuable.project)
|
||||
not_found!(type.split("_").map(&:capitalize).join(" "))
|
||||
end
|
||||
|
||||
todo = TodoService.new.mark_todo(issuable, current_user).first
|
||||
|
||||
if todo
|
||||
|
|
|
@ -9,7 +9,7 @@ module Banzai
|
|||
html.sub(Gitlab::FrontMatter::PATTERN) do |_match|
|
||||
lang = $~[:lang].presence || lang_mapping[$~[:delim]]
|
||||
|
||||
["```#{lang}:frontmatter", $~[:front_matter], "```", "\n"].join("\n")
|
||||
["```#{lang}:frontmatter", $~[:front_matter].strip!, "```", "\n"].join("\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -40,6 +40,7 @@ module Gitlab
|
|||
private
|
||||
|
||||
def prohibited_branch_checks
|
||||
return if deletion?
|
||||
return unless Feature.enabled?(:prohibit_hexadecimal_branch_names, project, default_enabled: true)
|
||||
|
||||
if branch_name =~ /\A\h{40}\z/
|
||||
|
|
|
@ -8,7 +8,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def signup_limited?
|
||||
domain_allowlist.present? || email_restrictions_enabled? || require_admin_approval_after_user_signup?
|
||||
domain_allowlist.present? || email_restrictions_enabled? || require_admin_approval_after_user_signup? || user_default_external?
|
||||
end
|
||||
|
||||
def current_application_settings
|
||||
|
|
|
@ -57,6 +57,7 @@ module Gitlab
|
|||
next false unless @position.unfoldable?
|
||||
next false if @diff_file.new_file? || @diff_file.deleted_file?
|
||||
next false unless @position.old_line
|
||||
next false unless @position.old_line.is_a?(Integer)
|
||||
# Invalid position (MR import scenario)
|
||||
next false if @position.old_line > @blob.lines.size
|
||||
next false if @diff_file.diff_lines.empty?
|
||||
|
|
|
@ -11,13 +11,11 @@ module Gitlab
|
|||
DELIM = Regexp.union(DELIM_LANG.keys)
|
||||
|
||||
PATTERN = %r{
|
||||
\A(?:[^\r\n]*coding:[^\r\n]*)? # optional encoding line
|
||||
\A(?:[^\r\n]*coding:[^\r\n]*\R)? # optional encoding line
|
||||
\s*
|
||||
^(?<delim>#{DELIM})[ \t]*(?<lang>\S*) # opening front matter marker (optional language specifier)
|
||||
\s*
|
||||
^(?<front_matter>.*?) # front matter block content (not greedy)
|
||||
\s*
|
||||
^(\k<delim> | \.{3}) # closing front matter marker
|
||||
^(?<delim>#{DELIM})[ \t]*(?<lang>\S*)\R # opening front matter marker (optional language specifier)
|
||||
(?<front_matter>.*?) # front matter block content (not greedy)
|
||||
^(\k<delim> | \.{3}) # closing front matter marker
|
||||
\s*
|
||||
}mx.freeze
|
||||
end
|
||||
|
|
|
@ -27,6 +27,13 @@ module Gitlab
|
|||
:create_wiki
|
||||
end
|
||||
|
||||
override :check_download_access!
|
||||
def check_download_access!
|
||||
super
|
||||
|
||||
raise ForbiddenError, download_forbidden_message if deploy_token && !deploy_token.can?(:download_wiki_code, container)
|
||||
end
|
||||
|
||||
override :check_change_access!
|
||||
def check_change_access!
|
||||
raise ForbiddenError, write_to_wiki_message unless user_can_push?
|
||||
|
|
|
@ -52,11 +52,20 @@ module Gitlab
|
|||
|
||||
@importable.members.destroy_all # rubocop: disable Cop/DestroyAll
|
||||
|
||||
relation_class.create!(user: @user, access_level: highest_access_level, source_id: @importable.id, importing: true)
|
||||
relation_class.create!(user: @user, access_level: importer_access_level, source_id: @importable.id, importing: true)
|
||||
rescue StandardError => e
|
||||
raise e, "Error adding importer user to #{@importable.class} members. #{e.message}"
|
||||
end
|
||||
|
||||
def importer_access_level
|
||||
if @importable.parent.is_a?(::Group) && !@user.admin?
|
||||
lvl = @importable.parent.max_member_access_for_user(@user, only_concrete_membership: true)
|
||||
[lvl, highest_access_level].min
|
||||
else
|
||||
highest_access_level
|
||||
end
|
||||
end
|
||||
|
||||
def user_already_member?
|
||||
member = @importable.members&.first
|
||||
|
||||
|
|
17
lib/gitlab/patch/sidekiq_cron_poller.rb
Normal file
17
lib/gitlab/patch/sidekiq_cron_poller.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Patch
|
||||
module SidekiqCronPoller
|
||||
def enqueue
|
||||
Rails.application.reloader.wrap do
|
||||
::Gitlab::WithRequestStore.with_request_store do
|
||||
super
|
||||
ensure
|
||||
::Gitlab::Database::LoadBalancing.release_hosts
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -29,9 +29,7 @@ module Gitlab
|
|||
# Anything, including `/cmd arg` which are ignored by this filter
|
||||
# `
|
||||
|
||||
`\n*
|
||||
.+?
|
||||
\n*`
|
||||
`.+?`
|
||||
)
|
||||
}mix.freeze
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def maven_version_regex
|
||||
@maven_version_regex ||= /\A(\.?[\w\+-]+\.?)+\z/.freeze
|
||||
@maven_version_regex ||= /\A(?!.*\.\.)[\w+.-]+\z/.freeze
|
||||
end
|
||||
|
||||
def maven_app_group_regex
|
||||
|
|
|
@ -11,6 +11,16 @@
|
|||
module Gitlab
|
||||
class SidekiqEnq
|
||||
def enqueue_jobs(now = Time.now.to_f.to_s, sorted_sets = Sidekiq::Scheduled::SETS)
|
||||
Rails.application.reloader.wrap do
|
||||
::Gitlab::WithRequestStore.with_request_store do
|
||||
find_jobs_and_enqueue(now, sorted_sets)
|
||||
end
|
||||
ensure
|
||||
::Gitlab::Database::LoadBalancing.release_hosts
|
||||
end
|
||||
end
|
||||
|
||||
def find_jobs_and_enqueue(now, sorted_sets)
|
||||
# A job's "score" in Redis is the time at which it should be processed.
|
||||
# Just check Redis for the set of jobs with a timestamp before now.
|
||||
Sidekiq.redis do |conn|
|
||||
|
|
|
@ -3,8 +3,18 @@
|
|||
module Gitlab
|
||||
module SlashCommands
|
||||
class Deploy < BaseCommand
|
||||
DEPLOY_REGEX = /\Adeploy\s/.freeze
|
||||
|
||||
def self.match(text)
|
||||
/\Adeploy\s+(?<from>\S+.*)\s+to+\s+(?<to>\S+.*)\z/.match(text)
|
||||
return unless text&.match?(DEPLOY_REGEX)
|
||||
|
||||
from, _, to = text.sub(DEPLOY_REGEX, '').rpartition(/\sto+\s/)
|
||||
return if from.blank? || to.blank?
|
||||
|
||||
{
|
||||
from: from.strip,
|
||||
to: to.strip
|
||||
}
|
||||
end
|
||||
|
||||
def self.help_message
|
||||
|
|
|
@ -54,7 +54,7 @@ module Gitlab
|
|||
|
||||
def initialize(delim = nil, lang = '', text = nil)
|
||||
@lang = lang.downcase.presence || Gitlab::FrontMatter::DELIM_LANG[delim]
|
||||
@text = text
|
||||
@text = text&.strip!
|
||||
end
|
||||
|
||||
def data
|
||||
|
|
|
@ -60,7 +60,7 @@ module Sidebars
|
|||
end
|
||||
|
||||
def repository_analytics_menu_item
|
||||
if context.project.empty_repo?
|
||||
if context.project.empty_repo? || !can?(context.current_user, :read_repository_graphs, context.project)
|
||||
return ::Sidebars::NilMenuItem.new(item_id: :repository_analytics)
|
||||
end
|
||||
|
||||
|
|
|
@ -40994,6 +40994,9 @@ msgstr ""
|
|||
msgid "must be a valid IPv4 or IPv6 address"
|
||||
msgstr ""
|
||||
|
||||
msgid "must be a valid json schema"
|
||||
msgstr ""
|
||||
|
||||
msgid "must be after start"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -148,7 +148,7 @@
|
|||
"lowlight": "^1.20.0",
|
||||
"marked": "^0.3.12",
|
||||
"mathjax": "3",
|
||||
"mermaid": "^8.13.2",
|
||||
"mermaid": "^8.13.4",
|
||||
"minimatch": "^3.0.4",
|
||||
"monaco-editor": "^0.25.2",
|
||||
"monaco-editor-webpack-plugin": "^4.0.0",
|
||||
|
|
|
@ -65,7 +65,7 @@ RSpec.describe Dashboard::TodosController do
|
|||
project_2 = create(:project)
|
||||
project_2.add_developer(user)
|
||||
merge_request_2 = create(:merge_request, source_project: project_2)
|
||||
create(:todo, project: project, author: author, user: user, target: merge_request_2)
|
||||
create(:todo, project: project_2, author: author, user: user, target: merge_request_2)
|
||||
|
||||
expect { get :index }.not_to exceed_query_limit(control)
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
|
|
|
@ -52,6 +52,44 @@ RSpec.describe GraphqlController do
|
|||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
|
||||
it 'executes a simple query with no errors' do
|
||||
post :execute, params: { query: '{ __typename }' }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response).to eq({ 'data' => { '__typename' => 'Query' } })
|
||||
end
|
||||
|
||||
it 'executes a simple multiplexed query with no errors' do
|
||||
multiplex = [{ query: '{ __typename }' }] * 2
|
||||
|
||||
post :execute, params: { _json: multiplex }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response).to eq([
|
||||
{ 'data' => { '__typename' => 'Query' } },
|
||||
{ 'data' => { '__typename' => 'Query' } }
|
||||
])
|
||||
end
|
||||
|
||||
it 'sets a limit on the total query size' do
|
||||
graphql_query = "{#{(['__typename'] * 1000).join(' ')}}"
|
||||
|
||||
post :execute, params: { query: graphql_query }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:unprocessable_entity)
|
||||
expect(json_response).to eq({ 'errors' => [{ 'message' => 'Query too large' }] })
|
||||
end
|
||||
|
||||
it 'sets a limit on the total query size for multiplex queries' do
|
||||
graphql_query = "{#{(['__typename'] * 200).join(' ')}}"
|
||||
multiplex = [{ query: graphql_query }] * 5
|
||||
|
||||
post :execute, params: { _json: multiplex }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:unprocessable_entity)
|
||||
expect(json_response).to eq({ 'errors' => [{ 'message' => 'Query too large' }] })
|
||||
end
|
||||
|
||||
it 'returns forbidden when user cannot access API' do
|
||||
# User cannot access API in a couple of cases
|
||||
# * When user is internal(like ghost users)
|
||||
|
|
|
@ -62,6 +62,32 @@ RSpec.describe Profiles::TwoFactorAuthsController do
|
|||
expect(flash[:alert]).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when password authentication is disabled' do
|
||||
before do
|
||||
stub_application_setting(password_authentication_enabled_for_web: false)
|
||||
end
|
||||
|
||||
it 'does not require the current password', :aggregate_failures do
|
||||
go
|
||||
|
||||
expect(response).not_to redirect_to(redirect_path)
|
||||
expect(flash[:alert]).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the user is an LDAP user' do
|
||||
before do
|
||||
allow(user).to receive(:ldap_user?).and_return(true)
|
||||
end
|
||||
|
||||
it 'does not require the current password', :aggregate_failures do
|
||||
go
|
||||
|
||||
expect(response).not_to redirect_to(redirect_path)
|
||||
expect(flash[:alert]).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET show' do
|
||||
|
|
|
@ -43,8 +43,12 @@ FactoryBot.define do
|
|||
trait :multi_line do
|
||||
line_range do
|
||||
{
|
||||
start_line_code: Gitlab::Git.diff_line_code(file, 10, 10),
|
||||
end_line_code: Gitlab::Git.diff_line_code(file, 12, 13)
|
||||
start: {
|
||||
line_code: Gitlab::Git.diff_line_code(file, 10, 10)
|
||||
},
|
||||
end: {
|
||||
line_code: Gitlab::Git.diff_line_code(file, 12, 13)
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,8 +7,8 @@ RSpec.describe 'File blob', :js do
|
|||
|
||||
let(:project) { create(:project, :public, :repository) }
|
||||
|
||||
def visit_blob(path, anchor: nil, ref: 'master')
|
||||
visit project_blob_path(project, File.join(ref, path), anchor: anchor)
|
||||
def visit_blob(path, anchor: nil, ref: 'master', **additional_args)
|
||||
visit project_blob_path(project, File.join(ref, path), anchor: anchor, **additional_args)
|
||||
|
||||
wait_for_requests
|
||||
end
|
||||
|
@ -1501,6 +1501,53 @@ RSpec.describe 'File blob', :js do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'openapi.yml' do
|
||||
before do
|
||||
file_name = 'openapi.yml'
|
||||
|
||||
create_file(file_name, '
|
||||
swagger: \'2.0\'
|
||||
info:
|
||||
title: Classic API Resource Documentation
|
||||
description: |
|
||||
<div class="foo-bar" style="background-color: red;" data-foo-bar="baz">
|
||||
<h1>Swagger API documentation</h1>
|
||||
</div>
|
||||
version: production
|
||||
basePath: /JSSResource/
|
||||
produces:
|
||||
- application/xml
|
||||
- application/json
|
||||
consumes:
|
||||
- application/xml
|
||||
- application/json
|
||||
security:
|
||||
- basicAuth: []
|
||||
paths:
|
||||
/accounts:
|
||||
get:
|
||||
responses:
|
||||
\'200\':
|
||||
description: No response was specified
|
||||
tags:
|
||||
- accounts
|
||||
operationId: findAccounts
|
||||
summary: Finds all accounts
|
||||
')
|
||||
visit_blob(file_name, useUnsafeMarkdown: '1')
|
||||
click_button('Display rendered file')
|
||||
|
||||
wait_for_requests
|
||||
end
|
||||
|
||||
it 'removes `style`, `class`, and `data-*`` attributes from HTML' do
|
||||
expect(page).to have_css('h1', text: 'Swagger API documentation')
|
||||
expect(page).not_to have_css('.foo-bar')
|
||||
expect(page).not_to have_css('[style="background-color: red;"]')
|
||||
expect(page).not_to have_css('[data-foo-bar="baz"]')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -147,7 +147,7 @@ RSpec.describe 'Project members list', :js do
|
|||
it 'does not show form used to change roles and "Expiration date" or the remove user button', :aggregate_failures do
|
||||
visit_members_page
|
||||
|
||||
page.within find_member_row(project_bot) do
|
||||
page.within find_username_row(project_bot) do
|
||||
expect(page).not_to have_button('Maintainer')
|
||||
expect(page).to have_field('Expiration date', disabled: true)
|
||||
expect(page).not_to have_button('Remove member')
|
||||
|
|
|
@ -383,6 +383,24 @@ RSpec.describe 'Project' do
|
|||
{ form: '.rspec-merge-request-settings', input: '#project_printing_merge_request_link_enabled' }]
|
||||
end
|
||||
|
||||
describe 'view for a user without an access to a repo' do
|
||||
let(:project) { create(:project, :repository) }
|
||||
let(:user) { create(:user) }
|
||||
|
||||
it 'does not contain default branch information in its content' do
|
||||
default_branch = 'merge-commit-analyze-side-branch'
|
||||
|
||||
project.add_guest(user)
|
||||
project.change_head(default_branch)
|
||||
|
||||
sign_in(user)
|
||||
visit project_path(project)
|
||||
|
||||
lines_with_default_branch = page.html.lines.select { |line| line.include?(default_branch) }
|
||||
expect(lines_with_default_branch).to eq([])
|
||||
end
|
||||
end
|
||||
|
||||
def remove_with_confirm(button_text, confirm_with, confirm_button_text = 'Confirm')
|
||||
click_button button_text
|
||||
fill_in 'confirm_name_input', with: confirm_with
|
||||
|
|
|
@ -118,12 +118,12 @@ RSpec.describe 'Protected Branches', :js do
|
|||
it "allows creating explicit protected branches" do
|
||||
visit project_protected_branches_path(project)
|
||||
set_defaults
|
||||
set_protected_branch_name('some-branch')
|
||||
set_protected_branch_name('some->branch')
|
||||
click_on "Protect"
|
||||
|
||||
within(".protected-branches-list") { expect(page).to have_content('some-branch') }
|
||||
within(".protected-branches-list") { expect(page).to have_content('some->branch') }
|
||||
expect(ProtectedBranch.count).to eq(1)
|
||||
expect(ProtectedBranch.last.name).to eq('some-branch')
|
||||
expect(ProtectedBranch.last.name).to eq('some->branch')
|
||||
end
|
||||
|
||||
it "displays the last commit on the matching branch if it exists" do
|
||||
|
|
|
@ -138,7 +138,7 @@ describe('DiffsStoreUtils', () => {
|
|||
old_line: 1,
|
||||
},
|
||||
linePosition: LINE_POSITION_LEFT,
|
||||
lineRange: { start_line_code: 'abc_1_1', end_line_code: 'abc_2_2' },
|
||||
lineRange: { start: { line_code: 'abc_1_1' }, end: { line_code: 'abc_2_2' } },
|
||||
};
|
||||
|
||||
const position = JSON.stringify({
|
||||
|
@ -608,7 +608,7 @@ describe('DiffsStoreUtils', () => {
|
|||
// When multi line comments are fully implemented `line_code` will be
|
||||
// included in all requests. Until then we need to ensure the logic does
|
||||
// not change when it is included only in the "comparison" argument.
|
||||
const lineRange = { start_line_code: 'abc_1_1', end_line_code: 'abc_1_2' };
|
||||
const lineRange = { start: { line_code: 'abc_1_1' }, end: { line_code: 'abc_1_2' } };
|
||||
|
||||
it('returns true when the discussion is up to date', () => {
|
||||
expect(
|
||||
|
|
|
@ -35,6 +35,10 @@ RSpec.describe GitlabSchema do
|
|||
expect(connection).to eq(Gitlab::Graphql::Pagination::ExternallyPaginatedArrayConnection)
|
||||
end
|
||||
|
||||
it 'sets an appropriate validation timeout' do
|
||||
expect(described_class.validate_timeout).to be <= 0.2.seconds
|
||||
end
|
||||
|
||||
describe '.execute' do
|
||||
describe 'setting query `max_complexity` and `max_depth`' do
|
||||
subject(:result) { described_class.execute('query', **kwargs).query }
|
||||
|
@ -195,6 +199,36 @@ RSpec.describe GitlabSchema do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'validate_max_errors' do
|
||||
it 'reports at most 5 errors' do
|
||||
query = <<~GQL
|
||||
query {
|
||||
currentUser {
|
||||
x: id
|
||||
x: bot
|
||||
x: username
|
||||
x: state
|
||||
x: name
|
||||
|
||||
x: id
|
||||
x: bot
|
||||
x: username
|
||||
x: state
|
||||
x: name
|
||||
|
||||
badField
|
||||
veryBadField
|
||||
alsoNotAGoodField
|
||||
}
|
||||
}
|
||||
GQL
|
||||
|
||||
result = described_class.execute(query)
|
||||
|
||||
expect(result.to_h['errors'].count).to eq 5
|
||||
end
|
||||
end
|
||||
|
||||
describe '.parse_gid' do
|
||||
let_it_be(:global_id) { 'gid://gitlab/TestOne/2147483647' }
|
||||
|
||||
|
|
|
@ -44,6 +44,86 @@ RSpec.describe GitlabSchema.types['User'] do
|
|||
expect(described_class).to have_graphql_fields(*expected_fields)
|
||||
end
|
||||
|
||||
describe 'name field' do
|
||||
let_it_be(:admin) { create(:user, :admin)}
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:requested_user) { create(:user, name: 'John Smith') }
|
||||
let_it_be(:requested_project_bot) { create(:user, :project_bot, name: 'Project bot') }
|
||||
let_it_be(:project) { create(:project, :public) }
|
||||
|
||||
before do
|
||||
project.add_maintainer(requested_project_bot)
|
||||
end
|
||||
|
||||
let(:username) { requested_user.username }
|
||||
|
||||
let(:query) do
|
||||
%(
|
||||
query {
|
||||
user(username: "#{username}") {
|
||||
name
|
||||
}
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
subject { GitlabSchema.execute(query, context: { current_user: current_user }).as_json.dig('data', 'user', 'name') }
|
||||
|
||||
context 'user requests' do
|
||||
let(:current_user) { user }
|
||||
|
||||
context 'a user' do
|
||||
it 'returns name' do
|
||||
expect(subject).to eq('John Smith')
|
||||
end
|
||||
end
|
||||
|
||||
context 'a project bot' do
|
||||
let(:username) { requested_project_bot.username }
|
||||
|
||||
context 'when requester is nil' do
|
||||
let(:current_user) { nil }
|
||||
|
||||
it 'returns `****`' do
|
||||
expect(subject).to eq('****')
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns `****` for a regular user' do
|
||||
expect(subject).to eq('****')
|
||||
end
|
||||
|
||||
context 'when requester is a project maintainer' do
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
end
|
||||
|
||||
it 'returns name' do
|
||||
expect(subject).to eq('Project bot')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'admin requests', :enable_admin_mode do
|
||||
let(:current_user) { admin }
|
||||
|
||||
context 'a user' do
|
||||
it 'returns name' do
|
||||
expect(subject).to eq('John Smith')
|
||||
end
|
||||
end
|
||||
|
||||
context 'a project bot' do
|
||||
let(:username) { requested_project_bot.username }
|
||||
|
||||
it 'returns name' do
|
||||
expect(subject).to eq('Project bot')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'snippets field' do
|
||||
subject { described_class.fields['snippets'] }
|
||||
|
||||
|
|
|
@ -174,12 +174,26 @@ RSpec.describe SearchHelper do
|
|||
context "with a current project" do
|
||||
before do
|
||||
@project = create(:project, :repository)
|
||||
|
||||
allow(self).to receive(:can?).and_return(true)
|
||||
allow(self).to receive(:can?).with(user, :read_feature_flag, @project).and_return(false)
|
||||
end
|
||||
|
||||
it "includes project-specific sections", :aggregate_failures do
|
||||
it 'returns repository related labels based on users abilities', :aggregate_failures do
|
||||
expect(search_autocomplete_opts("Files").size).to eq(1)
|
||||
expect(search_autocomplete_opts("Commits").size).to eq(1)
|
||||
expect(search_autocomplete_opts("Network").size).to eq(1)
|
||||
expect(search_autocomplete_opts("Graph").size).to eq(1)
|
||||
|
||||
allow(self).to receive(:can?).with(user, :download_code, @project).and_return(false)
|
||||
|
||||
expect(search_autocomplete_opts("Files").size).to eq(0)
|
||||
expect(search_autocomplete_opts("Commits").size).to eq(0)
|
||||
|
||||
allow(self).to receive(:can?).with(user, :read_repository_graphs, @project).and_return(false)
|
||||
|
||||
expect(search_autocomplete_opts("Network").size).to eq(0)
|
||||
expect(search_autocomplete_opts("Graph").size).to eq(0)
|
||||
end
|
||||
|
||||
context 'when user does not have access to project' do
|
||||
|
|
|
@ -13,6 +13,28 @@ RSpec.describe ::API::Entities::Project do
|
|||
|
||||
subject(:json) { entity.as_json }
|
||||
|
||||
describe '.service_desk_address' do
|
||||
before do
|
||||
allow(project).to receive(:service_desk_enabled?).and_return(true)
|
||||
end
|
||||
|
||||
context 'when a user can admin issues' do
|
||||
before do
|
||||
project.add_reporter(current_user)
|
||||
end
|
||||
|
||||
it 'is present' do
|
||||
expect(json[:service_desk_address]).to be_present
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a user can not admin project' do
|
||||
it 'is empty' do
|
||||
expect(json[:service_desk_address]).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.shared_with_groups' do
|
||||
let(:group) { create(:group, :private) }
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ RSpec.describe API::Entities::User do
|
|||
subject { entity.as_json }
|
||||
|
||||
it 'exposes correct attributes' do
|
||||
expect(subject).to include(:bio, :location, :public_email, :skype, :linkedin, :twitter, :website_url, :organization, :job_title, :work_information, :pronouns)
|
||||
expect(subject).to include(:name, :bio, :location, :public_email, :skype, :linkedin, :twitter, :website_url, :organization, :job_title, :work_information, :pronouns)
|
||||
end
|
||||
|
||||
it 'exposes created_at if the current user can read the user profile' do
|
||||
|
@ -31,12 +31,51 @@ RSpec.describe API::Entities::User do
|
|||
expect(subject[:bot]).to be_falsey
|
||||
end
|
||||
|
||||
context 'with bot user' do
|
||||
let(:user) { create(:user, :security_bot) }
|
||||
context 'with project bot user' do
|
||||
let(:project) { create(:project) }
|
||||
let(:user) { create(:user, :project_bot, name: 'secret') }
|
||||
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
end
|
||||
|
||||
it 'exposes user as a bot' do
|
||||
expect(subject[:bot]).to eq(true)
|
||||
end
|
||||
|
||||
context 'when the requester is not an admin' do
|
||||
it 'does not expose project bot user name' do
|
||||
expect(subject[:name]).to eq('****')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the requester is nil' do
|
||||
let(:current_user) { nil }
|
||||
|
||||
it 'does not expose project bot user name' do
|
||||
expect(subject[:name]).to eq('****')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the requester is a project maintainer' do
|
||||
let(:current_user) { create(:user) }
|
||||
|
||||
before do
|
||||
project.add_maintainer(current_user)
|
||||
end
|
||||
|
||||
it 'exposes project bot user name' do
|
||||
expect(subject[:name]).to eq('secret')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the requester is an admin' do
|
||||
let(:current_user) { create(:user, :admin) }
|
||||
|
||||
it 'exposes project bot user name', :enable_admin_mode do
|
||||
expect(subject[:name]).to eq('secret')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'exposes local_time' do
|
||||
|
|
|
@ -139,4 +139,20 @@ RSpec.describe Banzai::Filter::FrontMatterFilter do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'fails fast for strings with many spaces' do
|
||||
content = "coding:" + " " * 50_000 + ";"
|
||||
|
||||
expect do
|
||||
Timeout.timeout(3.seconds) { filter(content) }
|
||||
end.not_to raise_error
|
||||
end
|
||||
|
||||
it 'fails fast for strings with many newlines' do
|
||||
content = "coding:\n" + ";;;" + "\n" * 10_000 + "x"
|
||||
|
||||
expect do
|
||||
Timeout.timeout(3.seconds) { filter(content) }
|
||||
end.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
|
|
@ -32,6 +32,15 @@ RSpec.describe Gitlab::Checks::BranchCheck do
|
|||
expect { subject.validate! }.not_to raise_error
|
||||
end
|
||||
|
||||
context "deleting a hexadecimal branch" do
|
||||
let(:newrev) { "0000000000000000000000000000000000000000" }
|
||||
let(:ref) { "refs/heads/267208abfe40e546f5e847444276f7d43a39503e" }
|
||||
|
||||
it "doesn't prohibit the deletion of a hexadecimal branch name" do
|
||||
expect { subject.validate! }.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
context "the feature flag is disabled" do
|
||||
it "doesn't prohibit a 40-character hexadecimal branch name" do
|
||||
stub_feature_flags(prohibit_hexadecimal_branch_names: false)
|
||||
|
|
|
@ -51,9 +51,17 @@ RSpec.describe Gitlab::CurrentSettings do
|
|||
it { is_expected.to be_truthy }
|
||||
end
|
||||
|
||||
context 'when new users are set to external' do
|
||||
before do
|
||||
create(:application_setting, user_default_external: true)
|
||||
end
|
||||
|
||||
it { is_expected.to be_truthy }
|
||||
end
|
||||
|
||||
context 'when there are no restrictions' do
|
||||
before do
|
||||
create(:application_setting, domain_allowlist: [], email_restrictions_enabled: false, require_admin_approval_after_user_signup: false)
|
||||
create(:application_setting, domain_allowlist: [], email_restrictions_enabled: false, require_admin_approval_after_user_signup: false, user_default_external: false)
|
||||
end
|
||||
|
||||
it { is_expected.to be_falsey }
|
||||
|
|
|
@ -47,14 +47,14 @@ RSpec.describe Gitlab::Diff::Formatters::TextFormatter do
|
|||
|
||||
describe "#==" do
|
||||
it "is false when the line_range changes" do
|
||||
formatter_1 = described_class.new(base.merge(line_range: { start_line_code: "foo", end_line_code: "bar" }))
|
||||
formatter_2 = described_class.new(base.merge(line_range: { start_line_code: "foo", end_line_code: "baz" }))
|
||||
formatter_1 = described_class.new(base.merge(line_range: { "start": { "line_code" => "foo" }, "end": { "line_code" => "bar" } }))
|
||||
formatter_2 = described_class.new(base.merge(line_range: { "start": { "line_code" => "foo" }, "end": { "line_code" => "baz" } }))
|
||||
|
||||
expect(formatter_1).not_to eq(formatter_2)
|
||||
end
|
||||
|
||||
it "is true when the line_range doesn't change" do
|
||||
attrs = base.merge({ line_range: { start_line_code: "foo", end_line_code: "baz" } })
|
||||
attrs = base.merge({ line_range: { start: { line_code: "foo" }, end: { line_code: "baz" } } })
|
||||
formatter_1 = described_class.new(attrs)
|
||||
formatter_2 = described_class.new(attrs)
|
||||
|
||||
|
|
|
@ -215,6 +215,16 @@ RSpec.describe Gitlab::Diff::LinesUnfolder do
|
|||
build(:text_diff_position, old_line: 43, new_line: 40)
|
||||
end
|
||||
|
||||
context 'old_line is an invalid number' do
|
||||
let(:position) do
|
||||
build(:text_diff_position, old_line: "foo", new_line: 40)
|
||||
end
|
||||
|
||||
it 'fails gracefully' do
|
||||
expect(subject.unfolded_diff_lines).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'blob lines' do
|
||||
let(:expected_blob_lines) do
|
||||
[[40, 40, " \"config-opts\": [ \"--disable-introspection\" ],"],
|
||||
|
|
|
@ -295,8 +295,12 @@ RSpec.describe Gitlab::Diff::PositionTracer::LineStrategy, :clean_gitlab_redis_c
|
|||
new_path: file_name,
|
||||
new_line: 2,
|
||||
line_range: {
|
||||
"start_line_code" => 1,
|
||||
"end_line_code" => 2
|
||||
"start" => {
|
||||
"line_code" => 1
|
||||
},
|
||||
"end" => {
|
||||
"line_code" => 2
|
||||
}
|
||||
}
|
||||
)
|
||||
end
|
||||
|
@ -575,8 +579,12 @@ RSpec.describe Gitlab::Diff::PositionTracer::LineStrategy, :clean_gitlab_redis_c
|
|||
new_path: file_name,
|
||||
new_line: 2,
|
||||
line_range: {
|
||||
"start_line_code" => 1,
|
||||
"end_line_code" => 2
|
||||
"start" => {
|
||||
"line_code" => 1
|
||||
},
|
||||
"end" => {
|
||||
"line_code" => 2
|
||||
}
|
||||
}
|
||||
)
|
||||
end
|
||||
|
|
|
@ -79,5 +79,30 @@ RSpec.describe Gitlab::GitAccessWiki do
|
|||
let(:message) { include('wiki') }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the actor is a deploy token' do
|
||||
let_it_be(:actor) { create(:deploy_token, projects: [project]) }
|
||||
let_it_be(:user) { actor }
|
||||
|
||||
before do
|
||||
project.project_feature.update_attribute(:wiki_access_level, wiki_access_level)
|
||||
end
|
||||
|
||||
subject { access.check('git-upload-pack', changes) }
|
||||
|
||||
context 'when the wiki is enabled' do
|
||||
let(:wiki_access_level) { ProjectFeature::ENABLED }
|
||||
|
||||
it { expect { subject }.not_to raise_error }
|
||||
end
|
||||
|
||||
context 'when the wiki is disabled' do
|
||||
let(:wiki_access_level) { ProjectFeature::DISABLED }
|
||||
|
||||
it_behaves_like 'forbidden git access' do
|
||||
let(:message) { 'You are not allowed to download files from this wiki.' }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -267,6 +267,66 @@ RSpec.describe Gitlab::ImportExport::MembersMapper do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when importer is not an admin' do
|
||||
let(:user) { create(:user) }
|
||||
let(:group) { create(:group) }
|
||||
let(:members_mapper) do
|
||||
described_class.new(
|
||||
exported_members: [], user: user, importable: importable)
|
||||
end
|
||||
|
||||
shared_examples_for 'it fetches the access level from parent group' do
|
||||
before do
|
||||
group.add_users([user], group_access_level)
|
||||
end
|
||||
|
||||
it "and resolves it correctly" do
|
||||
members_mapper.map
|
||||
expect(member_class.find_by_user_id(user.id).access_level).to eq(resolved_access_level)
|
||||
end
|
||||
end
|
||||
|
||||
context 'and the imported project is part of a group' do
|
||||
let(:importable) { create(:project, namespace: group) }
|
||||
let(:member_class) { ProjectMember }
|
||||
|
||||
it_behaves_like 'it fetches the access level from parent group' do
|
||||
let(:group_access_level) { GroupMember::DEVELOPER }
|
||||
let(:resolved_access_level) { ProjectMember::DEVELOPER }
|
||||
end
|
||||
|
||||
it_behaves_like 'it fetches the access level from parent group' do
|
||||
let(:group_access_level) { GroupMember::MAINTAINER }
|
||||
let(:resolved_access_level) { ProjectMember::MAINTAINER }
|
||||
end
|
||||
|
||||
it_behaves_like 'it fetches the access level from parent group' do
|
||||
let(:group_access_level) { GroupMember::OWNER }
|
||||
let(:resolved_access_level) { ProjectMember::MAINTAINER }
|
||||
end
|
||||
end
|
||||
|
||||
context 'and the imported group is part of another group' do
|
||||
let(:importable) { create(:group, parent: group) }
|
||||
let(:member_class) { GroupMember }
|
||||
|
||||
it_behaves_like 'it fetches the access level from parent group' do
|
||||
let(:group_access_level) { GroupMember::DEVELOPER }
|
||||
let(:resolved_access_level) { GroupMember::DEVELOPER }
|
||||
end
|
||||
|
||||
it_behaves_like 'it fetches the access level from parent group' do
|
||||
let(:group_access_level) { GroupMember::MAINTAINER }
|
||||
let(:resolved_access_level) { GroupMember::MAINTAINER }
|
||||
end
|
||||
|
||||
it_behaves_like 'it fetches the access level from parent group' do
|
||||
let(:group_access_level) { GroupMember::OWNER }
|
||||
let(:resolved_access_level) { GroupMember::OWNER }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when importable is Group' do
|
||||
include_examples 'imports exported members' do
|
||||
let(:source_type) { 'Namespace' }
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::ImportExport::Project::RelationFactory, :use_clean_rails_memory_store_caching do
|
||||
let(:group) { create(:group) }
|
||||
let(:group) { create(:group).tap { |g| g.add_maintainer(importer_user) } }
|
||||
let(:project) { create(:project, :repository, group: group) }
|
||||
let(:members_mapper) { double('members_mapper').as_null_object }
|
||||
let(:admin) { create(:admin) }
|
||||
|
|
|
@ -675,6 +675,7 @@ RSpec.describe Gitlab::ImportExport::Project::TreeRestorer do
|
|||
# Project needs to be in a group for visibility level comparison
|
||||
# to happen
|
||||
group = create(:group)
|
||||
group.add_maintainer(user)
|
||||
project.group = group
|
||||
|
||||
project.create_import_data(data: { override_params: { visibility_level: Gitlab::VisibilityLevel::INTERNAL.to_s } })
|
||||
|
@ -716,13 +717,19 @@ RSpec.describe Gitlab::ImportExport::Project::TreeRestorer do
|
|||
end
|
||||
|
||||
context 'with a project that has a group' do
|
||||
let(:group) do
|
||||
create(:group, visibility_level: Gitlab::VisibilityLevel::PRIVATE).tap do |g|
|
||||
g.add_maintainer(user)
|
||||
end
|
||||
end
|
||||
|
||||
let!(:project) do
|
||||
create(:project,
|
||||
:builds_disabled,
|
||||
:issues_disabled,
|
||||
name: 'project',
|
||||
path: 'project',
|
||||
group: create(:group, visibility_level: Gitlab::VisibilityLevel::PRIVATE))
|
||||
group: group)
|
||||
end
|
||||
|
||||
before do
|
||||
|
@ -751,13 +758,14 @@ RSpec.describe Gitlab::ImportExport::Project::TreeRestorer do
|
|||
end
|
||||
|
||||
context 'with existing group models' do
|
||||
let(:group) { create(:group).tap { |g| g.add_maintainer(user) } }
|
||||
let!(:project) do
|
||||
create(:project,
|
||||
:builds_disabled,
|
||||
:issues_disabled,
|
||||
name: 'project',
|
||||
path: 'project',
|
||||
group: create(:group))
|
||||
group: group)
|
||||
end
|
||||
|
||||
before do
|
||||
|
@ -786,13 +794,14 @@ RSpec.describe Gitlab::ImportExport::Project::TreeRestorer do
|
|||
end
|
||||
|
||||
context 'with clashing milestones on IID' do
|
||||
let(:group) { create(:group).tap { |g| g.add_maintainer(user) } }
|
||||
let!(:project) do
|
||||
create(:project,
|
||||
:builds_disabled,
|
||||
:issues_disabled,
|
||||
name: 'project',
|
||||
path: 'project',
|
||||
group: create(:group))
|
||||
group: group)
|
||||
end
|
||||
|
||||
before do
|
||||
|
@ -871,7 +880,7 @@ RSpec.describe Gitlab::ImportExport::Project::TreeRestorer do
|
|||
context 'with group visibility' do
|
||||
before do
|
||||
group = create(:group, visibility_level: group_visibility)
|
||||
|
||||
group.add_users([user], GroupMember::MAINTAINER)
|
||||
project.update(group: group)
|
||||
end
|
||||
|
||||
|
|
|
@ -105,7 +105,7 @@ RSpec.describe Gitlab::ImportExport::RelationTreeRestorer do
|
|||
it_behaves_like 'import project successfully'
|
||||
|
||||
context 'logging of relations creation' do
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:group) { create(:group).tap { |g| g.add_maintainer(user) } }
|
||||
let_it_be(:importable) do
|
||||
create(:project, :builds_enabled, :issues_disabled, name: 'project', path: 'project', group: group)
|
||||
end
|
||||
|
@ -122,7 +122,7 @@ RSpec.describe Gitlab::ImportExport::RelationTreeRestorer do
|
|||
|
||||
context 'when inside a group' do
|
||||
let_it_be(:group) do
|
||||
create(:group, :disabled_and_unoverridable)
|
||||
create(:group, :disabled_and_unoverridable).tap { |g| g.add_maintainer(user) }
|
||||
end
|
||||
|
||||
before do
|
||||
|
@ -155,7 +155,7 @@ RSpec.describe Gitlab::ImportExport::RelationTreeRestorer do
|
|||
|
||||
context 'when restoring a group' do
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:importable) { create(:group, parent: group) }
|
||||
let_it_be(:importable) { create(:group, parent: group).tap { |g| g.add_owner(user) } }
|
||||
|
||||
let(:path) { 'spec/fixtures/lib/gitlab/import_export/group_exports/no_children/group.json' }
|
||||
let(:importable_name) { nil }
|
||||
|
|
|
@ -352,6 +352,14 @@ RSpec.describe Gitlab::QuickActions::Extractor do
|
|||
expect(commands).to eq(expected_commands)
|
||||
expect(msg).to eq expected_msg
|
||||
end
|
||||
|
||||
it 'fails fast for strings with many newlines' do
|
||||
msg = '`' + "\n" * 100_000
|
||||
|
||||
expect do
|
||||
Timeout.timeout(3.seconds) { extractor.extract_commands(msg) }
|
||||
end.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
describe '#redact_commands' do
|
||||
|
|
|
@ -344,6 +344,18 @@ RSpec.describe Gitlab::Regex do
|
|||
describe '.maven_version_regex' do
|
||||
subject { described_class.maven_version_regex }
|
||||
|
||||
it 'has no ReDoS issues with long strings' do
|
||||
Timeout.timeout(5) do
|
||||
expect(subject).to match("aaaaaaaa.aaaaaaaaa+aa-111111.11111111111111111111111111111111111111111111111111111111.11111111111111111111111111111111111111111111111111111111.11111111111111111111111111111111111111111111111111111111.11111111111111111111111111111111111111111111111111111111.11111111111111111111111111111111111111111111111111111111.11111111111111111111111111111111111111111111111111111111.11111111111111111111111111111111111111111111111111111111.11111111111111111111111111111111111111111111111111111111.11111111111111111111111111111111111111111111111111111111.11111111111111111111111111111111111111111111111111111111.11111111111111111111111111111111111111111111111111111111.11111111111111111111111111111111111111111111111111111111.11111111111111111111111111111111111111111111111111111111.11111111111111111111111111111111111111111111111111111111.11111111111111111111111111111111111111111111111111111111.11111111111111111111111111111111111111111111111111111111.11111111111111111111111111111111111111111111111111111111.11111111111111111111111111111111111111111111111111111111")
|
||||
end
|
||||
end
|
||||
|
||||
it 'has no ReDos issues with long strings ending with an exclamation mark' do
|
||||
Timeout.timeout(5) do
|
||||
expect(subject).not_to match('a' * 50000 + '!')
|
||||
end
|
||||
end
|
||||
|
||||
it { is_expected.to match('0')}
|
||||
it { is_expected.to match('1') }
|
||||
it { is_expected.to match('03') }
|
||||
|
@ -364,6 +376,7 @@ RSpec.describe Gitlab::Regex do
|
|||
it { is_expected.to match('703220b4e2cea9592caeb9f3013f6b1e5335c293') }
|
||||
it { is_expected.to match('RELEASE') }
|
||||
it { is_expected.not_to match('..1.2.3') }
|
||||
it { is_expected.not_to match('1.2.3..beta') }
|
||||
it { is_expected.not_to match(' 1.2.3') }
|
||||
it { is_expected.not_to match("1.2.3 \r\t") }
|
||||
it { is_expected.not_to match("\r\t 1.2.3") }
|
||||
|
|
|
@ -109,6 +109,21 @@ RSpec.describe Gitlab::SlashCommands::Deploy do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with extra spaces in the deploy command' do
|
||||
let(:regex_match) { described_class.match('deploy staging to production ') }
|
||||
|
||||
before do
|
||||
create(:ci_build, :manual, pipeline: pipeline, name: 'production', environment: 'production')
|
||||
create(:ci_build, :manual, pipeline: pipeline, name: 'not prod', environment: 'not prod')
|
||||
end
|
||||
|
||||
it 'deploys to production' do
|
||||
expect(subject[:text])
|
||||
.to start_with('Deployment started from staging to production')
|
||||
expect(subject[:response_type]).to be(:in_channel)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -119,5 +134,49 @@ RSpec.describe Gitlab::SlashCommands::Deploy do
|
|||
expect(match[:from]).to eq('staging')
|
||||
expect(match[:to]).to eq('production')
|
||||
end
|
||||
|
||||
it 'matches the environment with spaces in it' do
|
||||
match = described_class.match('deploy staging env to production env')
|
||||
|
||||
expect(match[:from]).to eq('staging env')
|
||||
expect(match[:to]).to eq('production env')
|
||||
end
|
||||
|
||||
it 'matches the environment name with surrounding spaces' do
|
||||
match = described_class.match('deploy staging to production ')
|
||||
|
||||
# The extra spaces are stripped later in the code
|
||||
expect(match[:from]).to eq('staging')
|
||||
expect(match[:to]).to eq('production')
|
||||
end
|
||||
|
||||
it 'returns nil for text that is not a deploy command' do
|
||||
match = described_class.match('foo bar')
|
||||
|
||||
expect(match).to be_nil
|
||||
end
|
||||
|
||||
it 'returns nil for a partial command' do
|
||||
match = described_class.match('deploy staging to ')
|
||||
|
||||
expect(match).to be_nil
|
||||
end
|
||||
|
||||
context 'with ReDoS attempts' do
|
||||
def duration_for(&block)
|
||||
start = Time.zone.now
|
||||
yield if block_given?
|
||||
Time.zone.now - start
|
||||
end
|
||||
|
||||
it 'has smaller than linear execution time growth with a malformed "to"' do
|
||||
Timeout.timeout(3.seconds) do
|
||||
sample1 = duration_for { described_class.match("deploy abc t" + "o" * 1000 + "X") }
|
||||
sample2 = duration_for { described_class.match("deploy abc t" + "o" * 4000 + "X") }
|
||||
|
||||
expect((sample2 / sample1) < 4).to be_truthy
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -118,7 +118,7 @@ RSpec.describe Gitlab::WikiPages::FrontMatterParser do
|
|||
MD
|
||||
end
|
||||
|
||||
it { is_expected.to have_attributes(reason: :not_mapping) }
|
||||
it { is_expected.to have_attributes(reason: :no_match) }
|
||||
end
|
||||
|
||||
context 'there is a string in the YAML block' do
|
||||
|
|
|
@ -102,6 +102,12 @@ RSpec.describe Sidebars::Projects::Menus::AnalyticsMenu do
|
|||
specify { is_expected.to be_nil }
|
||||
end
|
||||
|
||||
describe 'when a user does not have access to repository graphs' do
|
||||
let(:current_user) { guest }
|
||||
|
||||
specify { is_expected.to be_nil }
|
||||
end
|
||||
|
||||
describe 'when the user does not have access' do
|
||||
let(:current_user) { nil }
|
||||
|
||||
|
|
|
@ -289,7 +289,6 @@ RSpec.describe Packages::Package, type: :model do
|
|||
it { is_expected.to allow_value('1.1-beta-2').for(:version) }
|
||||
it { is_expected.to allow_value('1.2-SNAPSHOT').for(:version) }
|
||||
it { is_expected.to allow_value('12.1.2-2-1').for(:version) }
|
||||
it { is_expected.to allow_value('1.2.3..beta').for(:version) }
|
||||
it { is_expected.to allow_value('1.2.3-beta').for(:version) }
|
||||
it { is_expected.to allow_value('10.2.3-beta').for(:version) }
|
||||
it { is_expected.to allow_value('2.0.0.v200706041905-7C78EK9E_EkMNfNOd2d8qq').for(:version) }
|
||||
|
@ -297,6 +296,7 @@ RSpec.describe Packages::Package, type: :model do
|
|||
it { is_expected.to allow_value('703220b4e2cea9592caeb9f3013f6b1e5335c293').for(:version) }
|
||||
it { is_expected.to allow_value('RELEASE').for(:version) }
|
||||
it { is_expected.not_to allow_value('..1.2.3').for(:version) }
|
||||
it { is_expected.not_to allow_value('1.2.3..beta').for(:version) }
|
||||
it { is_expected.not_to allow_value(' 1.2.3').for(:version) }
|
||||
it { is_expected.not_to allow_value("1.2.3 \r\t").for(:version) }
|
||||
it { is_expected.not_to allow_value("\r\t 1.2.3").for(:version) }
|
||||
|
|
|
@ -13,13 +13,8 @@ RSpec.describe Preloaders::UserMaxAccessLevelInGroupsPreloader do
|
|||
|
||||
shared_examples 'executes N max member permission queries to the DB' do
|
||||
it 'executes the specified max membership queries' do
|
||||
queries = ActiveRecord::QueryRecorder.new do
|
||||
groups.each { |group| user.can?(:read_group, group) }
|
||||
end
|
||||
|
||||
max_queries = queries.log.grep(max_query_regex)
|
||||
|
||||
expect(max_queries.count).to eq(expected_query_count)
|
||||
expect { groups.each { |group| user.can?(:read_group, group) } }
|
||||
.to make_queries_matching(max_query_regex, expected_query_count)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -5,10 +5,11 @@ require 'spec_helper'
|
|||
RSpec.describe MergeRequestPolicy do
|
||||
include ExternalAuthorizationServiceHelpers
|
||||
|
||||
let(:guest) { create(:user) }
|
||||
let(:author) { create(:user) }
|
||||
let(:developer) { create(:user) }
|
||||
let(:non_team_member) { create(:user) }
|
||||
let_it_be(:guest) { create(:user) }
|
||||
let_it_be(:author) { create(:user) }
|
||||
let_it_be(:developer) { create(:user) }
|
||||
let_it_be(:non_team_member) { create(:user) }
|
||||
|
||||
let(:project) { create(:project, :public) }
|
||||
|
||||
def permissions(user, merge_request)
|
||||
|
@ -50,15 +51,31 @@ RSpec.describe MergeRequestPolicy do
|
|||
end
|
||||
|
||||
context 'when merge request is public' do
|
||||
context 'and user is anonymous' do
|
||||
let(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: author) }
|
||||
let(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: author) }
|
||||
|
||||
context 'and user is anonymous' do
|
||||
subject { permissions(nil, merge_request) }
|
||||
|
||||
it do
|
||||
is_expected.to be_disallowed(:create_todo, :update_subscription)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'the author, who became a guest' do
|
||||
subject { permissions(author, merge_request) }
|
||||
|
||||
it do
|
||||
is_expected.to be_allowed(:update_merge_request)
|
||||
end
|
||||
|
||||
it do
|
||||
is_expected.to be_allowed(:reopen_merge_request)
|
||||
end
|
||||
|
||||
it do
|
||||
is_expected.to be_allowed(:approve_merge_request)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when merge requests have been disabled' do
|
||||
|
@ -107,6 +124,12 @@ RSpec.describe MergeRequestPolicy do
|
|||
it_behaves_like 'a denied user'
|
||||
end
|
||||
|
||||
describe 'the author' do
|
||||
subject { author }
|
||||
|
||||
it_behaves_like 'a denied user'
|
||||
end
|
||||
|
||||
describe 'a developer' do
|
||||
subject { developer }
|
||||
|
||||
|
|
|
@ -488,5 +488,19 @@ RSpec.describe 'getting user information' do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'the user is project bot' do
|
||||
let(:user) { create(:user, :project_bot) }
|
||||
|
||||
before do
|
||||
post_graphql(query, current_user: current_user)
|
||||
end
|
||||
|
||||
context 'we only request basic fields' do
|
||||
let(:user_fields) { %i[id name username state web_url avatar_url] }
|
||||
|
||||
it_behaves_like 'a working graphql query'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -26,6 +26,35 @@ RSpec.describe API::Lint do
|
|||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when authenticated as external user' do
|
||||
let(:project) { create(:project) }
|
||||
let(:api_user) { create(:user, :external) }
|
||||
|
||||
context 'when reporter in a project' do
|
||||
before do
|
||||
project.add_reporter(api_user)
|
||||
end
|
||||
|
||||
it 'returns authorization failure' do
|
||||
post api('/ci/lint', api_user), params: { content: 'content' }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:unauthorized)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when developer in a project' do
|
||||
before do
|
||||
project.add_developer(api_user)
|
||||
end
|
||||
|
||||
it 'returns authorization success' do
|
||||
post api('/ci/lint', api_user), params: { content: 'content' }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when signup is enabled and not limited' do
|
||||
|
|
|
@ -224,7 +224,7 @@ RSpec.describe API::Projects do
|
|||
create(:project, :public, group: create(:group))
|
||||
end
|
||||
|
||||
it_behaves_like 'projects response without N + 1 queries', 0 do
|
||||
it_behaves_like 'projects response without N + 1 queries', 1 do
|
||||
let(:current_user) { user }
|
||||
let(:additional_project) { create(:project, :public, group: create(:group)) }
|
||||
end
|
||||
|
|
|
@ -372,30 +372,36 @@ RSpec.describe API::Todos do
|
|||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns an error if the issuable author does not have access' do
|
||||
project_1.add_guest(issuable.author)
|
||||
|
||||
post api("/projects/#{project_1.id}/#{issuable_type}/#{issuable.iid}/todo", issuable.author)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST :id/issuable_type/:issueable_id/todo' do
|
||||
context 'for an issue' do
|
||||
it_behaves_like 'an issuable', 'issues' do
|
||||
let_it_be(:issuable) do
|
||||
create(:issue, :confidential, author: author_1, project: project_1)
|
||||
end
|
||||
let_it_be(:issuable) do
|
||||
create(:issue, :confidential, project: project_1)
|
||||
end
|
||||
|
||||
it_behaves_like 'an issuable', 'issues'
|
||||
|
||||
it 'returns an error if the issue author does not have access' do
|
||||
post api("/projects/#{project_1.id}/issues/#{issuable.iid}/todo", issuable.author)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
|
||||
context 'for a merge request' do
|
||||
it_behaves_like 'an issuable', 'merge_requests' do
|
||||
let_it_be(:issuable) do
|
||||
create(:merge_request, :simple, source_project: project_1)
|
||||
end
|
||||
let_it_be(:issuable) do
|
||||
create(:merge_request, :simple, source_project: project_1)
|
||||
end
|
||||
|
||||
it_behaves_like 'an issuable', 'merge_requests'
|
||||
|
||||
it 'returns an error if the merge request author does not have access' do
|
||||
project_1.add_guest(issuable.author)
|
||||
|
||||
post api("/projects/#{project_1.id}/merge_requests/#{issuable.iid}/todo", issuable.author)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -24,6 +24,8 @@ RSpec.describe Ci::JobArtifacts::CreateService do
|
|||
def file_to_upload(path, params = {})
|
||||
upload = Tempfile.new('upload')
|
||||
FileUtils.copy(path, upload.path)
|
||||
# This is a workaround for https://github.com/docker/for-linux/issues/1015
|
||||
FileUtils.touch(upload.path)
|
||||
|
||||
UploadedFile.new(upload.path, **params)
|
||||
end
|
||||
|
|
|
@ -7,13 +7,15 @@ RSpec.describe ProtectedBranches::CreateService do
|
|||
let(:user) { project.owner }
|
||||
let(:params) do
|
||||
{
|
||||
name: 'master',
|
||||
name: name,
|
||||
merge_access_levels_attributes: [{ access_level: Gitlab::Access::MAINTAINER }],
|
||||
push_access_levels_attributes: [{ access_level: Gitlab::Access::MAINTAINER }]
|
||||
}
|
||||
end
|
||||
|
||||
describe '#execute' do
|
||||
let(:name) { 'master' }
|
||||
|
||||
subject(:service) { described_class.new(project, user, params) }
|
||||
|
||||
it 'creates a new protected branch' do
|
||||
|
@ -22,6 +24,41 @@ RSpec.describe ProtectedBranches::CreateService do
|
|||
expect(project.protected_branches.last.merge_access_levels.map(&:access_level)).to eq([Gitlab::Access::MAINTAINER])
|
||||
end
|
||||
|
||||
context 'when name has escaped HTML' do
|
||||
let(:name) { 'feature->test' }
|
||||
|
||||
it 'creates the new protected branch matching the unescaped version' do
|
||||
expect { service.execute }.to change(ProtectedBranch, :count).by(1)
|
||||
expect(project.protected_branches.last.name).to eq('feature->test')
|
||||
end
|
||||
|
||||
context 'and name contains HTML tags' do
|
||||
let(:name) { '<b>master</b>' }
|
||||
|
||||
it 'creates the new protected branch with sanitized name' do
|
||||
expect { service.execute }.to change(ProtectedBranch, :count).by(1)
|
||||
expect(project.protected_branches.last.name).to eq('master')
|
||||
end
|
||||
|
||||
context 'and contains unsafe HTML' do
|
||||
let(:name) { '<script>alert('foo');</script>' }
|
||||
|
||||
it 'does not create the new protected branch' do
|
||||
expect { service.execute }.not_to change(ProtectedBranch, :count)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when name contains unescaped HTML tags' do
|
||||
let(:name) { '<b>master</b>' }
|
||||
|
||||
it 'creates the new protected branch with sanitized name' do
|
||||
expect { service.execute }.to change(ProtectedBranch, :count).by(1)
|
||||
expect(project.protected_branches.last.name).to eq('master')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user does not have permission' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
|
|
|
@ -6,17 +6,50 @@ RSpec.describe ProtectedBranches::UpdateService do
|
|||
let(:protected_branch) { create(:protected_branch) }
|
||||
let(:project) { protected_branch.project }
|
||||
let(:user) { project.owner }
|
||||
let(:params) { { name: 'new protected branch name' } }
|
||||
let(:params) { { name: new_name } }
|
||||
|
||||
describe '#execute' do
|
||||
let(:new_name) { 'new protected branch name' }
|
||||
let(:result) { service.execute(protected_branch) }
|
||||
|
||||
subject(:service) { described_class.new(project, user, params) }
|
||||
|
||||
it 'updates a protected branch' do
|
||||
result = service.execute(protected_branch)
|
||||
|
||||
expect(result.reload.name).to eq(params[:name])
|
||||
end
|
||||
|
||||
context 'when name has escaped HTML' do
|
||||
let(:new_name) { 'feature->test' }
|
||||
|
||||
it 'updates protected branch name with unescaped HTML' do
|
||||
expect(result.reload.name).to eq('feature->test')
|
||||
end
|
||||
|
||||
context 'and name contains HTML tags' do
|
||||
let(:new_name) { '<b>master</b>' }
|
||||
|
||||
it 'updates protected branch name with sanitized name' do
|
||||
expect(result.reload.name).to eq('master')
|
||||
end
|
||||
|
||||
context 'and contains unsafe HTML' do
|
||||
let(:new_name) { '<script>alert('foo');</script>' }
|
||||
|
||||
it 'does not update the protected branch' do
|
||||
expect(result.reload.name).to eq(protected_branch.name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when name contains unescaped HTML tags' do
|
||||
let(:new_name) { '<b>master</b>' }
|
||||
|
||||
it 'updates protected branch name with sanitized name' do
|
||||
expect(result.reload.name).to eq('master')
|
||||
end
|
||||
end
|
||||
|
||||
context 'without admin_project permissions' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
|
|
|
@ -465,3 +465,14 @@ Rugged::Settings['search_path_global'] = Rails.root.join('tmp/tests').to_s
|
|||
|
||||
# Initialize FactoryDefault to use create_default helper
|
||||
TestProf::FactoryDefault.init
|
||||
|
||||
module TouchRackUploadedFile
|
||||
def initialize_from_file_path(path)
|
||||
super
|
||||
|
||||
# This is a no-op workaround for https://github.com/docker/for-linux/issues/1015
|
||||
File.utime @tempfile.atime, @tempfile.mtime, @tempfile.path # rubocop:disable Gitlab/ModuleWithInstanceVariables
|
||||
end
|
||||
end
|
||||
|
||||
Rack::Test::UploadedFile.prepend(TouchRackUploadedFile)
|
||||
|
|
|
@ -37,6 +37,10 @@ module Spec
|
|||
find_row(user.name)
|
||||
end
|
||||
|
||||
def find_username_row(user)
|
||||
find_row(user.username)
|
||||
end
|
||||
|
||||
def find_invited_member_row(email)
|
||||
find_row(email)
|
||||
end
|
||||
|
|
|
@ -374,6 +374,7 @@ module GraphqlHelpers
|
|||
allow_unlimited_graphql_depth if max_depth > 1
|
||||
allow_high_graphql_recursion
|
||||
allow_high_graphql_transaction_threshold
|
||||
allow_high_graphql_query_size
|
||||
|
||||
type = class_name.respond_to?(:kind) ? class_name : GitlabSchema.types[class_name.to_s]
|
||||
raise "#{class_name} is not a known type in the GitlabSchema" unless type
|
||||
|
@ -625,6 +626,10 @@ module GraphqlHelpers
|
|||
stub_const("Gitlab::QueryLimiting::Transaction::THRESHOLD", 1000)
|
||||
end
|
||||
|
||||
def allow_high_graphql_query_size
|
||||
stub_const('GraphqlController::MAX_QUERY_SIZE', 10_000_000)
|
||||
end
|
||||
|
||||
def node_array(data, extract_attribute = nil)
|
||||
data.map do |item|
|
||||
extract_attribute ? item['node'][extract_attribute] : item['node']
|
||||
|
|
|
@ -71,5 +71,38 @@ RSpec.shared_examples 'a valid diff positionable note' do |factory_on_commit|
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'schema validation' do
|
||||
where(:position_attrs) do
|
||||
[
|
||||
{ old_path: SecureRandom.alphanumeric(1001) },
|
||||
{ new_path: SecureRandom.alphanumeric(1001) },
|
||||
{ old_line: "foo" }, # this should be an integer
|
||||
{ new_line: "foo" }, # this should be an integer
|
||||
{ line_range: { "foo": "bar" } },
|
||||
{ line_range: { "line_code": SecureRandom.alphanumeric(101) } },
|
||||
{ line_range: { "type": SecureRandom.alphanumeric(101) } },
|
||||
{ line_range: { "old_line": "foo" } },
|
||||
{ line_range: { "new_line": "foo" } }
|
||||
]
|
||||
end
|
||||
|
||||
with_them do
|
||||
let(:position) do
|
||||
Gitlab::Diff::Position.new(
|
||||
{
|
||||
old_path: "files/ruby/popen.rb",
|
||||
new_path: "files/ruby/popen.rb",
|
||||
old_line: nil,
|
||||
new_line: 14,
|
||||
line_range: nil,
|
||||
diff_refs: diff_refs
|
||||
}.merge(position_attrs)
|
||||
)
|
||||
end
|
||||
|
||||
it { is_expected.to be_invalid }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue