Compare commits
4 commits
master
...
master-12.
Author | SHA1 | Date | |
---|---|---|---|
|
406c71498e | ||
|
1c9886d0ee | ||
|
1cfa946ec8 | ||
|
33a3b36430 |
84 changed files with 1198 additions and 399 deletions
|
@ -1,5 +1,35 @@
|
||||||
Please view this file on the master branch, on stable branches it's out of date.
|
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)
|
## 12.10.2 (2020-04-30)
|
||||||
|
|
||||||
### Security (3 changes)
|
### Security (3 changes)
|
||||||
|
|
45
CHANGELOG.md
45
CHANGELOG.md
|
@ -2,6 +2,51 @@
|
||||||
documentation](doc/development/changelog.md) for instructions on adding your own
|
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||||
entry.
|
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)
|
## 12.10.3 (2020-05-04)
|
||||||
|
|
||||||
### Fixed (6 changes)
|
### Fixed (6 changes)
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
12.10.3
|
12.10.7
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
8.30.1
|
8.30.2
|
||||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
12.10.3
|
12.10.7
|
||||||
|
|
|
@ -105,7 +105,6 @@ export default class Clusters {
|
||||||
});
|
});
|
||||||
|
|
||||||
this.installApplication = this.installApplication.bind(this);
|
this.installApplication = this.installApplication.bind(this);
|
||||||
this.showToken = this.showToken.bind(this);
|
|
||||||
|
|
||||||
this.errorContainer = document.querySelector('.js-cluster-error');
|
this.errorContainer = document.querySelector('.js-cluster-error');
|
||||||
this.successContainer = document.querySelector('.js-cluster-success');
|
this.successContainer = document.querySelector('.js-cluster-success');
|
||||||
|
@ -116,7 +115,6 @@ export default class Clusters {
|
||||||
);
|
);
|
||||||
this.errorReasonContainer = this.errorContainer.querySelector('.js-error-reason');
|
this.errorReasonContainer = this.errorContainer.querySelector('.js-error-reason');
|
||||||
this.successApplicationContainer = document.querySelector('.js-cluster-application-notice');
|
this.successApplicationContainer = document.querySelector('.js-cluster-application-notice');
|
||||||
this.showTokenButton = document.querySelector('.js-show-cluster-token');
|
|
||||||
this.tokenField = document.querySelector('.js-cluster-token');
|
this.tokenField = document.querySelector('.js-cluster-token');
|
||||||
this.ingressDomainHelpText = document.querySelector('.js-ingress-domain-help-text');
|
this.ingressDomainHelpText = document.querySelector('.js-ingress-domain-help-text');
|
||||||
this.ingressDomainSnippet =
|
this.ingressDomainSnippet =
|
||||||
|
@ -255,7 +253,6 @@ export default class Clusters {
|
||||||
}
|
}
|
||||||
|
|
||||||
addListeners() {
|
addListeners() {
|
||||||
if (this.showTokenButton) this.showTokenButton.addEventListener('click', this.showToken);
|
|
||||||
eventHub.$on('installApplication', this.installApplication);
|
eventHub.$on('installApplication', this.installApplication);
|
||||||
eventHub.$on('updateApplication', data => this.updateApplication(data));
|
eventHub.$on('updateApplication', data => this.updateApplication(data));
|
||||||
eventHub.$on('saveKnativeDomain', data => this.saveKnativeDomain(data));
|
eventHub.$on('saveKnativeDomain', data => this.saveKnativeDomain(data));
|
||||||
|
@ -271,7 +268,6 @@ export default class Clusters {
|
||||||
}
|
}
|
||||||
|
|
||||||
removeListeners() {
|
removeListeners() {
|
||||||
if (this.showTokenButton) this.showTokenButton.removeEventListener('click', this.showToken);
|
|
||||||
eventHub.$off('installApplication', this.installApplication);
|
eventHub.$off('installApplication', this.installApplication);
|
||||||
eventHub.$off('updateApplication', this.updateApplication);
|
eventHub.$off('updateApplication', this.updateApplication);
|
||||||
eventHub.$off('saveKnativeDomain');
|
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() {
|
hideAll() {
|
||||||
this.errorContainer.classList.add('hidden');
|
this.errorContainer.classList.add('hidden');
|
||||||
this.successContainer.classList.add('hidden');
|
this.successContainer.classList.add('hidden');
|
||||||
|
|
|
@ -101,7 +101,11 @@ export default class Issue {
|
||||||
|
|
||||||
this.disableCloseReopenButton($button);
|
this.disableCloseReopenButton($button);
|
||||||
|
|
||||||
const url = $button.attr('href');
|
const url = $button.data('close-reopen-url');
|
||||||
|
if (!url) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return axios
|
return axios
|
||||||
.put(url)
|
.put(url)
|
||||||
.then(({ data }) => {
|
.then(({ data }) => {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { __, s__, sprintf } from '~/locale';
|
import { __, s__, sprintf } from '~/locale';
|
||||||
import { GlFormGroup, GlFormInput, GlFormRadioGroup, GlFormTextarea } from '@gitlab/ui';
|
import { GlFormGroup, GlFormInput, GlFormRadioGroup, GlFormTextarea } from '@gitlab/ui';
|
||||||
|
import { escape as esc } from 'lodash';
|
||||||
|
|
||||||
const defaultFileName = dashboard => dashboard.path.split('/').reverse()[0];
|
const defaultFileName = dashboard => dashboard.path.split('/').reverse()[0];
|
||||||
|
|
||||||
|
@ -42,7 +43,7 @@ export default {
|
||||||
html: sprintf(
|
html: sprintf(
|
||||||
__('Commit to %{branchName} branch'),
|
__('Commit to %{branchName} branch'),
|
||||||
{
|
{
|
||||||
branchName: `<strong>${this.defaultBranch}</strong>`,
|
branchName: `<strong>${esc(this.defaultBranch)}</strong>`,
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
|
|
|
@ -40,7 +40,9 @@ export default class Profile {
|
||||||
bindEvents() {
|
bindEvents() {
|
||||||
$('.js-preferences-form').on('change.preference', 'input[type=radio]', this.submitForm);
|
$('.js-preferences-form').on('change.preference', 'input[type=radio]', this.submitForm);
|
||||||
$('.js-group-notification-email').on('change', 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);
|
$('#user_notified_of_own_activity').on('change', this.submitForm);
|
||||||
this.form.on('submit', this.onSubmitForm);
|
this.form.on('submit', this.onSubmitForm);
|
||||||
}
|
}
|
||||||
|
|
|
@ -191,8 +191,10 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
|
||||||
|
|
||||||
params[:application_setting][:import_sources]&.delete("")
|
params[:application_setting][:import_sources]&.delete("")
|
||||||
params[:application_setting][:restricted_visibility_levels]&.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?
|
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)
|
# 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_file]
|
||||||
params.delete(:domain_blacklist_raw) if params[:domain_blacklist]
|
params.delete(:domain_blacklist_raw) if params[:domain_blacklist]
|
||||||
|
@ -261,6 +263,10 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
|
||||||
render action
|
render action
|
||||||
end
|
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
|
# overridden in EE
|
||||||
def valid_setting_panels
|
def valid_setting_panels
|
||||||
VALID_SETTING_PANELS
|
VALID_SETTING_PANELS
|
||||||
|
|
|
@ -53,10 +53,16 @@ module MembershipActions
|
||||||
end
|
end
|
||||||
|
|
||||||
def request_access
|
def request_access
|
||||||
membershipable.request_access(current_user)
|
access_requester = membershipable.request_access(current_user)
|
||||||
|
|
||||||
redirect_to polymorphic_path(membershipable),
|
if access_requester.persisted?
|
||||||
notice: _('Your request for access has been queued for review.')
|
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
|
end
|
||||||
|
|
||||||
def approve_access_request
|
def approve_access_request
|
||||||
|
|
|
@ -4,6 +4,8 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
|
||||||
include Gitlab::Experimentation::ControllerConcern
|
include Gitlab::Experimentation::ControllerConcern
|
||||||
include InitializesCurrentUserMode
|
include InitializesCurrentUserMode
|
||||||
|
|
||||||
|
before_action :verify_confirmed_email!, only: [:new]
|
||||||
|
|
||||||
layout 'profile'
|
layout 'profile'
|
||||||
|
|
||||||
# Overridden from Doorkeeper::AuthorizationsController to
|
# Overridden from Doorkeeper::AuthorizationsController to
|
||||||
|
@ -21,4 +23,13 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
|
||||||
render "doorkeeper/authorizations/error"
|
render "doorkeeper/authorizations/error"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def verify_confirmed_email!
|
||||||
|
return if current_user&.confirmed?
|
||||||
|
|
||||||
|
pre_auth.error = :unconfirmed_email
|
||||||
|
render "doorkeeper/authorizations/error"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -37,6 +37,8 @@ class Projects::DeployKeysController < Projects::ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
|
access_denied! unless deploy_key
|
||||||
|
|
||||||
if deploy_key.update(update_params)
|
if deploy_key.update(update_params)
|
||||||
flash[:notice] = _('Deploy key was successfully updated.')
|
flash[:notice] = _('Deploy key was successfully updated.')
|
||||||
redirect_to_repository
|
redirect_to_repository
|
||||||
|
@ -85,10 +87,12 @@ class Projects::DeployKeysController < Projects::ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_params
|
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)
|
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
|
end
|
||||||
|
|
||||||
def authorize_update_deploy_key!
|
def authorize_update_deploy_key!
|
||||||
|
|
|
@ -7,6 +7,8 @@ class Projects::PagesDomainsController < Projects::ApplicationController
|
||||||
before_action :authorize_update_pages!
|
before_action :authorize_update_pages!
|
||||||
before_action :domain, except: [:new, :create]
|
before_action :domain, except: [:new, :create]
|
||||||
|
|
||||||
|
helper_method :domain_presenter
|
||||||
|
|
||||||
def show
|
def show
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -27,7 +29,7 @@ class Projects::PagesDomainsController < Projects::ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def retry_auto_ssl
|
def retry_auto_ssl
|
||||||
PagesDomains::RetryAcmeOrderService.new(@domain.pages_domain).execute
|
PagesDomains::RetryAcmeOrderService.new(@domain).execute
|
||||||
|
|
||||||
redirect_to project_pages_domain_path(@project, @domain)
|
redirect_to project_pages_domain_path(@project, @domain)
|
||||||
end
|
end
|
||||||
|
@ -88,6 +90,10 @@ class Projects::PagesDomainsController < Projects::ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def domain
|
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
|
||||||
end
|
end
|
||||||
|
|
|
@ -14,6 +14,7 @@ class NotificationSetting < ApplicationRecord
|
||||||
validates :user_id, uniqueness: { scope: [:source_type, :source_id],
|
validates :user_id, uniqueness: { scope: [:source_type, :source_id],
|
||||||
message: "already exists in source",
|
message: "already exists in source",
|
||||||
allow_nil: true }
|
allow_nil: true }
|
||||||
|
validate :owns_notification_email, if: :notification_email_changed?
|
||||||
|
|
||||||
scope :for_groups, -> { where(source_type: 'Namespace') }
|
scope :for_groups, -> { where(source_type: 'Namespace') }
|
||||||
|
|
||||||
|
@ -97,6 +98,13 @@ class NotificationSetting < ApplicationRecord
|
||||||
def event_enabled?(event)
|
def event_enabled?(event)
|
||||||
respond_to?(event) && !!public_send(event) # rubocop:disable GitlabSecurity/PublicSend
|
respond_to?(event) && !!public_send(event) # rubocop:disable GitlabSecurity/PublicSend
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
NotificationSetting.prepend_if_ee('EE::NotificationSetting')
|
NotificationSetting.prepend_if_ee('EE::NotificationSetting')
|
||||||
|
|
|
@ -228,9 +228,10 @@ class User < ApplicationRecord
|
||||||
if previous_changes.key?('email')
|
if previous_changes.key?('email')
|
||||||
# Grab previous_email here since previous_changes changes after
|
# Grab previous_email here since previous_changes changes after
|
||||||
# #update_emails_with_primary_email and #update_notification_email are called
|
# #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]
|
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
|
update_invalid_gpg_signatures
|
||||||
|
|
||||||
if previous_email == notification_email
|
if previous_email == notification_email
|
||||||
|
@ -774,15 +775,15 @@ class User < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def owns_notification_email
|
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
|
end
|
||||||
|
|
||||||
def owns_public_email
|
def owns_public_email
|
||||||
return if public_email.blank?
|
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
|
end
|
||||||
|
|
||||||
def owns_commit_email
|
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
|
# 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
|
# scenario, though it then requires us to use the `previous_changes` hash
|
||||||
# rubocop: disable CodeReuse/ServiceClass
|
# 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)
|
primary_email_record = emails.find_by(email: email)
|
||||||
Emails::DestroyService.new(self, user: self).execute(primary_email_record) if primary_email_record
|
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
|
# 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
|
# 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
|
end
|
||||||
# rubocop: enable CodeReuse/ServiceClass
|
# rubocop: enable CodeReuse/ServiceClass
|
||||||
|
|
||||||
|
@ -1228,18 +1231,20 @@ class User < ApplicationRecord
|
||||||
all_emails
|
all_emails
|
||||||
end
|
end
|
||||||
|
|
||||||
def all_public_emails
|
def verified_emails(include_private_email: true)
|
||||||
all_emails(include_private_email: false)
|
|
||||||
end
|
|
||||||
|
|
||||||
def verified_emails
|
|
||||||
verified_emails = []
|
verified_emails = []
|
||||||
verified_emails << email if primary_email_verified?
|
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.concat(emails.confirmed.pluck(:email))
|
||||||
verified_emails
|
verified_emails
|
||||||
end
|
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)
|
def any_email?(check_email)
|
||||||
downcased = check_email.downcase
|
downcased = check_email.downcase
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,12 @@ module Clusters
|
||||||
|
|
||||||
def execute(cluster)
|
def execute(cluster)
|
||||||
if validate_params(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)
|
cluster.update(params)
|
||||||
else
|
else
|
||||||
false
|
false
|
||||||
|
|
|
@ -4,6 +4,16 @@ module Prometheus
|
||||||
class ProxyVariableSubstitutionService < BaseService
|
class ProxyVariableSubstitutionService < BaseService
|
||||||
include Stepable
|
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,
|
steps :validate_variables,
|
||||||
:add_params_to_result,
|
:add_params_to_result,
|
||||||
:substitute_params,
|
:substitute_params,
|
||||||
|
@ -46,6 +56,14 @@ module Prometheus
|
||||||
success(result)
|
success(result)
|
||||||
end
|
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)
|
def substitute_liquid_variables(result)
|
||||||
return success(result) unless query(result)
|
return success(result) unless query(result)
|
||||||
|
|
||||||
|
@ -57,26 +75,20 @@ module Prometheus
|
||||||
error(e.message)
|
error(e.message)
|
||||||
end
|
end
|
||||||
|
|
||||||
def substitute_ruby_variables(result)
|
def gsub(string, context)
|
||||||
return success(result) unless query(result)
|
# Search for variables of the form `%{variable}` in the string and replace
|
||||||
|
# them with their value.
|
||||||
# The % operator doesn't replace variables if the hash contains string
|
string.gsub(VARIABLE_INTERPOLATION_REGEX) do |match|
|
||||||
# keys.
|
# Replace with the value of the variable, or if there is no such variable,
|
||||||
result[:params][:query] = query(result) % predefined_context.symbolize_keys
|
# replace the invalid variable with itself. So,
|
||||||
|
# `up{instance="%{invalid_variable}"}` will remain
|
||||||
success(result)
|
# `up{instance="%{invalid_variable}"}` after substitution.
|
||||||
rescue TypeError, ArgumentError => exception
|
context.fetch($~[:variable], match)
|
||||||
log_error(exception.message)
|
end
|
||||||
Gitlab::ErrorTracking.track_exception(exception, {
|
|
||||||
template_string: query(result),
|
|
||||||
variables: predefined_context
|
|
||||||
})
|
|
||||||
|
|
||||||
error(_('Malformed string'))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def predefined_context
|
def predefined_context
|
||||||
@predefined_context ||= Gitlab::Prometheus::QueryVariables.call(@environment)
|
Gitlab::Prometheus::QueryVariables.call(@environment).stringify_keys
|
||||||
end
|
end
|
||||||
|
|
||||||
def full_context
|
def full_context
|
||||||
|
|
|
@ -15,7 +15,7 @@ class FileUploader < GitlabUploader
|
||||||
prepend ObjectStorage::Extension::RecordsUploads
|
prepend ObjectStorage::Extension::RecordsUploads
|
||||||
|
|
||||||
MARKDOWN_PATTERN = %r{\!?\[.*?\]\(/uploads/(?<secret>[0-9a-f]{32})/(?<file>.*?)\)}.freeze
|
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
|
VALID_SECRET_PATTERN = %r{\A\h{10,32}\z}.freeze
|
||||||
|
|
||||||
InvalidSecret = Class.new(StandardError)
|
InvalidSecret = Class.new(StandardError)
|
||||||
|
|
|
@ -26,6 +26,6 @@
|
||||||
= f.text_field :eks_access_key_id, class: 'form-control'
|
= f.text_field :eks_access_key_id, class: 'form-control'
|
||||||
.form-group
|
.form-group
|
||||||
= f.label :eks_secret_access_key, 'Secret access key', class: 'label-bold'
|
= 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"
|
= f.submit 'Save changes', class: "btn btn-success"
|
||||||
|
|
|
@ -25,16 +25,10 @@
|
||||||
label: s_('ClusterIntegration|CA Certificate'), label_class: 'label-bold',
|
label: s_('ClusterIntegration|CA Certificate'), label_class: 'label-bold',
|
||||||
input_group_class: 'gl-field-error-anchor', append: copy_ca_cert_btn
|
input_group_class: 'gl-field-error-anchor', append: copy_ca_cert_btn
|
||||||
|
|
||||||
- show_token_btn = (platform_field.button s_('ClusterIntegration|Show'),
|
= platform_field.password_field :token, type: 'password', class: 'js-select-on-focus js-cluster-token',
|
||||||
type: 'button', class: 'js-show-cluster-token btn btn-default')
|
readonly: cluster.read_only_kubernetes_platform_fields?, autocomplete: 'new-password',
|
||||||
- copy_token_btn = clipboard_button(text: platform.token, title: s_('ClusterIntegration|Copy Service Token'),
|
label: s_('ClusterIntegration|Enter new Service Token'), label_class: 'label-bold',
|
||||||
class: 'input-group-text btn-default') if cluster.read_only_kubernetes_platform_fields?
|
input_group_class: 'gl-field-error-anchor'
|
||||||
|
|
||||||
= 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.form_group :authorization_type do
|
= platform_field.form_group :authorization_type do
|
||||||
= platform_field.check_box :authorization_type, { disabled: true, label: s_('ClusterIntegration|RBAC-enabled cluster'),
|
= platform_field.check_box :authorization_type, { disabled: true, label: s_('ClusterIntegration|RBAC-enabled cluster'),
|
||||||
|
|
|
@ -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
|
- 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.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") },
|
{ 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
|
control_class: 'select2 input-lg', disabled: email_change_disabled
|
||||||
- commit_email_link_url = help_page_path('user/profile/index', anchor: 'commit-email', target: '_blank')
|
- commit_email_link_url = help_page_path('user/profile/index', anchor: 'commit-email', target: '_blank')
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
- form = local_assigns.fetch(:form)
|
- form = local_assigns.fetch(:form)
|
||||||
.form-group
|
.form-group
|
||||||
= form.label :notification_email, class: "label-bold"
|
= 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
|
.help-block
|
||||||
= local_assigns.fetch(:help_text, nil)
|
= local_assigns.fetch(:help_text, nil)
|
||||||
|
|
|
@ -13,4 +13,4 @@
|
||||||
|
|
||||||
.table-section.section-30
|
.table-section.section-30
|
||||||
= form_for setting, url: profile_notifications_group_path(group), method: :put, html: { class: 'update-notifications' } do |f|
|
= 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'
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
- page_title 'Edit Deploy Key'
|
- page_title 'Edit Deploy Key'
|
||||||
%h3.page-title Edit Deploy Key
|
%h3.page-title= _('Edit Deploy Key')
|
||||||
%hr
|
%hr
|
||||||
|
|
||||||
%div
|
%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 }
|
= render partial: 'shared/deploy_keys/form', locals: { form: f, deploy_key: @deploy_key }
|
||||||
.form-actions
|
.form-actions
|
||||||
= f.submit 'Save changes', class: 'btn-success btn'
|
= f.submit 'Save changes', class: 'btn-success btn'
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
- auto_ssl_available = ::Gitlab::LetsEncrypt.enabled?
|
- 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
|
- 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
|
- if auto_ssl_available
|
||||||
.form-group.border-section
|
.form-group.border-section
|
||||||
|
@ -36,9 +36,9 @@
|
||||||
= _('Certificate')
|
= _('Certificate')
|
||||||
.d-flex.justify-content-between.align-items-center.p-3
|
.d-flex.justify-content-between.align-items-center.p-3
|
||||||
%span
|
%span
|
||||||
= @domain.pages_domain.subject || _('missing')
|
= domain_presenter.pages_domain.subject || _('missing')
|
||||||
= link_to _('Remove'),
|
= 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?') },
|
data: { confirm: _('Are you sure?') },
|
||||||
class: 'btn btn-remove btn-sm',
|
class: 'btn btn-remove btn-sm',
|
||||||
method: :delete
|
method: :delete
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
- verification_enabled = Gitlab::CurrentSettings.pages_domain_verification_enabled?
|
- 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
|
.form-group.border-section
|
||||||
.row
|
.row
|
||||||
|
@ -13,17 +13,17 @@
|
||||||
%p.form-text.text-muted
|
%p.form-text.text-muted
|
||||||
= _("To access this domain create a new DNS record")
|
= _("To access this domain create a new DNS record")
|
||||||
- if verification_enabled
|
- 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
|
.form-group.border-section
|
||||||
.row
|
.row
|
||||||
.col-sm-2
|
.col-sm-2
|
||||||
= _("Verification status")
|
= _("Verification status")
|
||||||
.col-sm-10
|
.col-sm-10
|
||||||
.status-badge
|
.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 }
|
.badge{ class: status }
|
||||||
= text
|
= 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
|
.input-group
|
||||||
= text_field_tag :domain_verification, verification_record, class: "monospace js-select-on-focus form-control", readonly: true
|
= text_field_tag :domain_verification, verification_record, class: "monospace js-select-on-focus form-control", readonly: true
|
||||||
.input-group-append
|
.input-group-append
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
- if @domain.errors.any?
|
- if domain_presenter.errors.any?
|
||||||
.alert.alert-danger
|
.alert.alert-danger
|
||||||
- @domain.errors.full_messages.each do |msg|
|
- domain_presenter.errors.full_messages.each do |msg|
|
||||||
= msg
|
= msg
|
||||||
|
|
||||||
.form-group.border-section
|
.form-group.border-section
|
||||||
.row
|
.row
|
||||||
- if @domain.persisted?
|
- if domain_presenter.persisted?
|
||||||
.col-sm-2
|
.col-sm-2
|
||||||
= _("Domain")
|
= _("Domain")
|
||||||
.col-sm-10
|
.col-sm-10
|
||||||
= external_link(@domain.url, @domain.url)
|
= external_link(domain_presenter.url, domain_presenter.url)
|
||||||
- else
|
- else
|
||||||
.col-sm-2
|
.col-sm-2
|
||||||
= f.label :domain, _("Domain")
|
= f.label :domain, _("Domain")
|
||||||
|
@ -17,7 +17,7 @@
|
||||||
.input-group
|
.input-group
|
||||||
= f.text_field :domain, required: true, autocomplete: "off", class: "form-control"
|
= f.text_field :domain, required: true, autocomplete: "off", class: "form-control"
|
||||||
|
|
||||||
- if @domain.persisted?
|
- if domain_presenter.persisted?
|
||||||
= render 'dns'
|
= render 'dns'
|
||||||
|
|
||||||
- if Gitlab.config.pages.external_https
|
- if Gitlab.config.pages.external_https
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
- if @domain.enabled?
|
- if domain_presenter.enabled?
|
||||||
- if @domain.auto_ssl_enabled
|
- if domain_presenter.auto_ssl_enabled
|
||||||
- if @domain.show_auto_ssl_failed_warning?
|
- 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) }
|
.form-group.border-section.js-shown-if-auto-ssl{ class: ("d-none" unless auto_ssl_available_and_enabled) }
|
||||||
.row
|
.row
|
||||||
.col-sm-10.offset-sm-2
|
.col-sm-10.offset-sm-2
|
||||||
|
@ -9,8 +9,8 @@
|
||||||
= icon('warning', class: 'mr-2')
|
= icon('warning', class: 'mr-2')
|
||||||
= _("Something went wrong while obtaining the Let's Encrypt certificate.")
|
= _("Something went wrong while obtaining the Let's Encrypt certificate.")
|
||||||
.row.mx-0.mt-3
|
.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
|
= 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.certificate_gitlab_provided?
|
- elsif !domain_presenter.certificate_gitlab_provided?
|
||||||
.form-group.border-section.js-shown-if-auto-ssl{ class: ("d-none" unless auto_ssl_available_and_enabled) }
|
.form-group.border-section.js-shown-if-auto-ssl{ class: ("d-none" unless auto_ssl_available_and_enabled) }
|
||||||
.row
|
.row
|
||||||
.col-sm-10.offset-sm-2
|
.col-sm-10.offset-sm-2
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
= _("New Pages Domain")
|
= _("New Pages Domain")
|
||||||
= render 'projects/pages_domains/helper_text'
|
= render 'projects/pages_domains/helper_text'
|
||||||
%div
|
%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 }
|
= render 'form', { f: f }
|
||||||
.form-actions
|
.form-actions
|
||||||
= f.submit _('Create New Domain'), class: "btn btn-success"
|
= f.submit _('Create New Domain'), class: "btn btn-success"
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
- add_to_breadcrumbs _("Pages"), project_pages_path(@project)
|
- add_to_breadcrumbs _("Pages"), project_pages_path(@project)
|
||||||
- breadcrumb_title @domain.domain
|
- breadcrumb_title domain_presenter.domain
|
||||||
- page_title @domain.domain
|
- page_title domain_presenter.domain
|
||||||
|
|
||||||
- verification_enabled = Gitlab::CurrentSettings.pages_domain_verification_enabled?
|
- verification_enabled = Gitlab::CurrentSettings.pages_domain_verification_enabled?
|
||||||
|
|
||||||
- if verification_enabled && @domain.unverified?
|
- if verification_enabled && domain_presenter.unverified?
|
||||||
= content_for :flash_message do
|
= content_for :flash_message do
|
||||||
.alert.alert-warning
|
.alert.alert-warning
|
||||||
.container-fluid.container-limited
|
.container-fluid.container-limited
|
||||||
|
@ -14,7 +14,7 @@
|
||||||
= _('Pages Domain')
|
= _('Pages Domain')
|
||||||
= render 'projects/pages_domains/helper_text'
|
= render 'projects/pages_domains/helper_text'
|
||||||
%div
|
%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 }
|
= render 'form', { f: f }
|
||||||
.form-actions.d-flex.justify-content-between
|
.form-actions.d-flex.justify-content-between
|
||||||
= f.submit _('Save Changes'), class: "btn btn-success"
|
= f.submit _('Save Changes'), class: "btn btn-success"
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
.float-left.btn-group.prepend-left-10.issuable-close-dropdown.droplab-dropdown.js-issuable-close-dropdown
|
.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),
|
= 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",
|
= button_tag type: 'button', class: "#{toggle_class} btn-#{button_action}-color",
|
||||||
data: { 'dropdown-trigger' => '#issuable-close-menu' }, 'aria-label' => 'Toggle dropdown' do
|
data: { 'dropdown-trigger' => '#issuable-close-menu' }, 'aria-label' => 'Toggle dropdown' do
|
||||||
|
|
|
@ -36,6 +36,7 @@ en:
|
||||||
access_denied: 'The resource owner or authorization server denied the request.'
|
access_denied: 'The resource owner or authorization server denied the request.'
|
||||||
invalid_scope: 'The requested scope is invalid, unknown, or malformed.'
|
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.'
|
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.'
|
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
|
#configuration error messages
|
||||||
|
|
|
@ -8,10 +8,13 @@ class DropIndexCiPipelinesOnProjectId < ActiveRecord::Migration[5.2]
|
||||||
disable_ddl_transaction!
|
disable_ddl_transaction!
|
||||||
|
|
||||||
def up
|
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
|
end
|
||||||
|
|
||||||
def down
|
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
|
||||||
end
|
end
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -13214,5 +13214,7 @@ COPY "schema_migrations" (version) FROM STDIN;
|
||||||
20200416111111
|
20200416111111
|
||||||
20200416120128
|
20200416120128
|
||||||
20200416120354
|
20200416120354
|
||||||
|
20200511162057
|
||||||
|
20200511162115
|
||||||
\.
|
\.
|
||||||
|
|
||||||
|
|
7
debian/changelog
vendored
7
debian/changelog
vendored
|
@ -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
|
gitlab (12.10.3-3) experimental; urgency=medium
|
||||||
|
|
||||||
* Update css-loader options in config/webpack.config.js
|
* Update css-loader options in config/webpack.config.js
|
||||||
|
|
8
debian/patches/0760-update-grape.patch
vendored
8
debian/patches/0760-update-grape.patch
vendored
|
@ -758,7 +758,7 @@ coerced to arrays of integers. Before this was done within Virtus.
|
||||||
+ class GroupImport < Grape::API::Instance
|
+ class GroupImport < Grape::API::Instance
|
||||||
MAXIMUM_FILE_SIZE = 50.megabytes.freeze
|
MAXIMUM_FILE_SIZE = 50.megabytes.freeze
|
||||||
|
|
||||||
helpers do
|
helpers Helpers::FileUploadHelpers
|
||||||
--- a/lib/api/group_labels.rb
|
--- a/lib/api/group_labels.rb
|
||||||
+++ b/lib/api/group_labels.rb
|
+++ b/lib/api/group_labels.rb
|
||||||
@@ -1,7 +1,7 @@
|
@@ -1,7 +1,7 @@
|
||||||
|
@ -1294,7 +1294,7 @@ coerced to arrays of integers. Before this was done within Virtus.
|
||||||
include PaginationParams
|
include PaginationParams
|
||||||
include Helpers::CustomAttributes
|
include Helpers::CustomAttributes
|
||||||
|
|
||||||
@@ -520,7 +520,7 @@
|
@@ -522,7 +522,7 @@
|
||||||
end
|
end
|
||||||
params do
|
params do
|
||||||
optional :search, type: String, desc: 'Return list of users matching the search criteria'
|
optional :search, type: String, desc: 'Return list of users matching the search criteria'
|
||||||
|
@ -1368,8 +1368,8 @@ coerced to arrays of integers. Before this was done within Virtus.
|
||||||
+ class Repositories < Grape::API::Instance
|
+ class Repositories < Grape::API::Instance
|
||||||
include PaginationParams
|
include PaginationParams
|
||||||
|
|
||||||
before { authorize! :download_code, user_project }
|
helpers ::API::Helpers::HeadersHelpers
|
||||||
@@ -139,7 +139,7 @@
|
@@ -143,7 +143,7 @@
|
||||||
success Entities::Commit
|
success Entities::Commit
|
||||||
end
|
end
|
||||||
params do
|
params do
|
||||||
|
|
|
@ -1197,7 +1197,7 @@ PUT /projects/:id
|
||||||
| `approvals_before_merge` | integer | no | **(STARTER)** How many approvers should approve merge request by default |
|
| `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 |
|
| `external_authorization_classification_label` | string | no | **(PREMIUM)** The classification label for the project |
|
||||||
| `mirror` | boolean | no | **(STARTER)** Enables pull mirroring in a 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 |
|
| `mirror_trigger_builds` | boolean | no | **(STARTER)** Pull mirroring triggers builds |
|
||||||
| `only_mirror_protected_branches` | boolean | no | **(STARTER)** Only mirror protected branches |
|
| `only_mirror_protected_branches` | boolean | no | **(STARTER)** Only mirror protected branches |
|
||||||
| `mirror_overwrites_diverged_branches` | boolean | no | **(STARTER)** Pull mirror overwrites diverged branches |
|
| `mirror_overwrites_diverged_branches` | boolean | no | **(STARTER)** Pull mirror overwrites diverged branches |
|
||||||
|
|
|
@ -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),
|
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),
|
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:
|
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
|
assets to issues and view them all together to easily share and
|
||||||
collaborate with your team.
|
collaborate with your team.
|
||||||
|
|
||||||
### Epics **(ULTIMATE)**
|
### Epics **(PREMIUM)**
|
||||||
|
|
||||||
[Epics](../../group/epics/index.md) let you manage your portfolio of projects more
|
[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
|
efficiently and with less effort by tracking groups of issues that share a theme, across
|
||||||
|
|
|
@ -16,7 +16,7 @@ You can find all the information for that issue on one screen.
|
||||||
- **2.** [To Do](#to-do)
|
- **2.** [To Do](#to-do)
|
||||||
- **3.** [Assignee](#assignee)
|
- **3.** [Assignee](#assignee)
|
||||||
- **3.1.** [Multiple Assignees **(STARTER)**](#multiple-assignees-starter)
|
- **3.1.** [Multiple Assignees **(STARTER)**](#multiple-assignees-starter)
|
||||||
- **4.** [Epic **(ULTIMATE)**](#epic-ultimate)
|
- **4.** [Epic **(PREMIUM)**](#epic-premium)
|
||||||
- **5.** [Milestone](#milestone)
|
- **5.** [Milestone](#milestone)
|
||||||
- **6.** [Time tracking](#time-tracking)
|
- **6.** [Time tracking](#time-tracking)
|
||||||
- **7.** [Due date](#due-date)
|
- **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
|
In [GitLab Starter](https://about.gitlab.com/pricing/), you can
|
||||||
[assign multiple people](multiple_assignees_for_issues.md) to an issue.
|
[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
|
You can assign issues to an [Epic](../../group/epics/index.md), which allows better
|
||||||
management of groups of related issues.
|
management of groups of related issues.
|
||||||
|
|
|
@ -4,6 +4,8 @@ module API
|
||||||
class GroupImport < Grape::API
|
class GroupImport < Grape::API
|
||||||
MAXIMUM_FILE_SIZE = 50.megabytes.freeze
|
MAXIMUM_FILE_SIZE = 50.megabytes.freeze
|
||||||
|
|
||||||
|
helpers Helpers::FileUploadHelpers
|
||||||
|
|
||||||
helpers do
|
helpers do
|
||||||
def parent_group
|
def parent_group
|
||||||
find_group!(params[:parent_id]) if params[:parent_id].present?
|
find_group!(params[:parent_id]) if params[:parent_id].present?
|
||||||
|
@ -48,29 +50,20 @@ module API
|
||||||
params do
|
params do
|
||||||
requires :path, type: String, desc: 'Group path'
|
requires :path, type: String, desc: 'Group path'
|
||||||
requires :name, type: String, desc: 'Group name'
|
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 :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
|
end
|
||||||
post 'import' do
|
post 'import' do
|
||||||
authorize_create_group!
|
authorize_create_group!
|
||||||
require_gitlab_workhorse!
|
require_gitlab_workhorse!
|
||||||
|
validate_file!
|
||||||
uploaded_file = UploadedFile.from_params(params, :file, ImportExportUploader.workhorse_local_upload_path)
|
|
||||||
|
|
||||||
bad_request!('Unable to process group import file') unless uploaded_file
|
|
||||||
|
|
||||||
group_params = {
|
group_params = {
|
||||||
path: params[:path],
|
path: params[:path],
|
||||||
name: params[:name],
|
name: params[:name],
|
||||||
parent_id: params[:parent_id],
|
parent_id: params[:parent_id],
|
||||||
visibility_level: closest_allowed_visibility_level,
|
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
|
group = ::Groups::CreateService.new(current_user, group_params).execute
|
||||||
|
|
|
@ -444,6 +444,8 @@ module API
|
||||||
|
|
||||||
not_found!("Source Project") unless fork_from_project
|
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)
|
result = ::Projects::ForkService.new(fork_from_project, current_user).execute(user_project)
|
||||||
|
|
||||||
if result
|
if result
|
||||||
|
|
|
@ -6,6 +6,8 @@ module API
|
||||||
class Repositories < Grape::API
|
class Repositories < Grape::API
|
||||||
include PaginationParams
|
include PaginationParams
|
||||||
|
|
||||||
|
helpers ::API::Helpers::HeadersHelpers
|
||||||
|
|
||||||
before { authorize! :download_code, user_project }
|
before { authorize! :download_code, user_project }
|
||||||
|
|
||||||
params do
|
params do
|
||||||
|
@ -67,6 +69,8 @@ module API
|
||||||
get ':id/repository/blobs/:sha/raw' do
|
get ':id/repository/blobs/:sha/raw' do
|
||||||
assign_blob_vars!
|
assign_blob_vars!
|
||||||
|
|
||||||
|
no_cache_headers
|
||||||
|
|
||||||
send_git_blob @repo, @blob
|
send_git_blob @repo, @blob
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,20 @@ module Gitlab
|
||||||
|
|
||||||
alias_method :has_next_page, :next_page?
|
alias_method :has_next_page, :next_page?
|
||||||
alias_method :has_previous_page, :previous_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
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -21,7 +21,7 @@ module Gitlab
|
||||||
project_id: project.id,
|
project_id: project.id,
|
||||||
project: project.path,
|
project: project.path,
|
||||||
namespace: project.namespace.path,
|
namespace: project.namespace.path,
|
||||||
return_url: return_url,
|
return_url: sanitize_url(return_url),
|
||||||
is_supported_content: supported_content?
|
is_supported_content: supported_content?
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
@ -47,6 +47,10 @@ module Gitlab
|
||||||
def file_exists?
|
def file_exists?
|
||||||
commit_id.present? && repository.blob_at(commit_id, file_path).present?
|
commit_id.present? && repository.blob_at(commit_id, file_path).present?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def sanitize_url(url)
|
||||||
|
url if Gitlab::UrlSanitizer.valid_web?(url)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
module Gitlab
|
module Gitlab
|
||||||
class UrlSanitizer
|
class UrlSanitizer
|
||||||
ALLOWED_SCHEMES = %w[http https ssh git].freeze
|
ALLOWED_SCHEMES = %w[http https ssh git].freeze
|
||||||
|
ALLOWED_WEB_SCHEMES = %w[http https].freeze
|
||||||
|
|
||||||
def self.sanitize(content)
|
def self.sanitize(content)
|
||||||
regexp = URI::DEFAULT_PARSER.make_regexp(ALLOWED_SCHEMES)
|
regexp = URI::DEFAULT_PARSER.make_regexp(ALLOWED_SCHEMES)
|
||||||
|
@ -12,17 +13,21 @@ module Gitlab
|
||||||
content.gsub(regexp, '')
|
content.gsub(regexp, '')
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.valid?(url)
|
def self.valid?(url, allowed_schemes: ALLOWED_SCHEMES)
|
||||||
return false unless url.present?
|
return false unless url.present?
|
||||||
return false unless url.is_a?(String)
|
return false unless url.is_a?(String)
|
||||||
|
|
||||||
uri = Addressable::URI.parse(url.strip)
|
uri = Addressable::URI.parse(url.strip)
|
||||||
|
|
||||||
ALLOWED_SCHEMES.include?(uri.scheme)
|
allowed_schemes.include?(uri.scheme)
|
||||||
rescue Addressable::URI::InvalidURIError
|
rescue Addressable::URI::InvalidURIError
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.valid_web?(url)
|
||||||
|
valid?(url, allowed_schemes: ALLOWED_WEB_SCHEMES)
|
||||||
|
end
|
||||||
|
|
||||||
def initialize(url, credentials: nil)
|
def initialize(url, credentials: nil)
|
||||||
%i[user password].each do |symbol|
|
%i[user password].each do |symbol|
|
||||||
credentials[symbol] = credentials[symbol].presence if credentials&.key?(symbol)
|
credentials[symbol] = credentials[symbol].presence if credentials&.key?(symbol)
|
||||||
|
|
|
@ -4362,9 +4362,6 @@ msgstr ""
|
||||||
msgid "ClusterIntegration|Copy Kubernetes cluster name"
|
msgid "ClusterIntegration|Copy Kubernetes cluster name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "ClusterIntegration|Copy Service Token"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "ClusterIntegration|Could not load IAM roles"
|
msgid "ClusterIntegration|Could not load IAM roles"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -4443,6 +4440,9 @@ msgstr ""
|
||||||
msgid "ClusterIntegration|Enabled stack"
|
msgid "ClusterIntegration|Enabled stack"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "ClusterIntegration|Enter new Service Token"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "ClusterIntegration|Enter the details for your Amazon EKS Kubernetes cluster"
|
msgid "ClusterIntegration|Enter the details for your Amazon EKS Kubernetes cluster"
|
||||||
msgstr ""
|
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."
|
msgid "ClusterIntegration|Helm streamlines installing and managing Kubernetes applications. Tiller runs inside of your Kubernetes Cluster, and manages releases of your charts."
|
||||||
msgstr ""
|
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}."
|
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
|
||||||
msgstr ""
|
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."
|
msgid "ClusterIntegration|Set the global mode for the WAF in this cluster. This can be overridden at the environmental level."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "ClusterIntegration|Show"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "ClusterIntegration|Something went wrong on our end."
|
msgid "ClusterIntegration|Something went wrong on our end."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -12440,9 +12434,6 @@ msgstr ""
|
||||||
msgid "Makes this issue confidential."
|
msgid "Makes this issue confidential."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Malformed string"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Manage"
|
msgid "Manage"
|
||||||
msgstr ""
|
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."
|
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 ""
|
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."
|
msgid "This variable can not be masked."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -23973,6 +23961,9 @@ msgstr ""
|
||||||
msgid "You will be removed from existing projects/groups"
|
msgid "You will be removed from existing projects/groups"
|
||||||
msgstr ""
|
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."
|
msgid "You will first need to set up Jira Integration to use this feature."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -24207,6 +24198,9 @@ msgstr ""
|
||||||
msgid "Your projects"
|
msgid "Your projects"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Your request for access could not be processed: %{error_meesage}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Your request for access has been queued for review."
|
msgid "Your request for access has been queued for review."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -24624,6 +24618,9 @@ msgstr ""
|
||||||
msgid "email '%{email}' does not match the allowed domain of '%{email_domain}'"
|
msgid "email '%{email}' does not match the allowed domain of '%{email_domain}'"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "email '%{email}' is not a verified email."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "enabled"
|
msgid "enabled"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -155,6 +155,46 @@ describe Admin::ApplicationSettingsController do
|
||||||
end
|
end
|
||||||
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
|
describe 'PUT #reset_registration_token' do
|
||||||
before do
|
before do
|
||||||
sign_in(admin)
|
sign_in(admin)
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
describe Oauth::AuthorizationsController do
|
describe Oauth::AuthorizationsController do
|
||||||
let(:user) { create(:user) }
|
|
||||||
let!(:application) { create(:oauth_application, scopes: 'api read_user', redirect_uri: 'http://example.com') }
|
let!(:application) { create(:oauth_application, scopes: 'api read_user', redirect_uri: 'http://example.com') }
|
||||||
let(:params) do
|
let(:params) do
|
||||||
{
|
{
|
||||||
|
@ -19,53 +18,68 @@ describe Oauth::AuthorizationsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'GET #new' do
|
describe 'GET #new' do
|
||||||
context 'without valid params' do
|
context 'when the user is confirmed' do
|
||||||
it 'returns 200 code and renders error view' do
|
let(:user) { create(:user) }
|
||||||
get :new
|
|
||||||
|
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 have_gitlab_http_status(:ok)
|
||||||
expect(response).to render_template('doorkeeper/authorizations/error')
|
expect(response).to render_template('doorkeeper/authorizations/error')
|
||||||
end
|
end
|
||||||
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
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,8 +5,8 @@ require 'spec_helper'
|
||||||
describe Profiles::NotificationsController do
|
describe Profiles::NotificationsController do
|
||||||
let(:user) do
|
let(:user) do
|
||||||
create(:user) do |user|
|
create(:user) do |user|
|
||||||
user.emails.create(email: 'original@example.com')
|
user.emails.create(email: 'original@example.com', confirmed_at: Time.current)
|
||||||
user.emails.create(email: 'new@example.com')
|
user.emails.create(email: 'new@example.com', confirmed_at: Time.current)
|
||||||
user.notification_email = 'original@example.com'
|
user.notification_email = 'original@example.com'
|
||||||
user.save!
|
user.save!
|
||||||
end
|
end
|
||||||
|
|
|
@ -256,7 +256,7 @@ describe Projects::DeployKeysController do
|
||||||
end
|
end
|
||||||
|
|
||||||
def deploy_key_params(title, can_push)
|
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 } }
|
{ deploy_key: { title: title, deploy_keys_projects_attributes: deploy_keys_projects_attributes } }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -300,6 +300,42 @@ describe Projects::DeployKeysController do
|
||||||
expect { subject }.to change { deploy_keys_project.reload.can_push }.from(false).to(true)
|
expect { subject }.to change { deploy_keys_project.reload.can_push }.from(false).to(true)
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
||||||
context 'with admin as project maintainer' do
|
context 'with admin as project maintainer' do
|
||||||
|
|
|
@ -148,16 +148,10 @@ describe Projects::PagesDomainsController do
|
||||||
describe 'POST verify' do
|
describe 'POST verify' do
|
||||||
let(:params) { request_params.merge(id: pages_domain.domain) }
|
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
|
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
|
post :verify, params: params
|
||||||
|
|
||||||
|
@ -166,7 +160,9 @@ describe Projects::PagesDomainsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'handles verification failure' do
|
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
|
post :verify, params: params
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,10 @@ FactoryBot.define do
|
||||||
after(:build) { |user, _| user.block! }
|
after(:build) { |user, _| user.block! }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
trait :unconfirmed do
|
||||||
|
confirmed_at { nil }
|
||||||
|
end
|
||||||
|
|
||||||
trait :with_avatar do
|
trait :with_avatar do
|
||||||
avatar { fixture_file_upload('spec/fixtures/dk.png') }
|
avatar { fixture_file_upload('spec/fixtures/dk.png') }
|
||||||
end
|
end
|
||||||
|
|
|
@ -39,7 +39,7 @@ describe 'User Cluster', :js do
|
||||||
expect(page.find_field('cluster[platform_kubernetes_attributes][api_url]').value)
|
expect(page.find_field('cluster[platform_kubernetes_attributes][api_url]').value)
|
||||||
.to have_content('http://example.com')
|
.to have_content('http://example.com')
|
||||||
expect(page.find_field('cluster[platform_kubernetes_attributes][token]').value)
|
expect(page.find_field('cluster[platform_kubernetes_attributes][token]').value)
|
||||||
.to have_content('my-token')
|
.to be_empty
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
21
spec/features/oauth_provider_authorize_spec.rb
Normal file
21
spec/features/oauth_provider_authorize_spec.rb
Normal 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
|
|
@ -46,7 +46,7 @@ describe 'User Cluster', :js do
|
||||||
expect(page.find_field('cluster[platform_kubernetes_attributes][api_url]').value)
|
expect(page.find_field('cluster[platform_kubernetes_attributes][api_url]').value)
|
||||||
.to have_content('http://example.com')
|
.to have_content('http://example.com')
|
||||||
expect(page.find_field('cluster[platform_kubernetes_attributes][token]').value)
|
expect(page.find_field('cluster[platform_kubernetes_attributes][token]').value)
|
||||||
.to have_content('my-token')
|
.to be_empty
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'user sees RBAC is enabled by default' do
|
it 'user sees RBAC is enabled by default' do
|
||||||
|
|
|
@ -158,6 +158,17 @@ shared_examples 'pages settings editing' do
|
||||||
expect(page).to have_content('my.test.domain.com')
|
expect(page).to have_content('my.test.domain.com')
|
||||||
end
|
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
|
describe 'with dns verification enabled' do
|
||||||
before do
|
before do
|
||||||
stub_application_setting(pages_domain_verification_enabled: true)
|
stub_application_setting(pages_domain_verification_enabled: true)
|
||||||
|
|
|
@ -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', () => {
|
describe('checkForNewInstalls', () => {
|
||||||
const INITIAL_APP_MAP = {
|
const INITIAL_APP_MAP = {
|
||||||
helm: { status: null, title: 'Helm Tiller' },
|
helm: { status: null, title: 'Helm Tiller' },
|
||||||
|
|
82
spec/frontend/fixtures/static/issue_with_mermaid_graph.html
Normal file
82
spec/frontend/fixtures/static/issue_with_mermaid_graph.html
Normal 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>
|
|
@ -18,6 +18,7 @@ describe('Issue', () => {
|
||||||
preloadFixtures('issues/closed-issue.html');
|
preloadFixtures('issues/closed-issue.html');
|
||||||
preloadFixtures('issues/issue-with-task-list.html');
|
preloadFixtures('issues/issue-with-task-list.html');
|
||||||
preloadFixtures('issues/open-issue.html');
|
preloadFixtures('issues/open-issue.html');
|
||||||
|
preloadFixtures('static/issue_with_mermaid_graph.html');
|
||||||
|
|
||||||
function expectErrorMessage() {
|
function expectErrorMessage() {
|
||||||
const $flashMessage = $('div.flash-alert');
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,9 +3,17 @@ import DuplicateDashboardForm from '~/monitoring/components/duplicate_dashboard_
|
||||||
|
|
||||||
import { dashboardGitResponse } from '../mock_data';
|
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 defaultBranch = 'master';
|
||||||
|
|
||||||
const findByRef = ref => wrapper.find({ ref });
|
const findByRef = ref => wrapper.find({ ref });
|
||||||
|
@ -20,14 +28,7 @@ describe('DuplicateDashboardForm', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// Use `mount` to render native input elements
|
createMountedWrapper({ dashboard: dashboardGitResponse[0], defaultBranch });
|
||||||
wrapper = mount(DuplicateDashboardForm, {
|
|
||||||
propsData: {
|
|
||||||
dashboard: dashboardGitResponse[0],
|
|
||||||
defaultBranch,
|
|
||||||
},
|
|
||||||
sync: false,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders correctly', () => {
|
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 = '<img/src='x'onerror=alert(document.domain)>';
|
||||||
|
|
||||||
|
expect(branchOptionHtml).toEqual(expect.stringContaining(escapedBranch));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -19,6 +19,20 @@ describe Gitlab::Graphql::Pagination::ExternallyPaginatedArrayConnection do
|
||||||
it_behaves_like 'connection with paged nodes' do
|
it_behaves_like 'connection with paged nodes' do
|
||||||
let(:paged_nodes_size) { values.size }
|
let(:paged_nodes_size) { values.size }
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
describe '#start_cursor' do
|
describe '#start_cursor' do
|
||||||
|
|
|
@ -57,5 +57,23 @@ describe Gitlab::StaticSiteEditor::Config do
|
||||||
|
|
||||||
it { is_expected.to include(is_supported_content: false) }
|
it { is_expected.to include(is_supported_content: false) }
|
||||||
end
|
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
|
||||||
end
|
end
|
||||||
|
|
|
@ -60,6 +60,30 @@ describe Gitlab::UrlSanitizer do
|
||||||
end
|
end
|
||||||
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
|
describe '#sanitized_url' do
|
||||||
context 'credentials in hash' do
|
context 'credentials in hash' do
|
||||||
where(username: ['foo', '', nil], password: ['bar', '', nil])
|
where(username: ['foo', '', nil], password: ['bar', '', nil])
|
||||||
|
|
|
@ -108,6 +108,11 @@ describe Group do
|
||||||
let(:group_notification_email) { 'user+group@example.com' }
|
let(:group_notification_email) { 'user+group@example.com' }
|
||||||
let(:subgroup_notification_email) { 'user+subgroup@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) }
|
subject { subgroup.notification_email_for(user) }
|
||||||
|
|
||||||
context 'when both group notification emails are set' do
|
context 'when both group notification emails are set' do
|
||||||
|
|
|
@ -48,6 +48,33 @@ RSpec.describe NotificationSetting do
|
||||||
expect(notification_setting.reopen_merge_request).to eq(false)
|
expect(notification_setting.reopen_merge_request).to eq(false)
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
||||||
describe '#for_projects' do
|
describe '#for_projects' do
|
||||||
|
|
|
@ -296,7 +296,7 @@ describe User, :do_not_mock_admin_mode do
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'an object with email-formated attributes', :public_email, :notification_email do
|
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
|
end
|
||||||
|
|
||||||
describe '#commit_email' do
|
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")
|
user = build(:user, email: "temp-email-for-oauth@example.com")
|
||||||
expect(user).to be_valid
|
expect(user).to be_valid
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
context 'set_commit_email' do
|
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.count).to eq 1
|
||||||
expect(@user.emails.first.confirmed_at).not_to eq nil
|
expect(@user.emails.first.confirmed_at).not_to eq nil
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
describe '#update_notification_email' do
|
describe '#update_notification_email' do
|
||||||
|
@ -2068,6 +2196,31 @@ describe User, :do_not_mock_admin_mode do
|
||||||
end
|
end
|
||||||
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
|
describe '#verified_email?' do
|
||||||
let(:user) { create(:user) }
|
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
|
context 'when an ancestor has a level other than Global' do
|
||||||
let(:ancestor) { create(:group) }
|
let(:ancestor) { create(:group) }
|
||||||
let(:group) { create(:group, parent: ancestor) }
|
let(:group) { create(:group, parent: ancestor) }
|
||||||
|
let(:email) { create(:email, :confirmed, email: 'ancestor@example.com', user: user) }
|
||||||
|
|
||||||
before do
|
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
|
end
|
||||||
|
|
||||||
it 'has the same level set' do
|
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(:grand_ancestor) { create(:group) }
|
||||||
let(:ancestor) { create(:group, parent: grand_ancestor) }
|
let(:ancestor) { create(:group, parent: grand_ancestor) }
|
||||||
let(:group) { create(:group, parent: 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
|
before do
|
||||||
create(:notification_setting, user: user, source: grand_ancestor, level: 'participating', notification_email: 'grand@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@example.com')
|
create(:notification_setting, user: user, source: ancestor, level: 'global', notification_email: ancestor_email.email)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'has the same email set' do
|
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
|
context 'when group has notification email set' do
|
||||||
it 'returns group notification email' do
|
it 'returns group notification email' do
|
||||||
group_notification_email = 'user+group@example.com'
|
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)
|
create(:notification_setting, user: user, source: group, notification_email: group_notification_email)
|
||||||
|
|
||||||
is_expected.to eq(group_notification_email)
|
is_expected.to eq(group_notification_email)
|
||||||
|
|
|
@ -11,7 +11,7 @@ describe API::GroupImport do
|
||||||
let(:file) { File.join('spec', 'fixtures', 'group_export.tar.gz') }
|
let(:file) { File.join('spec', 'fixtures', 'group_export.tar.gz') }
|
||||||
let(:export_path) { "#{Dir.tmpdir}/group_export_spec" }
|
let(:export_path) { "#{Dir.tmpdir}/group_export_spec" }
|
||||||
let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
|
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
|
before do
|
||||||
allow_next_instance_of(Gitlab::ImportExport) do |import_export|
|
allow_next_instance_of(Gitlab::ImportExport) do |import_export|
|
||||||
|
@ -35,7 +35,7 @@ describe API::GroupImport do
|
||||||
}
|
}
|
||||||
end
|
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
|
shared_examples 'when all params are correct' do
|
||||||
context 'when user is authorized to create new group' do
|
context 'when user is authorized to create new group' do
|
||||||
|
@ -151,7 +151,7 @@ describe API::GroupImport do
|
||||||
params[:file] = file_upload
|
params[:file] = file_upload
|
||||||
|
|
||||||
expect do
|
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)
|
end.not_to change { Group.count }.from(1)
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:bad_request)
|
expect(response).to have_gitlab_http_status(:bad_request)
|
||||||
|
@ -171,7 +171,7 @@ describe API::GroupImport do
|
||||||
|
|
||||||
context 'without a file from workhorse' do
|
context 'without a file from workhorse' do
|
||||||
it 'rejects the request' do
|
it 'rejects the request' do
|
||||||
subject
|
upload_archive(nil, workhorse_headers, params)
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:bad_request)
|
expect(response).to have_gitlab_http_status(:bad_request)
|
||||||
end
|
end
|
||||||
|
@ -179,7 +179,7 @@ describe API::GroupImport do
|
||||||
|
|
||||||
context 'without a workhorse header' do
|
context 'without a workhorse header' do
|
||||||
it 'rejects request 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)
|
expect(response).to have_gitlab_http_status(:forbidden)
|
||||||
end
|
end
|
||||||
|
@ -189,9 +189,7 @@ describe API::GroupImport do
|
||||||
let(:params) do
|
let(:params) do
|
||||||
{
|
{
|
||||||
path: 'test-import-group',
|
path: 'test-import-group',
|
||||||
name: 'test-import-group',
|
name: 'test-import-group'
|
||||||
'file.path' => file_upload.path,
|
|
||||||
'file.name' => file_upload.original_filename
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -229,9 +227,7 @@ describe API::GroupImport do
|
||||||
{
|
{
|
||||||
path: 'test-import-group',
|
path: 'test-import-group',
|
||||||
name: 'test-import-group',
|
name: 'test-import-group',
|
||||||
file: fog_file,
|
file: fog_file
|
||||||
'file.remote_id' => file_name,
|
|
||||||
'file.size' => fog_file.size
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -245,10 +241,21 @@ describe API::GroupImport do
|
||||||
include_examples 'when some params are missing'
|
include_examples 'when some params are missing'
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
||||||
describe 'POST /groups/import/authorize' do
|
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
|
it 'authorizes importing group with workhorse header' do
|
||||||
subject
|
subject
|
||||||
|
@ -258,7 +265,7 @@ describe API::GroupImport do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'rejects requests that bypassed gitlab-workhorse' do
|
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
|
subject
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ describe API::NotificationSettings do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "PUT /notification_settings" do
|
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
|
it "updates global notification settings for the current user" do
|
||||||
put api("/notification_settings", user), params: { level: 'watch', notification_email: email.email }
|
put api("/notification_settings", user), params: { level: 'watch', notification_email: email.email }
|
||||||
|
|
|
@ -1891,6 +1891,17 @@ describe API::Projects do
|
||||||
expect(project_fork_target).to be_forked
|
expect(project_fork_target).to be_forked
|
||||||
end
|
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
|
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)
|
post api("/projects/#{project_fork_target.id}/fork/#{private_project_fork_source.id}", user)
|
||||||
|
|
||||||
|
|
|
@ -177,6 +177,12 @@ describe API::Repositories do
|
||||||
expect(headers['Content-Disposition']).to eq 'inline'
|
expect(headers['Content-Disposition']).to eq 'inline'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'uncached response' do
|
||||||
|
before do
|
||||||
|
get api(route, current_user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'when sha does not exist' do
|
context 'when sha does not exist' do
|
||||||
it_behaves_like '404 response' do
|
it_behaves_like '404 response' do
|
||||||
let(:request) { get api(route.sub(sample_blob.oid, 'abcd9876'), current_user) }
|
let(:request) { get api(route.sub(sample_blob.oid, 'abcd9876'), current_user) }
|
||||||
|
|
|
@ -9,15 +9,11 @@ describe 'OpenID Connect requests' do
|
||||||
name: 'Alice',
|
name: 'Alice',
|
||||||
username: 'alice',
|
username: 'alice',
|
||||||
email: 'private@example.com',
|
email: 'private@example.com',
|
||||||
emails: [public_email],
|
|
||||||
public_email: public_email.email,
|
|
||||||
website_url: 'https://example.com',
|
website_url: 'https://example.com',
|
||||||
avatar: fixture_file_upload('spec/fixtures/dk.png')
|
avatar: fixture_file_upload('spec/fixtures/dk.png')
|
||||||
)
|
)
|
||||||
end
|
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_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 }
|
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',
|
'name' => 'Alice',
|
||||||
'nickname' => 'alice',
|
'nickname' => 'alice',
|
||||||
'email' => 'public@example.com',
|
'email' => 'public@example.com',
|
||||||
'email_verified' => false,
|
'email_verified' => true,
|
||||||
'website' => 'https://example.com',
|
'website' => 'https://example.com',
|
||||||
'profile' => 'http://localhost/alice',
|
'profile' => 'http://localhost/alice',
|
||||||
'picture' => "http://localhost/uploads/-/system/user/avatar/#{user.id}/dk.png",
|
'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}" }
|
get '/oauth/userinfo', params: {}, headers: { 'Authorization' => "Bearer #{access_token.token}" }
|
||||||
end
|
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
|
context 'Application without OpenID scope' do
|
||||||
let(:application) { create :oauth_application, scopes: 'api' }
|
let(:application) { create :oauth_application, scopes: 'api' }
|
||||||
|
|
||||||
|
@ -123,7 +124,7 @@ describe 'OpenID Connect requests' do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'has false in email_verified claim' do
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,8 @@ require 'spec_helper'
|
||||||
describe 'view user notifications' do
|
describe 'view user notifications' do
|
||||||
let(:user) do
|
let(:user) do
|
||||||
create(:user) do |user|
|
create(:user) do |user|
|
||||||
user.emails.create(email: 'original@example.com')
|
user.emails.create(email: 'original@example.com', confirmed_at: Time.current)
|
||||||
user.emails.create(email: 'new@example.com')
|
user.emails.create(email: 'new@example.com', confirmed_at: Time.current)
|
||||||
user.notification_email = 'original@example.com'
|
user.notification_email = 'original@example.com'
|
||||||
user.save!
|
user.save!
|
||||||
end
|
end
|
||||||
|
|
|
@ -47,6 +47,39 @@ describe Clusters::UpdateService do
|
||||||
expect(cluster.platform.namespace).to eq('custom-namespace')
|
expect(cluster.platform.namespace).to eq('custom-namespace')
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
||||||
context 'when invalid params' do
|
context 'when invalid params' do
|
||||||
|
|
|
@ -2395,6 +2395,8 @@ describe NotificationService, :mailer do
|
||||||
group = create(:group)
|
group = create(:group)
|
||||||
|
|
||||||
project.update(group: 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)
|
create(:notification_setting, user: u_custom_notification_enabled, source: group, notification_email: group_notification_email)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -2429,6 +2431,7 @@ describe NotificationService, :mailer do
|
||||||
group = create(:group)
|
group = create(:group)
|
||||||
|
|
||||||
project.update(group: 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)
|
create(:notification_setting, user: u_member, source: group, notification_email: group_notification_email)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -2522,6 +2525,7 @@ describe NotificationService, :mailer do
|
||||||
group = create(:group)
|
group = create(:group)
|
||||||
|
|
||||||
project.update(group: 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)
|
create(:notification_setting, user: u_member, source: group, notification_email: group_notification_email)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -6,19 +6,46 @@ describe Prometheus::ProxyVariableSubstitutionService do
|
||||||
describe '#execute' do
|
describe '#execute' do
|
||||||
let_it_be(:environment) { create(:environment) }
|
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(:params) { ActionController::Parameters.new(params_keys).permit! }
|
||||||
let(:result) { subject.execute }
|
let(:result) { subject.execute }
|
||||||
|
|
||||||
subject { described_class.new(environment, params) }
|
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
|
it 'replaces variables with values' do
|
||||||
expect(result[:status]).to eq(:success)
|
expect(result[:status]).to eq(:success)
|
||||||
expect(result[:params][:query]).to eq(expected_query)
|
expect(result[:params][:query]).to eq(expected_query)
|
||||||
end
|
end
|
||||||
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|
|
shared_examples 'error' do |message|
|
||||||
it 'returns error' do
|
it 'returns error' do
|
||||||
expect(result[:status]).to eq(:error)
|
expect(result[:status]).to eq(:error)
|
||||||
|
@ -39,11 +66,13 @@ describe Prometheus::ProxyVariableSubstitutionService do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with predefined variables' do
|
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
|
it_behaves_like 'success' do
|
||||||
let(:expected_query) do
|
let(:expected_query) do
|
||||||
%Q[up{container_name!="POD",environment="#{environment.slug}"}]
|
%Q[up{ container_name!="POD",environment="#{environment.slug}"}]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -54,26 +83,16 @@ describe Prometheus::ProxyVariableSubstitutionService do
|
||||||
let(:expected_query) { nil }
|
let(:expected_query) { nil }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'with liquid format' do
|
context 'with ruby and liquid formats' do
|
||||||
let(:params_keys) do
|
let(:params_keys) do
|
||||||
{ query: 'up{environment="{{ci_environment_slug}}"}' }
|
{ query: 'up{%{environment_filter},env2="{{ci_environment_slug}}"}' }
|
||||||
end
|
|
||||||
|
|
||||||
it_behaves_like 'success' do
|
|
||||||
let(:expected_query) { %Q[up{environment="#{environment.slug}"}] }
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with ruby and liquid formats' do
|
it_behaves_like 'success' do
|
||||||
let(:params_keys) do
|
let(:expected_query) do
|
||||||
{ query: 'up{%{environment_filter},env2="{{ci_environment_slug}}"}' }
|
%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
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -83,7 +102,7 @@ describe Prometheus::ProxyVariableSubstitutionService do
|
||||||
|
|
||||||
let(:params_keys) do
|
let(:params_keys) do
|
||||||
{
|
{
|
||||||
query: 'up{pod_name="{{pod_name}}"}',
|
query: "up{pod_name=\"#{w('pod_name')}\"}",
|
||||||
variables: ['pod_name', pod_name]
|
variables: ['pod_name', pod_name]
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
@ -92,24 +111,10 @@ describe Prometheus::ProxyVariableSubstitutionService do
|
||||||
let(:expected_query) { %q[up{pod_name="pod1"}] }
|
let(:expected_query) { %q[up{pod_name="pod1"}] }
|
||||||
end
|
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
|
context 'with predefined variables in variables parameter' do
|
||||||
let(:params_keys) 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']
|
variables: ['pod_name', pod_name, 'ci_environment_slug', 'custom_value']
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
@ -124,7 +129,7 @@ describe Prometheus::ProxyVariableSubstitutionService do
|
||||||
context 'with invalid variables parameter' do
|
context 'with invalid variables parameter' do
|
||||||
let(:params_keys) do
|
let(:params_keys) do
|
||||||
{
|
{
|
||||||
query: 'up{pod_name="{{pod_name}}"}',
|
query: "up{pod_name=\"#{w('pod_name')}\"}",
|
||||||
variables: ['a']
|
variables: ['a']
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
@ -136,68 +141,66 @@ describe Prometheus::ProxyVariableSubstitutionService do
|
||||||
context 'with nil variables' do
|
context 'with nil variables' do
|
||||||
let(:params_keys) do
|
let(:params_keys) do
|
||||||
{
|
{
|
||||||
query: 'up{pod_name="{{pod_name}}"}',
|
query: "up{pod_name=\"%{pod_name}\"}",
|
||||||
variables: nil
|
variables: nil
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'success' do
|
it_behaves_like 'replaces variables with values' do
|
||||||
let(:expected_query) { 'up{pod_name=""}' }
|
let(:expected_query) { "up{pod_name=\"%{pod_name}\"}" }
|
||||||
end
|
end
|
||||||
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
|
let(:params_keys) do
|
||||||
{
|
{
|
||||||
query: 'up{env1="%{ruby_variable}",env2="{{ liquid_variable }}"}',
|
query: 'up{' \
|
||||||
variables: %w(ruby_variable value liquid_variable env_slug)
|
"env1=#{w(' ci_environment_slug')}," \
|
||||||
|
"env2=#{w('ci_environment_slug ')}," \
|
||||||
|
"#{w(' environment_filter ')}" \
|
||||||
|
'}'
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'success' do
|
it_behaves_like 'success' do
|
||||||
# It should replace only liquid variables with their values
|
let(:expected_query) do
|
||||||
let(:expected_query) { %q[up{env1="%{ruby_variable}",env2="env_slug"}] }
|
'up{' \
|
||||||
|
"env1=#{environment.slug}," \
|
||||||
|
"env2=#{environment.slug}," \
|
||||||
|
"container_name!=\"POD\",environment=\"#{environment.slug}\"" \
|
||||||
|
'}'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
context 'with liquid tags and ruby format variables' do
|
context 'with liquid tags and ruby format variables' do
|
||||||
let(:params_keys) do
|
let(:params_keys) do
|
||||||
{
|
{
|
||||||
query: 'up{ {% if true %}env1="%{ci_environment_slug}",' \
|
query: 'up{ {% if true %}env1="%{ci_environment_slug}",' \
|
||||||
'env2="{{ci_environment_slug}}"{% endif %} }'
|
'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
|
end
|
||||||
|
|
||||||
# The following spec will fail and should be changed to a 'success' spec
|
context 'with empty variables' do
|
||||||
# once we remove support for the Ruby interpolation format.
|
let(:params_keys) do
|
||||||
# https://gitlab.com/gitlab-org/gitlab/issues/37990
|
{ query: "up{env1=%{},env2=%{ }}" }
|
||||||
#
|
end
|
||||||
# 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.
|
|
||||||
|
|
||||||
it_behaves_like 'error', 'Malformed string'
|
it_behaves_like 'replaces variables with values' do
|
||||||
end
|
let(:expected_query) { "up{env1=%{},env2=%{ }}" }
|
||||||
|
|
||||||
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}\"}"
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with multiple occurrences of variable in string' do
|
context 'with multiple occurrences of variable in string' do
|
||||||
let(:params_keys) 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
|
end
|
||||||
|
|
||||||
it_behaves_like 'success' do
|
it_behaves_like 'success' do
|
||||||
|
@ -207,7 +210,7 @@ describe Prometheus::ProxyVariableSubstitutionService do
|
||||||
|
|
||||||
context 'with multiple variables in string' do
|
context 'with multiple variables in string' do
|
||||||
let(:params_keys) do
|
let(:params_keys) do
|
||||||
{ query: 'up{env=%{ci_environment_slug},%{environment_filter}}' }
|
{ query: "up{env=#{w('ci_environment_slug')},#{w('environment_filter')}}" }
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'success' do
|
it_behaves_like 'success' do
|
||||||
|
@ -219,54 +222,24 @@ describe Prometheus::ProxyVariableSubstitutionService do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with unknown variables in string' do
|
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
|
it_behaves_like 'replaces variables with values' do
|
||||||
let(:expected_query) { 'up{env=%{env_slug}}' }
|
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
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# The ruby % operator will not replace known variables if there are unknown
|
# The ruby % operator will not replace known variables if there are unknown
|
||||||
# variables also in the string. It doesn't raise an error
|
# variables also in the string. It doesn't raise an error
|
||||||
# (though the `sprintf` and `format` methods do).
|
# (though the `sprintf` and `format` methods do).
|
||||||
|
# Fortunately, we do not use the % operator anymore.
|
||||||
context 'with unknown and known variables in string' do
|
context 'with unknown and known variables in string' do
|
||||||
let(:params_keys) 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
|
end
|
||||||
|
|
||||||
it_behaves_like 'success' do
|
it_behaves_like 'replaces variables with values' do
|
||||||
let(:expected_query) { 'up{env=%{ci_environment_slug},other_env=%{env_slug}}' }
|
let(:expected_query) { "up{env=#{environment.slug},other_env=#{w('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'
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -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
|
|
@ -28,6 +28,7 @@ RSpec.shared_examples 'an email sent to a user' do
|
||||||
it 'is sent to user\'s group notification email' do
|
it 'is sent to user\'s group notification email' do
|
||||||
group_notification_email = 'user+group@example.com'
|
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)
|
create(:notification_setting, user: recipient, source: group, notification_email: group_notification_email)
|
||||||
|
|
||||||
expect(subject).to deliver_to(group_notification_email)
|
expect(subject).to deliver_to(group_notification_email)
|
||||||
|
|
|
@ -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
|
|
@ -145,39 +145,57 @@ describe FileUploader do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.extract_dynamic_path' do
|
describe '.extract_dynamic_path' do
|
||||||
context 'with a 32-byte hexadecimal secret in the path' do
|
shared_examples 'a valid secret' do |root_path|
|
||||||
let(:secret) { SecureRandom.hex }
|
context 'with a 32-byte hexadecimal secret' do
|
||||||
let(:path) { "export/4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a/test/uploads/#{secret}/dummy.txt" }
|
let(:secret) { SecureRandom.hex }
|
||||||
|
let(:path) { File.join(*[root_path, secret, 'dummy.txt'].compact) }
|
||||||
|
|
||||||
it 'extracts the secret' do
|
it 'extracts the secret' do
|
||||||
expect(described_class.extract_dynamic_path(path)[:secret]).to eq(secret)
|
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
|
end
|
||||||
|
|
||||||
it 'extracts the identifier' do
|
context 'with a 10-byte hexadecimal secret' do
|
||||||
expect(described_class.extract_dynamic_path(path)[:identifier]).to eq('dummy.txt')
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with a 10-byte hexadecimal secret in the path' do
|
context 'with an absolute path without a slash in the beginning' do
|
||||||
let(:secret) { SecureRandom.hex(10) }
|
it_behaves_like 'a valid secret', 'export/4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a/test/uploads'
|
||||||
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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with an invalid secret in the path' do
|
context 'with an absolute path with a slash in the beginning' do
|
||||||
let(:secret) { 'foo' }
|
it_behaves_like 'a valid secret', '/export/4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a/test/uploads'
|
||||||
let(:path) { "export/4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a/test/uploads/#{secret}/dummy.txt" }
|
end
|
||||||
|
|
||||||
it 'returns nil' do
|
context 'with an relative path without a slash in the beginning' do
|
||||||
expect(described_class.extract_dynamic_path(path)).to be_nil
|
it_behaves_like 'a valid secret', nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with an relative path with a slash in the beginning' do
|
||||||
|
it_behaves_like 'a valid secret', '/'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -202,7 +220,7 @@ describe FileUploader do
|
||||||
end
|
end
|
||||||
|
|
||||||
context "10-byte hexadecimal" do
|
context "10-byte hexadecimal" do
|
||||||
let(:secret) { SecureRandom.hex(10) }
|
let(:secret) { SecureRandom.hex[0, 10] }
|
||||||
|
|
||||||
it "returns the secret" do
|
it "returns the secret" do
|
||||||
expect(uploader.secret).to eq(secret)
|
expect(uploader.secret).to eq(secret)
|
||||||
|
|
34
spec/views/admin/application_settings/_eks.html.haml_spec.rb
Normal file
34
spec/views/admin/application_settings/_eks.html.haml_spec.rb
Normal 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
|
|
@ -7,7 +7,7 @@ describe 'projects/pages_domains/show' do
|
||||||
|
|
||||||
before do
|
before do
|
||||||
assign(:project, project)
|
assign(:project, project)
|
||||||
assign(:domain, domain.present)
|
allow(view).to receive(:domain_presenter).and_return(domain.present)
|
||||||
stub_pages_setting(external_https: true)
|
stub_pages_setting(external_https: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue