Update upstream source from tag 'upstream/15.5.7+ds1'

Update to upstream version '15.5.7+ds1'
with Debian dir ab44def76f
This commit is contained in:
Mohammed Bilal 2023-01-10 11:25:35 +05:30
commit f142dbd410
102 changed files with 1639 additions and 823 deletions

View file

@ -2,6 +2,41 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 15.5.7 (2023-01-09)
### Security (10 changes)
- [Avoid regex with potential for poorly performing backtracking](gitlab-org/security/gitlab@c3f8d8c93e99ac3f226668086bfbf21739b02a0e) ([merge request](gitlab-org/security/gitlab!2989))
- [Protect web-hook url variables after changing URL](gitlab-org/security/gitlab@8a18fea752a2759938b4c3d28516b6ed9386404f) ([merge request](gitlab-org/security/gitlab!2978))
- [Limit the size of user agent to reduce ReDos attack](gitlab-org/security/gitlab@293db707009b7dd133a9a55b25892506013062fd) ([merge request](gitlab-org/security/gitlab!2991))
- [Only allow safe params for diff helper](gitlab-org/security/gitlab@0c5de464c1d062103d6bc81cca45f7298929ca68) ([merge request](gitlab-org/security/gitlab!2951))
- [Protect Sentry auth-token after changing URL](gitlab-org/security/gitlab@a2c3380748eb3aa36f23c74f1666c741fafec635) ([merge request](gitlab-org/security/gitlab!2986))
- [Delete project specific licenses when license policy is deleted](gitlab-org/security/gitlab@312a28196df206b501861b6528b4b6fcaf7cc686) ([merge request](gitlab-org/security/gitlab!2896))
- [Restrict user avatar availability based on visibility restrictions](gitlab-org/security/gitlab@f7b5c0a57b64c15edb0f555dd53c26b9d6147f0e) ([merge request](gitlab-org/security/gitlab!2973))
- [Policy change to read and destroy token without license for .com](gitlab-org/security/gitlab@b51bc20ba07d8ef3d339aeacd1b0f904521f4158) ([merge request](gitlab-org/security/gitlab!2914))
- [Restrict Grafana API access on public projects](gitlab-org/security/gitlab@d9798aa2d31ddef9ed6fedfc7b32bc8a8bac76bc) ([merge request](gitlab-org/security/gitlab!2959))
- [Fix "Race condition enables verified email forgery"](gitlab-org/security/gitlab@95e65f637ed193b9c8b3c39af58a9bc0d552bad2) ([merge request](gitlab-org/security/gitlab!2962))
## 15.5.6 (2022-12-07)
No changes.
## 15.5.5 (2022-11-30)
### Security (11 changes)
- [Send resolved_address param to gitaly during repository import](gitlab-org/security/gitlab@768edcdca74fa09f7ba50c324aacd86fb71ed7e7) ([merge request](gitlab-org/security/gitlab!2939))
- [Add size validation during nuspec file extraction](gitlab-org/security/gitlab@27f79d015684896b66e0418db253613e3efa1df7) ([merge request](gitlab-org/security/gitlab!2936))
- [Cross-site scripting in Jira Integration](gitlab-org/security/gitlab@efcb2fc3110b7cf997b3e1a1e173e6462a54f208) ([merge request](gitlab-org/security/gitlab!2931))
- [Protect web-hook secret tokens after changing URL](gitlab-org/security/gitlab@00b75ba0c52c10a578091ad89440e8ae78cbe066) ([merge request](gitlab-org/security/gitlab!2921))
- [Redact secret tokens from web-hook logs](gitlab-org/security/gitlab@27699db7e44e7808f5ec415860ed03c55ae554b0) ([merge request](gitlab-org/security/gitlab!2917))
- [Prevent unauthorized users from seeing Release information on tag pages](gitlab-org/security/gitlab@112d45bdba5e0d34f77eec1ffaf86443e28b2c8c) ([merge request](gitlab-org/security/gitlab!2926))
- [Update after_import to expire cache before removing prohibited branches](gitlab-org/security/gitlab@5e84ca50689dceb7614e181ee7addbc3671dc935) ([merge request](gitlab-org/security/gitlab!2904))
- [Deny all package permissions when group access is restricted by IP](gitlab-org/security/gitlab@23a8ba46641053317c45f58037499235438b5ad8) ([merge request](gitlab-org/security/gitlab!2901))
- [Redact user emails from project webhook data](gitlab-org/security/gitlab@9f49c4d34fffd598af19d2db548281847855f987) ([merge request](gitlab-org/security/gitlab!2907))
- [Disallow local URls for build_runner_session if dictated by app setting](gitlab-org/security/gitlab@087415cf7a780c97b1d4055590858a98c673c64b) ([merge request](gitlab-org/security/gitlab!2867))
- [Prevent token bypass for extenal authorisation](gitlab-org/security/gitlab@96a6193a6e03bd1f76c2792cca404d2e672dfcf4) ([merge request](gitlab-org/security/gitlab!2884))
## 15.5.4 (2022-11-11) ## 15.5.4 (2022-11-11)
### Fixed (3 changes) ### Fixed (3 changes)

View file

@ -1 +1 @@
15.5.4 15.5.7

View file

@ -1 +1 @@
1.62.0 15.5.7

View file

@ -503,7 +503,7 @@ gem 'ssh_data', '~> 1.3'
gem 'spamcheck', '~> 1.0.0' gem 'spamcheck', '~> 1.0.0'
# Gitaly GRPC protocol definitions # Gitaly GRPC protocol definitions
gem 'gitaly', '~> 15.4.0-rc2' gem 'gitaly', '~> 15.5.2'
# KAS GRPC protocol definitions # KAS GRPC protocol definitions
gem 'kas-grpc', '~> 0.0.2' gem 'kas-grpc', '~> 0.0.2'

View file

@ -198,7 +198,7 @@
{"name":"gettext_i18n_rails","version":"1.8.0","platform":"ruby","checksum":"95e5cf8440b1e08705b27f2bccb56143272c5a7a0dabcf54ea1bd701140a496f"}, {"name":"gettext_i18n_rails","version":"1.8.0","platform":"ruby","checksum":"95e5cf8440b1e08705b27f2bccb56143272c5a7a0dabcf54ea1bd701140a496f"},
{"name":"gettext_i18n_rails_js","version":"1.3.0","platform":"ruby","checksum":"5d10afe4be3639bff78c50a56768c20f39aecdabc580c08aa45573911c2bd687"}, {"name":"gettext_i18n_rails_js","version":"1.3.0","platform":"ruby","checksum":"5d10afe4be3639bff78c50a56768c20f39aecdabc580c08aa45573911c2bd687"},
{"name":"git","version":"1.11.0","platform":"ruby","checksum":"7e95ba4da8298a0373ef1a6862aa22007d761f3c8274b675aa787966fecea0f1"}, {"name":"git","version":"1.11.0","platform":"ruby","checksum":"7e95ba4da8298a0373ef1a6862aa22007d761f3c8274b675aa787966fecea0f1"},
{"name":"gitaly","version":"15.4.0.pre.rc2","platform":"ruby","checksum":"48764528a730605a46f00cf86c7cfcb92d25f4f3d8cb9e09557baac3e9f3f8e3"}, {"name":"gitaly","version":"15.5.2","platform":"ruby","checksum":"62babe0596a4505bf95051ea50f17160055e6cf6cacf209273691542120d7881"},
{"name":"github-markup","version":"1.7.0","platform":"ruby","checksum":"97eb27c70662d9cc1d5997cd6c99832026fae5d4913b5dce1ce6c9f65078e69d"}, {"name":"github-markup","version":"1.7.0","platform":"ruby","checksum":"97eb27c70662d9cc1d5997cd6c99832026fae5d4913b5dce1ce6c9f65078e69d"},
{"name":"gitlab","version":"4.16.1","platform":"ruby","checksum":"13fd7059cbdad5a1a21b15fa2cf9070b97d92e27f8c688581fe3d84dc038074f"}, {"name":"gitlab","version":"4.16.1","platform":"ruby","checksum":"13fd7059cbdad5a1a21b15fa2cf9070b97d92e27f8c688581fe3d84dc038074f"},
{"name":"gitlab-chronic","version":"0.10.5","platform":"ruby","checksum":"f80f18dc699b708870a80685243331290bc10cfeedb6b99c92219722f729c875"}, {"name":"gitlab-chronic","version":"0.10.5","platform":"ruby","checksum":"f80f18dc699b708870a80685243331290bc10cfeedb6b99c92219722f729c875"},

View file

@ -547,7 +547,7 @@ GEM
rails (>= 3.2.0) rails (>= 3.2.0)
git (1.11.0) git (1.11.0)
rchardet (~> 1.8) rchardet (~> 1.8)
gitaly (15.4.0.pre.rc2) gitaly (15.5.2)
grpc (~> 1.0) grpc (~> 1.0)
github-markup (1.7.0) github-markup (1.7.0)
gitlab (4.16.1) gitlab (4.16.1)
@ -1622,7 +1622,7 @@ DEPENDENCIES
gettext (~> 3.3) gettext (~> 3.3)
gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3) gettext_i18n_rails_js (~> 1.3)
gitaly (~> 15.4.0.pre.rc2) gitaly (~> 15.5.2)
github-markup (~> 1.7.0) github-markup (~> 1.7.0)
gitlab-chronic (~> 0.10.5) gitlab-chronic (~> 0.10.5)
gitlab-dangerfiles (~> 3.5.2) gitlab-dangerfiles (~> 3.5.2)

View file

@ -1 +1 @@
15.5.4 15.5.7

View file

@ -57,7 +57,7 @@ module PageLimiter
# Record the page limit being hit in Prometheus # Record the page limit being hit in Prometheus
def record_page_limit_interception def record_page_limit_interception
dd = DeviceDetector.new(request.user_agent) dd = Gitlab::SafeDeviceDetector.new(request.user_agent)
Gitlab::Metrics.counter(:gitlab_page_out_of_bounds, Gitlab::Metrics.counter(:gitlab_page_out_of_bounds,
controller: params[:controller], controller: params[:controller],

View file

@ -3,8 +3,6 @@
class Profiles::PersonalAccessTokensController < Profiles::ApplicationController class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
feature_category :authentication_and_authorization feature_category :authentication_and_authorization
before_action :check_personal_access_tokens_enabled
def index def index
set_index_vars set_index_vars
scopes = params[:scopes].split(',').map(&:squish).select(&:present?).map(&:to_sym) unless params[:scopes].nil? scopes = params[:scopes].split(',').map(&:squish).select(&:present?).map(&:to_sym) unless params[:scopes].nil?
@ -85,8 +83,4 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
def page def page
(params[:page] || 1).to_i (params[:page] || 1).to_i
end end
def check_personal_access_tokens_enabled
render_404 if Gitlab::CurrentSettings.personal_access_tokens_disabled?
end
end end

View file

@ -4,6 +4,8 @@ class Projects::GrafanaApiController < Projects::ApplicationController
include RenderServiceResults include RenderServiceResults
include MetricsDashboard include MetricsDashboard
before_action :authorize_read_grafana!, only: :proxy
feature_category :metrics feature_category :metrics
urgency :low urgency :low

View file

@ -52,6 +52,8 @@ class UploadsController < ApplicationController
# access to itself when a secret is given. # access to itself when a secret is given.
# For instance, user avatars are readable by anyone, # For instance, user avatars are readable by anyone,
# while temporary, user snippet uploads are not. # while temporary, user snippet uploads are not.
return false if !current_user && public_visibility_restricted?
!secret? || can?(current_user, :update_user, model) !secret? || can?(current_user, :update_user, model)
when Appearance when Appearance
true true

View file

@ -244,6 +244,10 @@ module DiffHelper
{} {}
end end
def params_with_whitespace
hide_whitespace? ? safe_params.except(:w) : safe_params.merge(w: 1)
end
private private
def diff_btn(title, name, selected) def diff_btn(title, name, selected)
@ -277,10 +281,6 @@ module DiffHelper
params[:w] == '1' params[:w] == '1'
end end
def params_with_whitespace
hide_whitespace? ? request.query_parameters.except(:w) : request.query_parameters.merge(w: 1)
end
def toggle_whitespace_link(url, options) def toggle_whitespace_link(url, options)
options[:class] = [*options[:class], 'btn gl-button btn-default'].join(' ') options[:class] = [*options[:class], 'btn gl-button btn-default'].join(' ')
link_to "#{hide_whitespace? ? 'Show' : 'Hide'} whitespace changes", url, class: options[:class] link_to "#{hide_whitespace? ? 'Show' : 'Hide'} whitespace changes", url, class: options[:class]

View file

@ -17,40 +17,26 @@ module SubmoduleHelper
url = File.join(Gitlab.config.gitlab.url, repository.project.full_path) url = File.join(Gitlab.config.gitlab.url, repository.project.full_path)
end end
if url =~ %r{([^/:]+)/([^/]+(?:\.git)?)\Z} namespace, project = extract_namespace_project(url)
namespace = Regexp.last_match(1)
project = Regexp.last_match(2)
gitlab_hosts = [Gitlab.config.gitlab.url,
Gitlab.config.gitlab_shell.ssh_path_prefix]
gitlab_hosts.each do |host| if namespace.blank? || project.blank?
if url.start_with?(host) return [sanitize_submodule_url(url), nil, nil]
namespace, _, project = url.sub(host, '').rpartition('/') end
break
end
end
namespace.delete_prefix!('/') if self_url?(url, namespace, project)
project.rstrip! [
project.delete_suffix!('.git') url_helpers.namespace_project_path(namespace, project),
url_helpers.namespace_project_tree_path(namespace, project, submodule_item_id),
if self_url?(url, namespace, project) (url_helpers.namespace_project_compare_path(namespace, project, to: submodule_item_id, from: old_submodule_item_id) if old_submodule_item_id)
[ ]
url_helpers.namespace_project_path(namespace, project), elsif relative_self_url?(url)
url_helpers.namespace_project_tree_path(namespace, project, submodule_item_id), relative_self_links(url, submodule_item_id, old_submodule_item_id, repository.project)
(url_helpers.namespace_project_compare_path(namespace, project, to: submodule_item_id, from: old_submodule_item_id) if old_submodule_item_id) elsif gist_github_dot_com_url?(url)
] gist_github_com_tree_links(namespace, project, submodule_item_id)
elsif relative_self_url?(url) elsif github_dot_com_url?(url)
relative_self_links(url, submodule_item_id, old_submodule_item_id, repository.project) github_com_tree_links(namespace, project, submodule_item_id, old_submodule_item_id)
elsif gist_github_dot_com_url?(url) elsif gitlab_dot_com_url?(url)
gist_github_com_tree_links(namespace, project, submodule_item_id) gitlab_com_tree_links(namespace, project, submodule_item_id, old_submodule_item_id)
elsif github_dot_com_url?(url)
github_com_tree_links(namespace, project, submodule_item_id, old_submodule_item_id)
elsif gitlab_dot_com_url?(url)
gitlab_com_tree_links(namespace, project, submodule_item_id, old_submodule_item_id)
else
[sanitize_submodule_url(url), nil, nil]
end
else else
[sanitize_submodule_url(url), nil, nil] [sanitize_submodule_url(url), nil, nil]
end end
@ -58,6 +44,30 @@ module SubmoduleHelper
protected protected
def extract_namespace_project(url)
namespace_fragment, _, project = url.rpartition('/')
namespace = namespace_fragment.rpartition(%r{[:/]}).last
return [nil, nil] unless project.present? && namespace.present?
gitlab_hosts = [Gitlab.config.gitlab.url,
Gitlab.config.gitlab_shell.ssh_path_prefix]
matching_host = gitlab_hosts.find do |host|
url.start_with?(host)
end
if matching_host
namespace, _, project = url.delete_prefix(matching_host).rpartition('/')
end
namespace.delete_prefix!('/')
project.rstrip!
project.delete_suffix!('.git')
[namespace, project]
end
def gist_github_dot_com_url?(url) def gist_github_dot_com_url?(url)
url =~ %r{gist\.github\.com[/:][^/]+/[^/]+\Z} url =~ %r{gist\.github\.com[/:][^/]+/[^/]+\Z}
end end

View file

@ -67,7 +67,7 @@ class ActiveSession
def self.set(user, request) def self.set(user, request)
Gitlab::Redis::Sessions.with do |redis| Gitlab::Redis::Sessions.with do |redis|
session_private_id = request.session.id.private_id session_private_id = request.session.id.private_id
client = DeviceDetector.new(request.user_agent) client = Gitlab::SafeDeviceDetector.new(request.user_agent)
timestamp = Time.current timestamp = Time.current
expiry = Settings.gitlab['session_expire_delay'] * 60 expiry = Settings.gitlab['session_expire_delay'] * 60

View file

@ -799,10 +799,6 @@ class ApplicationSetting < ApplicationRecord
::AsciidoctorExtensions::Kroki::SUPPORTED_DIAGRAM_NAMES.include?(diagram_type) ::AsciidoctorExtensions::Kroki::SUPPORTED_DIAGRAM_NAMES.include?(diagram_type)
end end
def personal_access_tokens_disabled?
false
end
private private
def parsed_grafana_url def parsed_grafana_url

View file

@ -13,14 +13,15 @@ module Ci
belongs_to :build, class_name: 'Ci::Build', inverse_of: :runner_session belongs_to :build, class_name: 'Ci::Build', inverse_of: :runner_session
validates :build, presence: true validates :build, presence: true
validates :url, addressable_url: { schemes: %w(https) } validates :url, public_url: { schemes: %w(https) }
def terminal_specification def terminal_specification
wss_url = Gitlab::UrlHelpers.as_wss(self.url) wss_url = Gitlab::UrlHelpers.as_wss(Addressable::URI.escape(self.url))
return {} unless wss_url.present? return {} unless wss_url.present?
wss_url = "#{wss_url}/exec" parsed_wss_url = URI.parse(wss_url)
channel_specification(wss_url, TERMINAL_SUBPROTOCOL) parsed_wss_url.path += '/exec'
channel_specification(parsed_wss_url, TERMINAL_SUBPROTOCOL)
end end
def service_specification(service: nil, path: nil, port: nil, subprotocols: nil) def service_specification(service: nil, path: nil, port: nil, subprotocols: nil)
@ -28,20 +29,21 @@ module Ci
port = port.presence || DEFAULT_PORT_NAME port = port.presence || DEFAULT_PORT_NAME
service = service.presence || DEFAULT_SERVICE_NAME service = service.presence || DEFAULT_SERVICE_NAME
url = "#{self.url}/proxy/#{service}/#{port}/#{path}" parsed_url = URI.parse(Addressable::URI.escape(self.url))
parsed_url.path += "/proxy/#{service}/#{port}/#{path}"
subprotocols = subprotocols.presence || ::Ci::BuildRunnerSession::TERMINAL_SUBPROTOCOL subprotocols = subprotocols.presence || ::Ci::BuildRunnerSession::TERMINAL_SUBPROTOCOL
channel_specification(url, subprotocols) channel_specification(parsed_url, subprotocols)
end end
private private
def channel_specification(url, subprotocol) def channel_specification(parsed_url, subprotocol)
return {} if subprotocol.blank? || url.blank? return {} if subprotocol.blank? || parsed_url.blank?
{ {
subprotocols: Array(subprotocol), subprotocols: Array(subprotocol),
url: url, url: Addressable::URI.unescape(parsed_url.to_s),
headers: { Authorization: [authorization.presence] }.compact, headers: { Authorization: [authorization.presence] }.compact,
ca_pem: certificate.presence ca_pem: certificate.presence
} }

View file

@ -41,6 +41,8 @@ class WebHook < ApplicationRecord
validate :no_missing_url_variables validate :no_missing_url_variables
after_initialize :initialize_url_variables after_initialize :initialize_url_variables
before_validation :reset_token
before_validation :reset_url_variables, unless: ->(hook) { hook.is_a?(ServiceHook) }
scope :executable, -> do scope :executable, -> do
next all unless Feature.enabled?(:web_hooks_disable_failed) next all unless Feature.enabled?(:web_hooks_disable_failed)
@ -200,6 +202,14 @@ class WebHook < ApplicationRecord
private private
def reset_token
self.token = nil if url_changed? && !encrypted_token_changed?
end
def reset_url_variables
self.url_variables = {} if url_changed? && !encrypted_url_variables_changed?
end
def web_hooks_disable_failed? def web_hooks_disable_failed?
self.class.web_hooks_disable_failed?(self) self.class.web_hooks_disable_failed?(self)
end end

View file

@ -48,6 +48,13 @@ class WebHookLog < ApplicationRecord
request_data == OVERSIZE_REQUEST_DATA request_data == OVERSIZE_REQUEST_DATA
end end
def request_headers
super unless web_hook.token?
super if self[:request_headers]['X-Gitlab-Token'] == _('[REDACTED]')
self[:request_headers].merge('X-Gitlab-Token' => _('[REDACTED]'))
end
private private
def obfuscate_basic_auth def obfuscate_basic_auth

View file

@ -98,7 +98,10 @@ module Integrations
def self.valid_jira_cloud_url?(url) def self.valid_jira_cloud_url?(url)
return false unless url.present? return false unless url.present?
!!URI(url).hostname&.end_with?(JIRA_CLOUD_HOST) uri = URI.parse(url)
uri.is_a?(URI::HTTPS) && !!uri.hostname&.end_with?(JIRA_CLOUD_HOST)
rescue URI::InvalidURIError
false
end end
def data_fields def data_fields

View file

@ -2145,8 +2145,8 @@ class Project < ApplicationRecord
end end
def after_import def after_import
repository.remove_prohibited_branches
repository.expire_content_cache repository.expire_content_cache
repository.remove_prohibited_branches
wiki.repository.expire_content_cache wiki.repository.expire_content_cache
DetectRepositoryLanguagesWorker.perform_async(id) DetectRepositoryLanguagesWorker.perform_async(id)

View file

@ -980,12 +980,12 @@ class Repository
end end
end end
def clone_as_mirror(url, http_authorization_header: "") def clone_as_mirror(url, http_authorization_header: "", resolved_address: "")
import_repository(url, http_authorization_header: http_authorization_header, mirror: true) import_repository(url, http_authorization_header: http_authorization_header, mirror: true, resolved_address: resolved_address)
end end
def fetch_as_mirror(url, forced: false, refmap: :all_refs, prune: true, http_authorization_header: "") def fetch_as_mirror(url, forced: false, refmap: :all_refs, prune: true, http_authorization_header: "", resolved_address: "")
fetch_remote(url, refmap: refmap, forced: forced, prune: prune, http_authorization_header: http_authorization_header) fetch_remote(url, refmap: refmap, forced: forced, prune: prune, http_authorization_header: http_authorization_header, resolved_address: resolved_address)
end end
def fetch_source_branch!(source_repository, source_branch, local_ref) def fetch_source_branch!(source_repository, source_branch, local_ref)

View file

@ -1574,7 +1574,7 @@ class User < ApplicationRecord
name: name, name: name,
username: username, username: username,
avatar_url: avatar_url(only_path: false), avatar_url: avatar_url(only_path: false),
email: public_email.presence || _('[REDACTED]') email: webhook_email
} }
end end

View file

@ -272,6 +272,9 @@ class GroupPolicy < Namespaces::GroupProjectNamespaceSharedPolicy
rule { can?(:admin_group) & resource_access_token_feature_available }.policy do rule { can?(:admin_group) & resource_access_token_feature_available }.policy do
enable :read_resource_access_tokens enable :read_resource_access_tokens
enable :destroy_resource_access_tokens enable :destroy_resource_access_tokens
end
rule { can?(:admin_group) & resource_access_token_creation_allowed }.policy do
enable :admin_setting_to_allow_project_access_token_creation enable :admin_setting_to_allow_project_access_token_creation
end end
@ -332,12 +335,16 @@ class GroupPolicy < Namespaces::GroupProjectNamespaceSharedPolicy
true true
end end
def resource_access_token_create_feature_available?
true
end
def can_read_group_member? def can_read_group_member?
!(@subject.private? && access_level == GroupMember::NO_ACCESS) !(@subject.private? && access_level == GroupMember::NO_ACCESS)
end end
def resource_access_token_creation_allowed? def resource_access_token_creation_allowed?
resource_access_token_feature_available? && group.root_ancestor.namespace_settings.resource_access_token_creation_allowed? resource_access_token_create_feature_available? && group.root_ancestor.namespace_settings.resource_access_token_creation_allowed?
end end
def valid_dependency_proxy_deploy_token def valid_dependency_proxy_deploy_token

View file

@ -25,3 +25,5 @@ module Packages
end end
end end
end end
Packages::Policies::GroupPolicy.prepend_mod_with('Packages::Policies::GroupPolicy')

View file

@ -52,3 +52,5 @@ module Packages
end end
end end
end end
Packages::Policies::ProjectPolicy.prepend_mod_with('Packages::Policies::ProjectPolicy')

View file

@ -157,7 +157,9 @@ class ProjectPolicy < BasePolicy
condition(:service_desk_enabled) { @subject.service_desk_enabled? } condition(:service_desk_enabled) { @subject.service_desk_enabled? }
with_scope :subject with_scope :subject
condition(:resource_access_token_feature_available) { resource_access_token_feature_available? } condition(:resource_access_token_feature_available) do
resource_access_token_feature_available?
end
condition(:resource_access_token_creation_allowed) { resource_access_token_creation_allowed? } condition(:resource_access_token_creation_allowed) { resource_access_token_creation_allowed? }
# We aren't checking `:read_issue` or `:read_merge_request` in this case # We aren't checking `:read_issue` or `:read_merge_request` in this case
@ -310,6 +312,8 @@ class ProjectPolicy < BasePolicy
rule { guest & can?(:download_code) }.enable :build_download_code rule { guest & can?(:download_code) }.enable :build_download_code
rule { guest & can?(:read_container_image) }.enable :build_read_container_image rule { guest & can?(:read_container_image) }.enable :build_read_container_image
rule { guest & ~public_project }.enable :read_grafana
rule { can?(:reporter_access) }.policy do rule { can?(:reporter_access) }.policy do
enable :admin_issue_board enable :admin_issue_board
enable :download_code enable :download_code
@ -342,6 +346,7 @@ class ProjectPolicy < BasePolicy
enable :read_package enable :read_package
enable :read_product_analytics enable :read_product_analytics
enable :read_ci_cd_analytics enable :read_ci_cd_analytics
enable :read_grafana
end end
# We define `:public_user_access` separately because there are cases in gitlab-ee # We define `:public_user_access` separately because there are cases in gitlab-ee
@ -794,7 +799,7 @@ class ProjectPolicy < BasePolicy
rule { project_bot }.enable :project_bot_access rule { project_bot }.enable :project_bot_access
rule { can?(:read_all_resources) & resource_access_token_feature_available }.enable :read_resource_access_tokens rule { can?(:read_all_resources) }.enable :read_resource_access_tokens
rule { can?(:admin_project) & resource_access_token_feature_available }.policy do rule { can?(:admin_project) & resource_access_token_feature_available }.policy do
enable :read_resource_access_tokens enable :read_resource_access_tokens
@ -915,12 +920,16 @@ class ProjectPolicy < BasePolicy
true true
end end
def resource_access_token_create_feature_available?
true
end
def resource_access_token_creation_allowed? def resource_access_token_creation_allowed?
group = project.group group = project.group
return true unless group # always enable for projects in personal namespaces return true unless group # always enable for projects in personal namespaces
resource_access_token_feature_available? && group.root_ancestor.namespace_settings.resource_access_token_creation_allowed? resource_access_token_create_feature_available? && group.root_ancestor.namespace_settings.resource_access_token_creation_allowed?
end end
def project def project

View file

@ -2,6 +2,8 @@
module ErrorTracking module ErrorTracking
class ListProjectsService < ErrorTracking::BaseService class ListProjectsService < ErrorTracking::BaseService
MASKED_TOKEN_REGEX = /\A\*+\z/.freeze
private private
def perform def perform
@ -21,23 +23,31 @@ module ErrorTracking
def project_error_tracking_setting def project_error_tracking_setting
@project_error_tracking_setting ||= begin @project_error_tracking_setting ||= begin
(super || project.build_error_tracking_setting).tap do |setting| (super || project.build_error_tracking_setting).tap do |setting|
url_changed = !setting.api_url&.start_with?(params[:api_host])
setting.api_url = ErrorTracking::ProjectErrorTrackingSetting.build_api_url_from( setting.api_url = ErrorTracking::ProjectErrorTrackingSetting.build_api_url_from(
api_host: params[:api_host], api_host: params[:api_host],
organization_slug: 'org', organization_slug: 'org',
project_slug: 'proj' project_slug: 'proj'
) )
setting.token = token(setting) setting.token = token(setting, url_changed)
setting.enabled = true setting.enabled = true
end end
end end
end end
def token(setting) def token(setting, url_changed)
return if url_changed && masked_token?
# Use param token if not masked, otherwise use database token # Use param token if not masked, otherwise use database token
return params[:token] unless /\A\*+\z/.match?(params[:token]) return params[:token] unless masked_token?
setting.token setting.token
end end
def masked_token?
MASKED_TOKEN_REGEX.match?(params[:token])
end
end end
end end

View file

@ -104,9 +104,15 @@ module Packages
entry = zip_file.glob('*.nuspec').first entry = zip_file.glob('*.nuspec').first
raise ExtractionError, 'nuspec file not found' unless entry raise ExtractionError, 'nuspec file not found' unless entry
raise ExtractionError, 'nuspec file too big' if entry.size > MAX_FILE_SIZE raise ExtractionError, 'nuspec file too big' if MAX_FILE_SIZE < entry.size
entry.get_input_stream.read Tempfile.open("nuget_extraction_package_file_#{@package_file_id}") do |file|
entry.extract(file.path) { true } # allow #extract to overwrite the file
file.unlink
file.read
end
rescue Zip::EntrySizeError => e
raise ExtractionError, "nuspec file has the wrong entry size: #{e.message}"
end end
end end

View file

@ -53,6 +53,8 @@ module Projects
private private
attr_reader :resolved_address
def after_execute_hook def after_execute_hook
# Defined in EE::Projects::ImportService # Defined in EE::Projects::ImportService
end end
@ -64,11 +66,7 @@ module Projects
def add_repository_to_project def add_repository_to_project
if project.external_import? && !unknown_url? if project.external_import? && !unknown_url?
begin begin
Gitlab::UrlBlocker.validate!( @resolved_address = get_resolved_address
project.import_url,
schemes: Project::VALID_IMPORT_PROTOCOLS,
ports: Project::VALID_IMPORT_PORTS
)
rescue Gitlab::UrlBlocker::BlockedUrlError => e rescue Gitlab::UrlBlocker::BlockedUrlError => e
raise e, s_("ImportProjects|Blocked import URL: %{message}") % { message: e.message } raise e, s_("ImportProjects|Blocked import URL: %{message}") % { message: e.message }
end end
@ -97,9 +95,9 @@ module Projects
if refmap if refmap
project.ensure_repository project.ensure_repository
project.repository.fetch_as_mirror(project.import_url, refmap: refmap) project.repository.fetch_as_mirror(project.import_url, refmap: refmap, resolved_address: resolved_address)
else else
project.repository.import_repository(project.import_url) project.repository.import_repository(project.import_url, resolved_address: resolved_address)
end end
rescue ::Gitlab::Git::CommandError => e rescue ::Gitlab::Git::CommandError => e
# Expire cache to prevent scenarios such as: # Expire cache to prevent scenarios such as:
@ -157,6 +155,26 @@ module Projects
def importer_imports_repository? def importer_imports_repository?
has_importer? && importer_class.try(:imports_repository?) has_importer? && importer_class.try(:imports_repository?)
end end
def get_resolved_address
Gitlab::UrlBlocker
.validate!(
project.import_url,
schemes: Project::VALID_IMPORT_PROTOCOLS,
ports: Project::VALID_IMPORT_PORTS,
dns_rebind_protection: dns_rebind_protection?)
.then do |(import_url, resolved_host)|
next '' if resolved_host.nil? || !import_url.scheme.in?(%w[http https])
import_url.host.to_s
end
end
def dns_rebind_protection?
return false if Gitlab.http_proxy_env?
Gitlab::CurrentSettings.dns_rebinding_protection_enabled?
end
end end
end end

View file

@ -13,6 +13,7 @@ module ResourceAccessTokens
return error("User does not have permission to create #{resource_type} access token") unless has_permission_to_create? return error("User does not have permission to create #{resource_type} access token") unless has_permission_to_create?
access_level = params[:access_level] || Gitlab::Access::MAINTAINER access_level = params[:access_level] || Gitlab::Access::MAINTAINER
return error("Could not provision owner access to project access token") if do_not_allow_owner_access_level_for_project_bot?(access_level)
user = create_user user = create_user
@ -107,7 +108,7 @@ module ResourceAccessTokens
end end
def create_membership(resource, user, access_level) def create_membership(resource, user, access_level)
resource.add_member(user, access_level, current_user: current_user, expires_at: params[:expires_at]) resource.add_member(user, access_level, expires_at: params[:expires_at])
end end
def log_event(token) def log_event(token)
@ -121,6 +122,12 @@ module ResourceAccessTokens
def success(access_token) def success(access_token)
ServiceResponse.success(payload: { access_token: access_token }) ServiceResponse.success(payload: { access_token: access_token })
end end
def do_not_allow_owner_access_level_for_project_bot?(access_level)
resource.is_a?(Project) &&
access_level == Gitlab::Access::OWNER &&
!current_user.can?(:manage_owners, resource)
end
end end
end end

View file

@ -31,6 +31,7 @@ module Users
assign_identity assign_identity
build_canonical_email build_canonical_email
reset_unconfirmed_email
if @user.save(validate: validate) && update_status if @user.save(validate: validate) && update_status
notify_success(user_exists) notify_success(user_exists)
@ -64,6 +65,13 @@ module Users
Users::UpdateCanonicalEmailService.new(user: @user).execute Users::UpdateCanonicalEmailService.new(user: @user).execute
end end
def reset_unconfirmed_email
return unless @user.persisted?
return unless @user.email_changed?
@user.update_column(:unconfirmed_email, nil)
end
def update_status def update_status
return true unless @status_params return true unless @status_params

View file

@ -24,6 +24,8 @@ module WebHooks
private private
def log_execution def log_execution
log_data[:request_headers]['X-Gitlab-Token'] = _('[REDACTED]') if hook.token?
WebHookLog.create!(web_hook: hook, **log_data) WebHookLog.create!(web_hook: hook, **log_data)
end end

View file

@ -1,7 +1,7 @@
- confirmation_link = confirmation_url(@resource, confirmation_token: @token) - confirmation_link = confirmation_url(@resource, confirmation_token: @token)
- if @resource.unconfirmed_email.present? || !@resource.created_recently? - if @resource.unconfirmed_email.present? || !@resource.created_recently?
#content #content
= email_default_heading(@resource.unconfirmed_email || @resource.email) = email_default_heading(@email)
%p= _('Click the link below to confirm your email address.') %p= _('Click the link below to confirm your email address.')
#cta #cta
= link_to _('Confirm your email address'), confirmation_link = link_to _('Confirm your email address'), confirmation_link

View file

@ -1,5 +1,5 @@
<% if @resource.unconfirmed_email.present? || !@resource.created_recently? %> <% if @resource.unconfirmed_email.present? || !@resource.created_recently? %>
<%= @resource.unconfirmed_email || @resource.email %>, <%= @email %>,
<%= _('Use the link below to confirm your email address.') %> <%= _('Use the link below to confirm your email address.') %>
<% else %> <% else %>
<% if Gitlab.com? %> <% if Gitlab.com? %>

View file

@ -51,18 +51,17 @@
= link_to profile_chat_names_path do = link_to profile_chat_names_path do
%strong.fly-out-top-item-name %strong.fly-out-top-item-name
= _('Chat') = _('Chat')
- unless Gitlab::CurrentSettings.personal_access_tokens_disabled? = nav_link(controller: :personal_access_tokens) do
= nav_link(controller: :personal_access_tokens) do = link_to profile_personal_access_tokens_path do
= link_to profile_personal_access_tokens_path do .nav-icon-container
.nav-icon-container = sprite_icon('token')
= sprite_icon('token') %span.nav-item-name
%span.nav-item-name = _('Access Tokens')
= _('Access Tokens') %ul.sidebar-sub-level-items.is-fly-out-only
%ul.sidebar-sub-level-items.is-fly-out-only = nav_link(controller: :personal_access_tokens, html_options: { class: "fly-out-top-item" } ) do
= nav_link(controller: :personal_access_tokens, html_options: { class: "fly-out-top-item" } ) do = link_to profile_personal_access_tokens_path do
= link_to profile_personal_access_tokens_path do %strong.fly-out-top-item-name
%strong.fly-out-top-item-name = _('Access Tokens')
= _('Access Tokens')
= nav_link(controller: :emails) do = nav_link(controller: :emails) do
= link_to profile_emails_path, data: { qa_selector: 'profile_emails_link' } do = link_to profile_emails_path, data: { qa_selector: 'profile_emails_link' } do
.nav-icon-container .nav-icon-container

View file

@ -1,4 +1,5 @@
.gl-text-secondary - if can?(current_user, :read_release, release)
= sprite_icon("rocket", size: 12) .gl-text-secondary
= _("Release") = sprite_icon("rocket", size: 12)
= link_to release.name, project_release_path(project, release), class: "gl-text-blue-600!" = _("Release")
= link_to release.name, project_release_path(project, release), class: "gl-text-blue-600!"

View file

@ -57,12 +57,13 @@
%pre.wrap{ data: { qa_selector: 'tag_message_content' } } %pre.wrap{ data: { qa_selector: 'tag_message_content' } }
= strip_signature(@tag.message) = strip_signature(@tag.message)
.gl-mb-3.gl-mt-3 - if can?(current_user, :read_release, @release)
- if @release&.description.present? .gl-mb-3.gl-mt-3
.description.md{ data: { qa_selector: 'tag_release_notes_content' } } - if @release&.description.present?
= markdown_field(@release, :description) .description.md{ data: { qa_selector: 'tag_release_notes_content' } }
- else = markdown_field(@release, :description)
= s_('TagsPage|This tag has no release notes.') - else
= s_('TagsPage|This tag has no release notes.')
- if can?(current_user, :admin_tag, @project) - if can?(current_user, :admin_tag, @project)
.js-delete-tag-modal .js-delete-tag-modal

View file

@ -55,9 +55,10 @@ To set up the Grafana API in Grafana:
1. Select **Save Changes**. 1. Select **Save Changes**.
NOTE: NOTE:
If the Grafana integration is enabled, any user with read access to the GitLab If the Grafana integration is enabled, users with the Reporter role on public
project can query metrics from the Prometheus instance. All requests proxied projects and the Guest role on non-public projects can query metrics from the
through GitLab are authenticated with the same Grafana Administrator API token. Prometheus instance. All requests proxied through GitLab are authenticated with
the same Grafana Administrator API token.
### Generate a link to a panel ### Generate a link to a panel

View file

@ -43,6 +43,9 @@ using Omnibus, learn to install a custom CA in the
Alternatively, learn where to install custom certificates by using Alternatively, learn where to install custom certificates by using
`openssl version -d`. `openssl version -d`.
When external authorization is enabled, [deploy tokens](../../project/deploy_tokens/index.md)
and [deploy keys](../../project/deploy_keys/index.md) can't be used for Git operations.
## Configuration ## Configuration
The external authorization service can be enabled by an administrator: The external authorization service can be enabled by an administrator:

View file

@ -48,9 +48,6 @@ You cannot use group access tokens to create other group, project, or personal a
Group access tokens inherit the [default prefix setting](../../admin_area/settings/account_and_limit_settings.md#personal-access-token-prefix) Group access tokens inherit the [default prefix setting](../../admin_area/settings/account_and_limit_settings.md#personal-access-token-prefix)
configured for personal access tokens. configured for personal access tokens.
NOTE:
Group access tokens are not FIPS compliant and creation and use are disabled when [FIPS mode](../../../development/fips_compliance.md) is enabled.
## Create a group access token using UI ## Create a group access token using UI
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214045) in GitLab 14.7. > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214045) in GitLab 14.7.

View file

@ -62,6 +62,7 @@ For most package types, the following credential types are valid:
NOTE: NOTE:
If you have not activated the "Packages" feature for your project at **Settings > General > Project features**, you will receive a 403 Forbidden response. If you have not activated the "Packages" feature for your project at **Settings > General > Project features**, you will receive a 403 Forbidden response.
Accessing package registry via deploy token is not available when external authorization is enabled.
## Use GitLab CI/CD to build packages ## Use GitLab CI/CD to build packages

View file

@ -45,9 +45,6 @@ For examples of how you can use a personal access token to authenticate with the
Alternately, GitLab administrators can use the API to create [impersonation tokens](../../api/index.md#impersonation-tokens). Alternately, GitLab administrators can use the API to create [impersonation tokens](../../api/index.md#impersonation-tokens).
Use impersonation tokens to automate authentication as a specific user. Use impersonation tokens to automate authentication as a specific user.
NOTE:
Personal access tokens are not FIPS compliant and creation and use are disabled when [FIPS mode](../../development/fips_compliance.md) is enabled.
## Create a personal access token ## Create a personal access token
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/348660) in GitLab 15.3, default expiration of 30 days is populated in the UI. > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/348660) in GitLab 15.3, default expiration of 30 days is populated in the UI.

View file

@ -18,6 +18,8 @@ Depending on your needs, you might want to use a [deploy token](../deploy_tokens
| Validity | Valid as long as it's registered and enabled. | Can be given an expiration date. | | Validity | Valid as long as it's registered and enabled. | Can be given an expiration date. |
| Registry access | Cannot access a package registry. | Can read from and write to a package registry. | | Registry access | Cannot access a package registry. | Can read from and write to a package registry. |
Deploy keys can't be used for Git operations if [external authorization](../../admin_area/settings/external_authorization.md) is enabled.
## Scope ## Scope
A deploy key has a defined scope when it is created: A deploy key has a defined scope when it is created:

View file

@ -41,6 +41,8 @@ You can create deploy tokens at either the project or group level:
By default, a deploy token does not expire. You can optionally set an expiry date when you create By default, a deploy token does not expire. You can optionally set an expiry date when you create
it. Expiry occurs at midnight UTC on that date. it. Expiry occurs at midnight UTC on that date.
Deploy tokens can't be used for Git operations and Package Registry operations if [external authorization](../../admin_area/settings/external_authorization.md) is enabled.
## Scope ## Scope
A deploy token's scope determines the actions it can perform. A deploy token's scope determines the actions it can perform.

View file

@ -48,9 +48,6 @@ You cannot use project access tokens to create other group, project, or personal
Project access tokens inherit the [default prefix setting](../../admin_area/settings/account_and_limit_settings.md#personal-access-token-prefix) Project access tokens inherit the [default prefix setting](../../admin_area/settings/account_and_limit_settings.md#personal-access-token-prefix)
configured for personal access tokens. configured for personal access tokens.
NOTE:
Project access tokens are not FIPS compliant and creation and use are disabled when [FIPS mode](../../../development/fips_compliance.md) is enabled.
## Create a project access token ## Create a project access token
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/89114) in GitLab 15.1, Owners can select Owner role for project access tokens. > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/89114) in GitLab 15.1, Owners can select Owner role for project access tokens.

View file

@ -94,7 +94,8 @@ module API
success Entities::Ci::JobRequest::Response success Entities::Ci::JobRequest::Response
http_codes [[201, 'Job was scheduled'], http_codes [[201, 'Job was scheduled'],
[204, 'No job for Runner'], [204, 'No job for Runner'],
[403, 'Forbidden']] [403, 'Forbidden'],
[409, 'Conflict']]
end end
params do params do
requires :token, type: String, desc: %q(Runner's authentication token) requires :token, type: String, desc: %q(Runner's authentication token)

View file

@ -165,6 +165,8 @@ module Gitlab
end end
def with_deploy_token(raw, &block) def with_deploy_token(raw, &block)
raise ::Gitlab::Auth::UnauthorizedError if Gitlab::ExternalAuthorization.enabled?
token = ::DeployToken.active.find_by_token(raw.password) token = ::DeployToken.active.find_by_token(raw.password)
return unless token return unless token

View file

@ -147,6 +147,7 @@ module Gitlab
# deploy tokens are accepted with deploy token headers and basic auth headers # deploy tokens are accepted with deploy token headers and basic auth headers
def deploy_token_from_request def deploy_token_from_request
return unless route_authentication_setting[:deploy_token_allowed] return unless route_authentication_setting[:deploy_token_allowed]
return if Gitlab::ExternalAuthorization.enabled?
token = current_request.env[DEPLOY_TOKEN_HEADER].presence || parsed_oauth_token token = current_request.env[DEPLOY_TOKEN_HEADER].presence || parsed_oauth_token

View file

@ -855,7 +855,11 @@ module Gitlab
# no_tags - should we use --no-tags flag? # no_tags - should we use --no-tags flag?
# prune - should we use --prune flag? # prune - should we use --prune flag?
# check_tags_changed - should we ask gitaly to calculate whether any tags changed? # check_tags_changed - should we ask gitaly to calculate whether any tags changed?
def fetch_remote(url, refmap: nil, ssh_auth: nil, forced: false, no_tags: false, prune: true, check_tags_changed: false, http_authorization_header: "") # resolved_address - resolved IP address for provided URL
def fetch_remote( # rubocop:disable Metrics/ParameterLists
url,
refmap: nil, ssh_auth: nil, forced: false, no_tags: false, prune: true,
check_tags_changed: false, http_authorization_header: "", resolved_address: "")
wrapped_gitaly_errors do wrapped_gitaly_errors do
gitaly_repository_client.fetch_remote( gitaly_repository_client.fetch_remote(
url, url,
@ -866,16 +870,17 @@ module Gitlab
prune: prune, prune: prune,
check_tags_changed: check_tags_changed, check_tags_changed: check_tags_changed,
timeout: GITLAB_PROJECTS_TIMEOUT, timeout: GITLAB_PROJECTS_TIMEOUT,
http_authorization_header: http_authorization_header http_authorization_header: http_authorization_header,
resolved_address: resolved_address
) )
end end
end end
def import_repository(url, http_authorization_header: '', mirror: false) def import_repository(url, http_authorization_header: '', mirror: false, resolved_address: '')
raise ArgumentError, "don't use disk paths with import_repository: #{url.inspect}" if url.start_with?('.', '/') raise ArgumentError, "don't use disk paths with import_repository: #{url.inspect}" if url.start_with?('.', '/')
wrapped_gitaly_errors do wrapped_gitaly_errors do
gitaly_repository_client.import_repository(url, http_authorization_header: http_authorization_header, mirror: mirror) gitaly_repository_client.import_repository(url, http_authorization_header: http_authorization_header, mirror: mirror, resolved_address: resolved_address)
end end
end end

View file

@ -367,7 +367,7 @@ module Gitlab
end end
def deploy_key? def deploy_key?
actor.is_a?(DeployKey) actor.is_a?(DeployKey) && !Gitlab::ExternalAuthorization.enabled?
end end
def deploy_token def deploy_token
@ -375,7 +375,7 @@ module Gitlab
end end
def deploy_token? def deploy_token?
actor.is_a?(DeployToken) actor.is_a?(DeployToken) && !Gitlab::ExternalAuthorization.enabled?
end end
def ci? def ci?

View file

@ -78,7 +78,7 @@ module Gitlab
# rubocop: disable Metrics/ParameterLists # rubocop: disable Metrics/ParameterLists
# The `remote` parameter is going away soonish anyway, at which point the # The `remote` parameter is going away soonish anyway, at which point the
# Rubocop warning can be enabled again. # Rubocop warning can be enabled again.
def fetch_remote(url, refmap:, ssh_auth:, forced:, no_tags:, timeout:, prune: true, check_tags_changed: false, http_authorization_header: "") def fetch_remote(url, refmap:, ssh_auth:, forced:, no_tags:, timeout:, prune: true, check_tags_changed: false, http_authorization_header: "", resolved_address: "")
request = Gitaly::FetchRemoteRequest.new( request = Gitaly::FetchRemoteRequest.new(
repository: @gitaly_repo, repository: @gitaly_repo,
force: forced, force: forced,
@ -89,7 +89,8 @@ module Gitlab
remote_params: Gitaly::Remote.new( remote_params: Gitaly::Remote.new(
url: url, url: url,
mirror_refmaps: Array.wrap(refmap).map(&:to_s), mirror_refmaps: Array.wrap(refmap).map(&:to_s),
http_authorization_header: http_authorization_header http_authorization_header: http_authorization_header,
resolved_address: resolved_address
) )
) )
@ -145,12 +146,13 @@ module Gitlab
) )
end end
def import_repository(source, http_authorization_header: '', mirror: false) def import_repository(source, http_authorization_header: '', mirror: false, resolved_address: '')
request = Gitaly::CreateRepositoryFromURLRequest.new( request = Gitaly::CreateRepositoryFromURLRequest.new(
repository: @gitaly_repo, repository: @gitaly_repo,
url: source, url: source,
http_authorization_header: http_authorization_header, http_authorization_header: http_authorization_header,
mirror: mirror mirror: mirror,
resolved_address: resolved_address
) )
GitalyClient.call( GitalyClient.call(

View file

@ -57,7 +57,7 @@ module Gitlab
end end
def user_email(user) def user_email(user)
user.respond_to?(:email) ? user.email : "" user.respond_to?(:webhook_email) ? user.webhook_email : ""
end end
def event_specific_project_data(event) def event_specific_project_data(event)

View file

@ -43,7 +43,7 @@ module Gitlab
project_id: project.id, project_id: project.id,
user_username: project_member.user.username, user_username: project_member.user.username,
user_name: project_member.user.name, user_name: project_member.user.name,
user_email: project_member.user.email, user_email: project_member.user.webhook_email,
user_id: project_member.user.id, user_id: project_member.user.id,
access_level: project_member.human_access, access_level: project_member.human_access,
project_visibility: project.visibility project_visibility: project.visibility

View file

@ -0,0 +1,16 @@
# frozen_string_literal: true
# rubocop:disable Gitlab/NamespacedClass
require 'device_detector'
module Gitlab
class SafeDeviceDetector < ::DeviceDetector
USER_AGENT_MAX_SIZE = 1024
def initialize(user_agent)
super(user_agent)
@user_agent = user_agent && user_agent[0..USER_AGENT_MAX_SIZE]
end
end
end
# rubocop:enable Gitlab/NamespacedClass

View file

@ -36,14 +36,6 @@ RSpec.describe Profiles::PersonalAccessTokensController do
expect(created_token.expires_at).to eq(expires_at) expect(created_token.expires_at).to eq(expires_at)
end end
it 'does not allow creation when personal access tokens are disabled' do
allow(::Gitlab::CurrentSettings).to receive_messages(personal_access_tokens_disabled?: true)
post :create, params: { personal_access_token: token_attributes }
expect(response).to have_gitlab_http_status(:not_found)
end
it_behaves_like "#create access token" do it_behaves_like "#create access token" do
let(:url) { :create } let(:url) { :create }
end end
@ -78,14 +70,6 @@ RSpec.describe Profiles::PersonalAccessTokensController do
) )
end end
it 'returns 404 when personal access tokens are disabled' do
allow(::Gitlab::CurrentSettings).to receive_messages(personal_access_tokens_disabled?: true)
get :index
expect(response).to have_gitlab_http_status(:not_found)
end
context "access_token_pagination feature flag is enabled" do context "access_token_pagination feature flag is enabled" do
before do before do
stub_feature_flags(access_token_pagination: true) stub_feature_flags(access_token_pagination: true)

View file

@ -2,13 +2,20 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Projects::GrafanaApiController do RSpec.describe Projects::GrafanaApiController, feature_category: :metrics do
let_it_be(:project) { create(:project) } let_it_be(:project) { create(:project, :public) }
let_it_be(:user) { create(:user) } let_it_be(:reporter) { create(:user) }
let_it_be(:guest) { create(:user) }
let(:anonymous) { nil }
let(:user) { reporter }
before_all do
project.add_reporter(reporter)
project.add_guest(guest)
end
before do before do
project.add_reporter(user) sign_in(user) if user
sign_in(user)
end end
describe 'GET #proxy' do describe 'GET #proxy' do
@ -41,6 +48,39 @@ RSpec.describe Projects::GrafanaApiController do
end end
end end
shared_examples_for 'accessible' do
let(:service_result) { nil }
it 'returns non erroneous response' do
get :proxy, params: params
# We don't care about the specific code as long it's not an error.
expect(response).to have_gitlab_http_status(:no_content)
end
end
shared_examples_for 'not accessible' do
let(:service_result) { nil }
it 'returns 404 Not found' do
get :proxy, params: params
expect(response).to have_gitlab_http_status(:not_found)
expect(Grafana::ProxyService).not_to have_received(:new)
end
end
shared_examples_for 'login required' do
let(:service_result) { nil }
it 'redirects to login page' do
get :proxy, params: params
expect(response).to redirect_to(new_user_session_path)
expect(Grafana::ProxyService).not_to have_received(:new)
end
end
context 'with a successful result' do context 'with a successful result' do
let(:service_result) { { status: :success, body: '{}' } } let(:service_result) { { status: :success, body: '{}' } }
@ -96,6 +136,38 @@ RSpec.describe Projects::GrafanaApiController do
it_behaves_like 'error response', :bad_request it_behaves_like 'error response', :bad_request
end end
end end
context 'as guest' do
let(:user) { guest }
it_behaves_like 'not accessible'
end
context 'as anonymous' do
let(:user) { anonymous }
it_behaves_like 'not accessible'
end
context 'on a private project' do
let_it_be(:project) { create(:project, :private) }
before_all do
project.add_guest(guest)
end
context 'as anonymous' do
let(:user) { anonymous }
it_behaves_like 'login required'
end
context 'as guest' do
let(:user) { guest }
it_behaves_like 'accessible'
end
end
end end
describe 'GET #metrics_dashboard' do describe 'GET #metrics_dashboard' do

View file

@ -1380,7 +1380,7 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do
{ {
'Channel' => { 'Channel' => {
'Subprotocols' => ["terminal.gitlab.com"], 'Subprotocols' => ["terminal.gitlab.com"],
'Url' => 'wss://localhost/proxy/build/default_port/', 'Url' => 'wss://gitlab.example.com/proxy/build/default_port/',
'Header' => { 'Header' => {
'Authorization' => [nil] 'Authorization' => [nil]
}, },
@ -1536,7 +1536,8 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do
allow(Gitlab::Workhorse).to receive(:verify_api_request!).and_return(nil) allow(Gitlab::Workhorse).to receive(:verify_api_request!).and_return(nil)
expect(job.runner_session_url).to start_with('https://') expect(job.runner_session_url).to start_with('https://')
expect(Gitlab::Workhorse).to receive(:channel_websocket).with(a_hash_including(url: "wss://localhost/proxy/build/default_port/")) expect(Gitlab::Workhorse).to receive(:channel_websocket)
.with(a_hash_including(url: "wss://gitlab.example.com/proxy/build/default_port/"))
make_request make_request
end end

View file

@ -268,17 +268,35 @@ RSpec.describe UploadsController do
end end
context "when not signed in" do context "when not signed in" do
it "responds with status 200" do context "when restricted visibility level is not set to public" do
get :show, params: { model: "user", mounted_as: "avatar", id: user.id, filename: "dk.png" } before do
stub_application_setting(restricted_visibility_levels: [])
end
expect(response).to have_gitlab_http_status(:ok) it "responds with status 200" do
get :show, params: { model: "user", mounted_as: "avatar", id: user.id, filename: "dk.png" }
expect(response).to have_gitlab_http_status(:ok)
end
it_behaves_like 'content publicly cached' do
subject do
get :show, params: { model: 'user', mounted_as: 'avatar', id: user.id, filename: 'dk.png' }
response
end
end
end end
it_behaves_like 'content publicly cached' do context "when restricted visibility level is set to public" do
subject do before do
get :show, params: { model: 'user', mounted_as: 'avatar', id: user.id, filename: 'dk.png' } stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
end
response it "responds with status 401" do
get :show, params: { model: "user", mounted_as: "avatar", id: user.id, filename: "dk.png" }
expect(response).to have_gitlab_http_status(:unauthorized)
end end
end end
end end

View file

@ -716,7 +716,7 @@ FactoryBot.define do
trait :with_runner_session do trait :with_runner_session do
after(:build) do |build| after(:build) do |build|
build.build_runner_session(url: 'https://localhost') build.build_runner_session(url: 'https://gitlab.example.com')
end end
end end

View file

@ -6,6 +6,10 @@ FactoryBot.define do
enable_ssl_verification { false } enable_ssl_verification { false }
project project
trait :url_variables do
url_variables { { 'abc' => 'supers3cret' } }
end
trait :token do trait :token do
token { generate(:token) } token { generate(:token) }
end end

View file

@ -329,7 +329,7 @@ RSpec.describe 'Project' do
it 'has working links to submodules' do it 'has working links to submodules' do
click_link('645f6c4c') click_link('645f6c4c')
expect(page).to have_selector('[data-testid="branches-select"]', text: '645f6c4c82fd3f5e06f67134450a570b795e55a6') expect(page).to have_selector('.ref-selector', text: '645f6c4c82fd3f5e06f67134450a570b795e55a6')
end end
context 'for signed commit on default branch', :js do context 'for signed commit on default branch', :js do

View file

@ -53,6 +53,8 @@ RSpec.describe 'Developer views tags' do
end end
it 'views a specific tag page' do it 'views a specific tag page' do
create(:release, project: project, tag: 'v1.0.0', name: 'v1.0.0', description: nil)
click_on 'v1.0.0' click_on 'v1.0.0'
expect(page).to have_current_path( expect(page).to have_current_path(

View file

@ -1,67 +1,66 @@
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIGYzCCBUugAwIBAgIQAaQHyOeT/PBR4ioLKYneZDANBgkqhkiG9w0BAQsFADBY MIIGYjCCBUqgAwIBAgIQATfkha/xTr3pbLHVWlPq4jANBgkqhkiG9w0BAQsFADBY
MQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTEuMCwGA1UE MQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTEuMCwGA1UE
AxMlR2xvYmFsU2lnbiBBdGxhcyBSMyBEViBUTFMgQ0EgSDIgMjAyMTAeFw0yMTEw AxMlR2xvYmFsU2lnbiBBdGxhcyBSMyBEViBUTFMgQ0EgMjAyMiBRMzAeFw0yMjA3
MTgxODUwMDRaFw0yMjExMTkxODUwMDNaMBsxGTAXBgNVBAMMEGFib3V0LmdpdGxh MjIxOTQyMTFaFw0yMzA4MjMxOTQyMTBaMBsxGTAXBgNVBAMMEGFib3V0LmdpdGxh
Yi5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDWSo0eziN/0lq5 Yi5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDFFFQs8EITaWo5
dIcS7ZceJw2odzZeT0tRkcKEW8iagNul6JetrFlk6h5lxoLEu35+MK6/fWHNmt7u 0U18/mPTDLencU/7siJT/4P8oeDkemyx98wzK6vuNj/2JEZ3v1psKun5n8Pb/fHa
eQk7HS0uRipskAzeGrL1Hvk8EjIcHXXTxpRu7JqWOu7ZSXwNxW5cqn7L9/N2gYwt somKd/4icHgC4rnxrO6zayfb+cKzVghQe12Nj75lx6RtppqTgAmSOa3Tai5niICT
Jg/sfkv9AFQiNOdKrarKfbcBstxmra6rQbh5ggLG5UBT23N4ZrA3XnzvEx3+GjtO I8s3d2wsHtfEgqAavcD0/zdPIk25Ji7yfquldSthnlhQqI4Pm3OxTiyFj/V5ZhFl
u/a5izbk7FQP3gyXKyfm/SQRpNsytYa9jJqu5Hmyzfap5KaueOJbtJEOk8dR/HWR IWZLvQaENjBSDVZQDcaPdWwodfXNA8fJmqk7cTLQ9P9NgjWvva7acl+Yd6hOFzV0
i/gmAUevq62MNxorYbz8YU/P1468tS7iORkD31Tc2QWCMQSPya5qGaCGnz7dVgWy EllBl/WF1KB+YzGuHI0CQHT7sv3GW1lXeE2EqrWoSdLTOSAqm6y02DyE79d1xvG6
E1xTPbBXAgMBAAGjggNkMIIDYDAbBgNVHREEFDASghBhYm91dC5naXRsYWIuY29t XXfX5ILlAgMBAAGjggNjMIIDXzAbBgNVHREEFDASghBhYm91dC5naXRsYWIuY29t
MA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw MA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw
HQYDVR0OBBYEFJFVruwpjWeUfGJXl3m5grAjhAwPMFcGA1UdIARQME4wCAYGZ4EM HQYDVR0OBBYEFHK7MnjGDptQWjfmJ2fr3IrjxEopMFcGA1UdIARQME4wCAYGZ4EM
AQIBMEIGCisGAQQBoDIKAQMwNDAyBggrBgEFBQcCARYmaHR0cHM6Ly93d3cuZ2xv AQIBMEIGCisGAQQBoDIKAQMwNDAyBggrBgEFBQcCARYmaHR0cHM6Ly93d3cuZ2xv
YmFsc2lnbi5jb20vcmVwb3NpdG9yeS8wDAYDVR0TAQH/BAIwADCBngYIKwYBBQUH YmFsc2lnbi5jb20vcmVwb3NpdG9yeS8wDAYDVR0TAQH/BAIwADCBngYIKwYBBQUH
AQEEgZEwgY4wQAYIKwYBBQUHMAGGNGh0dHA6Ly9vY3NwLmdsb2JhbHNpZ24uY29t AQEEgZEwgY4wQAYIKwYBBQUHMAGGNGh0dHA6Ly9vY3NwLmdsb2JhbHNpZ24uY29t
L2NhL2dzYXRsYXNyM2R2dGxzY2FoMjIwMjEwSgYIKwYBBQUHMAKGPmh0dHA6Ly9z L2NhL2dzYXRsYXNyM2R2dGxzY2EyMDIycTMwSgYIKwYBBQUHMAKGPmh0dHA6Ly9z
ZWN1cmUuZ2xvYmFsc2lnbi5jb20vY2FjZXJ0L2dzYXRsYXNyM2R2dGxzY2FoMjIw ZWN1cmUuZ2xvYmFsc2lnbi5jb20vY2FjZXJ0L2dzYXRsYXNyM2R2dGxzY2EyMDIy
MjEuY3J0MB8GA1UdIwQYMBaAFCo0uar6vzyI8Ufy0hJ4vsXlqrBpMEgGA1UdHwRB cTMuY3J0MB8GA1UdIwQYMBaAFPqROWOa+60QJOW+tbnaq9nERmmrMEgGA1UdHwRB
MD8wPaA7oDmGN2h0dHA6Ly9jcmwuZ2xvYmFsc2lnbi5jb20vY2EvZ3NhdGxhc3Iz MD8wPaA7oDmGN2h0dHA6Ly9jcmwuZ2xvYmFsc2lnbi5jb20vY2EvZ3NhdGxhc3Iz
ZHZ0bHNjYWgyMjAyMS5jcmwwggF+BgorBgEEAdZ5AgQCBIIBbgSCAWoBaAB3AG9T ZHZ0bHNjYTIwMjJxMy5jcmwwggF9BgorBgEEAdZ5AgQCBIIBbQSCAWkBZwB1AG9T
dqwx8DEZ2JkApFEV/3cVHBHZAsEAKQaNsgiaN9kTAAABfJS9R5YAAAQDAEgwRgIh dqwx8DEZ2JkApFEV/3cVHBHZAsEAKQaNsgiaN9kTAAABgiduiHEAAAQDAEYwRAIg
AOOZmc41vB2ICwkwEB5Bmpm/X8UHfjbxwrCXEdeRmO+qAiEAg/JugZIrG2PeV4bA SYQrru/KAKfe+hUqpJmk7Fc8drkgtY3IcAurTOwbM68CIBYO9sbDspd5p7v17RQi
Gm6rry7HUfB954bQJ4p0PeQVmwsAdABGpVXrdfqRIDC1oolp9PN9ESxBdL79SbiF QQkjdRwSjHiIgvlX0Y1JqmXjAHYArfe++nz/EMiLnT2cHj4YarRnKV3PsQwkyoWG
q/L8cP5tRwAAAXyUvUeOAAAEAwBFMEMCHyRAiTz2fZ8DuQF6hrVP+IMTCPBtjB3D NOvcgooAAAGCJ26IcQAABAMARzBFAiBc5a10annqMEH69bdEFy/Vo1gb3S3GQ993
m4naI8tC/foCIDXFCRIYjRb00CFI6piLYGihRy+GYF5nMQhQ9uE6hltzAHcAUaOw BCRV7ZXG4gIhAMqnsoKkU6ITwRXwE9KGjHnijJ8QrBrnK0i+JFaGe1ffAHYAs3N3
9f0BeZxWbbg3eI8MpHrMGyfL956IQpoN/tSLBeUAAAF8lL1ICgAABAMASDBGAiEA B+GEUPhjhtYFqdwRCUp5LbFnDAuH3PADDnk2pZoAAAGCJ26I6QAABAMARzBFAiAf
5d/bXb9TPZWhwSH8GGji/LDFL6OJnZtOV94sBaDiFgMCIQCtl00oCRMFFnqsvBo6 lW8Agd0DB68YA8XAbnlq7QNHw3uRMzNdS8gtRUe75gIhANTe+mt2p1ryW83P31OW
SRtnDqJkEHYBS12I4LyC+D1onjANBgkqhkiG9w0BAQsFAAOCAQEAE5xcno79J+Ec jH3cEGJxdUNT/oDM3Fzesx94MA0GCSqGSIb3DQEBCwUAA4IBAQAfivKEmjqqOFFh
DIPJKnJCugKiM7yKjCjCp/63osCbRC+jUwRyXBIe/oTdY3geKwDOQAvyEeJPSWP1 VsX2XYkoDtreghpqMwHMCLwNk852Alr/Seyv9Ilng8cunU4NmhvEtsYVXkfE4XvB
LbNp0l3yHbYXfsYl/NMTrJpjrJrrRO5BxG/d3IPwXIlcZrrdDSoGfGYIF9N23iqB 0QIVxkg1w7A+p7ejMjh6doLJ0aWNWIVW/DwOeP0qstF9lqvLdLDABoVn0BtYCDTH
in15L7B+PodTl8/mSQZTjbLoecPvl+AOcLyStcWCKYQUlQb3x4UV3R4Z1ukwGbBC gjG80e2xpvPiKHGvBL+hlOIJwUuIAT3jN23sS1GoiYQGKsz0lovB09/6MGG0Qj8C
cDbTR2XOSJzA9ECJcxKnWjQRQUc54pdG3pt13Wu2dVapX5sWZpV05rga3bBDjCqw 3i9a59T9XBpwSKdpKd4u/CB6koBXD3atbBNBACuAMcFckTEtmkCFtSpqBuocJGKf
DcfKuYbOChm2i6CQ578lAntPTIS02EkGFHrmYxrIAvlhGksHpJNJtRoff1KkQKni LB4MFVaEwrd7Lc1ACC1et5FDtEI4I3/CerkRZTV+mRz5n6tB91AK3dRvjElfhiuh
r8emWp7D2Q== XXYRULvB
-----END CERTIFICATE----- -----END CERTIFICATE-----
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIExTCCA62gAwIBAgIQeimFGrf0XWZ5UGZBtv/XHTANBgkqhkiG9w0BAQsFADBM MIIEjzCCA3egAwIBAgIQfCoMIT/GVVNFyR8ZH7hO+jANBgkqhkiG9w0BAQsFADBM
MSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xv MSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xv
YmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjAeFw0yMTA2MTYxMjAwMDBaFw0y YmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjAeFw0yMjA0MjAxMjAwMDBaFw0y
NDA2MTYwMDAwMDBaMFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWdu NTA0MjAwMDAwMDBaMFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWdu
IG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFIzIERWIFRMUyBDQSBI IG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFIzIERWIFRMUyBDQSAy
MiAyMDIxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1JTAQMj+QUYF MDIyIFEzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuKh6ZjxOZpzO
3d9X5eOWFOphbB6GpHE3J0uvUXcQwxnd8Jz26aQCE1ZYxJFEc2WmsxuVeVXU+rZj N6VUNU02x5nTqCc28i/G1Rg+6QndBdbXLDQyfAhjSdEQN+V4XRFizm37Lz83lNuP
7+MYD7Mg72bhuiwUdwRGRN4a2N122LfIQlTFlHu/fwcNqYX/fe3phvZt9upnH4oJ ezDpXizZVT+y27mgtWA3i6QGMjVQpAmvCkX/qB+bZY7dSuBAoeNjN1iQ3XU7/A4c
aLBbay+t+HPPC4em74x2WKaIl31ZXzgzllLomnlLISLOKiQe1rEHp4yy3/yE2a4G gkCYvXCxwUgUFDwES2nd1JwBpukh44IK/uSqvzSgjMvJeW4+XGpSnsTtK8Vp/lA8
1l/lprA49dcyM/oylm9Bbkum2F4C+EOjHgTAoDVJrJpdWvPj0CU+HkmftujfFp4S k521/y0oqGwGbJ3Fr7JZ+1l3DXR6iISk1B3UuiAGzLUeSE50IRWGdcDMWtEFz1cW
55LECSr2TfJt7xjgR3eLUx12nlpoauWEzZ0/i6OIDPfbmqcksw4ani/YO07LbRM6 ehMX7MJKrtUecqoiWoycgjLEEOZCbiGGaHyAIzA1072wXgopK/AUsRg32Vklw+c4
cY9VZzkAvwIDAQABo4IBlTCCAZEwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQG 2enULTY1ZQIDAQABo4IBXzCCAVswDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQG
CCsGAQUFBwMBBggrBgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQW CCsGAQUFBwMBBggrBgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQW
BBQqNLmq+r88iPFH8tISeL7F5aqwaTAfBgNVHSMEGDAWgBSP8Et/qC5FJK5NUPpj BBT6kTljmvutECTlvrW52qvZxEZpqzAfBgNVHSMEGDAWgBSP8Et/qC5FJK5NUPpj
move4t0bvDB7BggrBgEFBQcBAQRvMG0wLgYIKwYBBQUHMAGGImh0dHA6Ly9vY3Nw move4t0bvDB7BggrBgEFBQcBAQRvMG0wLgYIKwYBBQUHMAGGImh0dHA6Ly9vY3Nw
Mi5nbG9iYWxzaWduLmNvbS9yb290cjMwOwYIKwYBBQUHMAKGL2h0dHA6Ly9zZWN1 Mi5nbG9iYWxzaWduLmNvbS9yb290cjMwOwYIKwYBBQUHMAKGL2h0dHA6Ly9zZWN1
cmUuZ2xvYmFsc2lnbi5jb20vY2FjZXJ0L3Jvb3QtcjMuY3J0MDYGA1UdHwQvMC0w cmUuZ2xvYmFsc2lnbi5jb20vY2FjZXJ0L3Jvb3QtcjMuY3J0MDYGA1UdHwQvMC0w
K6ApoCeGJWh0dHA6Ly9jcmwuZ2xvYmFsc2lnbi5jb20vcm9vdC1yMy5jcmwwVwYD K6ApoCeGJWh0dHA6Ly9jcmwuZ2xvYmFsc2lnbi5jb20vcm9vdC1yMy5jcmwwIQYD
VR0gBFAwTjAIBgZngQwBAgEwQgYKKwYBBAGgMgoBAzA0MDIGCCsGAQUFBwIBFiZo VR0gBBowGDAIBgZngQwBAgEwDAYKKwYBBAGgMgoBAzANBgkqhkiG9w0BAQsFAAOC
dHRwczovL3d3dy5nbG9iYWxzaWduLmNvbS9yZXBvc2l0b3J5LzANBgkqhkiG9w0B AQEAFDMseeU/gsZwP9pZOKe7onasYRgFaFfZDfuKRrzxqOgMcAIdxi+X7TY+nlKG
AQsFAAOCAQEAEsIwXEhdAfoUGaKAnYfVI7zsOY7Sx8bpC/obGxXa4Kyu8CVx+TtT L1xi2NVHQ5pz0Sslh59EtBTrJrwhR3QgvZ+kv7OAHU01fc25tdpV8pBQyLIXTg60
g8WmKNF7+I7C51NZEmhvb8UDI1G9ny7iYIRDajQD5AeZowbfC69aHQSI9LiOeAZb YYgpX0RdA39XkYHQ6zCu1SrsgiDOTtKwi5UCYXPYaTT0rWMOXOQgH6l97Y7lHAS7
YaRDJfWps9redPwoaC0iT5R4xLOnWwCtmIho1bv/YG3pMAvaQ+qn04kuUvWO7LEp Ip/HqSLKmT0Cp2foBi36BGu7SdJsmVdjbC3CYXjhILH79r/hgjk5PHvvfRqVSrJy
u7FdHmx1DdgkefcqYgN/rAZ8E39S9VxWV+64PNUDey8vkAIH8FCTxbWiITty6dsH 2lWQru3d4nCQfBrutTJaXc/W+kXyngEMMS+JhP4xYA/97qZbhNXHGOak+UAwKRge
SulKQ9pSa93k9PHTf+di08mMQBq5WBWTiFeMYZEWyE/z7NHdU3eLMZjq6y/nKlF9 /vxBtbkpBXWLYhpbIi6/5FlssA==
nywrToh4AgdZK6JnbU+lqbNiexJbaBoA3w==
-----END CERTIFICATE----- -----END CERTIFICATE-----
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G

View file

@ -1,28 +1,27 @@
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIExTCCA62gAwIBAgIQeimFGrf0XWZ5UGZBtv/XHTANBgkqhkiG9w0BAQsFADBM MIIEjzCCA3egAwIBAgIQfCoMIT/GVVNFyR8ZH7hO+jANBgkqhkiG9w0BAQsFADBM
MSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xv MSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEGA1UEChMKR2xv
YmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjAeFw0yMTA2MTYxMjAwMDBaFw0y YmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjAeFw0yMjA0MjAxMjAwMDBaFw0y
NDA2MTYwMDAwMDBaMFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWdu NTA0MjAwMDAwMDBaMFgxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWdu
IG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFIzIERWIFRMUyBDQSBI IG52LXNhMS4wLAYDVQQDEyVHbG9iYWxTaWduIEF0bGFzIFIzIERWIFRMUyBDQSAy
MiAyMDIxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1JTAQMj+QUYF MDIyIFEzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuKh6ZjxOZpzO
3d9X5eOWFOphbB6GpHE3J0uvUXcQwxnd8Jz26aQCE1ZYxJFEc2WmsxuVeVXU+rZj N6VUNU02x5nTqCc28i/G1Rg+6QndBdbXLDQyfAhjSdEQN+V4XRFizm37Lz83lNuP
7+MYD7Mg72bhuiwUdwRGRN4a2N122LfIQlTFlHu/fwcNqYX/fe3phvZt9upnH4oJ ezDpXizZVT+y27mgtWA3i6QGMjVQpAmvCkX/qB+bZY7dSuBAoeNjN1iQ3XU7/A4c
aLBbay+t+HPPC4em74x2WKaIl31ZXzgzllLomnlLISLOKiQe1rEHp4yy3/yE2a4G gkCYvXCxwUgUFDwES2nd1JwBpukh44IK/uSqvzSgjMvJeW4+XGpSnsTtK8Vp/lA8
1l/lprA49dcyM/oylm9Bbkum2F4C+EOjHgTAoDVJrJpdWvPj0CU+HkmftujfFp4S k521/y0oqGwGbJ3Fr7JZ+1l3DXR6iISk1B3UuiAGzLUeSE50IRWGdcDMWtEFz1cW
55LECSr2TfJt7xjgR3eLUx12nlpoauWEzZ0/i6OIDPfbmqcksw4ani/YO07LbRM6 ehMX7MJKrtUecqoiWoycgjLEEOZCbiGGaHyAIzA1072wXgopK/AUsRg32Vklw+c4
cY9VZzkAvwIDAQABo4IBlTCCAZEwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQG 2enULTY1ZQIDAQABo4IBXzCCAVswDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQG
CCsGAQUFBwMBBggrBgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQW CCsGAQUFBwMBBggrBgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQW
BBQqNLmq+r88iPFH8tISeL7F5aqwaTAfBgNVHSMEGDAWgBSP8Et/qC5FJK5NUPpj BBT6kTljmvutECTlvrW52qvZxEZpqzAfBgNVHSMEGDAWgBSP8Et/qC5FJK5NUPpj
move4t0bvDB7BggrBgEFBQcBAQRvMG0wLgYIKwYBBQUHMAGGImh0dHA6Ly9vY3Nw move4t0bvDB7BggrBgEFBQcBAQRvMG0wLgYIKwYBBQUHMAGGImh0dHA6Ly9vY3Nw
Mi5nbG9iYWxzaWduLmNvbS9yb290cjMwOwYIKwYBBQUHMAKGL2h0dHA6Ly9zZWN1 Mi5nbG9iYWxzaWduLmNvbS9yb290cjMwOwYIKwYBBQUHMAKGL2h0dHA6Ly9zZWN1
cmUuZ2xvYmFsc2lnbi5jb20vY2FjZXJ0L3Jvb3QtcjMuY3J0MDYGA1UdHwQvMC0w cmUuZ2xvYmFsc2lnbi5jb20vY2FjZXJ0L3Jvb3QtcjMuY3J0MDYGA1UdHwQvMC0w
K6ApoCeGJWh0dHA6Ly9jcmwuZ2xvYmFsc2lnbi5jb20vcm9vdC1yMy5jcmwwVwYD K6ApoCeGJWh0dHA6Ly9jcmwuZ2xvYmFsc2lnbi5jb20vcm9vdC1yMy5jcmwwIQYD
VR0gBFAwTjAIBgZngQwBAgEwQgYKKwYBBAGgMgoBAzA0MDIGCCsGAQUFBwIBFiZo VR0gBBowGDAIBgZngQwBAgEwDAYKKwYBBAGgMgoBAzANBgkqhkiG9w0BAQsFAAOC
dHRwczovL3d3dy5nbG9iYWxzaWduLmNvbS9yZXBvc2l0b3J5LzANBgkqhkiG9w0B AQEAFDMseeU/gsZwP9pZOKe7onasYRgFaFfZDfuKRrzxqOgMcAIdxi+X7TY+nlKG
AQsFAAOCAQEAEsIwXEhdAfoUGaKAnYfVI7zsOY7Sx8bpC/obGxXa4Kyu8CVx+TtT L1xi2NVHQ5pz0Sslh59EtBTrJrwhR3QgvZ+kv7OAHU01fc25tdpV8pBQyLIXTg60
g8WmKNF7+I7C51NZEmhvb8UDI1G9ny7iYIRDajQD5AeZowbfC69aHQSI9LiOeAZb YYgpX0RdA39XkYHQ6zCu1SrsgiDOTtKwi5UCYXPYaTT0rWMOXOQgH6l97Y7lHAS7
YaRDJfWps9redPwoaC0iT5R4xLOnWwCtmIho1bv/YG3pMAvaQ+qn04kuUvWO7LEp Ip/HqSLKmT0Cp2foBi36BGu7SdJsmVdjbC3CYXjhILH79r/hgjk5PHvvfRqVSrJy
u7FdHmx1DdgkefcqYgN/rAZ8E39S9VxWV+64PNUDey8vkAIH8FCTxbWiITty6dsH 2lWQru3d4nCQfBrutTJaXc/W+kXyngEMMS+JhP4xYA/97qZbhNXHGOak+UAwKRge
SulKQ9pSa93k9PHTf+di08mMQBq5WBWTiFeMYZEWyE/z7NHdU3eLMZjq6y/nKlF9 /vxBtbkpBXWLYhpbIi6/5FlssA==
nywrToh4AgdZK6JnbU+lqbNiexJbaBoA3w==
-----END CERTIFICATE----- -----END CERTIFICATE-----

View file

@ -1,37 +1,37 @@
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIGYzCCBUugAwIBAgIQAaQHyOeT/PBR4ioLKYneZDANBgkqhkiG9w0BAQsFADBY MIIGYjCCBUqgAwIBAgIQATfkha/xTr3pbLHVWlPq4jANBgkqhkiG9w0BAQsFADBY
MQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTEuMCwGA1UE MQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTEuMCwGA1UE
AxMlR2xvYmFsU2lnbiBBdGxhcyBSMyBEViBUTFMgQ0EgSDIgMjAyMTAeFw0yMTEw AxMlR2xvYmFsU2lnbiBBdGxhcyBSMyBEViBUTFMgQ0EgMjAyMiBRMzAeFw0yMjA3
MTgxODUwMDRaFw0yMjExMTkxODUwMDNaMBsxGTAXBgNVBAMMEGFib3V0LmdpdGxh MjIxOTQyMTFaFw0yMzA4MjMxOTQyMTBaMBsxGTAXBgNVBAMMEGFib3V0LmdpdGxh
Yi5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDWSo0eziN/0lq5 Yi5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDFFFQs8EITaWo5
dIcS7ZceJw2odzZeT0tRkcKEW8iagNul6JetrFlk6h5lxoLEu35+MK6/fWHNmt7u 0U18/mPTDLencU/7siJT/4P8oeDkemyx98wzK6vuNj/2JEZ3v1psKun5n8Pb/fHa
eQk7HS0uRipskAzeGrL1Hvk8EjIcHXXTxpRu7JqWOu7ZSXwNxW5cqn7L9/N2gYwt somKd/4icHgC4rnxrO6zayfb+cKzVghQe12Nj75lx6RtppqTgAmSOa3Tai5niICT
Jg/sfkv9AFQiNOdKrarKfbcBstxmra6rQbh5ggLG5UBT23N4ZrA3XnzvEx3+GjtO I8s3d2wsHtfEgqAavcD0/zdPIk25Ji7yfquldSthnlhQqI4Pm3OxTiyFj/V5ZhFl
u/a5izbk7FQP3gyXKyfm/SQRpNsytYa9jJqu5Hmyzfap5KaueOJbtJEOk8dR/HWR IWZLvQaENjBSDVZQDcaPdWwodfXNA8fJmqk7cTLQ9P9NgjWvva7acl+Yd6hOFzV0
i/gmAUevq62MNxorYbz8YU/P1468tS7iORkD31Tc2QWCMQSPya5qGaCGnz7dVgWy EllBl/WF1KB+YzGuHI0CQHT7sv3GW1lXeE2EqrWoSdLTOSAqm6y02DyE79d1xvG6
E1xTPbBXAgMBAAGjggNkMIIDYDAbBgNVHREEFDASghBhYm91dC5naXRsYWIuY29t XXfX5ILlAgMBAAGjggNjMIIDXzAbBgNVHREEFDASghBhYm91dC5naXRsYWIuY29t
MA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw MA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw
HQYDVR0OBBYEFJFVruwpjWeUfGJXl3m5grAjhAwPMFcGA1UdIARQME4wCAYGZ4EM HQYDVR0OBBYEFHK7MnjGDptQWjfmJ2fr3IrjxEopMFcGA1UdIARQME4wCAYGZ4EM
AQIBMEIGCisGAQQBoDIKAQMwNDAyBggrBgEFBQcCARYmaHR0cHM6Ly93d3cuZ2xv AQIBMEIGCisGAQQBoDIKAQMwNDAyBggrBgEFBQcCARYmaHR0cHM6Ly93d3cuZ2xv
YmFsc2lnbi5jb20vcmVwb3NpdG9yeS8wDAYDVR0TAQH/BAIwADCBngYIKwYBBQUH YmFsc2lnbi5jb20vcmVwb3NpdG9yeS8wDAYDVR0TAQH/BAIwADCBngYIKwYBBQUH
AQEEgZEwgY4wQAYIKwYBBQUHMAGGNGh0dHA6Ly9vY3NwLmdsb2JhbHNpZ24uY29t AQEEgZEwgY4wQAYIKwYBBQUHMAGGNGh0dHA6Ly9vY3NwLmdsb2JhbHNpZ24uY29t
L2NhL2dzYXRsYXNyM2R2dGxzY2FoMjIwMjEwSgYIKwYBBQUHMAKGPmh0dHA6Ly9z L2NhL2dzYXRsYXNyM2R2dGxzY2EyMDIycTMwSgYIKwYBBQUHMAKGPmh0dHA6Ly9z
ZWN1cmUuZ2xvYmFsc2lnbi5jb20vY2FjZXJ0L2dzYXRsYXNyM2R2dGxzY2FoMjIw ZWN1cmUuZ2xvYmFsc2lnbi5jb20vY2FjZXJ0L2dzYXRsYXNyM2R2dGxzY2EyMDIy
MjEuY3J0MB8GA1UdIwQYMBaAFCo0uar6vzyI8Ufy0hJ4vsXlqrBpMEgGA1UdHwRB cTMuY3J0MB8GA1UdIwQYMBaAFPqROWOa+60QJOW+tbnaq9nERmmrMEgGA1UdHwRB
MD8wPaA7oDmGN2h0dHA6Ly9jcmwuZ2xvYmFsc2lnbi5jb20vY2EvZ3NhdGxhc3Iz MD8wPaA7oDmGN2h0dHA6Ly9jcmwuZ2xvYmFsc2lnbi5jb20vY2EvZ3NhdGxhc3Iz
ZHZ0bHNjYWgyMjAyMS5jcmwwggF+BgorBgEEAdZ5AgQCBIIBbgSCAWoBaAB3AG9T ZHZ0bHNjYTIwMjJxMy5jcmwwggF9BgorBgEEAdZ5AgQCBIIBbQSCAWkBZwB1AG9T
dqwx8DEZ2JkApFEV/3cVHBHZAsEAKQaNsgiaN9kTAAABfJS9R5YAAAQDAEgwRgIh dqwx8DEZ2JkApFEV/3cVHBHZAsEAKQaNsgiaN9kTAAABgiduiHEAAAQDAEYwRAIg
AOOZmc41vB2ICwkwEB5Bmpm/X8UHfjbxwrCXEdeRmO+qAiEAg/JugZIrG2PeV4bA SYQrru/KAKfe+hUqpJmk7Fc8drkgtY3IcAurTOwbM68CIBYO9sbDspd5p7v17RQi
Gm6rry7HUfB954bQJ4p0PeQVmwsAdABGpVXrdfqRIDC1oolp9PN9ESxBdL79SbiF QQkjdRwSjHiIgvlX0Y1JqmXjAHYArfe++nz/EMiLnT2cHj4YarRnKV3PsQwkyoWG
q/L8cP5tRwAAAXyUvUeOAAAEAwBFMEMCHyRAiTz2fZ8DuQF6hrVP+IMTCPBtjB3D NOvcgooAAAGCJ26IcQAABAMARzBFAiBc5a10annqMEH69bdEFy/Vo1gb3S3GQ993
m4naI8tC/foCIDXFCRIYjRb00CFI6piLYGihRy+GYF5nMQhQ9uE6hltzAHcAUaOw BCRV7ZXG4gIhAMqnsoKkU6ITwRXwE9KGjHnijJ8QrBrnK0i+JFaGe1ffAHYAs3N3
9f0BeZxWbbg3eI8MpHrMGyfL956IQpoN/tSLBeUAAAF8lL1ICgAABAMASDBGAiEA B+GEUPhjhtYFqdwRCUp5LbFnDAuH3PADDnk2pZoAAAGCJ26I6QAABAMARzBFAiAf
5d/bXb9TPZWhwSH8GGji/LDFL6OJnZtOV94sBaDiFgMCIQCtl00oCRMFFnqsvBo6 lW8Agd0DB68YA8XAbnlq7QNHw3uRMzNdS8gtRUe75gIhANTe+mt2p1ryW83P31OW
SRtnDqJkEHYBS12I4LyC+D1onjANBgkqhkiG9w0BAQsFAAOCAQEAE5xcno79J+Ec jH3cEGJxdUNT/oDM3Fzesx94MA0GCSqGSIb3DQEBCwUAA4IBAQAfivKEmjqqOFFh
DIPJKnJCugKiM7yKjCjCp/63osCbRC+jUwRyXBIe/oTdY3geKwDOQAvyEeJPSWP1 VsX2XYkoDtreghpqMwHMCLwNk852Alr/Seyv9Ilng8cunU4NmhvEtsYVXkfE4XvB
LbNp0l3yHbYXfsYl/NMTrJpjrJrrRO5BxG/d3IPwXIlcZrrdDSoGfGYIF9N23iqB 0QIVxkg1w7A+p7ejMjh6doLJ0aWNWIVW/DwOeP0qstF9lqvLdLDABoVn0BtYCDTH
in15L7B+PodTl8/mSQZTjbLoecPvl+AOcLyStcWCKYQUlQb3x4UV3R4Z1ukwGbBC gjG80e2xpvPiKHGvBL+hlOIJwUuIAT3jN23sS1GoiYQGKsz0lovB09/6MGG0Qj8C
cDbTR2XOSJzA9ECJcxKnWjQRQUc54pdG3pt13Wu2dVapX5sWZpV05rga3bBDjCqw 3i9a59T9XBpwSKdpKd4u/CB6koBXD3atbBNBACuAMcFckTEtmkCFtSpqBuocJGKf
DcfKuYbOChm2i6CQ578lAntPTIS02EkGFHrmYxrIAvlhGksHpJNJtRoff1KkQKni LB4MFVaEwrd7Lc1ACC1et5FDtEI4I3/CerkRZTV+mRz5n6tB91AK3dRvjElfhiuh
r8emWp7D2Q== XXYRULvB
-----END CERTIFICATE----- -----END CERTIFICATE-----

Binary file not shown.

View file

@ -445,6 +445,19 @@ RSpec.describe DiffHelper do
end end
end end
describe '#params_with_whitespace' do
before do
controller.params[:protocol] = 'HACKED!'
controller.params[:host] = 'HACKED!'
end
subject { helper.params_with_whitespace }
it "filters with safe_params" do
expect(subject).to eq({ 'w' => 1 })
end
end
describe "#render_fork_suggestion" do describe "#render_fork_suggestion" do
subject { helper.render_fork_suggestion } subject { helper.render_fork_suggestion }

View file

@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe SubmoduleHelper do RSpec.describe SubmoduleHelper, feature_category: :source_code_management do
include RepoHelpers include RepoHelpers
let(:submodule_item) { double(id: 'hash', path: 'rack') } let(:submodule_item) { double(id: 'hash', path: 'rack') }

View file

@ -114,6 +114,18 @@ RSpec.describe Gitlab::APIAuthentication::TokenResolver do
it_behaves_like 'an unauthorized request' it_behaves_like 'an unauthorized request'
end end
context 'when the external_authorization_service is enabled' do
before do
stub_application_setting(external_authorization_service_enabled: true)
end
context 'with a valid deploy token' do
let(:raw) { username_and_password(token.username, token.token) }
it_behaves_like 'an unauthorized request'
end
end
end end
context 'with :personal_access_token' do context 'with :personal_access_token' do

View file

@ -188,7 +188,7 @@ RSpec.describe Gitlab::Auth::AuthFinders do
end end
it 'returns nil if valid feed_token and disabled' do it 'returns nil if valid feed_token and disabled' do
allow(Gitlab::CurrentSettings).to receive_messages(disable_feed_token: true) stub_application_setting(disable_feed_token: true)
set_param(:feed_token, user.feed_token) set_param(:feed_token, user.feed_token)
expect(find_user_from_feed_token(:rss)).to be_nil expect(find_user_from_feed_token(:rss)).to be_nil
@ -388,6 +388,15 @@ RSpec.describe Gitlab::Auth::AuthFinders do
it { is_expected.to be_nil } it { is_expected.to be_nil }
end end
end end
context 'when the external_authorization_service is enabled' do
before do
stub_application_setting(external_authorization_service_enabled: true)
set_header(described_class::DEPLOY_TOKEN_HEADER, deploy_token.token)
end
it { is_expected.to be_nil }
end
end end
describe '#find_user_from_access_token' do describe '#find_user_from_access_token' do

View file

@ -528,12 +528,13 @@ RSpec.describe Gitlab::Git::Repository do
prune: false, prune: false,
check_tags_changed: false, check_tags_changed: false,
refmap: nil, refmap: nil,
http_authorization_header: "" http_authorization_header: "",
resolved_address: '172.16.123.1'
} }
expect(repository.gitaly_repository_client).to receive(:fetch_remote).with(url, expected_opts) expect(repository.gitaly_repository_client).to receive(:fetch_remote).with(url, expected_opts)
repository.fetch_remote(url, ssh_auth: ssh_auth, forced: true, no_tags: true, prune: false, check_tags_changed: false) repository.fetch_remote(url, ssh_auth: ssh_auth, forced: true, no_tags: true, prune: false, check_tags_changed: false, resolved_address: '172.16.123.1')
end end
it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::RepositoryService, :fetch_remote do it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::RepositoryService, :fetch_remote do
@ -2454,7 +2455,7 @@ RSpec.describe Gitlab::Git::Repository do
it 'delegates to Gitaly' do it 'delegates to Gitaly' do
expect_next_instance_of(Gitlab::GitalyClient::RepositoryService) do |svc| expect_next_instance_of(Gitlab::GitalyClient::RepositoryService) do |svc|
expect(svc).to receive(:import_repository).with(url, http_authorization_header: '', mirror: false).and_return(nil) expect(svc).to receive(:import_repository).with(url, http_authorization_header: '', mirror: false, resolved_address: '').and_return(nil)
end end
repository.import_repository(url) repository.import_repository(url)

View file

@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Gitlab::GitAccess, :aggregate_failures do RSpec.describe Gitlab::GitAccess, :aggregate_failures do
include TermsHelper include TermsHelper
include AdminModeHelper include AdminModeHelper
include ExternalAuthorizationServiceHelpers
let(:user) { create(:user) } let(:user) { create(:user) }
let(:actor) { user } let(:actor) { user }
@ -111,6 +112,19 @@ RSpec.describe Gitlab::GitAccess, :aggregate_failures do
end end
end end
end end
context 'when the external_authorization_service is enabled' do
before do
stub_application_setting(external_authorization_service_enabled: true)
end
it 'blocks push and pull with "not found"' do
aggregate_failures do
expect { push_access_check }.to raise_not_found
expect { pull_access_check }.to raise_not_found
end
end
end
end end
context 'when actor is a User' do context 'when actor is a User' do
@ -176,6 +190,20 @@ RSpec.describe Gitlab::GitAccess, :aggregate_failures do
expect { push_access_check }.to raise_not_found expect { push_access_check }.to raise_not_found
end end
end end
context 'when the external_authorization_service is enabled' do
before do
stub_application_setting(external_authorization_service_enabled: true)
end
it 'blocks pull access' do
expect { pull_access_check }.to raise_not_found
end
it 'blocks the push' do
expect { push_access_check }.to raise_not_found
end
end
end end
end end

View file

@ -133,6 +133,40 @@ RSpec.describe Gitlab::GitalyClient::RepositoryService do
end end
end end
describe '#import_repository' do
let(:source) { 'https://example.com/git/repo.git' }
it 'sends a create_repository_from_url message' do
expected_request = gitaly_request_with_params(
url: source,
resolved_address: ''
)
expect_any_instance_of(Gitaly::RepositoryService::Stub)
.to receive(:create_repository_from_url)
.with(expected_request, kind_of(Hash))
.and_return(double(value: true))
client.import_repository(source)
end
context 'when http_host is provided' do
it 'sends a create_repository_from_url message with http_host provided in the request' do
expected_request = gitaly_request_with_params(
url: source,
resolved_address: '172.16.123.1'
)
expect_any_instance_of(Gitaly::RepositoryService::Stub)
.to receive(:create_repository_from_url)
.with(expected_request, kind_of(Hash))
.and_return(double(value: true))
client.import_repository(source, resolved_address: '172.16.123.1')
end
end
end
describe '#fetch_remote' do describe '#fetch_remote' do
let(:url) { 'https://example.com/git/repo.git' } let(:url) { 'https://example.com/git/repo.git' }
@ -141,7 +175,8 @@ RSpec.describe Gitlab::GitalyClient::RepositoryService do
remote_params: Gitaly::Remote.new( remote_params: Gitaly::Remote.new(
url: url, url: url,
http_authorization_header: "", http_authorization_header: "",
mirror_refmaps: [] mirror_refmaps: [],
resolved_address: ''
), ),
ssh_key: '', ssh_key: '',
known_hosts: '', known_hosts: '',
@ -159,6 +194,32 @@ RSpec.describe Gitlab::GitalyClient::RepositoryService do
client.fetch_remote(url, refmap: nil, ssh_auth: nil, forced: false, no_tags: false, timeout: 1, check_tags_changed: false) client.fetch_remote(url, refmap: nil, ssh_auth: nil, forced: false, no_tags: false, timeout: 1, check_tags_changed: false)
end end
context 'with resolved address' do
it 'sends a fetch_remote_request message' do
expected_request = gitaly_request_with_params(
remote_params: Gitaly::Remote.new(
url: url,
http_authorization_header: "",
mirror_refmaps: [],
resolved_address: '172.16.123.1'
),
ssh_key: '',
known_hosts: '',
force: false,
no_tags: false,
no_prune: false,
check_tags_changed: false
)
expect_any_instance_of(Gitaly::RepositoryService::Stub)
.to receive(:fetch_remote)
.with(expected_request, kind_of(Hash))
.and_return(double(value: true))
client.fetch_remote(url, refmap: nil, ssh_auth: nil, forced: false, no_tags: false, timeout: 1, check_tags_changed: false, resolved_address: '172.16.123.1')
end
end
context 'SSH auth' do context 'SSH auth' do
where(:ssh_mirror_url, :ssh_key_auth, :ssh_private_key, :ssh_known_hosts, :expected_params) do where(:ssh_mirror_url, :ssh_key_auth, :ssh_private_key, :ssh_known_hosts, :expected_params) do
false | false | 'key' | 'known_hosts' | {} false | false | 'key' | 'known_hosts' | {}

View file

@ -29,8 +29,8 @@ RSpec.describe Gitlab::HookData::ProjectBuilder do
expect(data[:path_with_namespace]).to eq(project.full_path) expect(data[:path_with_namespace]).to eq(project.full_path)
expect(data[:project_id]).to eq(project.id) expect(data[:project_id]).to eq(project.id)
expect(data[:owner_name]).to eq('John') expect(data[:owner_name]).to eq('John')
expect(data[:owner_email]).to eq('john@example.com') expect(data[:owner_email]).to eq(_('[REDACTED]'))
expect(data[:owners]).to contain_exactly({ name: 'John', email: 'john@example.com' }) expect(data[:owners]).to contain_exactly({ name: 'John', email: _('[REDACTED]') })
expect(data[:project_visibility]).to eq('internal') expect(data[:project_visibility]).to eq('internal')
end end
end end

View file

@ -27,7 +27,7 @@ RSpec.describe Gitlab::HookData::ProjectMemberBuilder do
expect(data[:user_username]).to eq('johndoe') expect(data[:user_username]).to eq('johndoe')
expect(data[:user_name]).to eq('John Doe') expect(data[:user_name]).to eq('John Doe')
expect(data[:user_id]).to eq(user.id) expect(data[:user_id]).to eq(user.id)
expect(data[:user_email]).to eq('john@example.com') expect(data[:user_email]).to eq(_('[REDACTED]'))
expect(data[:access_level]).to eq('Developer') expect(data[:access_level]).to eq('Developer')
expect(data[:project_visibility]).to eq('internal') expect(data[:project_visibility]).to eq('internal')
end end

View file

@ -0,0 +1,20 @@
# frozen_string_literal: true
require 'fast_spec_helper'
require 'device_detector'
require_relative '../../../lib/gitlab/safe_device_detector'
RSpec.describe Gitlab::SafeDeviceDetector, feature_category: :authentication_and_authorization do
it 'retains the behavior for normal user agents' do
chrome_user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 \
(KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"
expect(described_class.new(chrome_user_agent).user_agent).to be_eql(chrome_user_agent)
expect(described_class.new(chrome_user_agent).name).to be_eql('Chrome')
end
it 'truncates big user agents' do
big_user_agent = "chrome #{'abc' * 1024}"
expect(described_class.new(big_user_agent).user_agent).not_to be_eql(big_user_agent)
end
end

View file

@ -89,31 +89,32 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
describe '.categories' do describe '.categories' do
it 'gets CE unique category names' do it 'gets CE unique category names' do
expect(described_class.categories).to include( expect(described_class.categories).to include(
'deploy_token_packages',
'user_packages',
'ecosystem',
'analytics', 'analytics',
'ide_edit',
'search',
'source_code',
'incident_management',
'incident_management_alerts',
'testing',
'issues_edit',
'snippets',
'code_review',
'terraform',
'ci_templates', 'ci_templates',
'quickactions',
'pipeline_authoring',
'secure',
'importer',
'geo',
'work_items',
'ci_users', 'ci_users',
'code_review',
'deploy_token_packages',
'ecosystem',
'environments',
'error_tracking', 'error_tracking',
'geo',
'ide_edit',
'importer',
'incident_management_alerts',
'incident_management',
'issues_edit',
'kubernetes_agent',
'manage', 'manage',
'kubernetes_agent' 'pipeline_authoring',
'quickactions',
'search',
'secure',
'snippets',
'source_code',
'terraform',
'testing',
'user_packages',
'work_items'
) )
end end
end end

View file

@ -11,7 +11,7 @@ RSpec.describe DeviseMailer do
subject { described_class.confirmation_instructions(user, 'faketoken', {}) } subject { described_class.confirmation_instructions(user, 'faketoken', {}) }
context "when confirming a new account" do context "when confirming a new account" do
let(:user) { build(:user, created_at: 1.minute.ago, unconfirmed_email: nil) } let(:user) { create(:user, created_at: 1.minute.ago) }
it "shows the expected text" do it "shows the expected text" do
expect(subject.body.encoded).to have_text "Welcome" expect(subject.body.encoded).to have_text "Welcome"
@ -20,7 +20,13 @@ RSpec.describe DeviseMailer do
end end
context "when confirming the unconfirmed_email" do context "when confirming the unconfirmed_email" do
let(:user) { build(:user, unconfirmed_email: 'jdoe@example.com') } subject { described_class.confirmation_instructions(user, user.confirmation_token, { to: user.unconfirmed_email }) }
let(:user) { create(:user) }
before do
user.update!(email: 'unconfirmed-email@example.com')
end
it "shows the expected text" do it "shows the expected text" do
expect(subject.body.encoded).not_to have_text "Welcome" expect(subject.body.encoded).not_to have_text "Welcome"
@ -30,7 +36,7 @@ RSpec.describe DeviseMailer do
end end
context "when re-confirming the primary email after a security issue" do context "when re-confirming the primary email after a security issue" do
let(:user) { build(:user, created_at: 10.days.ago, unconfirmed_email: nil) } let(:user) { create(:user, created_at: Devise.confirm_within.ago) }
it "shows the expected text" do it "shows the expected text" do
expect(subject.body.encoded).not_to have_text "Welcome" expect(subject.body.encoded).not_to have_text "Welcome"

View file

@ -1466,10 +1466,4 @@ RSpec.describe ApplicationSetting do
expect(setting.personal_access_token_prefix).to eql('glpat-') expect(setting.personal_access_token_prefix).to eql('glpat-')
end end
end end
describe '.personal_access_tokens_disabled?' do
it 'is false' do
expect(setting.personal_access_tokens_disabled?).to eq(false)
end
end
end end

View file

@ -13,6 +13,45 @@ RSpec.describe Ci::BuildRunnerSession, model: true do
it { is_expected.to validate_presence_of(:build) } it { is_expected.to validate_presence_of(:build) }
it { is_expected.to validate_presence_of(:url).with_message('must be a valid URL') } it { is_expected.to validate_presence_of(:url).with_message('must be a valid URL') }
context 'url validation of local web hook address' do
let(:url) { 'https://127.0.0.1:7777' }
subject(:build_with_local_runner_session_url) do
create(:ci_build).tap { |b| b.update!(runner_session_attributes: { url: url }) }
end
context 'with allow_local_requests_from_web_hooks_and_services? stubbed' do
before do
allow(ApplicationSetting).to receive(:current).and_return(ApplicationSetting.new)
stub_application_setting(allow_local_requests_from_web_hooks_and_services: allow_local_requests)
end
context 'as returning true' do
let(:allow_local_requests) { true }
it 'creates a new session', :aggregate_failures do
session = build_with_local_runner_session_url.reload.runner_session
expect(session.errors).to be_empty
expect(session).to be_a(Ci::BuildRunnerSession)
expect(session.url).to eq(url)
end
end
context 'as returning false' do
let(:allow_local_requests) { false }
it 'does not create a new session' do
expect { build_with_local_runner_session_url }.to raise_error(ActiveRecord::RecordInvalid) do |err|
expect(err.record.errors.full_messages).to include(
'Runner session url is blocked: Requests to localhost are not allowed'
)
end
end
end
end
end
context 'nested attribute assignment' do context 'nested attribute assignment' do
it 'creates a new session' do it 'creates a new session' do
simple_build = create(:ci_build) simple_build = create(:ci_build)
@ -49,6 +88,12 @@ RSpec.describe Ci::BuildRunnerSession, model: true do
expect(specification).to be_empty expect(specification).to be_empty
end end
it 'returns url with appended query if url has query' do
subject.url = 'https://new.example.com:7777/some_path?dummy='
expect(specification[:url]).to eq('wss://new.example.com:7777/some_path/exec?dummy=')
end
context 'when url is present' do context 'when url is present' do
it 'returns ca_pem nil if empty certificate' do it 'returns ca_pem nil if empty certificate' do
subject.certificate = '' subject.certificate = ''
@ -72,7 +117,7 @@ RSpec.describe Ci::BuildRunnerSession, model: true do
let(:specification) { subject.service_specification(service: service, port: port, path: path, subprotocols: subprotocols) } let(:specification) { subject.service_specification(service: service, port: port, path: path, subprotocols: subprotocols) }
it 'returns service proxy url' do it 'returns service proxy url' do
expect(specification[:url]).to eq "https://localhost/proxy/#{service}/#{port}/#{path}" expect(specification[:url]).to eq "https://gitlab.example.com/proxy/#{service}/#{port}/#{path}"
end end
it 'returns default service proxy websocket subprotocol' do it 'returns default service proxy websocket subprotocol' do
@ -85,11 +130,17 @@ RSpec.describe Ci::BuildRunnerSession, model: true do
expect(specification).to be_empty expect(specification).to be_empty
end end
it 'returns url with appended query if url has query' do
subject.url = 'https://new.example.com:7777/some_path?dummy='
expect(specification[:url]).to eq("https://new.example.com:7777/some_path/proxy/#{service}/#{port}/#{path}?dummy=")
end
context 'when port is not present' do context 'when port is not present' do
let(:port) { nil } let(:port) { nil }
it 'uses the default port name' do it 'uses the default port name' do
expect(specification[:url]).to eq "https://localhost/proxy/#{service}/default_port/#{path}" expect(specification[:url]).to eq "https://gitlab.example.com/proxy/#{service}/default_port/#{path}"
end end
end end
@ -97,7 +148,7 @@ RSpec.describe Ci::BuildRunnerSession, model: true do
let(:service) { '' } let(:service) { '' }
it 'uses the service name "build" as default' do it 'uses the service name "build" as default' do
expect(specification[:url]).to eq "https://localhost/proxy/build/#{port}/#{path}" expect(specification[:url]).to eq "https://gitlab.example.com/proxy/build/#{port}/#{path}"
end end
end end

View file

@ -185,4 +185,22 @@ RSpec.describe WebHookLog do
it { expect(web_hook_log.internal_error?).to be_truthy } it { expect(web_hook_log.internal_error?).to be_truthy }
end end
end end
describe '#request_headers' do
let(:hook) { build(:project_hook, :token) }
let(:web_hook_log) { build(:web_hook_log, request_headers: request_headers) }
let(:expected_headers) { { 'X-Gitlab-Token' => _('[REDACTED]') } }
context 'with redacted headers token' do
let(:request_headers) { { 'X-Gitlab-Token' => _('[REDACTED]') } }
it { expect(web_hook_log.request_headers).to eq(expected_headers) }
end
context 'with exposed headers token' do
let(:request_headers) { { 'X-Gitlab-Token' => hook.token } }
it { expect(web_hook_log.request_headers).to eq(expected_headers) }
end
end
end end

View file

@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe WebHook do RSpec.describe WebHook, feature_category: :integrations do
include AfterNextHelpers include AfterNextHelpers
let_it_be(:project) { create(:project) } let_it_be(:project) { create(:project) }
@ -131,6 +131,62 @@ RSpec.describe WebHook do
expect(hook.push_events_branch_filter).to eq('') expect(hook.push_events_branch_filter).to eq('')
end end
end end
describe 'before_validation :reset_token' do
subject(:hook) { build_stubbed(:project_hook, :token, project: project) }
it 'resets token if url changed' do
hook.url = 'https://webhook.example.com/new-hook'
expect(hook).to be_valid
expect(hook.token).to be_nil
end
it 'does not reset token if new url is set together with the same token' do
hook.url = 'https://webhook.example.com/new-hook'
current_token = hook.token
hook.token = current_token
expect(hook).to be_valid
expect(hook.token).to eq(current_token)
expect(hook.url).to eq('https://webhook.example.com/new-hook')
end
it 'does not reset token if new url is set together with a new token' do
hook.url = 'https://webhook.example.com/new-hook'
hook.token = 'token'
expect(hook).to be_valid
expect(hook.token).to eq('token')
expect(hook.url).to eq('https://webhook.example.com/new-hook')
end
end
describe 'before_validation :reset_url_variables' do
subject(:hook) { build_stubbed(:project_hook, :url_variables, project: project, url: 'http://example.com/{abc}') }
it 'resets url variables if url changed' do
hook.url = 'http://example.com/new-hook'
expect(hook).to be_valid
expect(hook.url_variables).to eq({})
end
it 'resets url variables if url is changed but url variables stayed the same' do
hook.url = 'http://test.example.com/{abc}'
expect(hook).not_to be_valid
expect(hook.url_variables).to eq({})
end
it 'does not reset url variables if both url and url variables are changed' do
hook.url = 'http://example.com/{one}/{two}'
hook.url_variables = { 'one' => 'foo', 'two' => 'bar' }
expect(hook).to be_valid
expect(hook.url_variables).to eq({ 'one' => 'foo', 'two' => 'bar' })
end
end
end end
describe 'encrypted attributes' do describe 'encrypted attributes' do
@ -242,7 +298,7 @@ RSpec.describe WebHook do
end end
describe '#executable?' do describe '#executable?' do
let(:web_hook) { create(:project_hook, project: project) } let_it_be(:web_hook) { create(:project_hook, project: project) }
where(:recent_failures, :not_until, :executable) do where(:recent_failures, :not_until, :executable) do
[ [

View file

@ -230,9 +230,12 @@ RSpec.describe Integrations::Jira do
where(:url, :result) do where(:url, :result) do
'https://abc.atlassian.net' | true 'https://abc.atlassian.net' | true
'http://abc.atlassian.net' | false
'abc.atlassian.net' | false # This is how it behaves currently, but we may need to consider adding scheme if missing 'abc.atlassian.net' | false # This is how it behaves currently, but we may need to consider adding scheme if missing
'https://somethingelse.com' | false 'https://somethingelse.com' | false
nil | false 'javascript://test.atlassian.net/%250dalert(document.domain)' | false
'https://example.com".atlassian.net' | false
nil | false
end end
with_them do with_them do
@ -289,7 +292,7 @@ RSpec.describe Integrations::Jira do
let(:server_info_results) { { 'deploymentType' => 'FutureCloud' } } let(:server_info_results) { { 'deploymentType' => 'FutureCloud' } }
context 'and URL ends in .atlassian.net' do context 'and URL ends in .atlassian.net' do
let(:api_url) { 'http://example-api.atlassian.net' } let(:api_url) { 'https://example-api.atlassian.net' }
it 'deployment_type is set to cloud' do it 'deployment_type is set to cloud' do
expect(integration.jira_tracker_data).to be_deployment_cloud expect(integration.jira_tracker_data).to be_deployment_cloud
@ -297,7 +300,7 @@ RSpec.describe Integrations::Jira do
end end
context 'and URL is something else' do context 'and URL is something else' do
let(:api_url) { 'http://my-jira-api.someserver.com' } let(:api_url) { 'https://my-jira-api.someserver.com' }
it 'deployment_type is set to server' do it 'deployment_type is set to server' do
expect(integration.jira_tracker_data).to be_deployment_server expect(integration.jira_tracker_data).to be_deployment_server
@ -309,7 +312,7 @@ RSpec.describe Integrations::Jira do
let(:server_info_results) { {} } let(:server_info_results) { {} }
context 'and URL ends in .atlassian.net' do context 'and URL ends in .atlassian.net' do
let(:api_url) { 'http://example-api.atlassian.net' } let(:api_url) { 'https://example-api.atlassian.net' }
it 'deployment_type is set to cloud' do it 'deployment_type is set to cloud' do
expect(Gitlab::AppLogger).to receive(:warn).with(message: "Jira API returned no ServerInfo, setting deployment_type from URL", server_info: server_info_results, url: api_url) expect(Gitlab::AppLogger).to receive(:warn).with(message: "Jira API returned no ServerInfo, setting deployment_type from URL", server_info: server_info_results, url: api_url)
@ -318,7 +321,7 @@ RSpec.describe Integrations::Jira do
end end
context 'and URL is something else' do context 'and URL is something else' do
let(:api_url) { 'http://my-jira-api.someserver.com' } let(:api_url) { 'https://my-jira-api.someserver.com' }
it 'deployment_type is set to server' do it 'deployment_type is set to server' do
expect(Gitlab::AppLogger).to receive(:warn).with(message: "Jira API returned no ServerInfo, setting deployment_type from URL", server_info: server_info_results, url: api_url) expect(Gitlab::AppLogger).to receive(:warn).with(message: "Jira API returned no ServerInfo, setting deployment_type from URL", server_info: server_info_results, url: api_url)

View file

@ -22,7 +22,7 @@ RSpec.describe ProjectImportState, type: :model do
before do before do
allow_any_instance_of(Gitlab::GitalyClient::RepositoryService).to receive(:import_repository) allow_any_instance_of(Gitlab::GitalyClient::RepositoryService).to receive(:import_repository)
.with(project.import_url, http_authorization_header: '', mirror: false).and_return(true) .with(project.import_url, http_authorization_header: '', mirror: false, resolved_address: '').and_return(true)
# Works around https://github.com/rspec/rspec-mocks/issues/910 # Works around https://github.com/rspec/rspec-mocks/issues/910
allow(Project).to receive(:find).with(project.id).and_return(project) allow(Project).to receive(:find).with(project.id).and_return(project)

View file

@ -5521,8 +5521,8 @@ RSpec.describe Project, factory_default: :keep do
let(:import_state) { create(:import_state, project: project) } let(:import_state) { create(:import_state, project: project) }
it 'runs the correct hooks' do it 'runs the correct hooks' do
expect(project.repository).to receive(:remove_prohibited_branches) expect(project.repository).to receive(:expire_content_cache).ordered
expect(project.repository).to receive(:expire_content_cache) expect(project.repository).to receive(:remove_prohibited_branches).ordered
expect(project.wiki.repository).to receive(:expire_content_cache) expect(project.wiki.repository).to receive(:expire_content_cache)
expect(import_state).to receive(:finish) expect(import_state).to receive(:finish)
expect(project).to receive(:update_project_counter_caches) expect(project).to receive(:update_project_counter_caches)

View file

@ -1224,11 +1224,22 @@ RSpec.describe Repository do
it 'fetches the URL without creating a remote' do it 'fetches the URL without creating a remote' do
expect(repository) expect(repository)
.to receive(:fetch_remote) .to receive(:fetch_remote)
.with(url, forced: false, prune: true, refmap: :all_refs, http_authorization_header: "") .with(url, forced: false, prune: true, refmap: :all_refs, http_authorization_header: "", resolved_address: '')
.and_return(nil) .and_return(nil)
repository.fetch_as_mirror(url) repository.fetch_as_mirror(url)
end end
context 'with http_host provided' do
it 'fetches the URL with resolved_address value' do
expect(repository)
.to receive(:fetch_remote)
.with(url, forced: false, prune: true, refmap: :all_refs, http_authorization_header: "", resolved_address: '172.16.123.1')
.and_return(nil)
repository.fetch_as_mirror(url, resolved_address: '172.16.123.1')
end
end
end end
describe '#fetch_ref' do describe '#fetch_ref' do

View file

@ -292,6 +292,34 @@ RSpec.describe User do
end end
end end
end end
describe 'confirmation instructions for unconfirmed email' do
let(:unconfirmed_email) { 'first-unconfirmed-email@example.com' }
let(:another_unconfirmed_email) { 'another-unconfirmed-email@example.com' }
context 'when email is changed to another before performing the job that sends confirmation instructions for previous email change request' do
it "mentions the recipient's email in the message body", :aggregate_failures do
same_user = User.find(user.id)
same_user.update!(email: unconfirmed_email)
user.update!(email: another_unconfirmed_email)
perform_enqueued_jobs
confirmation_instructions_for_unconfirmed_email = ActionMailer::Base.deliveries.find do |message|
message.subject == 'Confirmation instructions' && message.to.include?(unconfirmed_email)
end
expect(confirmation_instructions_for_unconfirmed_email.html_part.body.encoded).to match same_user.unconfirmed_email
expect(confirmation_instructions_for_unconfirmed_email.text_part.body.encoded).to match same_user.unconfirmed_email
confirmation_instructions_for_another_unconfirmed_email = ActionMailer::Base.deliveries.find do |message|
message.subject == 'Confirmation instructions' && message.to.include?(another_unconfirmed_email)
end
expect(confirmation_instructions_for_another_unconfirmed_email.html_part.body.encoded).to match user.unconfirmed_email
expect(confirmation_instructions_for_another_unconfirmed_email.text_part.body.encoded).to match user.unconfirmed_email
end
end
end
end end
describe 'validations' do describe 'validations' do

View file

@ -643,6 +643,35 @@ RSpec.describe ProjectPolicy do
end end
end end
describe 'read_grafana', feature_category: :metrics do
using RSpec::Parameterized::TableSyntax
let(:policy) { :read_grafana }
where(:project_visibility, :role, :allowed) do
:public | :anonymous | false
:public | :guest | false
:public | :reporter | true
:internal | :anonymous | false
:internal | :guest | true
:internal | :reporter | true
:private | :anonymous | false
:private | :guest | true
:private | :reporter | true
end
with_them do
let(:current_user) { public_send(role) }
let(:project) { public_send("#{project_visibility}_project") }
if params[:allowed]
it { is_expected.to be_allowed(policy) }
else
it { is_expected.not_to be_allowed(policy) }
end
end
end
describe 'update_max_artifacts_size' do describe 'update_max_artifacts_size' do
context 'when no user' do context 'when no user' do
let(:current_user) { anonymous } let(:current_user) { anonymous }

View file

@ -10,18 +10,6 @@ RSpec.describe Admin::ImpersonationTokensController, :enable_admin_mode do
sign_in(admin) sign_in(admin)
end end
context 'when impersonation is enabled' do
before do
stub_config_setting(impersonation_enabled: true)
end
it 'responds ok' do
get admin_user_impersonation_tokens_path(user_id: user.username)
expect(response).to have_gitlab_http_status(:ok)
end
end
context "when impersonation is disabled" do context "when impersonation is disabled" do
before do before do
stub_config_setting(impersonation_enabled: false) stub_config_setting(impersonation_enabled: false)

View file

@ -949,6 +949,41 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
end end
end end
context 'with session url set to local URL' do
let(:job_params) { { session: { url: 'https://127.0.0.1:7777' } } }
context 'with allow_local_requests_from_web_hooks_and_services? stubbed' do
before do
allow(ApplicationSetting).to receive(:current).and_return(ApplicationSetting.new)
stub_application_setting(allow_local_requests_from_web_hooks_and_services: allow_local_requests)
ci_build
end
let(:ci_build) { create(:ci_build, :pending, :queued, pipeline: pipeline) }
context 'as returning true' do
let(:allow_local_requests) { true }
it 'creates a new session' do
request_job(**job_params)
expect(response).to have_gitlab_http_status(:created)
end
end
context 'as returning false' do
let(:allow_local_requests) { false }
it 'returns :unprocessable_entity status code', :aggregate_failures do
request_job(**job_params)
expect(response).to have_gitlab_http_status(:conflict)
expect(response.body).to include('409 Conflict')
end
end
end
end
def request_job(token = runner.token, **params) def request_job(token = runner.token, **params)
new_params = params.merge(token: token, last_update: last_update) new_params = params.merge(token: token, last_update: last_update)
post api('/jobs/request'), params: new_params.to_json, headers: { 'User-Agent' => user_agent, 'Content-Type': 'application/json' } post api('/jobs/request'), params: new_params.to_json, headers: { 'User-Agent' => user_agent, 'Content-Type': 'application/json' }

View file

@ -31,5 +31,16 @@ RSpec.describe JiraConnect::UsersController do
expect(response.body).not_to include('Return to GitLab') expect(response.body).not_to include('Return to GitLab')
end end
end end
context 'with a script injected' do
let(:return_to) { 'javascript://test.atlassian.net/%250dalert(document.domain)' }
it 'does not include a return url' do
get '/-/jira_connect/users', params: { return_to: return_to }
expect(response).to have_gitlab_http_status(:ok)
expect(response.body).not_to include('Return to GitLab')
end
end
end end
end end

View file

@ -2,7 +2,7 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe ErrorTracking::ListProjectsService do RSpec.describe ErrorTracking::ListProjectsService, feature_category: :integrations do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:project, reload: true) { create(:project) } let_it_be(:project, reload: true) { create(:project) }
@ -51,15 +51,33 @@ RSpec.describe ErrorTracking::ListProjectsService do
end end
context 'masked param token' do context 'masked param token' do
let(:params) { ActionController::Parameters.new(token: "*********", api_host: new_api_host) } let(:params) { ActionController::Parameters.new(token: "*********", api_host: api_host) }
before do context 'with the current api host' do
expect(error_tracking_setting).to receive(:list_sentry_projects) let(:api_host) { 'https://sentrytest.gitlab.com' }
before do
expect(error_tracking_setting).to receive(:list_sentry_projects)
.and_return({ projects: [] }) .and_return({ projects: [] })
end
it 'uses database token' do
expect { subject.execute }.not_to change { error_tracking_setting.token }
end
end end
it 'uses database token' do context 'with a new api host' do
expect { subject.execute }.not_to change { error_tracking_setting.token } let(:api_host) { new_api_host }
it 'returns an error' do
expect(result[:message]).to start_with('Token is a required field')
expect(error_tracking_setting).not_to be_valid
expect(error_tracking_setting).not_to receive(:list_sentry_projects)
end
it 'resets the token' do
expect { subject.execute }.to change { error_tracking_setting.token }.from(token).to(nil)
end
end end
end end

View file

@ -114,5 +114,16 @@ RSpec.describe Packages::Nuget::MetadataExtractionService do
it { expect { subject }.to raise_error(::Packages::Nuget::MetadataExtractionService::ExtractionError, 'nuspec file too big') } it { expect { subject }.to raise_error(::Packages::Nuget::MetadataExtractionService::ExtractionError, 'nuspec file too big') }
end end
context 'with a corrupted nupkg file with a wrong entry size' do
let(:nupkg_fixture_path) { expand_fixture_path('packages/nuget/corrupted_package.nupkg') }
let(:expected_error) { "nuspec file has the wrong entry size: entry 'DummyProject.DummyPackage.nuspec' should be 255B, but is larger when inflated." }
before do
allow(Zip::File).to receive(:new).and_return(Zip::File.new(nupkg_fixture_path, false, false))
end
it { expect { subject }.to raise_error(::Packages::Nuget::MetadataExtractionService::ExtractionError, expected_error) }
end
end end
end end

View file

@ -127,30 +127,67 @@ RSpec.describe Projects::ImportService do
project.import_type = 'bitbucket' project.import_type = 'bitbucket'
end end
it 'succeeds if repository import is successful' do context 'when importer supports refmap' do
expect(project.repository).to receive(:import_repository).and_return(true) before do
expect_next_instance_of(Gitlab::BitbucketImport::Importer) do |importer| project.import_type = 'gitea'
expect(importer).to receive(:execute).and_return(true)
end end
expect_next_instance_of(Projects::LfsPointers::LfsImportService) do |service| it 'succeeds if repository fetch as mirror is successful' do
expect(service).to receive(:execute).and_return(status: :success) expect(project).to receive(:ensure_repository)
expect(project.repository).to receive(:fetch_as_mirror).with('https://bitbucket.org/vim/vim.git', refmap: Gitlab::LegacyGithubImport::Importer.refmap, resolved_address: '').and_return(true)
expect_next_instance_of(Gitlab::LegacyGithubImport::Importer) do |importer|
expect(importer).to receive(:execute).and_return(true)
end
expect_next_instance_of(Projects::LfsPointers::LfsImportService) do |service|
expect(service).to receive(:execute).and_return(status: :success)
end
result = subject.execute
expect(result[:status]).to eq :success
end end
result = subject.execute it 'fails if repository fetch as mirror fails' do
expect(project).to receive(:ensure_repository)
expect(project.repository)
.to receive(:fetch_as_mirror)
.and_raise(Gitlab::Git::CommandError, 'Failed to import the repository /a/b/c')
expect(result[:status]).to eq :success result = subject.execute
expect(result[:status]).to eq :error
expect(result[:message]).to eq "Error importing repository #{project.safe_import_url} into #{project.full_path} - Failed to import the repository [FILTERED]"
end
end end
it 'fails if repository import fails' do context 'when importer does not support refmap' do
expect(project.repository) it 'succeeds if repository import is successful' do
.to receive(:import_repository) expect(project.repository).to receive(:import_repository).and_return(true)
.and_raise(Gitlab::Git::CommandError, 'Failed to import the repository /a/b/c') expect_next_instance_of(Gitlab::BitbucketImport::Importer) do |importer|
expect(importer).to receive(:execute).and_return(true)
end
result = subject.execute expect_next_instance_of(Projects::LfsPointers::LfsImportService) do |service|
expect(service).to receive(:execute).and_return(status: :success)
end
expect(result[:status]).to eq :error result = subject.execute
expect(result[:message]).to eq "Error importing repository #{project.safe_import_url} into #{project.full_path} - Failed to import the repository [FILTERED]"
expect(result[:status]).to eq :success
end
it 'fails if repository import fails' do
expect(project.repository)
.to receive(:import_repository)
.with('https://bitbucket.org/vim/vim.git', resolved_address: '')
.and_raise(Gitlab::Git::CommandError, 'Failed to import the repository /a/b/c')
result = subject.execute
expect(result[:status]).to eq :error
expect(result[:message]).to eq "Error importing repository #{project.safe_import_url} into #{project.full_path} - Failed to import the repository [FILTERED]"
end
end end
context 'when lfs import fails' do context 'when lfs import fails' do
@ -287,6 +324,102 @@ RSpec.describe Projects::ImportService do
end end
end end
context 'when DNS rebind protection is disabled' do
before do
allow(Gitlab::CurrentSettings).to receive(:dns_rebinding_protection_enabled?).and_return(false)
project.import_url = "https://example.com/group/project"
allow(Gitlab::UrlBlocker).to receive(:validate!)
.with(project.import_url, ports: Project::VALID_IMPORT_PORTS, schemes: Project::VALID_IMPORT_PROTOCOLS, dns_rebind_protection: false)
.and_return([Addressable::URI.parse("https://example.com/group/project"), nil])
end
it 'imports repository with url without additional resolved address' do
expect(project.repository).to receive(:import_repository).with('https://example.com/group/project', resolved_address: '').and_return(true)
expect_next_instance_of(Projects::LfsPointers::LfsImportService) do |service|
expect(service).to receive(:execute).and_return(status: :success)
end
result = subject.execute
expect(result[:status]).to eq(:success)
end
end
context 'when DNS rebind protection is enabled' do
before do
allow(Gitlab::CurrentSettings).to receive(:http_proxy_env?).and_return(false)
allow(Gitlab::CurrentSettings).to receive(:dns_rebinding_protection_enabled?).and_return(true)
end
context 'when https url is provided' do
before do
project.import_url = "https://example.com/group/project"
allow(Gitlab::UrlBlocker).to receive(:validate!)
.with(project.import_url, ports: Project::VALID_IMPORT_PORTS, schemes: Project::VALID_IMPORT_PROTOCOLS, dns_rebind_protection: true)
.and_return([Addressable::URI.parse("https://172.16.123.1/group/project"), 'example.com'])
end
it 'imports repository with url and additional resolved address' do
expect(project.repository).to receive(:import_repository).with('https://example.com/group/project', resolved_address: '172.16.123.1').and_return(true)
expect_next_instance_of(Projects::LfsPointers::LfsImportService) do |service|
expect(service).to receive(:execute).and_return(status: :success)
end
result = subject.execute
expect(result[:status]).to eq(:success)
end
end
context 'when http url is provided' do
before do
project.import_url = "http://example.com/group/project"
allow(Gitlab::UrlBlocker).to receive(:validate!)
.with(project.import_url, ports: Project::VALID_IMPORT_PORTS, schemes: Project::VALID_IMPORT_PROTOCOLS, dns_rebind_protection: true)
.and_return([Addressable::URI.parse("http://172.16.123.1/group/project"), 'example.com'])
end
it 'imports repository with url and additional resolved address' do
expect(project.repository).to receive(:import_repository).with('http://example.com/group/project', resolved_address: '172.16.123.1').and_return(true)
expect_next_instance_of(Projects::LfsPointers::LfsImportService) do |service|
expect(service).to receive(:execute).and_return(status: :success)
end
result = subject.execute
expect(result[:status]).to eq(:success)
end
end
context 'when git address is provided' do
before do
project.import_url = "git://example.com/group/project.git"
allow(Gitlab::UrlBlocker).to receive(:validate!)
.with(project.import_url, ports: Project::VALID_IMPORT_PORTS, schemes: Project::VALID_IMPORT_PROTOCOLS, dns_rebind_protection: true)
.and_return([Addressable::URI.parse("git://172.16.123.1/group/project"), 'example.com'])
end
it 'imports repository with url and without resolved address' do
expect(project.repository).to receive(:import_repository).with('git://example.com/group/project.git', resolved_address: '').and_return(true)
expect_next_instance_of(Projects::LfsPointers::LfsImportService) do |service|
expect(service).to receive(:execute).and_return(status: :success)
end
result = subject.execute
expect(result[:status]).to eq(:success)
end
end
end
it_behaves_like 'measurable service' do it_behaves_like 'measurable service' do
let(:base_log_data) do let(:base_log_data) do
{ {

View file

@ -77,6 +77,34 @@ RSpec.describe Users::UpdateService do
subject subject
end end
context 'when race condition' do
# See https://gitlab.com/gitlab-org/gitlab/-/issues/382957
it 'updates email for stale user', :aggregate_failures do
unconfirmed_email = 'unconfirmed-email-user-has-access-to@example.com'
forgery_email = 'forgery@example.com'
user.update!(email: unconfirmed_email)
stale_user = User.find(user.id)
service1 = described_class.new(stale_user, { email: unconfirmed_email }.merge(user: stale_user))
service2 = described_class.new(user, { email: forgery_email }.merge(user: user))
service2.execute
reloaded_user = User.find(user.id)
expect(reloaded_user.unconfirmed_email).to eq(forgery_email)
expect(stale_user.confirmation_token).not_to eq(user.confirmation_token)
expect(reloaded_user.confirmation_token).to eq(user.confirmation_token)
service1.execute
reloaded_user = User.find(user.id)
expect(reloaded_user.unconfirmed_email).to eq(unconfirmed_email)
expect(stale_user.confirmation_token).not_to eq(user.confirmation_token)
expect(reloaded_user.confirmation_token).to eq(stale_user.confirmation_token)
end
end
context 'when check_password is true' do context 'when check_password is true' do
def update_user(user, opts) def update_user(user, opts)
described_class.new(user, opts.merge(user: user)).execute(check_password: true) described_class.new(user, opts.merge(user: user)).execute(check_password: true)
@ -139,9 +167,24 @@ RSpec.describe Users::UpdateService do
update_user(user, job_title: 'supreme leader of the universe') update_user(user, job_title: 'supreme leader of the universe')
end.not_to change { user.user_canonical_email } end.not_to change { user.user_canonical_email }
end end
it 'does not reset unconfirmed email' do
unconfirmed_email = 'unconfirmed-email@example.com'
user.update!(email: unconfirmed_email)
expect do
update_user(user, job_title: 'supreme leader of the universe')
end.not_to change { user.unconfirmed_email }
end
end end
end end
it 'does not try to reset unconfirmed email for a new user' do
expect do
update_user(build(:user), job_title: 'supreme leader of the universe')
end.not_to raise_error
end
def update_user(user, opts) def update_user(user, opts)
described_class.new(user, opts.merge(user: user)).execute described_class.new(user, opts.merge(user: user)).execute
end end

View file

@ -129,7 +129,10 @@ RSpec.describe WebHookService, :request_store, :clean_gitlab_redis_shared_state
context 'there is userinfo' do context 'there is userinfo' do
before do before do
project_hook.update!(url: 'http://{one}:{two}@example.com') project_hook.update!(
url: 'http://{one}:{two}@example.com',
url_variables: { 'one' => 'a', 'two' => 'b' }
)
stub_full_request('http://example.com', method: :post) stub_full_request('http://example.com', method: :post)
end end

View file

@ -11,14 +11,15 @@ RSpec.describe WebHooks::LogExecutionService do
travel_to(Time.current) { example.run } travel_to(Time.current) { example.run }
end end
let_it_be_with_reload(:project_hook) { create(:project_hook) } let_it_be_with_reload(:project_hook) { create(:project_hook, :token) }
let(:response_category) { :ok } let(:response_category) { :ok }
let(:request_headers) { { 'Header' => 'header value' } }
let(:data) do let(:data) do
{ {
trigger: 'trigger_name', trigger: 'trigger_name',
url: 'https://example.com', url: 'https://example.com',
request_headers: { 'Header' => 'header value' }, request_headers: request_headers,
request_data: { 'Request Data' => 'request data value' }, request_data: { 'Request Data' => 'request data value' },
response_body: 'Response body', response_body: 'Response body',
response_status: '200', response_status: '200',
@ -163,5 +164,15 @@ RSpec.describe WebHooks::LogExecutionService do
service.execute service.execute
end end
end end
context 'with X-Gitlab-Token' do
let(:request_headers) { { 'X-Gitlab-Token' => project_hook.token } }
it 'redacts the token' do
service.execute
expect(WebHookLog.recent.first.request_headers).to include('X-Gitlab-Token' => '[REDACTED]')
end
end
end end
end end

View file

@ -145,91 +145,92 @@ module GpgHelpers
'5F7EA3981A5845B141ABD522CCFBE19F00AC8B1D' '5F7EA3981A5845B141ABD522CCFBE19F00AC8B1D'
end end
# passphrase for secret key 2 is:
# qHGKKXBZ72VamRMUH6d7LsLWsHnJJx3p
def secret_key2 def secret_key2
<<~KEY.strip <<~KEY.strip
-----BEGIN PGP PRIVATE KEY BLOCK----- -----BEGIN PGP PRIVATE KEY BLOCK-----
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
4f6xMfFu2A== =VtBY
=3YL6
-----END PGP PRIVATE KEY BLOCK----- -----END PGP PRIVATE KEY BLOCK-----
KEY KEY
end end
@ -238,44 +239,44 @@ module GpgHelpers
<<~KEY.strip <<~KEY.strip
-----BEGIN PGP PUBLIC KEY BLOCK----- -----BEGIN PGP PUBLIC KEY BLOCK-----
mQGNBF+7O0oBDADvRto4K9PT83Lbyp/qaMPIzBbXHB6ljdDoyb+Pn2UrHk9MhB5v mQGNBGN931ABDADe6KRsn1d37PKH9QSZiDqyGu77Av3vPlAwRHypUEEAc47WNle7
bTgBv+rctOabmimPPalcyaxOQ1GtrYizo1l33YQZupSvaOoStVLWqnBx8eKKcUv8 87CmIaDPKQ8f5R7vu9hpVX+Lisoy23s7lM9nvZcjfR/t465oP5JimGSOiQ1Ilcgz
QucS3S2qFhj9G0tdHW7RW2BGrSwEM09d2xFsFKKAj/4RTTU5idYWrvB24DNcrBh+ eCvOmbvVdiSQthqrQ5oUY0jmRtnEbpNC4LMV3+i3Npj4UcCeORFOWNf+I1AiTtLX
iKsoa+rmJf1bwL6Mn9f9NwzundG16qibY/UwMlltQriWaVMn2AKVuu6HrX9pe3g5 fRyw+ifGjqxe/0dVt4w65kZbpetYlxGoYCjAMPZT287chfJCYeNm8N+8T9BKx3ex
Er2Szjc7DZitt6eAy3PmuWHXzDCCvsO7iPxXlywY49hLhDen3/Warwn1pSbp+im4 Z4bpAGY0hcZwH2Qo5Dg6MFGn2oQjvGmD3iVJ48IEN7HPtiHeOoD8rlUG2smiAk7u
/0oJExLZBSS1xHbRSQoR6matF0+V/6TQz8Yo3g8z9HgyEtn1V7QJo3PoNrnEl73e ZuSiNhEKf43p1hSOhUyB1KBrFs8/npNyhOIXzrmE8cFuUgDeUZQX/c7BS0QmNdmr
9yslTqVtzba0Q132oRoO7eEYf82KrPOmVGj6Q9LpSXFLfsl3GlPgoBxRZXpT62CV nRn1CUzYYHSsQB8oGMueQPCmjZh68AqRfIYjZW4KbsqydjPmTRUomZfCB0RWBd/T
3rGalIa2yKmcBQtyICjR1+PTIAJcVIPyr92xTo4RfLwVFW0czX7LM2H0FT2Ksj7L 8Ycvdh8NFQRCCcHfcUVj/PnMyaUE1aAf4xApA694ceXK6GtMz+2ZGNAThsLDrzfw
U450ewBz8N6bFDMAEQEAAbQtTmFubmllIEJlcm5oYXJkIDxuYW5uaWUuYmVybmhh VtR6WSOjq+E9Ab8AEQEAAbQtTmFubmllIEJlcm5oYXJkIDxuYW5uaWUuYmVybmhh
cmRAZXhhbXBsZS5jb20+iQHUBBMBCgA+FiEExEem9r/Zzvj7NxeFVxYlqTAkEXkF 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 7ysn+Hu60VIDw8enikBOwZE1oTbrKiTywMbufg==
=RAwd =cp3L
-----END PGP PUBLIC KEY BLOCK----- -----END PGP PUBLIC KEY BLOCK-----
KEY KEY
end end
@ -285,7 +286,7 @@ module GpgHelpers
end end
def fingerprint2 def fingerprint2
'C447A6F6BFD9CEF8FB371785571625A930241179' 'EC164842311CCF9F005DA1A34FD830CEB3E86C97'
end end
def names def names

View file

@ -2,33 +2,54 @@
RSpec.shared_examples 'user views tag' do RSpec.shared_examples 'user views tag' do
context 'when user views with the tag' do context 'when user views with the tag' do
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository, :public) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:tag_name) { "stable" } let(:tag_name) { "stable" }
let!(:release) { create(:release, project: project, tag: tag_name, name: "ReleaseName") } let(:release_name) { 'ReleaseName' }
let(:release_notes) { 'Release notes' }
let!(:release) do
create(:release, project: project, tag: tag_name, name: release_name, description: release_notes)
end
before do before do
project.add_developer(user)
project.repository.add_tag(user, tag_name, project.default_branch_or_main) project.repository.add_tag(user, tag_name, project.default_branch_or_main)
sign_in(user) sign_in(user)
end end
shared_examples 'shows tag' do context 'and user is authorized to read release' do
it do before do
visit tag_page project.add_developer(user)
end
expect(page).to have_content tag_name shared_examples 'shows tag' do
expect(page).to have_link("ReleaseName", href: project_release_path(project, release)) it do
visit tag_page
expect(page).to have_content tag_name
expect(page).to have_link(release_name, href: project_release_path(project, release))
end
end
it_behaves_like 'shows tag'
context 'when tag name contains a slash' do
let(:tag_name) { "stable/v0.1" }
it_behaves_like 'shows tag'
end end
end end
it_behaves_like 'shows tag' context 'and user is not authorized to read release' do
before do
project.project_feature.update!(releases_access_level: Featurable::PRIVATE)
end
context 'when tag name contains a slash' do it 'hides release link and notes', :aggregate_failures do
let(:tag_name) { "stable/v0.1" } visit tag_page
it_behaves_like 'shows tag' expect(page).not_to have_link(release_name, href: project_release_path(project, release))
expect(page).not_to have_text(release_notes)
end
end end
end end
end end

View file

@ -71,26 +71,3 @@ RSpec.shared_examples 'Self-managed Core resource access tokens' do
end end
end end
end end
RSpec.shared_examples 'GitLab.com Core resource access tokens' do
before do
allow(::Gitlab).to receive(:com?).and_return(true)
stub_ee_application_setting(should_check_namespace_plan: true)
end
context 'with owner access' do
let(:current_user) { owner }
context 'create resource access tokens' do
it { is_expected.not_to be_allowed(:create_resource_access_tokens) }
end
context 'read resource access tokens' do
it { is_expected.not_to be_allowed(:read_resource_access_tokens) }
end
context 'destroy resource access tokens' do
it { is_expected.not_to be_allowed(:destroy_resource_access_tokens) }
end
end
end

Some files were not shown because too many files have changed in this diff Show more