summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2019-12-17 09:07:48 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2019-12-17 09:07:48 +0000
commit5bd24a54ef4ce3a38a860eb53b66d062c2382971 (patch)
tree5f5e65571dfcb2c62c27600ee7655dec4b44c923 /app
parent74673d04d25ffed35cbcf17cd42969bed0a4e705 (diff)
downloadgitlab-ce-5bd24a54ef4ce3a38a860eb53b66d062c2382971.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/clusters/clusters_bundle.js22
-rw-r--r--app/assets/javascripts/clusters/components/remove_cluster_confirmation.vue168
-rw-r--r--app/assets/javascripts/project_find_file.js3
-rw-r--r--app/assets/javascripts/vue_shared/components/split_button.vue6
-rw-r--r--app/finders/groups_finder.rb2
-rw-r--r--app/finders/issues_finder.rb2
-rw-r--r--app/finders/keys_finder.rb32
-rw-r--r--app/finders/merge_request_target_project_finder.rb20
-rw-r--r--app/finders/personal_access_tokens_finder.rb16
-rw-r--r--app/helpers/merge_requests_helper.rb2
-rw-r--r--app/helpers/users_helper.rb8
-rw-r--r--app/models/ci/build.rb2
-rw-r--r--app/models/issue.rb2
-rw-r--r--app/models/key.rb4
-rw-r--r--app/models/personal_access_token.rb3
-rw-r--r--app/models/project.rb1
-rw-r--r--app/models/project_feature.rb2
-rw-r--r--app/models/user.rb4
-rw-r--r--app/policies/base_policy.rb1
-rw-r--r--app/views/clusters/clusters/_advanced_settings.html.haml3
-rw-r--r--app/views/layouts/nav/sidebar/_admin.html.haml2
-rw-r--r--app/views/shared/projects/_list.html.haml1
22 files changed, 283 insertions, 23 deletions
diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js
index f4a7e64ceee..d990d2677a8 100644
--- a/app/assets/javascripts/clusters/clusters_bundle.js
+++ b/app/assets/javascripts/clusters/clusters_bundle.js
@@ -12,6 +12,7 @@ import { APPLICATION_STATUS, INGRESS, INGRESS_DOMAIN_SUFFIX, CROSSPLANE } from '
import ClustersService from './services/clusters_service';
import ClustersStore from './stores/clusters_store';
import Applications from './components/applications.vue';
+import RemoveClusterConfirmation from './components/remove_cluster_confirmation.vue';
import setupToggleButtons from '../toggle_buttons';
import initProjectSelectDropdown from '~/project_select';
@@ -144,6 +145,8 @@ export default class Clusters {
() => this.handlePollError(),
);
}
+
+ this.initRemoveClusterActions();
}
initApplications(type) {
@@ -205,6 +208,25 @@ export default class Clusters {
});
}
+ initRemoveClusterActions() {
+ const el = document.querySelector('#js-cluster-remove-actions');
+ if (el && el.dataset) {
+ const { clusterName, clusterPath } = el.dataset;
+
+ this.removeClusterAction = new Vue({
+ el,
+ render(createElement) {
+ return createElement(RemoveClusterConfirmation, {
+ props: {
+ clusterName,
+ clusterPath,
+ },
+ });
+ },
+ });
+ }
+ }
+
handleClusterEnvironmentsSuccess(data) {
this.store.toggleFetchEnvironments(false);
this.store.updateEnvironments(data.data);
diff --git a/app/assets/javascripts/clusters/components/remove_cluster_confirmation.vue b/app/assets/javascripts/clusters/components/remove_cluster_confirmation.vue
new file mode 100644
index 00000000000..c31ba7ef14a
--- /dev/null
+++ b/app/assets/javascripts/clusters/components/remove_cluster_confirmation.vue
@@ -0,0 +1,168 @@
+<script>
+import _ from 'underscore';
+import SplitButton from '~/vue_shared/components/split_button.vue';
+import { GlModal, GlButton, GlFormInput } from '@gitlab/ui';
+import { s__, sprintf } from '~/locale';
+import csrf from '~/lib/utils/csrf';
+
+const splitButtonActionItems = [
+ {
+ title: s__('ClusterIntegration|Remove integration and resources'),
+ description: s__(
+ 'ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal',
+ ),
+ eventName: 'remove-cluster-and-cleanup',
+ },
+ {
+ title: s__('ClusterIntegration|Remove integration'),
+ description: s__(
+ 'ClusterIntegration|Removes cluster from project but keeps associated resources',
+ ),
+ eventName: 'remove-cluster',
+ },
+];
+
+export default {
+ splitButtonActionItems,
+ components: {
+ SplitButton,
+ GlModal,
+ GlButton,
+ GlFormInput,
+ },
+ props: {
+ clusterPath: {
+ type: String,
+ required: true,
+ },
+ clusterName: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ enteredClusterName: '',
+ confirmCleanup: false,
+ };
+ },
+ computed: {
+ csrfToken() {
+ return csrf.token;
+ },
+ modalTitle() {
+ return this.confirmCleanup
+ ? s__('ClusterIntegration|Remove integration and resources?')
+ : s__('ClusterIntegration|Remove integration?');
+ },
+ warningMessage() {
+ return this.confirmCleanup
+ ? s__(
+ 'ClusterIntegration|You are about to remove your cluster integration and all GitLab-created resources associated with this cluster.',
+ )
+ : s__('ClusterIntegration|You are about to remove your cluster integration.');
+ },
+ warningToBeRemoved() {
+ return s__(`ClusterIntegration|
+ This will permanently delete the following resources:
+ <ul>
+ <li>All installed applications and related resources</li>
+ <li>The <code>gitlab-managed-apps</code> namespace</li>
+ <li>Any project namespaces</li>
+ <li><code>clusterroles</code></li>
+ <li><code>clusterrolebindings</code></li>
+ </ul>
+ `);
+ },
+ confirmationTextLabel() {
+ return sprintf(
+ this.confirmCleanup
+ ? s__(
+ 'ClusterIntegration|To remove your integration and resources, type %{clusterName} to confirm:',
+ )
+ : s__('ClusterIntegration|To remove your integration, type %{clusterName} to confirm:'),
+ {
+ clusterName: `<code>${_.escape(this.clusterName)}</code>`,
+ },
+ false,
+ );
+ },
+ canSubmit() {
+ return this.enteredClusterName === this.clusterName;
+ },
+ },
+ methods: {
+ handleClickRemoveCluster(cleanup = false) {
+ this.confirmCleanup = cleanup;
+ this.$refs.modal.show();
+ },
+ handleCancel() {
+ this.$refs.modal.hide();
+ this.enteredClusterName = '';
+ },
+ handleSubmit(cleanup = false) {
+ this.$refs.cleanup.name = cleanup === true ? 'cleanup' : 'no_cleanup';
+ this.$refs.form.submit();
+ this.enteredClusterName = '';
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <split-button
+ :action-items="$options.splitButtonActionItems"
+ menu-class="dropdown-menu-large"
+ variant="danger"
+ @remove-cluster="handleClickRemoveCluster(false)"
+ @remove-cluster-and-cleanup="handleClickRemoveCluster(true)"
+ />
+ <gl-modal
+ ref="modal"
+ size="lg"
+ modal-id="delete-cluster-modal"
+ :title="modalTitle"
+ kind="danger"
+ >
+ <template>
+ <p>{{ warningMessage }}</p>
+ <div v-if="confirmCleanup" v-html="warningToBeRemoved"></div>
+ <strong v-html="confirmationTextLabel"></strong>
+ <form ref="form" :action="clusterPath" method="post" class="append-bottom-20">
+ <input ref="method" type="hidden" name="_method" value="delete" />
+ <input :value="csrfToken" type="hidden" name="authenticity_token" />
+ <input ref="cleanup" type="hidden" name="cleanup" value="true" />
+ <gl-form-input
+ v-model="enteredClusterName"
+ autofocus
+ type="text"
+ name="confirm_cluster_name_input"
+ autocomplete="off"
+ />
+ </form>
+ <span v-if="confirmCleanup">{{
+ s__(
+ 'ClusterIntegration|If you do not wish to delete all associated GitLab resources, you can simply remove the integration.',
+ )
+ }}</span>
+ </template>
+ <template slot="modal-footer">
+ <gl-button variant="secondary" @click="handleCancel">{{ s__('Cancel') }}</gl-button>
+ <template v-if="confirmCleanup">
+ <gl-button :disabled="!canSubmit" variant="warning" @click="handleSubmit">{{
+ s__('ClusterIntegration|Remove integration')
+ }}</gl-button>
+ <gl-button :disabled="!canSubmit" variant="danger" @click="handleSubmit(true)">{{
+ s__('ClusterIntegration|Remove integration and resources')
+ }}</gl-button>
+ </template>
+ <template v-else>
+ <gl-button :disabled="!canSubmit" variant="danger" @click="handleSubmit">{{
+ s__('ClusterIntegration|Remove integration')
+ }}</gl-button>
+ </template>
+ </template>
+ </gl-modal>
+ </div>
+</template>
diff --git a/app/assets/javascripts/project_find_file.js b/app/assets/javascripts/project_find_file.js
index 81c4f6711e7..d6cdd37a2c3 100644
--- a/app/assets/javascripts/project_find_file.js
+++ b/app/assets/javascripts/project_find_file.js
@@ -4,6 +4,7 @@ import $ from 'jquery';
import fuzzaldrinPlus from 'fuzzaldrin-plus';
import sanitize from 'sanitize-html';
import axios from '~/lib/utils/axios_utils';
+import { joinPaths, escapeFileUrl } from '~/lib/utils/url_utility';
import flash from '~/flash';
import { __ } from '~/locale';
@@ -116,7 +117,7 @@ export default class ProjectFindFile {
if (searchText) {
matches = fuzzaldrinPlus.match(filePath, searchText);
}
- const blobItemUrl = `${this.options.blobUrlTemplate}/${encodeURIComponent(filePath)}`;
+ const blobItemUrl = joinPaths(this.options.blobUrlTemplate, escapeFileUrl(filePath));
const html = ProjectFindFile.makeHtml(filePath, matches, blobItemUrl);
results.push(this.element.find('.tree-table > tbody').append(html));
}
diff --git a/app/assets/javascripts/vue_shared/components/split_button.vue b/app/assets/javascripts/vue_shared/components/split_button.vue
index f7dc00a345c..9aacde49264 100644
--- a/app/assets/javascripts/vue_shared/components/split_button.vue
+++ b/app/assets/javascripts/vue_shared/components/split_button.vue
@@ -26,6 +26,11 @@ export default {
required: false,
default: '',
},
+ variant: {
+ type: String,
+ required: false,
+ default: 'secondary',
+ },
},
data() {
@@ -53,6 +58,7 @@ export default {
:menu-class="`dropdown-menu-selectable ${menuClass}`"
split
:text="dropdownToggleText"
+ :variant="variant"
v-bind="$attrs"
@click="triggerEvent"
>
diff --git a/app/finders/groups_finder.rb b/app/finders/groups_finder.rb
index 7d419103b1c..54715557399 100644
--- a/app/finders/groups_finder.rb
+++ b/app/finders/groups_finder.rb
@@ -45,7 +45,7 @@ class GroupsFinder < UnionFinder
def all_groups
return [owned_groups] if params[:owned]
return [groups_with_min_access_level] if min_access_level?
- return [Group.all] if current_user&.full_private_access? && all_available?
+ return [Group.all] if current_user&.can_read_all_resources? && all_available?
groups = []
groups << Gitlab::ObjectHierarchy.new(groups_for_ancestors, groups_for_descendants).all_objects if current_user
diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb
index 74e89a1e66c..641b4422db9 100644
--- a/app/finders/issues_finder.rb
+++ b/app/finders/issues_finder.rb
@@ -127,7 +127,7 @@ class IssuesFinder < IssuableFinder
return @user_can_see_all_confidential_issues if defined?(@user_can_see_all_confidential_issues)
return @user_can_see_all_confidential_issues = false if current_user.blank?
- return @user_can_see_all_confidential_issues = true if current_user.full_private_access?
+ return @user_can_see_all_confidential_issues = true if current_user.can_read_all_resources?
@user_can_see_all_confidential_issues =
if project? && project
diff --git a/app/finders/keys_finder.rb b/app/finders/keys_finder.rb
index d6ba7cb290d..6fd914c88cd 100644
--- a/app/finders/keys_finder.rb
+++ b/app/finders/keys_finder.rb
@@ -15,15 +15,43 @@ class KeysFinder
def execute
raise GitLabAccessDeniedError unless current_user.admin?
- raise InvalidFingerprint unless valid_fingerprint_param?
- Key.where(fingerprint_query).first # rubocop: disable CodeReuse/ActiveRecord
+ keys = by_key_type
+ keys = by_user(keys)
+ keys = sort(keys)
+
+ by_fingerprint(keys)
end
private
attr_reader :current_user, :params
+ def by_key_type
+ if params[:key_type] == 'ssh'
+ Key.regular_keys
+ else
+ Key.all
+ end
+ end
+
+ def sort(keys)
+ keys.order_last_used_at_desc
+ end
+
+ def by_user(keys)
+ return keys unless params[:user]
+
+ keys.for_user(params[:user])
+ end
+
+ def by_fingerprint(keys)
+ return keys unless params[:fingerprint].present?
+ raise InvalidFingerprint unless valid_fingerprint_param?
+
+ keys.where(fingerprint_query).first # rubocop: disable CodeReuse/ActiveRecord
+ end
+
def valid_fingerprint_param?
if fingerprint_type == "sha256"
Base64.decode64(fingerprint).length == 32
diff --git a/app/finders/merge_request_target_project_finder.rb b/app/finders/merge_request_target_project_finder.rb
index 5f0589f6c8b..85a73e0c6ff 100644
--- a/app/finders/merge_request_target_project_finder.rb
+++ b/app/finders/merge_request_target_project_finder.rb
@@ -11,15 +11,23 @@ class MergeRequestTargetProjectFinder
end
# rubocop: disable CodeReuse/ActiveRecord
- def execute
- if @source_project.fork_network
- @source_project.fork_network.projects
- .public_or_visible_to_user(current_user)
- .non_archived
- .with_feature_available_for_user(:merge_requests, current_user)
+ def execute(include_routes: false)
+ if source_project.fork_network
+ include_routes ? projects.inc_routes : projects
else
Project.where(id: source_project)
end
end
# rubocop: enable CodeReuse/ActiveRecord
+
+ private
+
+ def projects
+ source_project
+ .fork_network
+ .projects
+ .public_or_visible_to_user(current_user)
+ .non_archived
+ .with_feature_available_for_user(:merge_requests, current_user)
+ end
end
diff --git a/app/finders/personal_access_tokens_finder.rb b/app/finders/personal_access_tokens_finder.rb
index bd95dcd323f..7b15a3b0c10 100644
--- a/app/finders/personal_access_tokens_finder.rb
+++ b/app/finders/personal_access_tokens_finder.rb
@@ -13,18 +13,26 @@ class PersonalAccessTokensFinder
tokens = PersonalAccessToken.all
tokens = by_user(tokens)
tokens = by_impersonation(tokens)
- by_state(tokens)
+ tokens = by_state(tokens)
+
+ sort(tokens)
end
private
- # rubocop: disable CodeReuse/ActiveRecord
def by_user(tokens)
return tokens unless @params[:user]
- tokens.where(user: @params[:user])
+ tokens.for_user(@params[:user])
+ end
+
+ def sort(tokens)
+ available_sort_orders = PersonalAccessToken.simple_sorts.keys
+
+ return tokens unless available_sort_orders.include?(params[:sort])
+
+ tokens.order_by(params[:sort])
end
- # rubocop: enable CodeReuse/ActiveRecord
def by_impersonation(tokens)
case @params[:impersonation]
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index 46db3b78fd0..7940ec1162b 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -88,7 +88,7 @@ module MergeRequestsHelper
def target_projects(project)
MergeRequestTargetProjectFinder.new(current_user: current_user, source_project: project)
- .execute
+ .execute(include_routes: true)
end
def merge_request_button_visibility(merge_request, closed)
diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb
index 195e4154c67..e87bb27cf62 100644
--- a/app/helpers/users_helper.rb
+++ b/app/helpers/users_helper.rb
@@ -44,6 +44,14 @@ module UsersHelper
current_user_menu_items.include?(item)
end
+ # Used to preload when you are rendering many projects and checking access
+ #
+ # rubocop: disable CodeReuse/ActiveRecord: `projects` can be array which also responds to pluck
+ def load_max_project_member_accesses(projects)
+ current_user&.max_member_access_for_project_ids(projects.pluck(:id))
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
def max_project_member_access(project)
current_user&.max_member_access_for_project(project.id) || Gitlab::Access::NO_ACCESS
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index b6c71f81a49..ba87369f30b 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -887,7 +887,7 @@ module Ci
def each_report(report_types)
job_artifacts_for_types(report_types).each do |report_artifact|
report_artifact.each_blob do |blob|
- yield report_artifact.file_type, blob
+ yield report_artifact.file_type, blob, report_artifact
end
end
end
diff --git a/app/models/issue.rb b/app/models/issue.rb
index f16fd8e63ec..88df3baa809 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -242,7 +242,7 @@ class Issue < ApplicationRecord
return false unless readable_by?(user)
- user.full_private_access? ||
+ user.can_read_all_resources? ||
::Gitlab::ExternalAuthorization.access_allowed?(
user, project.external_authorization_classification_label)
end
diff --git a/app/models/key.rb b/app/models/key.rb
index f66aa4fb329..e549c59b58f 100644
--- a/app/models/key.rb
+++ b/app/models/key.rb
@@ -39,6 +39,10 @@ class Key < ApplicationRecord
alias_attribute :fingerprint_md5, :fingerprint
+ scope :preload_users, -> { preload(:user) }
+ scope :for_user, -> (user) { where(user: user) }
+ scope :order_last_used_at_desc, -> { reorder(::Gitlab::Database.nulls_last_order('last_used_at', 'DESC')) }
+
def self.regular_keys
where(type: ['Key', nil])
end
diff --git a/app/models/personal_access_token.rb b/app/models/personal_access_token.rb
index 9ccc90fb74d..af079f7ebc4 100644
--- a/app/models/personal_access_token.rb
+++ b/app/models/personal_access_token.rb
@@ -3,6 +3,7 @@
class PersonalAccessToken < ApplicationRecord
include Expirable
include TokenAuthenticatable
+ include Sortable
add_authentication_token_field :token, digest: true
@@ -20,6 +21,8 @@ class PersonalAccessToken < ApplicationRecord
scope :inactive, -> { where("revoked = true OR expires_at < NOW()") }
scope :with_impersonation, -> { where(impersonation: true) }
scope :without_impersonation, -> { where(impersonation: false) }
+ scope :for_user, -> (user) { where(user: user) }
+ scope :preload_users, -> { preload(:user) }
validates :scopes, presence: true
validate :validate_scopes
diff --git a/app/models/project.rb b/app/models/project.rb
index 5ed47032dab..bc8757ea888 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -408,6 +408,7 @@ class Project < ApplicationRecord
scope :for_milestones, ->(ids) { joins(:milestones).where('milestones.id' => ids).distinct }
scope :with_push, -> { joins(:events).where('events.action = ?', Event::PUSHED) }
scope :with_project_feature, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id') }
+ scope :inc_routes, -> { includes(:route, namespace: :route) }
scope :with_statistics, -> { includes(:statistics) }
scope :with_service, ->(service) { joins(service).eager_load(service) }
scope :with_shared_runners, -> { where(shared_runners_enabled: true) }
diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb
index caa65d32c86..4973c7761c1 100644
--- a/app/models/project_feature.rb
+++ b/app/models/project_feature.rb
@@ -186,7 +186,7 @@ class ProjectFeature < ApplicationRecord
def team_access?(user, feature)
return unless user
- return true if user.full_private_access?
+ return true if user.can_read_all_resources?
project.team.member?(user, ProjectFeature.required_minimum_access_level(feature))
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 441ad1e70be..18bf5ceaa0e 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1473,9 +1473,7 @@ class User < ApplicationRecord
self.admin = (new_level == 'admin')
end
- # Does the user have access to all private groups & projects?
- # Overridden in EE to also check auditor?
- def full_private_access?
+ def can_read_all_resources?
can?(:read_all_resources)
end
diff --git a/app/policies/base_policy.rb b/app/policies/base_policy.rb
index 8f5c6957a20..3a16f7dc239 100644
--- a/app/policies/base_policy.rb
+++ b/app/policies/base_policy.rb
@@ -40,6 +40,7 @@ class BasePolicy < DeclarativePolicy::Base
prevent :read_cross_project
end
+ # Policy extended in EE to also enable auditors
rule { admin }.enable :read_all_resources
rule { default }.enable :read_cross_project
diff --git a/app/views/clusters/clusters/_advanced_settings.html.haml b/app/views/clusters/clusters/_advanced_settings.html.haml
index 5e34b457231..77f7c478ffa 100644
--- a/app/views/clusters/clusters/_advanced_settings.html.haml
+++ b/app/views/clusters/clusters/_advanced_settings.html.haml
@@ -41,4 +41,5 @@
= s_('ClusterIntegration|Remove Kubernetes cluster integration')
%p
= s_("ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster.")
- = link_to(s_('ClusterIntegration|Remove integration'), clusterable.cluster_path(@cluster), method: :delete, class: 'btn btn-danger', data: { confirm: s_("ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster.")})
+
+ #js-cluster-remove-actions{ data: { cluster_path: clusterable.cluster_path(@cluster), cluster_name: @cluster.name } }
diff --git a/app/views/layouts/nav/sidebar/_admin.html.haml b/app/views/layouts/nav/sidebar/_admin.html.haml
index 5bb92c8d1cf..71fef5df5bc 100644
--- a/app/views/layouts/nav/sidebar/_admin.html.haml
+++ b/app/views/layouts/nav/sidebar/_admin.html.haml
@@ -182,6 +182,8 @@
%strong.fly-out-top-item-name
= _('Deploy Keys')
+ = render_if_exists 'layouts/nav/sidebar/credentials_link'
+
= nav_link(controller: :services) do
= link_to admin_application_settings_services_path do
.nav-icon-container
diff --git a/app/views/shared/projects/_list.html.haml b/app/views/shared/projects/_list.html.haml
index 59b4facdbe5..fab7ee9d763 100644
--- a/app/views/shared/projects/_list.html.haml
+++ b/app/views/shared/projects/_list.html.haml
@@ -35,6 +35,7 @@
.js-projects-list-holder{ data: { qa_selector: 'projects_list' } }
- if any_projects?(projects)
- load_pipeline_status(projects) if pipeline_status
+ - load_max_project_member_accesses(projects) # Prime cache used in shared/projects/project view rendered below
%ul.projects-list{ class: css_classes }
- projects.each_with_index do |project, i|
- css_class = (i >= projects_limit) || project.pending_delete? ? 'hide' : nil