Compare commits

...

4 commits

Author SHA1 Message Date
Pirate Praveen
406c71498e Upload to experimental 2020-05-28 23:31:58 +05:30
Pirate Praveen
1c9886d0ee Refresh patches (remove fuzz) 2020-05-28 23:30:46 +05:30
Pirate Praveen
1cfa946ec8 Update upstream source from tag 'upstream/12.10.7'
Update to upstream version '12.10.7'
with Debian dir cbc7381234
2020-05-28 23:00:39 +05:30
Pirate Praveen
33a3b36430 New upstream version 12.10.7 2020-05-28 22:55:37 +05:30
84 changed files with 1198 additions and 399 deletions

View file

@ -1,5 +1,35 @@
Please view this file on the master branch, on stable branches it's out of date.
## 12.10.6 (2020-05-15)
- No changes.
## 12.10.5 (2020-05-13)
### Fixed (1 change)
- Remove check for user being an applicable code owner. !31809
## 12.10.4 (2020-05-05)
- No changes.
## 12.10.3 (2020-05-04)
### Fixed (1 change)
- Fixes file row commits not showing for certain projects.
### Changed (1 change)
- Move deploy keys section back to repository settings. !29184
### Added (1 change)
- Enable expiring subscription banner. !30304
## 12.10.2 (2020-04-30)
### Security (3 changes)

View file

@ -2,6 +2,51 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
## 12.10.7 (2020-05-27)
### Security (14 changes)
- Add an extra validation to Static Site Editor payload.
- Hide EKS secret key in admin integrations settings.
- Added data integrity check before updating a deploy key.
- Display only verified emails on notifications and profile page.
- Disable caching on repo/blobs/[sha]/raw endpoint.
- Require confirmed email address for GitLab OAuth authentication.
- Kubernetes cluster details page no longer exposes Service Token.
- Fix confirming unverified emails with soft email confirmation flow enabled.
- Disallow user to control PUT request using mermaid markdown in issue description.
- Check forked project permissions before allowing fork.
- Limit memory footprint of a command that generates ZIP artifacts metadata.
- Fix file enuming using Group Import.
- Prevent XSS in the monitoring dashboard.
- Use `gsub` instead of the Ruby `%` operator to perform variable substitution in Prometheus proxy API.
## 12.10.6 (2020-05-15)
### Fixed (5 changes)
- Fix duplicate index removal on ci_pipelines.project_id. !31043
- Fix 500 on creating an invalid domains and verification. !31190
- Fix incorrect number of errors returned when querying sentry errors. !31252
- Add instance column to services table if it's missing. !31631
- Fix incorrect regex used in FileUploader#extract_dynamic_path. !32271
## 12.10.5 (2020-05-13)
### Added (1 change)
- Consider project group and group ancestors when processing CODEOWNERS entries. !31804
## 12.10.4 (2020-05-05)
### Fixed (1 change)
- Add a Project's group to list of groups when parsing for codeowner entries. !30934
## 12.10.3 (2020-05-04)
### Fixed (6 changes)

View file

@ -1 +1 @@
12.10.3
12.10.7

View file

@ -1 +1 @@
8.30.1
8.30.2

View file

@ -1 +1 @@
12.10.3
12.10.7

View file

@ -105,7 +105,6 @@ export default class Clusters {
});
this.installApplication = this.installApplication.bind(this);
this.showToken = this.showToken.bind(this);
this.errorContainer = document.querySelector('.js-cluster-error');
this.successContainer = document.querySelector('.js-cluster-success');
@ -116,7 +115,6 @@ export default class Clusters {
);
this.errorReasonContainer = this.errorContainer.querySelector('.js-error-reason');
this.successApplicationContainer = document.querySelector('.js-cluster-application-notice');
this.showTokenButton = document.querySelector('.js-show-cluster-token');
this.tokenField = document.querySelector('.js-cluster-token');
this.ingressDomainHelpText = document.querySelector('.js-ingress-domain-help-text');
this.ingressDomainSnippet =
@ -255,7 +253,6 @@ export default class Clusters {
}
addListeners() {
if (this.showTokenButton) this.showTokenButton.addEventListener('click', this.showToken);
eventHub.$on('installApplication', this.installApplication);
eventHub.$on('updateApplication', data => this.updateApplication(data));
eventHub.$on('saveKnativeDomain', data => this.saveKnativeDomain(data));
@ -271,7 +268,6 @@ export default class Clusters {
}
removeListeners() {
if (this.showTokenButton) this.showTokenButton.removeEventListener('click', this.showToken);
eventHub.$off('installApplication', this.installApplication);
eventHub.$off('updateApplication', this.updateApplication);
eventHub.$off('saveKnativeDomain');
@ -339,18 +335,6 @@ export default class Clusters {
}
}
showToken() {
const type = this.tokenField.getAttribute('type');
if (type === 'password') {
this.tokenField.setAttribute('type', 'text');
this.showTokenButton.textContent = s__('ClusterIntegration|Hide');
} else {
this.tokenField.setAttribute('type', 'password');
this.showTokenButton.textContent = s__('ClusterIntegration|Show');
}
}
hideAll() {
this.errorContainer.classList.add('hidden');
this.successContainer.classList.add('hidden');

View file

@ -101,7 +101,11 @@ export default class Issue {
this.disableCloseReopenButton($button);
const url = $button.attr('href');
const url = $button.data('close-reopen-url');
if (!url) {
return;
}
return axios
.put(url)
.then(({ data }) => {

View file

@ -1,6 +1,7 @@
<script>
import { __, s__, sprintf } from '~/locale';
import { GlFormGroup, GlFormInput, GlFormRadioGroup, GlFormTextarea } from '@gitlab/ui';
import { escape as esc } from 'lodash';
const defaultFileName = dashboard => dashboard.path.split('/').reverse()[0];
@ -42,7 +43,7 @@ export default {
html: sprintf(
__('Commit to %{branchName} branch'),
{
branchName: `<strong>${this.defaultBranch}</strong>`,
branchName: `<strong>${esc(this.defaultBranch)}</strong>`,
},
false,
),

View file

@ -40,7 +40,9 @@ export default class Profile {
bindEvents() {
$('.js-preferences-form').on('change.preference', 'input[type=radio]', this.submitForm);
$('.js-group-notification-email').on('change', this.submitForm);
$('#user_notification_email').on('change', this.submitForm);
$('#user_notification_email').on('select2-selecting', event => {
setTimeout(this.submitForm.bind(event.currentTarget));
});
$('#user_notified_of_own_activity').on('change', this.submitForm);
this.form.on('submit', this.onSubmitForm);
}

View file

@ -191,8 +191,10 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
params[:application_setting][:import_sources]&.delete("")
params[:application_setting][:restricted_visibility_levels]&.delete("")
params[:application_setting].delete(:elasticsearch_aws_secret_access_key) if params[:application_setting][:elasticsearch_aws_secret_access_key].blank?
params[:application_setting][:required_instance_ci_template] = nil if params[:application_setting][:required_instance_ci_template].blank?
remove_blank_params_for!(:elasticsearch_aws_secret_access_key, :eks_secret_access_key)
# TODO Remove domain_blacklist_raw in APIv5 (See https://gitlab.com/gitlab-org/gitlab-foss/issues/67204)
params.delete(:domain_blacklist_raw) if params[:domain_blacklist_file]
params.delete(:domain_blacklist_raw) if params[:domain_blacklist]
@ -261,6 +263,10 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
render action
end
def remove_blank_params_for!(*keys)
params[:application_setting].delete_if { |setting, value| setting.to_sym.in?(keys) && value.blank? }
end
# overridden in EE
def valid_setting_panels
VALID_SETTING_PANELS

View file

@ -53,10 +53,16 @@ module MembershipActions
end
def request_access
membershipable.request_access(current_user)
access_requester = membershipable.request_access(current_user)
redirect_to polymorphic_path(membershipable),
notice: _('Your request for access has been queued for review.')
if access_requester.persisted?
redirect_to polymorphic_path(membershipable),
notice: _('Your request for access has been queued for review.')
else
redirect_to polymorphic_path(membershipable),
alert: _("Your request for access could not be processed: %{error_meesage}") %
{ error_meesage: access_requester.errors.full_messages.to_sentence }
end
end
def approve_access_request

View file

@ -4,6 +4,8 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
include Gitlab::Experimentation::ControllerConcern
include InitializesCurrentUserMode
before_action :verify_confirmed_email!, only: [:new]
layout 'profile'
# Overridden from Doorkeeper::AuthorizationsController to
@ -21,4 +23,13 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
render "doorkeeper/authorizations/error"
end
end
private
def verify_confirmed_email!
return if current_user&.confirmed?
pre_auth.error = :unconfirmed_email
render "doorkeeper/authorizations/error"
end
end

View file

@ -37,6 +37,8 @@ class Projects::DeployKeysController < Projects::ApplicationController
end
def update
access_denied! unless deploy_key
if deploy_key.update(update_params)
flash[:notice] = _('Deploy key was successfully updated.')
redirect_to_repository
@ -85,10 +87,12 @@ class Projects::DeployKeysController < Projects::ApplicationController
end
def update_params
permitted_params = [deploy_keys_projects_attributes: [:id, :can_push]]
permitted_params = [deploy_keys_projects_attributes: [:can_push]]
permitted_params << :title if can?(current_user, :update_deploy_key, deploy_key)
params.require(:deploy_key).permit(*permitted_params)
key_update_params = params.require(:deploy_key).permit(*permitted_params)
key_update_params.dig(:deploy_keys_projects_attributes, '0')&.merge!(id: deploy_keys_project.id)
key_update_params
end
def authorize_update_deploy_key!

View file

@ -7,6 +7,8 @@ class Projects::PagesDomainsController < Projects::ApplicationController
before_action :authorize_update_pages!
before_action :domain, except: [:new, :create]
helper_method :domain_presenter
def show
end
@ -27,7 +29,7 @@ class Projects::PagesDomainsController < Projects::ApplicationController
end
def retry_auto_ssl
PagesDomains::RetryAcmeOrderService.new(@domain.pages_domain).execute
PagesDomains::RetryAcmeOrderService.new(@domain).execute
redirect_to project_pages_domain_path(@project, @domain)
end
@ -88,6 +90,10 @@ class Projects::PagesDomainsController < Projects::ApplicationController
end
def domain
@domain ||= @project.pages_domains.find_by_domain!(params[:id].to_s).present(current_user: current_user)
@domain ||= @project.pages_domains.find_by_domain!(params[:id].to_s)
end
def domain_presenter
@domain_presenter ||= domain.present(current_user: current_user)
end
end

View file

@ -14,6 +14,7 @@ class NotificationSetting < ApplicationRecord
validates :user_id, uniqueness: { scope: [:source_type, :source_id],
message: "already exists in source",
allow_nil: true }
validate :owns_notification_email, if: :notification_email_changed?
scope :for_groups, -> { where(source_type: 'Namespace') }
@ -97,6 +98,13 @@ class NotificationSetting < ApplicationRecord
def event_enabled?(event)
respond_to?(event) && !!public_send(event) # rubocop:disable GitlabSecurity/PublicSend
end
def owns_notification_email
return if user.temp_oauth_email?
return if notification_email.empty?
errors.add(:notification_email, _("is not an email you own")) unless user.verified_emails.include?(notification_email)
end
end
NotificationSetting.prepend_if_ee('EE::NotificationSetting')

View file

@ -228,9 +228,10 @@ class User < ApplicationRecord
if previous_changes.key?('email')
# Grab previous_email here since previous_changes changes after
# #update_emails_with_primary_email and #update_notification_email are called
previous_confirmed_at = previous_changes.key?('confirmed_at') ? previous_changes['confirmed_at'][0] : confirmed_at
previous_email = previous_changes[:email][0]
update_emails_with_primary_email(previous_email)
update_emails_with_primary_email(previous_confirmed_at, previous_email)
update_invalid_gpg_signatures
if previous_email == notification_email
@ -774,15 +775,15 @@ class User < ApplicationRecord
end
def owns_notification_email
return if temp_oauth_email?
return if new_record? || temp_oauth_email?
errors.add(:notification_email, _("is not an email you own")) unless all_emails.include?(notification_email)
errors.add(:notification_email, _("is not an email you own")) unless verified_emails.include?(notification_email)
end
def owns_public_email
return if public_email.blank?
errors.add(:public_email, _("is not an email you own")) unless all_emails.include?(public_email)
errors.add(:public_email, _("is not an email you own")) unless verified_emails.include?(public_email)
end
def owns_commit_email
@ -830,13 +831,15 @@ class User < ApplicationRecord
# By using an `after_commit` instead of `after_update`, we avoid the recursive callback
# scenario, though it then requires us to use the `previous_changes` hash
# rubocop: disable CodeReuse/ServiceClass
def update_emails_with_primary_email(previous_email)
def update_emails_with_primary_email(previous_confirmed_at, previous_email)
primary_email_record = emails.find_by(email: email)
Emails::DestroyService.new(self, user: self).execute(primary_email_record) if primary_email_record
# the original primary email was confirmed, and we want that to carry over. We don't
# have access to the original confirmation values at this point, so just set confirmed_at
Emails::CreateService.new(self, user: self, email: previous_email).execute(confirmed_at: confirmed_at)
Emails::CreateService.new(self, user: self, email: previous_email).execute(confirmed_at: previous_confirmed_at)
update_columns(confirmed_at: primary_email_record.confirmed_at) if primary_email_record&.confirmed_at
end
# rubocop: enable CodeReuse/ServiceClass
@ -1228,18 +1231,20 @@ class User < ApplicationRecord
all_emails
end
def all_public_emails
all_emails(include_private_email: false)
end
def verified_emails
def verified_emails(include_private_email: true)
verified_emails = []
verified_emails << email if primary_email_verified?
verified_emails << private_commit_email
verified_emails << private_commit_email if include_private_email
verified_emails.concat(emails.confirmed.pluck(:email))
verified_emails
end
def public_verified_emails
emails = verified_emails(include_private_email: false)
emails << email unless temp_oauth_email?
emails.uniq
end
def any_email?(check_email)
downcased = check_email.downcase

View file

@ -10,6 +10,12 @@ module Clusters
def execute(cluster)
if validate_params(cluster)
token = params.dig(:platform_kubernetes_attributes, :token)
if token.blank?
params[:platform_kubernetes_attributes]&.delete(:token)
end
cluster.update(params)
else
false

View file

@ -4,6 +4,16 @@ module Prometheus
class ProxyVariableSubstitutionService < BaseService
include Stepable
VARIABLE_INTERPOLATION_REGEX = /
%{ # Variable needs to be wrapped in these chars.
\s* # Allow whitespace before and after the variable name.
(?<variable> # Named capture.
\w+ # Match one or more word characters.
)
\s*
}
/x.freeze
steps :validate_variables,
:add_params_to_result,
:substitute_params,
@ -46,6 +56,14 @@ module Prometheus
success(result)
end
def substitute_ruby_variables(result)
return success(result) unless query(result)
result[:params][:query] = gsub(query(result), full_context)
success(result)
end
def substitute_liquid_variables(result)
return success(result) unless query(result)
@ -57,26 +75,20 @@ module Prometheus
error(e.message)
end
def substitute_ruby_variables(result)
return success(result) unless query(result)
# The % operator doesn't replace variables if the hash contains string
# keys.
result[:params][:query] = query(result) % predefined_context.symbolize_keys
success(result)
rescue TypeError, ArgumentError => exception
log_error(exception.message)
Gitlab::ErrorTracking.track_exception(exception, {
template_string: query(result),
variables: predefined_context
})
error(_('Malformed string'))
def gsub(string, context)
# Search for variables of the form `%{variable}` in the string and replace
# them with their value.
string.gsub(VARIABLE_INTERPOLATION_REGEX) do |match|
# Replace with the value of the variable, or if there is no such variable,
# replace the invalid variable with itself. So,
# `up{instance="%{invalid_variable}"}` will remain
# `up{instance="%{invalid_variable}"}` after substitution.
context.fetch($~[:variable], match)
end
end
def predefined_context
@predefined_context ||= Gitlab::Prometheus::QueryVariables.call(@environment)
Gitlab::Prometheus::QueryVariables.call(@environment).stringify_keys
end
def full_context

View file

@ -15,7 +15,7 @@ class FileUploader < GitlabUploader
prepend ObjectStorage::Extension::RecordsUploads
MARKDOWN_PATTERN = %r{\!?\[.*?\]\(/uploads/(?<secret>[0-9a-f]{32})/(?<file>.*?)\)}.freeze
DYNAMIC_PATH_PATTERN = %r{.*/(?<secret>\h{10,32})/(?<identifier>.*)}.freeze
DYNAMIC_PATH_PATTERN = %r{.*(?<secret>\b(\h{10}|\h{32}))\/(?<identifier>.*)}.freeze
VALID_SECRET_PATTERN = %r{\A\h{10,32}\z}.freeze
InvalidSecret = Class.new(StandardError)

View file

@ -26,6 +26,6 @@
= f.text_field :eks_access_key_id, class: 'form-control'
.form-group
= f.label :eks_secret_access_key, 'Secret access key', class: 'label-bold'
= f.password_field :eks_secret_access_key, value: @application_setting.eks_secret_access_key, class: 'form-control'
= f.password_field :eks_secret_access_key, autocomplete: 'off', class: 'form-control'
= f.submit 'Save changes', class: "btn btn-success"

View file

@ -25,16 +25,10 @@
label: s_('ClusterIntegration|CA Certificate'), label_class: 'label-bold',
input_group_class: 'gl-field-error-anchor', append: copy_ca_cert_btn
- show_token_btn = (platform_field.button s_('ClusterIntegration|Show'),
type: 'button', class: 'js-show-cluster-token btn btn-default')
- copy_token_btn = clipboard_button(text: platform.token, title: s_('ClusterIntegration|Copy Service Token'),
class: 'input-group-text btn-default') if cluster.read_only_kubernetes_platform_fields?
= platform_field.text_field :token, type: 'password', class: 'js-select-on-focus js-cluster-token',
required: true, title: s_('ClusterIntegration|Service token is required.'),
readonly: cluster.read_only_kubernetes_platform_fields?,
label: s_('ClusterIntegration|Service Token'), label_class: 'label-bold',
input_group_class: 'gl-field-error-anchor', append: show_token_btn + copy_token_btn
= platform_field.password_field :token, type: 'password', class: 'js-select-on-focus js-cluster-token',
readonly: cluster.read_only_kubernetes_platform_fields?, autocomplete: 'new-password',
label: s_('ClusterIntegration|Enter new Service Token'), label_class: 'label-bold',
input_group_class: 'gl-field-error-anchor'
= platform_field.form_group :authorization_type do
= platform_field.check_box :authorization_type, { disabled: true, label: s_('ClusterIntegration|RBAC-enabled cluster'),

View file

@ -5,7 +5,7 @@
- help_text = email_change_disabled ? s_("Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO.") % { group_name: @user.managing_group.name } : read_only_help_text
= form.text_field :email, required: true, class: 'input-lg', value: (@user.email unless @user.temp_oauth_email?), help: help_text.html_safe, readonly: readonly || email_change_disabled
= form.select :public_email, options_for_select(@user.all_public_emails, selected: @user.public_email),
= form.select :public_email, options_for_select(@user.public_verified_emails, selected: @user.public_email),
{ help: s_("Profiles|This email will be displayed on your public profile"), include_blank: s_("Profiles|Do not show on profile") },
control_class: 'select2 input-lg', disabled: email_change_disabled
- commit_email_link_url = help_page_path('user/profile/index', anchor: 'commit-email', target: '_blank')

View file

@ -1,6 +1,6 @@
- form = local_assigns.fetch(:form)
.form-group
= form.label :notification_email, class: "label-bold"
= form.select :notification_email, @user.all_public_emails, { include_blank: false }, class: "select2", disabled: local_assigns.fetch(:email_change_disabled, nil)
= form.select :notification_email, @user.public_verified_emails, { include_blank: false }, class: "select2", disabled: local_assigns.fetch(:email_change_disabled, nil)
.help-block
= local_assigns.fetch(:help_text, nil)

View file

@ -13,4 +13,4 @@
.table-section.section-30
= form_for setting, url: profile_notifications_group_path(group), method: :put, html: { class: 'update-notifications' } do |f|
= f.select :notification_email, @user.all_public_emails, { include_blank: 'Global notification email' }, class: 'select2 js-group-notification-email'
= f.select :notification_email, @user.public_verified_emails, { include_blank: 'Global notification email' }, class: 'select2 js-group-notification-email'

View file

@ -1,9 +1,9 @@
- page_title 'Edit Deploy Key'
%h3.page-title Edit Deploy Key
%h3.page-title= _('Edit Deploy Key')
%hr
%div
= form_for [@project.namespace.becomes(Namespace), @project, @deploy_key], html: { class: 'js-requires-input' } do |f|
= form_for [@project.namespace.becomes(Namespace), @project, @deploy_key], include_id: false, html: { class: 'js-requires-input' } do |f|
= render partial: 'shared/deploy_keys/form', locals: { form: f, deploy_key: @deploy_key }
.form-actions
= f.submit 'Save changes', class: 'btn-success btn'

View file

@ -1,7 +1,7 @@
- auto_ssl_available = ::Gitlab::LetsEncrypt.enabled?
- auto_ssl_enabled = @domain.auto_ssl_enabled?
- auto_ssl_enabled = domain_presenter.auto_ssl_enabled?
- auto_ssl_available_and_enabled = auto_ssl_available && auto_ssl_enabled
- has_user_defined_certificate = @domain.certificate && @domain.certificate_user_provided?
- has_user_defined_certificate = domain_presenter.certificate && domain_presenter.certificate_user_provided?
- if auto_ssl_available
.form-group.border-section
@ -36,9 +36,9 @@
= _('Certificate')
.d-flex.justify-content-between.align-items-center.p-3
%span
= @domain.pages_domain.subject || _('missing')
= domain_presenter.pages_domain.subject || _('missing')
= link_to _('Remove'),
clean_certificate_project_pages_domain_path(@project, @domain),
clean_certificate_project_pages_domain_path(@project, domain_presenter),
data: { confirm: _('Are you sure?') },
class: 'btn btn-remove btn-sm',
method: :delete

View file

@ -1,5 +1,5 @@
- verification_enabled = Gitlab::CurrentSettings.pages_domain_verification_enabled?
- dns_record = "#{@domain.domain} CNAME #{@domain.project.pages_subdomain}.#{Settings.pages.host}."
- dns_record = "#{domain_presenter.domain} CNAME #{domain_presenter.project.pages_subdomain}.#{Settings.pages.host}."
.form-group.border-section
.row
@ -13,17 +13,17 @@
%p.form-text.text-muted
= _("To access this domain create a new DNS record")
- if verification_enabled
- verification_record = "#{@domain.verification_domain} TXT #{@domain.keyed_verification_code}"
- verification_record = "#{domain_presenter.verification_domain} TXT #{domain_presenter.keyed_verification_code}"
.form-group.border-section
.row
.col-sm-2
= _("Verification status")
.col-sm-10
.status-badge
- text, status = @domain.unverified? ? [_('Unverified'), 'badge-danger'] : [_('Verified'), 'badge-success']
- text, status = domain_presenter.unverified? ? [_('Unverified'), 'badge-danger'] : [_('Verified'), 'badge-success']
.badge{ class: status }
= text
= link_to sprite_icon("redo"), verify_project_pages_domain_path(@project, @domain), method: :post, class: "btn has-tooltip", title: _("Retry verification")
= link_to sprite_icon("redo"), verify_project_pages_domain_path(@project, domain_presenter), method: :post, class: "btn has-tooltip", title: _("Retry verification")
.input-group
= text_field_tag :domain_verification, verification_record, class: "monospace js-select-on-focus form-control", readonly: true
.input-group-append

View file

@ -1,15 +1,15 @@
- if @domain.errors.any?
- if domain_presenter.errors.any?
.alert.alert-danger
- @domain.errors.full_messages.each do |msg|
- domain_presenter.errors.full_messages.each do |msg|
= msg
.form-group.border-section
.row
- if @domain.persisted?
- if domain_presenter.persisted?
.col-sm-2
= _("Domain")
.col-sm-10
= external_link(@domain.url, @domain.url)
= external_link(domain_presenter.url, domain_presenter.url)
- else
.col-sm-2
= f.label :domain, _("Domain")
@ -17,7 +17,7 @@
.input-group
= f.text_field :domain, required: true, autocomplete: "off", class: "form-control"
- if @domain.persisted?
- if domain_presenter.persisted?
= render 'dns'
- if Gitlab.config.pages.external_https

View file

@ -1,6 +1,6 @@
- if @domain.enabled?
- if @domain.auto_ssl_enabled
- if @domain.show_auto_ssl_failed_warning?
- if domain_presenter.enabled?
- if domain_presenter.auto_ssl_enabled
- if domain_presenter.show_auto_ssl_failed_warning?
.form-group.border-section.js-shown-if-auto-ssl{ class: ("d-none" unless auto_ssl_available_and_enabled) }
.row
.col-sm-10.offset-sm-2
@ -9,8 +9,8 @@
= icon('warning', class: 'mr-2')
= _("Something went wrong while obtaining the Let's Encrypt certificate.")
.row.mx-0.mt-3
= link_to s_('GitLabPagesDomains|Retry'), retry_auto_ssl_project_pages_domain_path(@project, @domain), class: "btn btn-sm btn-grouped btn-warning", method: :post
- elsif !@domain.certificate_gitlab_provided?
= link_to s_('GitLabPagesDomains|Retry'), retry_auto_ssl_project_pages_domain_path(@project, domain_presenter), class: "btn btn-sm btn-grouped btn-warning", method: :post
- elsif !domain_presenter.certificate_gitlab_provided?
.form-group.border-section.js-shown-if-auto-ssl{ class: ("d-none" unless auto_ssl_available_and_enabled) }
.row
.col-sm-10.offset-sm-2

View file

@ -4,7 +4,7 @@
= _("New Pages Domain")
= render 'projects/pages_domains/helper_text'
%div
= form_for [@project.namespace.becomes(Namespace), @project, @domain], html: { class: 'fieldset-form' } do |f|
= form_for [@project.namespace.becomes(Namespace), @project, domain_presenter], html: { class: 'fieldset-form' } do |f|
= render 'form', { f: f }
.form-actions
= f.submit _('Create New Domain'), class: "btn btn-success"

View file

@ -1,10 +1,10 @@
- add_to_breadcrumbs _("Pages"), project_pages_path(@project)
- breadcrumb_title @domain.domain
- page_title @domain.domain
- breadcrumb_title domain_presenter.domain
- page_title domain_presenter.domain
- verification_enabled = Gitlab::CurrentSettings.pages_domain_verification_enabled?
- if verification_enabled && @domain.unverified?
- if verification_enabled && domain_presenter.unverified?
= content_for :flash_message do
.alert.alert-warning
.container-fluid.container-limited
@ -14,7 +14,7 @@
= _('Pages Domain')
= render 'projects/pages_domains/helper_text'
%div
= form_for [@project.namespace.becomes(Namespace), @project, @domain], html: { class: 'fieldset-form' } do |f|
= form_for [@project.namespace.becomes(Namespace), @project, domain_presenter], html: { class: 'fieldset-form' } do |f|
= render 'form', { f: f }
.form-actions.d-flex.justify-content-between
= f.submit _('Save Changes'), class: "btn btn-success"

View file

@ -8,7 +8,7 @@
.float-left.btn-group.prepend-left-10.issuable-close-dropdown.droplab-dropdown.js-issuable-close-dropdown
= link_to "#{display_button_action} #{display_issuable_type}", close_reopen_issuable_path(issuable),
method: button_method, class: "#{button_class} btn-#{button_action}", title: "#{display_button_action} #{display_issuable_type}"
method: button_method, class: "#{button_class} btn-#{button_action}", title: "#{display_button_action} #{display_issuable_type}", data: { 'close-reopen-url': close_reopen_issuable_path(issuable) }
= button_tag type: 'button', class: "#{toggle_class} btn-#{button_action}-color",
data: { 'dropdown-trigger' => '#issuable-close-menu' }, 'aria-label' => 'Toggle dropdown' do

View file

@ -36,6 +36,7 @@ en:
access_denied: 'The resource owner or authorization server denied the request.'
invalid_scope: 'The requested scope is invalid, unknown, or malformed.'
server_error: 'The authorization server encountered an unexpected condition which prevented it from fulfilling the request.'
unconfirmed_email: 'Verify the email address in your account profile before you sign in.'
temporarily_unavailable: 'The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server.'
#configuration error messages

View file

@ -8,10 +8,13 @@ class DropIndexCiPipelinesOnProjectId < ActiveRecord::Migration[5.2]
disable_ddl_transaction!
def up
remove_concurrent_index :ci_pipelines, :project_id
remove_concurrent_index_by_name :ci_pipelines, 'index_ci_pipelines_on_project_id'
# extra (duplicate) index that already existed on some installs
remove_concurrent_index_by_name :ci_pipelines, 'ci_pipelines_project_id_idx'
end
def down
add_concurrent_index :ci_pipelines, :project_id
add_concurrent_index :ci_pipelines, :project_id, name: 'index_ci_pipelines_on_project_id'
end
end

View file

@ -0,0 +1,30 @@
# frozen_string_literal: true
class AddMissingInstanceToServicess < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
# This is a corrective migration to keep the instance column.
# Upgrade from 12.7 to 12.9 removes the instance column as it was first added
# in the normal migration and then removed in the post migration.
#
# 12.8 removed the instance column in a post deployment migration https://gitlab.com/gitlab-org/gitlab/-/merge_requests/24885
# 12.9 added the instance column in a normal migration https://gitlab.com/gitlab-org/gitlab/-/merge_requests/25714
#
# rubocop:disable Migration/AddColumnWithDefault
# rubocop:disable Migration/UpdateLargeTable
def up
unless column_exists?(:services, :instance)
add_column_with_default(:services, :instance, :boolean, default: false)
end
end
# rubocop:enable Migration/AddColumnWithDefault
# rubocop:enable Migration/UpdateLargeTable
def down
# Does not apply
end
end

View file

@ -0,0 +1,25 @@
# frozen_string_literal: true
class AddMissingIndexToServiceUniqueInstancePerType < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
# This is a corrective migration to keep the index on instance column.
# Upgrade from 12.7 to 12.9 removes the instance column as it was first added
# in the normal migration and then removed in the post migration.
#
# 12.8 removed the instance column in a post deployment migration https://gitlab.com/gitlab-org/gitlab/-/merge_requests/24885
# 12.9 added the instance column in a normal migration https://gitlab.com/gitlab-org/gitlab/-/merge_requests/25714
def up
unless index_exists_by_name?(:services, 'index_services_on_type_and_instance')
add_concurrent_index(:services, [:type, :instance], unique: true, where: 'instance IS TRUE')
end
end
def down
# Does not apply
end
end

View file

@ -13214,5 +13214,7 @@ COPY "schema_migrations" (version) FROM STDIN;
20200416111111
20200416120128
20200416120354
20200511162057
20200511162115
\.

7
debian/changelog vendored
View file

@ -1,3 +1,10 @@
gitlab (12.10.7-1) experimental; urgency=medium
* New upstream version 12.10.7 with many security fixes (CVE IDs not
assigned yet)
-- Pirate Praveen <praveen@debian.org> Thu, 28 May 2020 23:13:13 +0530
gitlab (12.10.3-3) experimental; urgency=medium
* Update css-loader options in config/webpack.config.js

View file

@ -758,7 +758,7 @@ coerced to arrays of integers. Before this was done within Virtus.
+ class GroupImport < Grape::API::Instance
MAXIMUM_FILE_SIZE = 50.megabytes.freeze
helpers do
helpers Helpers::FileUploadHelpers
--- a/lib/api/group_labels.rb
+++ b/lib/api/group_labels.rb
@@ -1,7 +1,7 @@
@ -1294,7 +1294,7 @@ coerced to arrays of integers. Before this was done within Virtus.
include PaginationParams
include Helpers::CustomAttributes
@@ -520,7 +520,7 @@
@@ -522,7 +522,7 @@
end
params do
optional :search, type: String, desc: 'Return list of users matching the search criteria'
@ -1368,8 +1368,8 @@ coerced to arrays of integers. Before this was done within Virtus.
+ class Repositories < Grape::API::Instance
include PaginationParams
before { authorize! :download_code, user_project }
@@ -139,7 +139,7 @@
helpers ::API::Helpers::HeadersHelpers
@@ -143,7 +143,7 @@
success Entities::Commit
end
params do

View file

@ -1197,7 +1197,7 @@ PUT /projects/:id
| `approvals_before_merge` | integer | no | **(STARTER)** How many approvers should approve merge request by default |
| `external_authorization_classification_label` | string | no | **(PREMIUM)** The classification label for the project |
| `mirror` | boolean | no | **(STARTER)** Enables pull mirroring in a project |
| `mirror_user_id` | integer | no | **(STARTER)** User responsible for all the activity surrounding a pull mirror event |
| `mirror_user_id` | integer | no | **(STARTER)** User responsible for all the activity surrounding a pull mirror event. Can only be set by admins. |
| `mirror_trigger_builds` | boolean | no | **(STARTER)** Pull mirroring triggers builds |
| `only_mirror_protected_branches` | boolean | no | **(STARTER)** Only mirror protected branches |
| `mirror_overwrites_diverged_branches` | boolean | no | **(STARTER)** Pull mirror overwrites diverged branches |

View file

@ -82,7 +82,7 @@ must be set.
While you can view and manage the full details of an issue on the [issue page](#issue-page),
you can also work with multiple issues at a time using the [Issues List](#issues-list),
[Issue Boards](#issue-boards), Issue references, and [Epics](#epics-ultimate)**(ULTIMATE)**.
[Issue Boards](#issue-boards), Issue references, and [Epics](#epics-premium)**(PREMIUM)**.
Key actions for Issues include:
@ -132,7 +132,7 @@ With [Design Management](design_management.md), you can upload design
assets to issues and view them all together to easily share and
collaborate with your team.
### Epics **(ULTIMATE)**
### Epics **(PREMIUM)**
[Epics](../../group/epics/index.md) let you manage your portfolio of projects more
efficiently and with less effort by tracking groups of issues that share a theme, across

View file

@ -16,7 +16,7 @@ You can find all the information for that issue on one screen.
- **2.** [To Do](#to-do)
- **3.** [Assignee](#assignee)
- **3.1.** [Multiple Assignees **(STARTER)**](#multiple-assignees-starter)
- **4.** [Epic **(ULTIMATE)**](#epic-ultimate)
- **4.** [Epic **(PREMIUM)**](#epic-premium)
- **5.** [Milestone](#milestone)
- **6.** [Time tracking](#time-tracking)
- **7.** [Due date](#due-date)
@ -100,7 +100,7 @@ to track in large teams where there is shared ownership of an issue.
In [GitLab Starter](https://about.gitlab.com/pricing/), you can
[assign multiple people](multiple_assignees_for_issues.md) to an issue.
### Epic **(ULTIMATE)**
### Epic **(PREMIUM)**
You can assign issues to an [Epic](../../group/epics/index.md), which allows better
management of groups of related issues.

View file

@ -4,6 +4,8 @@ module API
class GroupImport < Grape::API
MAXIMUM_FILE_SIZE = 50.megabytes.freeze
helpers Helpers::FileUploadHelpers
helpers do
def parent_group
find_group!(params[:parent_id]) if params[:parent_id].present?
@ -48,29 +50,20 @@ module API
params do
requires :path, type: String, desc: 'Group path'
requires :name, type: String, desc: 'Group name'
requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The group export file to be imported'
optional :parent_id, type: Integer, desc: "The ID of the parent group that the group will be imported into. Defaults to the current user's namespace."
optional 'file.path', type: String, desc: 'Path to locally stored body (generated by Workhorse)'
optional 'file.name', type: String, desc: 'Real filename as send in Content-Disposition (generated by Workhorse)'
optional 'file.type', type: String, desc: 'Real content type as send in Content-Type (generated by Workhorse)'
optional 'file.size', type: Integer, desc: 'Real size of file (generated by Workhorse)'
optional 'file.md5', type: String, desc: 'MD5 checksum of the file (generated by Workhorse)'
optional 'file.sha1', type: String, desc: 'SHA1 checksum of the file (generated by Workhorse)'
optional 'file.sha256', type: String, desc: 'SHA256 checksum of the file (generated by Workhorse)'
end
post 'import' do
authorize_create_group!
require_gitlab_workhorse!
uploaded_file = UploadedFile.from_params(params, :file, ImportExportUploader.workhorse_local_upload_path)
bad_request!('Unable to process group import file') unless uploaded_file
validate_file!
group_params = {
path: params[:path],
name: params[:name],
parent_id: params[:parent_id],
visibility_level: closest_allowed_visibility_level,
import_export_upload: ImportExportUpload.new(import_file: uploaded_file)
import_export_upload: ImportExportUpload.new(import_file: params[:file])
}
group = ::Groups::CreateService.new(current_user, group_params).execute

View file

@ -444,6 +444,8 @@ module API
not_found!("Source Project") unless fork_from_project
authorize! :fork_project, fork_from_project
result = ::Projects::ForkService.new(fork_from_project, current_user).execute(user_project)
if result

View file

@ -6,6 +6,8 @@ module API
class Repositories < Grape::API
include PaginationParams
helpers ::API::Helpers::HeadersHelpers
before { authorize! :download_code, user_project }
params do
@ -67,6 +69,8 @@ module API
get ':id/repository/blobs/:sha/raw' do
assign_blob_vars!
no_cache_headers
send_git_blob @repo, @blob
end

View file

@ -23,6 +23,20 @@ module Gitlab
alias_method :has_next_page, :next_page?
alias_method :has_previous_page, :previous_page?
private
def load_nodes
@nodes ||= begin
# As the pagination happens externally we just grab all the nodes
limited_nodes = items
limited_nodes = limited_nodes.first(first) if first
limited_nodes = limited_nodes.last(last) if last
limited_nodes
end
end
end
end
end

View file

@ -21,7 +21,7 @@ module Gitlab
project_id: project.id,
project: project.path,
namespace: project.namespace.path,
return_url: return_url,
return_url: sanitize_url(return_url),
is_supported_content: supported_content?
}
end
@ -47,6 +47,10 @@ module Gitlab
def file_exists?
commit_id.present? && repository.blob_at(commit_id, file_path).present?
end
def sanitize_url(url)
url if Gitlab::UrlSanitizer.valid_web?(url)
end
end
end
end

View file

@ -3,6 +3,7 @@
module Gitlab
class UrlSanitizer
ALLOWED_SCHEMES = %w[http https ssh git].freeze
ALLOWED_WEB_SCHEMES = %w[http https].freeze
def self.sanitize(content)
regexp = URI::DEFAULT_PARSER.make_regexp(ALLOWED_SCHEMES)
@ -12,17 +13,21 @@ module Gitlab
content.gsub(regexp, '')
end
def self.valid?(url)
def self.valid?(url, allowed_schemes: ALLOWED_SCHEMES)
return false unless url.present?
return false unless url.is_a?(String)
uri = Addressable::URI.parse(url.strip)
ALLOWED_SCHEMES.include?(uri.scheme)
allowed_schemes.include?(uri.scheme)
rescue Addressable::URI::InvalidURIError
false
end
def self.valid_web?(url)
valid?(url, allowed_schemes: ALLOWED_WEB_SCHEMES)
end
def initialize(url, credentials: nil)
%i[user password].each do |symbol|
credentials[symbol] = credentials[symbol].presence if credentials&.key?(symbol)

View file

@ -4362,9 +4362,6 @@ msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
msgstr ""
msgid "ClusterIntegration|Copy Service Token"
msgstr ""
msgid "ClusterIntegration|Could not load IAM roles"
msgstr ""
@ -4443,6 +4440,9 @@ msgstr ""
msgid "ClusterIntegration|Enabled stack"
msgstr ""
msgid "ClusterIntegration|Enter new Service Token"
msgstr ""
msgid "ClusterIntegration|Enter the details for your Amazon EKS Kubernetes cluster"
msgstr ""
@ -4518,9 +4518,6 @@ msgstr ""
msgid "ClusterIntegration|Helm streamlines installing and managing Kubernetes applications. Tiller runs inside of your Kubernetes Cluster, and manages releases of your charts."
msgstr ""
msgid "ClusterIntegration|Hide"
msgstr ""
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
msgstr ""
@ -4896,9 +4893,6 @@ msgstr ""
msgid "ClusterIntegration|Set the global mode for the WAF in this cluster. This can be overridden at the environmental level."
msgstr ""
msgid "ClusterIntegration|Show"
msgstr ""
msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
@ -12440,9 +12434,6 @@ msgstr ""
msgid "Makes this issue confidential."
msgstr ""
msgid "Malformed string"
msgstr ""
msgid "Manage"
msgstr ""
@ -21248,9 +21239,6 @@ msgstr ""
msgid "This user will be the author of all events in the activity feed that are the result of an update, like new branches being created or new commits being pushed to existing branches."
msgstr ""
msgid "This user will be the author of all events in the activity feed that are the result of an update, like new branches being created or new commits being pushed to existing branches. Upon creation or when reassigning you can only assign yourself to be the mirror user."
msgstr ""
msgid "This variable can not be masked."
msgstr ""
@ -23973,6 +23961,9 @@ msgstr ""
msgid "You will be removed from existing projects/groups"
msgstr ""
msgid "You will be the author of all events in the activity feed that are the result of an update, like new branches being created or new commits being pushed to existing branches."
msgstr ""
msgid "You will first need to set up Jira Integration to use this feature."
msgstr ""
@ -24207,6 +24198,9 @@ msgstr ""
msgid "Your projects"
msgstr ""
msgid "Your request for access could not be processed: %{error_meesage}"
msgstr ""
msgid "Your request for access has been queued for review."
msgstr ""
@ -24624,6 +24618,9 @@ msgstr ""
msgid "email '%{email}' does not match the allowed domain of '%{email_domain}'"
msgstr ""
msgid "email '%{email}' is not a verified email."
msgstr ""
msgid "enabled"
msgstr ""

View file

@ -155,6 +155,46 @@ describe Admin::ApplicationSettingsController do
end
end
describe 'PATCH #integrations' do
before do
stub_feature_flags(instance_level_integrations: false)
sign_in(admin)
end
describe 'EKS integration' do
let(:application_setting) { ApplicationSetting.current }
let(:settings_params) do
{
eks_integration_enabled: '1',
eks_account_id: '123456789012',
eks_access_key_id: 'dummy access key',
eks_secret_access_key: 'dummy secret key'
}
end
it 'updates EKS settings' do
patch :integrations, params: { application_setting: settings_params }
expect(application_setting.eks_integration_enabled).to be_truthy
expect(application_setting.eks_account_id).to eq '123456789012'
expect(application_setting.eks_access_key_id).to eq 'dummy access key'
expect(application_setting.eks_secret_access_key).to eq 'dummy secret key'
end
context 'secret access key is blank' do
let(:settings_params) { { eks_secret_access_key: '' } }
it 'does not update the secret key' do
application_setting.update!(eks_secret_access_key: 'dummy secret key')
patch :integrations, params: { application_setting: settings_params }
expect(application_setting.reload.eks_secret_access_key).to eq 'dummy secret key'
end
end
end
end
describe 'PUT #reset_registration_token' do
before do
sign_in(admin)

View file

@ -3,7 +3,6 @@
require 'spec_helper'
describe Oauth::AuthorizationsController do
let(:user) { create(:user) }
let!(:application) { create(:oauth_application, scopes: 'api read_user', redirect_uri: 'http://example.com') }
let(:params) do
{
@ -19,53 +18,68 @@ describe Oauth::AuthorizationsController do
end
describe 'GET #new' do
context 'without valid params' do
it 'returns 200 code and renders error view' do
get :new
context 'when the user is confirmed' do
let(:user) { create(:user) }
context 'without valid params' do
it 'returns 200 code and renders error view' do
get :new
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template('doorkeeper/authorizations/error')
end
end
context 'with valid params' do
render_views
it 'returns 200 code and renders view' do
get :new, params: params
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template('doorkeeper/authorizations/new')
end
it 'deletes session.user_return_to and redirects when skip authorization' do
application.update(trusted: true)
request.session['user_return_to'] = 'http://example.com'
get :new, params: params
expect(request.session['user_return_to']).to be_nil
expect(response).to have_gitlab_http_status(:found)
end
context 'when there is already an access token for the application' do
context 'when the request scope matches any of the created token scopes' do
before do
scopes = Doorkeeper::OAuth::Scopes.from_string('api')
allow(Doorkeeper.configuration).to receive(:scopes).and_return(scopes)
create :oauth_access_token, application: application, resource_owner_id: user.id, scopes: scopes
end
it 'authorizes the request and redirects' do
get :new, params: params
expect(request.session['user_return_to']).to be_nil
expect(response).to have_gitlab_http_status(:found)
end
end
end
end
end
context 'when the user is unconfirmed' do
let(:user) { create(:user, confirmed_at: nil) }
it 'returns 200 and renders error view' do
get :new, params: params
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template('doorkeeper/authorizations/error')
end
end
context 'with valid params' do
render_views
it 'returns 200 code and renders view' do
get :new, params: params
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template('doorkeeper/authorizations/new')
end
it 'deletes session.user_return_to and redirects when skip authorization' do
application.update(trusted: true)
request.session['user_return_to'] = 'http://example.com'
get :new, params: params
expect(request.session['user_return_to']).to be_nil
expect(response).to have_gitlab_http_status(:found)
end
context 'when there is already an access token for the application' do
context 'when the request scope matches any of the created token scopes' do
before do
scopes = Doorkeeper::OAuth::Scopes.from_string('api')
allow(Doorkeeper.configuration).to receive(:scopes).and_return(scopes)
create :oauth_access_token, application: application, resource_owner_id: user.id, scopes: scopes
end
it 'authorizes the request and redirects' do
get :new, params: params
expect(request.session['user_return_to']).to be_nil
expect(response).to have_gitlab_http_status(:found)
end
end
end
end
end
end

View file

@ -5,8 +5,8 @@ require 'spec_helper'
describe Profiles::NotificationsController do
let(:user) do
create(:user) do |user|
user.emails.create(email: 'original@example.com')
user.emails.create(email: 'new@example.com')
user.emails.create(email: 'original@example.com', confirmed_at: Time.current)
user.emails.create(email: 'new@example.com', confirmed_at: Time.current)
user.notification_email = 'original@example.com'
user.save!
end

View file

@ -256,7 +256,7 @@ describe Projects::DeployKeysController do
end
def deploy_key_params(title, can_push)
deploy_keys_projects_attributes = { '0' => { id: deploy_keys_project, can_push: can_push } }
deploy_keys_projects_attributes = { '0' => { can_push: can_push } }
{ deploy_key: { title: title, deploy_keys_projects_attributes: deploy_keys_projects_attributes } }
end
@ -300,6 +300,42 @@ describe Projects::DeployKeysController do
expect { subject }.to change { deploy_keys_project.reload.can_push }.from(false).to(true)
end
end
context 'when a different deploy key id param is injected' do
let(:extra_params) { deploy_key_params('updated title', '1') }
let(:hacked_params) do
extra_params.reverse_merge(id: other_deploy_key_id,
namespace_id: project.namespace,
project_id: project)
end
subject { put :update, params: hacked_params }
context 'and that deploy key id exists' do
let(:other_project) { create(:project) }
let(:other_deploy_key) do
key = create(:deploy_key)
project.deploy_keys << key
key
end
let(:other_deploy_key_id) { other_deploy_key.id }
it 'does not update the can_push attribute' do
expect { subject }.not_to change { deploy_key.deploy_keys_project_for(project).can_push }
end
end
context 'and that deploy key id does not exist' do
let(:other_deploy_key_id) { 9999 }
it 'returns 404' do
subject
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
end
context 'with admin as project maintainer' do

View file

@ -148,16 +148,10 @@ describe Projects::PagesDomainsController do
describe 'POST verify' do
let(:params) { request_params.merge(id: pages_domain.domain) }
def stub_service
service = double(:service)
expect(VerifyPagesDomainService).to receive(:new) { service }
service
end
it 'handles verification success' do
expect(stub_service).to receive(:execute).and_return(status: :success)
expect_next_instance_of(VerifyPagesDomainService, pages_domain) do |service|
expect(service).to receive(:execute).and_return(status: :success)
end
post :verify, params: params
@ -166,7 +160,9 @@ describe Projects::PagesDomainsController do
end
it 'handles verification failure' do
expect(stub_service).to receive(:execute).and_return(status: :failed)
expect_next_instance_of(VerifyPagesDomainService, pages_domain) do |service|
expect(service).to receive(:execute).and_return(status: :failed)
end
post :verify, params: params

View file

@ -44,6 +44,10 @@ FactoryBot.define do
after(:build) { |user, _| user.block! }
end
trait :unconfirmed do
confirmed_at { nil }
end
trait :with_avatar do
avatar { fixture_file_upload('spec/fixtures/dk.png') }
end

View file

@ -39,7 +39,7 @@ describe 'User Cluster', :js do
expect(page.find_field('cluster[platform_kubernetes_attributes][api_url]').value)
.to have_content('http://example.com')
expect(page.find_field('cluster[platform_kubernetes_attributes][token]').value)
.to have_content('my-token')
.to be_empty
end
end

View file

@ -0,0 +1,21 @@
# frozen_string_literal: true
require 'spec_helper'
describe 'OAuth Provider' do
describe 'Standard OAuth Authorization' do
let(:application) { create(:oauth_application, scopes: 'read_user') }
before do
sign_in(user)
visit oauth_authorization_path(client_id: application.uid,
redirect_uri: application.redirect_uri.split.first,
response_type: 'code',
state: 'my_state',
scope: 'read_user')
end
it_behaves_like 'Secure OAuth Authorizations'
end
end

View file

@ -46,7 +46,7 @@ describe 'User Cluster', :js do
expect(page.find_field('cluster[platform_kubernetes_attributes][api_url]').value)
.to have_content('http://example.com')
expect(page.find_field('cluster[platform_kubernetes_attributes][token]').value)
.to have_content('my-token')
.to be_empty
end
it 'user sees RBAC is enabled by default' do

View file

@ -158,6 +158,17 @@ shared_examples 'pages settings editing' do
expect(page).to have_content('my.test.domain.com')
end
it 'shows validation error if domain is duplicated' do
project.pages_domains.create!(domain: 'my.test.domain.com')
visit new_project_pages_domain_path(project)
fill_in 'Domain', with: 'my.test.domain.com'
click_button 'Create New Domain'
expect(page).to have_content('Domain has already been taken')
end
describe 'with dns verification enabled' do
before do
stub_application_setting(pages_domain_verification_enabled: true)

View file

@ -82,28 +82,6 @@ describe('Clusters', () => {
});
});
describe('showToken', () => {
it('should update token field type', () => {
cluster.showTokenButton.click();
expect(cluster.tokenField.getAttribute('type')).toEqual('text');
cluster.showTokenButton.click();
expect(cluster.tokenField.getAttribute('type')).toEqual('password');
});
it('should update show token button text', () => {
cluster.showTokenButton.click();
expect(cluster.showTokenButton.textContent).toEqual('Hide');
cluster.showTokenButton.click();
expect(cluster.showTokenButton.textContent).toEqual('Show');
});
});
describe('checkForNewInstalls', () => {
const INITIAL_APP_MAP = {
helm: { status: null, title: 'Helm Tiller' },

View file

@ -0,0 +1,82 @@
<div class="description" updated-at="">
<div class="md issue-realtime-trigger-pulse">
<svg
id="mermaid-1587752414912"
width="100%"
xmlns="http://www.w3.org/2000/svg"
style="max-width: 185.35000610351562px;"
viewBox="0 0 185.35000610351562 50.5"
class="mermaid"
>
<g transform="translate(0, 0)">
<g class="output">
<g class="clusters"></g>
<g class="edgePaths"></g>
<g class="edgeLabels"></g>
<g class="nodes">
<g
class="node js-issuable-actions btn-close clickable"
style="opacity: 1;"
id="A"
transform="translate(92.67500305175781,25.25)"
title="click to PUT"
>
<a
class="js-issuable-actions btn-close clickable"
href="https://invalid"
rel="noopener"
>
<rect
rx="0"
ry="0"
x="-84.67500305175781"
y="-17.25"
width="169.35000610351562"
height="34.5"
class="label-container"
></rect>
<g class="label" transform="translate(0,0)">
<g transform="translate(-74.67500305175781,-7.25)">
<text style="">
<tspan xml:space="preserve" dy="1em" x="1">Click to send a PUT request</tspan>
</text>
</g>
</g>
</a>
</g>
</g>
</g>
</g>
<text class="source" display="none">
Click to send a PUT request
</text>
</svg>
</div>
<textarea
data-update-url="/h5bp/html5-boilerplate/-/issues/35.json"
dir="auto"
class="hidden js-task-list-field"
></textarea>
<div class="modal-open recaptcha-modal js-recaptcha-modal" style="display: none;">
<div role="dialog" tabindex="-1" class="modal d-block">
<div role="document" class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title float-left">Please solve the reCAPTCHA</h4>
<button type="button" data-dismiss="modal" aria-label="Close" class="close float-right">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<div>
<p>We want to be sure it is you, please confirm you are not a robot.</p>
<div></div>
</div>
</div>
<!---->
</div>
</div>
</div>
<div class="modal-backdrop fade show"></div>
</div>
</div>

View file

@ -18,6 +18,7 @@ describe('Issue', () => {
preloadFixtures('issues/closed-issue.html');
preloadFixtures('issues/issue-with-task-list.html');
preloadFixtures('issues/open-issue.html');
preloadFixtures('static/issue_with_mermaid_graph.html');
function expectErrorMessage() {
const $flashMessage = $('div.flash-alert');
@ -228,4 +229,30 @@ describe('Issue', () => {
});
});
});
describe('when not displaying blocked warning', () => {
describe('when clicking a mermaid graph inside an issue description', () => {
let mock;
let spy;
beforeEach(() => {
loadFixtures('static/issue_with_mermaid_graph.html');
mock = new MockAdapter(axios);
spy = jest.spyOn(axios, 'put');
});
afterEach(() => {
mock.restore();
jest.clearAllMocks();
});
it('does not make a PUT request', () => {
Issue.prototype.initIssueBtnEventListeners();
$('svg a.js-issuable-actions').trigger('click');
expect(spy).not.toHaveBeenCalled();
});
});
});
});

View file

@ -3,9 +3,17 @@ import DuplicateDashboardForm from '~/monitoring/components/duplicate_dashboard_
import { dashboardGitResponse } from '../mock_data';
describe('DuplicateDashboardForm', () => {
let wrapper;
let wrapper;
const createMountedWrapper = (props = {}) => {
// Use `mount` to render native input elements
wrapper = mount(DuplicateDashboardForm, {
propsData: { ...props },
sync: false,
});
};
describe('DuplicateDashboardForm', () => {
const defaultBranch = 'master';
const findByRef = ref => wrapper.find({ ref });
@ -20,14 +28,7 @@ describe('DuplicateDashboardForm', () => {
};
beforeEach(() => {
// Use `mount` to render native input elements
wrapper = mount(DuplicateDashboardForm, {
propsData: {
dashboard: dashboardGitResponse[0],
defaultBranch,
},
sync: false,
});
createMountedWrapper({ dashboard: dashboardGitResponse[0], defaultBranch });
});
it('renders correctly', () => {
@ -144,3 +145,18 @@ describe('DuplicateDashboardForm', () => {
});
});
});
describe('DuplicateDashboardForm escapes elements', () => {
const branchToEscape = "<img/src='x'onerror=alert(document.domain)>";
beforeEach(() => {
createMountedWrapper({ dashboard: dashboardGitResponse[0], defaultBranch: branchToEscape });
});
it('should escape branch name data', () => {
const branchOptionHtml = wrapper.vm.branchOptions[0].html;
const escapedBranch = '&lt;img/src=&#39;x&#39;onerror=alert(document.domain)&gt';
expect(branchOptionHtml).toEqual(expect.stringContaining(escapedBranch));
});
});

View file

@ -19,6 +19,20 @@ describe Gitlab::Graphql::Pagination::ExternallyPaginatedArrayConnection do
it_behaves_like 'connection with paged nodes' do
let(:paged_nodes_size) { values.size }
end
context 'when after or before is specified, they are ignored' do
# after and before are not used to filter the array, as they
# were already used to directly fetch the external array
it_behaves_like 'connection with paged nodes' do
let(:arguments) { { after: next_cursor } }
let(:paged_nodes_size) { values.size }
end
it_behaves_like 'connection with paged nodes' do
let(:arguments) { { before: prev_cursor } }
let(:paged_nodes_size) { values.size }
end
end
end
describe '#start_cursor' do

View file

@ -57,5 +57,23 @@ describe Gitlab::StaticSiteEditor::Config do
it { is_expected.to include(is_supported_content: false) }
end
context 'when return_url is not a valid URL' do
let(:return_url) { 'example.com' }
it { is_expected.to include(return_url: nil) }
end
context 'when return_url has a javascript scheme' do
let(:return_url) { 'javascript:alert(document.domain)' }
it { is_expected.to include(return_url: nil) }
end
context 'when return_url is missing' do
let(:return_url) { nil }
it { is_expected.to include(return_url: nil) }
end
end
end

View file

@ -60,6 +60,30 @@ describe Gitlab::UrlSanitizer do
end
end
describe '.valid_web?' do
where(:value, :url) do
false | nil
false | ''
false | '123://invalid:url'
false | 'valid@project:url.git'
false | 'valid:pass@project:url.git'
false | %w(test array)
false | 'ssh://example.com'
false | 'ssh://:@example.com'
false | 'ssh://foo@example.com'
false | 'ssh://foo:bar@example.com'
false | 'ssh://foo:bar@example.com/group/group/project.git'
false | 'git://example.com/group/group/project.git'
false | 'git://foo:bar@example.com/group/group/project.git'
true | 'http://foo:bar@example.com/group/group/project.git'
true | 'https://foo:bar@example.com/group/group/project.git'
end
with_them do
it { expect(described_class.valid_web?(url)).to eq(value) }
end
end
describe '#sanitized_url' do
context 'credentials in hash' do
where(username: ['foo', '', nil], password: ['bar', '', nil])

View file

@ -108,6 +108,11 @@ describe Group do
let(:group_notification_email) { 'user+group@example.com' }
let(:subgroup_notification_email) { 'user+subgroup@example.com' }
before do
create(:email, :confirmed, user: user, email: group_notification_email)
create(:email, :confirmed, user: user, email: subgroup_notification_email)
end
subject { subgroup.notification_email_for(user) }
context 'when both group notification emails are set' do

View file

@ -48,6 +48,33 @@ RSpec.describe NotificationSetting do
expect(notification_setting.reopen_merge_request).to eq(false)
end
end
context 'notification_email' do
let_it_be(:user) { create(:user) }
subject { described_class.new(source_id: 1, source_type: 'Project', user_id: user.id) }
it 'allows to change email to verified one' do
email = create(:email, :confirmed, user: user)
subject.update(notification_email: email.email)
expect(subject).to be_valid
end
it 'does not allow to change email to not verified one' do
email = create(:email, user: user)
subject.update(notification_email: email.email)
expect(subject).to be_invalid
end
it 'allows to change email to empty one' do
subject.update(notification_email: '')
expect(subject).to be_valid
end
end
end
describe '#for_projects' do

View file

@ -296,7 +296,7 @@ describe User, :do_not_mock_admin_mode do
end
it_behaves_like 'an object with email-formated attributes', :public_email, :notification_email do
subject { build(:user).tap { |user| user.emails << build(:email, email: email_value) } }
subject { create(:user).tap { |user| user.emails << build(:email, email: email_value, confirmed_at: Time.current) } }
end
describe '#commit_email' do
@ -565,6 +565,32 @@ describe User, :do_not_mock_admin_mode do
user = build(:user, email: "temp-email-for-oauth@example.com")
expect(user).to be_valid
end
it 'does not accept not verified emails' do
email = create(:email)
user = email.user
user.update(notification_email: email.email)
expect(user).to be_invalid
end
end
context 'owns_public_email' do
it 'accepts verified emails' do
email = create(:email, :confirmed, email: 'test@test.com')
user = email.user
user.update(public_email: email.email)
expect(user).to be_valid
end
it 'does not accept not verified emails' do
email = create(:email)
user = email.user
user.update(public_email: email.email)
expect(user).to be_invalid
end
end
context 'set_commit_email' do
@ -914,6 +940,108 @@ describe User, :do_not_mock_admin_mode do
expect(@user.emails.count).to eq 1
expect(@user.emails.first.confirmed_at).not_to eq nil
end
context 'when the first email was unconfirmed and the second email gets confirmed' do
let(:user) { create(:user, :unconfirmed, email: 'should-be-unconfirmed@test.com') }
before do
user.update!(email: 'should-be-confirmed@test.com')
user.confirm
end
it 'updates user.email' do
expect(user.email).to eq('should-be-confirmed@test.com')
end
it 'confirms user.email' do
expect(user).to be_confirmed
end
it 'keeps the unconfirmed email unconfirmed' do
email = user.emails.first
expect(email.email).to eq('should-be-unconfirmed@test.com')
expect(email).not_to be_confirmed
end
it 'has only one email association' do
expect(user.emails.size).to be(1)
end
end
end
context 'when an existing email record is set as primary' do
let(:user) { create(:user, email: 'confirmed@test.com') }
context 'when it is unconfirmed' do
let(:originally_unconfirmed_email) { 'should-stay-unconfirmed@test.com' }
before do
user.emails << create(:email, email: originally_unconfirmed_email, confirmed_at: nil)
user.update!(email: originally_unconfirmed_email)
end
it 'keeps the user confirmed' do
expect(user).to be_confirmed
end
it 'keeps the original email' do
expect(user.email).to eq('confirmed@test.com')
end
context 'when the email gets confirmed' do
before do
user.confirm
end
it 'keeps the user confirmed' do
expect(user).to be_confirmed
end
it 'updates the email' do
expect(user.email).to eq(originally_unconfirmed_email)
end
end
end
context 'when it is confirmed' do
let!(:old_confirmed_email) { user.email }
let(:confirmed_email) { 'already-confirmed@test.com' }
before do
user.emails << create(:email, :confirmed, email: confirmed_email)
user.update!(email: confirmed_email)
end
it 'keeps the user confirmed' do
expect(user).to be_confirmed
end
it 'updates the email' do
expect(user.email).to eq(confirmed_email)
end
it 'moves the old email' do
email = user.reload.emails.first
expect(email.email).to eq(old_confirmed_email)
expect(email).to be_confirmed
end
end
end
context 'when unconfirmed user deletes a confirmed additional email' do
let(:user) { create(:user, :unconfirmed) }
before do
user.emails << create(:email, :confirmed)
end
it 'does not affect the confirmed status' do
expect { user.emails.confirmed.destroy_all }.not_to change { user.confirmed? } # rubocop: disable Cop/DestroyAll
end
end
describe '#update_notification_email' do
@ -2068,6 +2196,31 @@ describe User, :do_not_mock_admin_mode do
end
end
describe '#public_verified_emails' do
let(:user) { create(:user) }
it 'returns only confirmed public emails' do
email_confirmed = create :email, user: user, confirmed_at: Time.current
create :email, user: user
expect(user.public_verified_emails).to contain_exactly(
user.email,
email_confirmed.email
)
end
it 'returns confirmed public emails plus main user email when user is not confirmed' do
user = create(:user, confirmed_at: nil)
email_confirmed = create :email, user: user, confirmed_at: Time.current
create :email, user: user
expect(user.public_verified_emails).to contain_exactly(
user.email,
email_confirmed.email
)
end
end
describe '#verified_email?' do
let(:user) { create(:user) }
@ -4231,9 +4384,10 @@ describe User, :do_not_mock_admin_mode do
context 'when an ancestor has a level other than Global' do
let(:ancestor) { create(:group) }
let(:group) { create(:group, parent: ancestor) }
let(:email) { create(:email, :confirmed, email: 'ancestor@example.com', user: user) }
before do
create(:notification_setting, user: user, source: ancestor, level: 'participating', notification_email: 'ancestor@example.com')
create(:notification_setting, user: user, source: ancestor, level: 'participating', notification_email: email.email)
end
it 'has the same level set' do
@ -4258,10 +4412,12 @@ describe User, :do_not_mock_admin_mode do
let(:grand_ancestor) { create(:group) }
let(:ancestor) { create(:group, parent: grand_ancestor) }
let(:group) { create(:group, parent: ancestor) }
let(:ancestor_email) { create(:email, :confirmed, email: 'ancestor@example.com', user: user) }
let(:grand_email) { create(:email, :confirmed, email: 'grand@example.com', user: user) }
before do
create(:notification_setting, user: user, source: grand_ancestor, level: 'participating', notification_email: 'grand@example.com')
create(:notification_setting, user: user, source: ancestor, level: 'global', notification_email: 'ancestor@example.com')
create(:notification_setting, user: user, source: grand_ancestor, level: 'participating', notification_email: grand_email.email)
create(:notification_setting, user: user, source: ancestor, level: 'global', notification_email: ancestor_email.email)
end
it 'has the same email set' do
@ -4299,7 +4455,7 @@ describe User, :do_not_mock_admin_mode do
context 'when group has notification email set' do
it 'returns group notification email' do
group_notification_email = 'user+group@example.com'
create(:email, :confirmed, user: user, email: group_notification_email)
create(:notification_setting, user: user, source: group, notification_email: group_notification_email)
is_expected.to eq(group_notification_email)

View file

@ -11,7 +11,7 @@ describe API::GroupImport do
let(:file) { File.join('spec', 'fixtures', 'group_export.tar.gz') }
let(:export_path) { "#{Dir.tmpdir}/group_export_spec" }
let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
let(:workhorse_header) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
let(:workhorse_headers) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
before do
allow_next_instance_of(Gitlab::ImportExport) do |import_export|
@ -35,7 +35,7 @@ describe API::GroupImport do
}
end
subject { post api('/groups/import', user), params: params, headers: workhorse_header }
subject { upload_archive(file_upload, workhorse_headers, params) }
shared_examples 'when all params are correct' do
context 'when user is authorized to create new group' do
@ -151,7 +151,7 @@ describe API::GroupImport do
params[:file] = file_upload
expect do
post api('/groups/import', user), params: params, headers: workhorse_header
upload_archive(file_upload, workhorse_headers, params)
end.not_to change { Group.count }.from(1)
expect(response).to have_gitlab_http_status(:bad_request)
@ -171,7 +171,7 @@ describe API::GroupImport do
context 'without a file from workhorse' do
it 'rejects the request' do
subject
upload_archive(nil, workhorse_headers, params)
expect(response).to have_gitlab_http_status(:bad_request)
end
@ -179,7 +179,7 @@ describe API::GroupImport do
context 'without a workhorse header' do
it 'rejects request without a workhorse header' do
post api('/groups/import', user), params: params
upload_archive(file_upload, {}, params)
expect(response).to have_gitlab_http_status(:forbidden)
end
@ -189,9 +189,7 @@ describe API::GroupImport do
let(:params) do
{
path: 'test-import-group',
name: 'test-import-group',
'file.path' => file_upload.path,
'file.name' => file_upload.original_filename
name: 'test-import-group'
}
end
@ -229,9 +227,7 @@ describe API::GroupImport do
{
path: 'test-import-group',
name: 'test-import-group',
file: fog_file,
'file.remote_id' => file_name,
'file.size' => fog_file.size
file: fog_file
}
end
@ -245,10 +241,21 @@ describe API::GroupImport do
include_examples 'when some params are missing'
end
end
def upload_archive(file, headers = {}, params = {})
workhorse_finalize(
api('/groups/import', user),
method: :post,
file_key: :file,
params: params.merge(file: file),
headers: headers,
send_rewritten_field: true
)
end
end
describe 'POST /groups/import/authorize' do
subject { post api('/groups/import/authorize', user), headers: workhorse_header }
subject { post api('/groups/import/authorize', user), headers: workhorse_headers }
it 'authorizes importing group with workhorse header' do
subject
@ -258,7 +265,7 @@ describe API::GroupImport do
end
it 'rejects requests that bypassed gitlab-workhorse' do
workhorse_header.delete(Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER)
workhorse_headers.delete(Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER)
subject

View file

@ -19,7 +19,7 @@ describe API::NotificationSettings do
end
describe "PUT /notification_settings" do
let(:email) { create(:email, user: user) }
let(:email) { create(:email, :confirmed, user: user) }
it "updates global notification settings for the current user" do
put api("/notification_settings", user), params: { level: 'watch', notification_email: email.email }

View file

@ -1891,6 +1891,17 @@ describe API::Projects do
expect(project_fork_target).to be_forked
end
it 'fails without permission from forked_from project' do
project_fork_source.project_feature.update_attribute(:forking_access_level, ProjectFeature::PRIVATE)
post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", user)
expect(response).to have_gitlab_http_status(:forbidden)
expect(project_fork_target.forked_from_project).to be_nil
expect(project_fork_target.fork_network_member).not_to be_present
expect(project_fork_target).not_to be_forked
end
it 'denies project to be forked from a private project' do
post api("/projects/#{project_fork_target.id}/fork/#{private_project_fork_source.id}", user)

View file

@ -177,6 +177,12 @@ describe API::Repositories do
expect(headers['Content-Disposition']).to eq 'inline'
end
it_behaves_like 'uncached response' do
before do
get api(route, current_user)
end
end
context 'when sha does not exist' do
it_behaves_like '404 response' do
let(:request) { get api(route.sub(sample_blob.oid, 'abcd9876'), current_user) }

View file

@ -9,15 +9,11 @@ describe 'OpenID Connect requests' do
name: 'Alice',
username: 'alice',
email: 'private@example.com',
emails: [public_email],
public_email: public_email.email,
website_url: 'https://example.com',
avatar: fixture_file_upload('spec/fixtures/dk.png')
)
end
let(:public_email) { build :email, email: 'public@example.com' }
let(:access_grant) { create :oauth_access_grant, application: application, resource_owner_id: user.id }
let(:access_token) { create :oauth_access_token, application: application, resource_owner_id: user.id }
@ -37,7 +33,7 @@ describe 'OpenID Connect requests' do
'name' => 'Alice',
'nickname' => 'alice',
'email' => 'public@example.com',
'email_verified' => false,
'email_verified' => true,
'website' => 'https://example.com',
'profile' => 'http://localhost/alice',
'picture' => "http://localhost/uploads/-/system/user/avatar/#{user.id}/dk.png",
@ -62,6 +58,11 @@ describe 'OpenID Connect requests' do
get '/oauth/userinfo', params: {}, headers: { 'Authorization' => "Bearer #{access_token.token}" }
end
before do
email = create(:email, :confirmed, email: 'public@example.com', user: user)
user.update!(public_email: email.email)
end
context 'Application without OpenID scope' do
let(:application) { create :oauth_application, scopes: 'api' }
@ -123,7 +124,7 @@ describe 'OpenID Connect requests' do
end
it 'has false in email_verified claim' do
expect(json_response['email_verified']).to eq(false)
expect(json_response['email_verified']).to eq(true)
end
end

View file

@ -5,8 +5,8 @@ require 'spec_helper'
describe 'view user notifications' do
let(:user) do
create(:user) do |user|
user.emails.create(email: 'original@example.com')
user.emails.create(email: 'new@example.com')
user.emails.create(email: 'original@example.com', confirmed_at: Time.current)
user.emails.create(email: 'new@example.com', confirmed_at: Time.current)
user.notification_email = 'original@example.com'
user.save!
end

View file

@ -47,6 +47,39 @@ describe Clusters::UpdateService do
expect(cluster.platform.namespace).to eq('custom-namespace')
end
end
context 'when service token is empty' do
let(:params) do
{
platform_kubernetes_attributes: {
token: ''
}
}
end
it 'does not update the token' do
current_token = cluster.platform.token
is_expected.to eq(true)
cluster.platform.reload
expect(cluster.platform.token).to eq(current_token)
end
end
context 'when service token is not empty' do
let(:params) do
{
platform_kubernetes_attributes: {
token: 'new secret token'
}
}
end
it 'updates the token' do
is_expected.to eq(true)
expect(cluster.platform.token).to eq('new secret token')
end
end
end
context 'when invalid params' do

View file

@ -2395,6 +2395,8 @@ describe NotificationService, :mailer do
group = create(:group)
project.update(group: group)
create(:email, :confirmed, user: u_custom_notification_enabled, email: group_notification_email)
create(:notification_setting, user: u_custom_notification_enabled, source: group, notification_email: group_notification_email)
end
@ -2429,6 +2431,7 @@ describe NotificationService, :mailer do
group = create(:group)
project.update(group: group)
create(:email, :confirmed, user: u_member, email: group_notification_email)
create(:notification_setting, user: u_member, source: group, notification_email: group_notification_email)
end
@ -2522,6 +2525,7 @@ describe NotificationService, :mailer do
group = create(:group)
project.update(group: group)
create(:email, :confirmed, user: u_member, email: group_notification_email)
create(:notification_setting, user: u_member, source: group, notification_email: group_notification_email)
end

View file

@ -6,19 +6,46 @@ describe Prometheus::ProxyVariableSubstitutionService do
describe '#execute' do
let_it_be(:environment) { create(:environment) }
let(:params_keys) { { query: 'up{environment="%{ci_environment_slug}"}' } }
let(:params_keys) { { query: "up{environment=\"#{w('ci_environment_slug')}\"}" } }
let(:params) { ActionController::Parameters.new(params_keys).permit! }
let(:result) { subject.execute }
subject { described_class.new(environment, params) }
shared_examples 'success' do
# Default implementation of the w method. The `success` shared example overrides
# this implementation to test the Ruby and Liquid syntaxes.
# This method wraps the given variable name in the variable interpolation
# syntax. Using this method along with the `success` shared example allows
# a spec to test both syntaxes.
def w(variable_name)
"%{#{variable_name}}"
end
shared_examples 'replaces variables with values' do
it 'replaces variables with values' do
expect(result[:status]).to eq(:success)
expect(result[:params][:query]).to eq(expected_query)
end
end
shared_examples 'success' do
context 'with Ruby syntax `${}`' do
it_behaves_like 'replaces variables with values'
def w(variable_name)
"%{#{variable_name}}"
end
end
context 'with Liquid syntax `{{}}`' do
it_behaves_like 'replaces variables with values'
def w(variable_name)
"{{#{variable_name}}}"
end
end
end
shared_examples 'error' do |message|
it 'returns error' do
expect(result[:status]).to eq(:error)
@ -39,11 +66,13 @@ describe Prometheus::ProxyVariableSubstitutionService do
end
context 'with predefined variables' do
let(:params_keys) { { query: 'up{%{environment_filter}}' } }
# Liquid replaces the opening brace of the query as well, if there is no space
# between `up{` and the rest of the string.
let(:params_keys) { { query: "up{ #{w('environment_filter')}}" } }
it_behaves_like 'success' do
let(:expected_query) do
%Q[up{container_name!="POD",environment="#{environment.slug}"}]
%Q[up{ container_name!="POD",environment="#{environment.slug}"}]
end
end
@ -54,26 +83,16 @@ describe Prometheus::ProxyVariableSubstitutionService do
let(:expected_query) { nil }
end
end
end
context 'with liquid format' do
let(:params_keys) do
{ query: 'up{environment="{{ci_environment_slug}}"}' }
end
it_behaves_like 'success' do
let(:expected_query) { %Q[up{environment="#{environment.slug}"}] }
end
context 'with ruby and liquid formats' do
let(:params_keys) do
{ query: 'up{%{environment_filter},env2="{{ci_environment_slug}}"}' }
end
context 'with ruby and liquid formats' do
let(:params_keys) do
{ query: 'up{%{environment_filter},env2="{{ci_environment_slug}}"}' }
end
it_behaves_like 'success' do
let(:expected_query) do
%Q[up{container_name!="POD",environment="#{environment.slug}",env2="#{environment.slug}"}]
end
it_behaves_like 'success' do
let(:expected_query) do
%Q[up{container_name!="POD",environment="#{environment.slug}",env2="#{environment.slug}"}]
end
end
end
@ -83,7 +102,7 @@ describe Prometheus::ProxyVariableSubstitutionService do
let(:params_keys) do
{
query: 'up{pod_name="{{pod_name}}"}',
query: "up{pod_name=\"#{w('pod_name')}\"}",
variables: ['pod_name', pod_name]
}
end
@ -92,24 +111,10 @@ describe Prometheus::ProxyVariableSubstitutionService do
let(:expected_query) { %q[up{pod_name="pod1"}] }
end
context 'with ruby variable interpolation format' do
let(:params_keys) do
{
query: 'up{pod_name="%{pod_name}"}',
variables: ['pod_name', pod_name]
}
end
it_behaves_like 'success' do
# Custom variables cannot be used with the Ruby interpolation format.
let(:expected_query) { "up{pod_name=\"%{pod_name}\"}" }
end
end
context 'with predefined variables in variables parameter' do
let(:params_keys) do
{
query: 'up{pod_name="{{pod_name}}",env="{{ci_environment_slug}}"}',
query: "up{pod_name=\"#{w('pod_name')}\",env=\"#{w('ci_environment_slug')}\"}",
variables: ['pod_name', pod_name, 'ci_environment_slug', 'custom_value']
}
end
@ -124,7 +129,7 @@ describe Prometheus::ProxyVariableSubstitutionService do
context 'with invalid variables parameter' do
let(:params_keys) do
{
query: 'up{pod_name="{{pod_name}}"}',
query: "up{pod_name=\"#{w('pod_name')}\"}",
variables: ['a']
}
end
@ -136,68 +141,66 @@ describe Prometheus::ProxyVariableSubstitutionService do
context 'with nil variables' do
let(:params_keys) do
{
query: 'up{pod_name="{{pod_name}}"}',
query: "up{pod_name=\"%{pod_name}\"}",
variables: nil
}
end
it_behaves_like 'success' do
let(:expected_query) { 'up{pod_name=""}' }
it_behaves_like 'replaces variables with values' do
let(:expected_query) { "up{pod_name=\"%{pod_name}\"}" }
end
end
end
context 'with ruby and liquid variables' do
context 'gsub variable substitution tolerance for weirdness' do
context 'with whitespace around variable' do
let(:params_keys) do
{
query: 'up{env1="%{ruby_variable}",env2="{{ liquid_variable }}"}',
variables: %w(ruby_variable value liquid_variable env_slug)
query: 'up{' \
"env1=#{w(' ci_environment_slug')}," \
"env2=#{w('ci_environment_slug ')}," \
"#{w(' environment_filter ')}" \
'}'
}
end
it_behaves_like 'success' do
# It should replace only liquid variables with their values
let(:expected_query) { %q[up{env1="%{ruby_variable}",env2="env_slug"}] }
let(:expected_query) do
'up{' \
"env1=#{environment.slug}," \
"env2=#{environment.slug}," \
"container_name!=\"POD\",environment=\"#{environment.slug}\"" \
'}'
end
end
end
end
context 'with liquid tags and ruby format variables' do
let(:params_keys) do
{
query: 'up{ {% if true %}env1="%{ci_environment_slug}",' \
'env2="{{ci_environment_slug}}"{% endif %} }'
}
context 'with liquid tags and ruby format variables' do
let(:params_keys) do
{
query: 'up{ {% if true %}env1="%{ci_environment_slug}",' \
'env2="{{ci_environment_slug}}"{% endif %} }'
}
end
it_behaves_like 'replaces variables with values' do
let(:expected_query) { "up{ env1=\"#{environment.slug}\",env2=\"#{environment.slug}\" }" }
end
end
# The following spec will fail and should be changed to a 'success' spec
# once we remove support for the Ruby interpolation format.
# https://gitlab.com/gitlab-org/gitlab/issues/37990
#
# Liquid tags `{% %}` cannot be used currently because the Ruby `%`
# operator raises an error when it encounters a Liquid `{% %}` tag in the
# string.
#
# Once we remove support for the Ruby format, users can start using
# Liquid tags.
context 'with empty variables' do
let(:params_keys) do
{ query: "up{env1=%{},env2=%{ }}" }
end
it_behaves_like 'error', 'Malformed string'
end
context 'ruby template rendering' do
let(:params_keys) do
{ query: 'up{env=%{ci_environment_slug},%{environment_filter}}' }
end
it_behaves_like 'success' do
let(:expected_query) do
"up{env=#{environment.slug},container_name!=\"POD\"," \
"environment=\"#{environment.slug}\"}"
it_behaves_like 'replaces variables with values' do
let(:expected_query) { "up{env1=%{},env2=%{ }}" }
end
end
context 'with multiple occurrences of variable in string' do
let(:params_keys) do
{ query: 'up{env1=%{ci_environment_slug},env2=%{ci_environment_slug}}' }
{ query: "up{env1=#{w('ci_environment_slug')},env2=#{w('ci_environment_slug')}}" }
end
it_behaves_like 'success' do
@ -207,7 +210,7 @@ describe Prometheus::ProxyVariableSubstitutionService do
context 'with multiple variables in string' do
let(:params_keys) do
{ query: 'up{env=%{ci_environment_slug},%{environment_filter}}' }
{ query: "up{env=#{w('ci_environment_slug')},#{w('environment_filter')}}" }
end
it_behaves_like 'success' do
@ -219,54 +222,24 @@ describe Prometheus::ProxyVariableSubstitutionService do
end
context 'with unknown variables in string' do
let(:params_keys) { { query: 'up{env=%{env_slug}}' } }
let(:params_keys) { { query: "up{env=#{w('env_slug')}}" } }
it_behaves_like 'success' do
let(:expected_query) { 'up{env=%{env_slug}}' }
end
end
# This spec is needed if there are multiple keys in the context provided
# by `Gitlab::Prometheus::QueryVariables.call(environment)` which is
# passed to the Ruby `%` operator.
# If the number of keys in the context is one, there is no need for
# this spec.
context 'with extra variables in context' do
let(:params_keys) { { query: 'up{env=%{ci_environment_slug}}' } }
it_behaves_like 'success' do
let(:expected_query) { "up{env=#{environment.slug}}" }
end
it 'has more than one variable in context' do
expect(Gitlab::Prometheus::QueryVariables.call(environment).size).to be > 1
it_behaves_like 'replaces variables with values' do
let(:expected_query) { "up{env=%{env_slug}}" }
end
end
# The ruby % operator will not replace known variables if there are unknown
# variables also in the string. It doesn't raise an error
# (though the `sprintf` and `format` methods do).
# Fortunately, we do not use the % operator anymore.
context 'with unknown and known variables in string' do
let(:params_keys) do
{ query: 'up{env=%{ci_environment_slug},other_env=%{env_slug}}' }
{ query: "up{env=%{ci_environment_slug},other_env=%{env_slug}}" }
end
it_behaves_like 'success' do
let(:expected_query) { 'up{env=%{ci_environment_slug},other_env=%{env_slug}}' }
end
end
context 'when rendering raises error' do
context 'when TypeError is raised' do
let(:params_keys) { { query: '{% a %}' } }
it_behaves_like 'error', 'Malformed string'
end
context 'when ArgumentError is raised' do
let(:params_keys) { { query: '%<' } }
it_behaves_like 'error', 'Malformed string'
it_behaves_like 'replaces variables with values' do
let(:expected_query) { "up{env=#{environment.slug},other_env=#{w('env_slug')}}" }
end
end
end

View file

@ -0,0 +1,19 @@
# frozen_string_literal: true
RSpec.shared_examples 'Secure OAuth Authorizations' do
context 'when user is confirmed' do
let(:user) { create(:user) }
it 'asks the user to authorize the application' do
expect(page).to have_text "Authorize #{application.name} to use your account?"
end
end
context 'when user is unconfirmed' do
let(:user) { create(:user, confirmed_at: nil) }
it 'displays an error' do
expect(page).to have_text I18n.t('doorkeeper.errors.messages.unconfirmed_email')
end
end
end

View file

@ -28,6 +28,7 @@ RSpec.shared_examples 'an email sent to a user' do
it 'is sent to user\'s group notification email' do
group_notification_email = 'user+group@example.com'
create(:email, :confirmed, user: recipient, email: group_notification_email)
create(:notification_setting, user: recipient, source: group, notification_email: group_notification_email)
expect(subject).to deliver_to(group_notification_email)

View file

@ -0,0 +1,12 @@
# frozen_string_literal: true
#
# Pairs with lib/gitlab/no_cache_headers.rb
#
RSpec.shared_examples 'uncached response' do
it 'defines an uncached header response' do
expect(response.headers["Cache-Control"]).to include("no-store", "no-cache")
expect(response.headers["Pragma"]).to eq("no-cache")
expect(response.headers["Expires"]).to eq("Fri, 01 Jan 1990 00:00:00 GMT")
end
end

View file

@ -145,39 +145,57 @@ describe FileUploader do
end
describe '.extract_dynamic_path' do
context 'with a 32-byte hexadecimal secret in the path' do
let(:secret) { SecureRandom.hex }
let(:path) { "export/4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a/test/uploads/#{secret}/dummy.txt" }
shared_examples 'a valid secret' do |root_path|
context 'with a 32-byte hexadecimal secret' do
let(:secret) { SecureRandom.hex }
let(:path) { File.join(*[root_path, secret, 'dummy.txt'].compact) }
it 'extracts the secret' do
expect(described_class.extract_dynamic_path(path)[:secret]).to eq(secret)
it 'extracts the secret' do
expect(described_class.extract_dynamic_path(path)[:secret]).to eq(secret)
end
it 'extracts the identifier' do
expect(described_class.extract_dynamic_path(path)[:identifier]).to eq('dummy.txt')
end
end
it 'extracts the identifier' do
expect(described_class.extract_dynamic_path(path)[:identifier]).to eq('dummy.txt')
context 'with a 10-byte hexadecimal secret' do
let(:secret) { SecureRandom.hex[0, 10] }
let(:path) { File.join(*[root_path, secret, 'dummy.txt'].compact) }
it 'extracts the secret' do
expect(described_class.extract_dynamic_path(path)[:secret]).to eq(secret)
end
it 'extracts the identifier' do
expect(described_class.extract_dynamic_path(path)[:identifier]).to eq('dummy.txt')
end
end
context 'with an invalid secret' do
let(:secret) { 'foo' }
let(:path) { File.join(*[root_path, secret, 'dummy.txt'].compact) }
it 'returns nil' do
expect(described_class.extract_dynamic_path(path)).to be_nil
end
end
end
context 'with a 10-byte hexadecimal secret in the path' do
let(:secret) { SecureRandom.hex(10) }
let(:path) { "export/4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a/test/uploads/#{secret}/dummy.txt" }
it 'extracts the secret' do
expect(described_class.extract_dynamic_path(path)[:secret]).to eq(secret)
end
it 'extracts the identifier' do
expect(described_class.extract_dynamic_path(path)[:identifier]).to eq('dummy.txt')
end
context 'with an absolute path without a slash in the beginning' do
it_behaves_like 'a valid secret', 'export/4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a/test/uploads'
end
context 'with an invalid secret in the path' do
let(:secret) { 'foo' }
let(:path) { "export/4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a/test/uploads/#{secret}/dummy.txt" }
context 'with an absolute path with a slash in the beginning' do
it_behaves_like 'a valid secret', '/export/4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a/test/uploads'
end
it 'returns nil' do
expect(described_class.extract_dynamic_path(path)).to be_nil
end
context 'with an relative path without a slash in the beginning' do
it_behaves_like 'a valid secret', nil
end
context 'with an relative path with a slash in the beginning' do
it_behaves_like 'a valid secret', '/'
end
end
@ -202,7 +220,7 @@ describe FileUploader do
end
context "10-byte hexadecimal" do
let(:secret) { SecureRandom.hex(10) }
let(:secret) { SecureRandom.hex[0, 10] }
it "returns the secret" do
expect(uploader.secret).to eq(secret)

View file

@ -0,0 +1,34 @@
# frozen_string_literal: true
require 'spec_helper'
describe 'admin/application_settings/_eks' do
let_it_be(:admin) { create(:admin) }
let(:page) { Capybara::Node::Simple.new(rendered) }
before do
assign(:application_setting, application_setting)
allow(view).to receive(:current_user) { admin }
allow(view).to receive(:expanded) { true }
end
shared_examples 'EKS secret access key input' do
it 'renders an empty password field' do
render
expect(rendered).to have_field('Secret access key', type: 'password')
expect(page.find_field('Secret access key').value).to be_blank
end
end
context 'when eks_secret_access_key is not set' do
let(:application_setting) { build(:application_setting) }
include_examples 'EKS secret access key input'
end
context 'when eks_secret_access_key is set' do
let(:application_setting) { build(:application_setting, eks_secret_access_key: 'eks_secret_access_key') }
include_examples 'EKS secret access key input'
end
end

View file

@ -7,7 +7,7 @@ describe 'projects/pages_domains/show' do
before do
assign(:project, project)
assign(:domain, domain.present)
allow(view).to receive(:domain_presenter).and_return(domain.present)
stub_pages_setting(external_https: true)
end