diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-05-13 09:08:37 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-05-13 09:08:37 +0000 |
commit | 0e65189f85bb393e16e60335a42933beb0834295 (patch) | |
tree | ce5160a3dd1ec3c06999d847783f5372c7b312fb /app | |
parent | 2c1525618498a2aab2eed6a36f5045ce3f93ac6f (diff) | |
download | gitlab-ce-0e65189f85bb393e16e60335a42933beb0834295.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
22 files changed, 280 insertions, 27 deletions
diff --git a/app/assets/javascripts/alert_management/components/alert_details.vue b/app/assets/javascripts/alert_management/components/alert_details.vue index cee1988e3ec..d73e7892f54 100644 --- a/app/assets/javascripts/alert_management/components/alert_details.vue +++ b/app/assets/javascripts/alert_management/components/alert_details.vue @@ -1,5 +1,7 @@ <script> +import * as Sentry from '@sentry/browser'; import { + GlAlert, GlLoadingIcon, GlNewDropdown, GlNewDropdownItem, @@ -19,10 +21,14 @@ export default { resolved: s__('AlertManagement|Resolved'), }, i18n: { + errorMsg: s__( + 'AlertManagement|There was an error displaying the alert. Please refresh the page to try again.', + ), fullAlertDetailsTitle: s__('AlertManagement|Full Alert Details'), overviewTitle: s__('AlertManagement|Overview'), }, components: { + GlAlert, GlLoadingIcon, GlNewDropdown, GlNewDropdownItem, @@ -58,20 +64,35 @@ export default { update(data) { return data?.project?.alertManagementAlerts?.nodes?.[0] ?? null; }, + error(error) { + this.errored = true; + Sentry.captureException(error); + }, }, }, data() { - return { alert: null }; + return { alert: null, errored: false, isErrorDismissed: false }; }, computed: { loading() { return this.$apollo.queries.alert.loading; }, + showErrorMsg() { + return this.errored && !this.isErrorDismissed; + }, + }, + methods: { + dismissError() { + this.isErrorDismissed = true; + }, }, }; </script> <template> <div> + <gl-alert v-if="showErrorMsg" variant="danger" @dismiss="dismissError"> + {{ $options.i18n.errorMsg }} + </gl-alert> <div v-if="loading"><gl-loading-icon size="lg" class="mt-3" /></div> <div v-if="alert" diff --git a/app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue b/app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue index 8f5acd4a0a0..f6ade0867cd 100644 --- a/app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue +++ b/app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue @@ -46,6 +46,7 @@ export default { 'isGroup', 'maskableRegex', 'selectedEnvironment', + 'isProtectedByDefault', ]), canSubmit() { return ( @@ -123,6 +124,7 @@ export default { 'addWildCardScope', 'resetSelectedEnvironment', 'setSelectedEnvironment', + 'setVariableProtected', ]), deleteVarAndClose() { this.deleteVariable(this.variableBeingEdited); @@ -147,6 +149,11 @@ export default { } this.hideModal(); }, + setVariableProtectedByDefault() { + if (this.isProtectedByDefault && !this.variableBeingEdited) { + this.setVariableProtected(); + } + }, }, }; </script> @@ -159,6 +166,7 @@ export default { static lazy @hidden="resetModalHandler" + @shown="setVariableProtectedByDefault" > <form> <ci-key-field diff --git a/app/assets/javascripts/ci_variable_list/index.js b/app/assets/javascripts/ci_variable_list/index.js index 58501b216c1..2b4a56a4e6d 100644 --- a/app/assets/javascripts/ci_variable_list/index.js +++ b/app/assets/javascripts/ci_variable_list/index.js @@ -5,14 +5,16 @@ import { parseBoolean } from '~/lib/utils/common_utils'; export default () => { const el = document.getElementById('js-ci-project-variables'); - const { endpoint, projectId, group, maskableRegex } = el.dataset; + const { endpoint, projectId, group, maskableRegex, protectedByDefault } = el.dataset; const isGroup = parseBoolean(group); + const isProtectedByDefault = parseBoolean(protectedByDefault); const store = createStore({ endpoint, projectId, isGroup, maskableRegex, + isProtectedByDefault, }); return new Vue({ diff --git a/app/assets/javascripts/ci_variable_list/store/actions.js b/app/assets/javascripts/ci_variable_list/store/actions.js index a22fa03e16d..d9129c919f8 100644 --- a/app/assets/javascripts/ci_variable_list/store/actions.js +++ b/app/assets/javascripts/ci_variable_list/store/actions.js @@ -20,6 +20,10 @@ export const resetEditing = ({ commit, dispatch }) => { commit(types.RESET_EDITING); }; +export const setVariableProtected = ({ commit }) => { + commit(types.SET_VARIABLE_PROTECTED); +}; + export const requestAddVariable = ({ commit }) => { commit(types.REQUEST_ADD_VARIABLE); }; diff --git a/app/assets/javascripts/ci_variable_list/store/mutation_types.js b/app/assets/javascripts/ci_variable_list/store/mutation_types.js index 0b41c20bce7..ccf8fbd3cb5 100644 --- a/app/assets/javascripts/ci_variable_list/store/mutation_types.js +++ b/app/assets/javascripts/ci_variable_list/store/mutation_types.js @@ -2,6 +2,7 @@ export const TOGGLE_VALUES = 'TOGGLE_VALUES'; export const VARIABLE_BEING_EDITED = 'VARIABLE_BEING_EDITED'; export const RESET_EDITING = 'RESET_EDITING'; export const CLEAR_MODAL = 'CLEAR_MODAL'; +export const SET_VARIABLE_PROTECTED = 'SET_VARIABLE_PROTECTED'; export const REQUEST_VARIABLES = 'REQUEST_VARIABLES'; export const RECEIVE_VARIABLES_SUCCESS = 'RECEIVE_VARIABLES_SUCCESS'; diff --git a/app/assets/javascripts/ci_variable_list/store/mutations.js b/app/assets/javascripts/ci_variable_list/store/mutations.js index 7ee7d7bdc26..7d9cd0dd727 100644 --- a/app/assets/javascripts/ci_variable_list/store/mutations.js +++ b/app/assets/javascripts/ci_variable_list/store/mutations.js @@ -104,4 +104,8 @@ export default { [types.SET_SELECTED_ENVIRONMENT](state, environment) { state.selectedEnvironment = environment; }, + + [types.SET_VARIABLE_PROTECTED](state) { + state.variable.protected = true; + }, }; diff --git a/app/assets/javascripts/ci_variable_list/store/state.js b/app/assets/javascripts/ci_variable_list/store/state.js index 8c0b9c6966f..2fffd115589 100644 --- a/app/assets/javascripts/ci_variable_list/store/state.js +++ b/app/assets/javascripts/ci_variable_list/store/state.js @@ -5,6 +5,7 @@ export default () => ({ projectId: null, isGroup: null, maskableRegex: null, + isProtectedByDefault: null, isLoading: false, isDeleting: false, variable: { diff --git a/app/assets/javascripts/helpers/event_hub_factory.js b/app/assets/javascripts/helpers/event_hub_factory.js index 4bd390c3535..4d7f7550a94 100644 --- a/app/assets/javascripts/helpers/event_hub_factory.js +++ b/app/assets/javascripts/helpers/event_hub_factory.js @@ -3,7 +3,16 @@ import mitt from 'mitt'; export default () => { const emitter = mitt(); + emitter.once = (event, handler) => { + const wrappedHandler = evt => { + handler(evt); + emitter.off(event, wrappedHandler); + }; + emitter.on(event, wrappedHandler); + }; + emitter.$on = emitter.on; + emitter.$once = emitter.once; emitter.$off = emitter.off; emitter.$emit = emitter.emit; diff --git a/app/assets/javascripts/issuables_list/components/issuable_list_root_app.vue b/app/assets/javascripts/issuables_list/components/issuable_list_root_app.vue new file mode 100644 index 00000000000..a8adf69661d --- /dev/null +++ b/app/assets/javascripts/issuables_list/components/issuable_list_root_app.vue @@ -0,0 +1,63 @@ +<script> +import { GlAlert } from '@gitlab/ui'; +import getJiraImportDetailsQuery from '~/jira_import/queries/get_jira_import_details.query.graphql'; +import { isInProgress } from '~/jira_import/utils'; + +export default { + name: 'IssuableListRoot', + components: { + GlAlert, + }, + props: { + canEdit: { + type: Boolean, + required: true, + }, + isJiraConfigured: { + type: Boolean, + required: true, + }, + projectPath: { + type: String, + required: true, + }, + }, + data() { + return { + isAlertShowing: true, + }; + }, + apollo: { + jiraImport: { + query: getJiraImportDetailsQuery, + variables() { + return { + fullPath: this.projectPath, + }; + }, + update: ({ project }) => ({ + isInProgress: isInProgress(project.jiraImportStatus), + }), + skip() { + return !this.isJiraConfigured || !this.canEdit; + }, + }, + }, + computed: { + shouldShowAlert() { + return this.isAlertShowing && this.jiraImport?.isInProgress; + }, + }, + methods: { + hideAlert() { + this.isAlertShowing = false; + }, + }, +}; +</script> + +<template> + <gl-alert v-if="shouldShowAlert" @dismiss="hideAlert"> + {{ __('Import in progress. Refresh page to see newly added issues.') }} + </gl-alert> +</template> diff --git a/app/assets/javascripts/issuables_list/index.js b/app/assets/javascripts/issuables_list/index.js index 9fc7fa837ff..53df83edf2b 100644 --- a/app/assets/javascripts/issuables_list/index.js +++ b/app/assets/javascripts/issuables_list/index.js @@ -1,24 +1,62 @@ import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import createDefaultClient from '~/lib/graphql'; +import { parseBoolean } from '~/lib/utils/common_utils'; +import IssuableListRootApp from './components/issuable_list_root_app.vue'; import IssuablesListApp from './components/issuables_list_app.vue'; -export default function initIssuablesList() { - if (!gon.features || !gon.features.vueIssuablesList) { +function mountIssuableListRootApp() { + const el = document.querySelector('.js-projects-issues-root'); + + if (!el) { + return false; + } + + Vue.use(VueApollo); + + const defaultClient = createDefaultClient(); + const apolloProvider = new VueApollo({ + defaultClient, + }); + + return new Vue({ + el, + apolloProvider, + render(createComponent) { + return createComponent(IssuableListRootApp, { + props: { + canEdit: parseBoolean(el.dataset.canEdit), + isJiraConfigured: parseBoolean(el.dataset.isJiraConfigured), + projectPath: el.dataset.projectPath, + }, + }); + }, + }); +} + +function mountIssuablesListApp() { + if (!gon.features?.vueIssuablesList) { return; } document.querySelectorAll('.js-issuables-list').forEach(el => { const { canBulkEdit, ...data } = el.dataset; - const props = { - ...data, - canBulkEdit: Boolean(canBulkEdit), - }; - return new Vue({ el, render(createElement) { - return createElement(IssuablesListApp, { props }); + return createElement(IssuablesListApp, { + props: { + ...data, + canBulkEdit: Boolean(canBulkEdit), + }, + }); }, }); }); } + +export default function initIssuablesList() { + mountIssuableListRootApp(); + mountIssuablesListApp(); +} diff --git a/app/assets/javascripts/issue_show/components/app.vue b/app/assets/javascripts/issue_show/components/app.vue index 6edcb974670..8cf2cda64a4 100644 --- a/app/assets/javascripts/issue_show/components/app.vue +++ b/app/assets/javascripts/issue_show/components/app.vue @@ -295,7 +295,7 @@ export default { .then(res => res.data) .then(data => this.checkForSpam(data)) .then(data => { - if (window.location.pathname !== data.web_url) { + if (!window.location.pathname.includes(data.web_url)) { visitUrl(data.web_url); } }) diff --git a/app/assets/javascripts/pages/projects/issues/index/index.js b/app/assets/javascripts/pages/projects/issues/index/index.js index bf54ca972b2..e8e0cda2139 100644 --- a/app/assets/javascripts/pages/projects/issues/index/index.js +++ b/app/assets/javascripts/pages/projects/issues/index/index.js @@ -7,6 +7,7 @@ import UsersSelect from '~/users_select'; import initFilteredSearch from '~/pages/search/init_filtered_search'; import { FILTERED_SEARCH } from '~/pages/constants'; import { ISSUABLE_INDEX } from '~/pages/projects/constants'; +import initIssuablesList from '~/issuables_list'; import initManualOrdering from '~/manual_ordering'; document.addEventListener('DOMContentLoaded', () => { @@ -16,9 +17,11 @@ document.addEventListener('DOMContentLoaded', () => { page: FILTERED_SEARCH.ISSUES, filteredSearchTokenKeys: IssuableFilteredSearchTokenKeys, }); - new IssuableIndex(ISSUABLE_INDEX.ISSUE); + new IssuableIndex(ISSUABLE_INDEX.ISSUE); new ShortcutsNavigation(); new UsersSelect(); + initManualOrdering(); + initIssuablesList(); }); diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index ae7965a2eae..398d46316f7 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -14,7 +14,13 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo skip_before_action :merge_request, only: [:index, :bulk_update] before_action :whitelist_query_limiting, only: [:assign_related_issues, :update] before_action :authorize_update_issuable!, only: [:close, :edit, :update, :remove_wip, :sort] - before_action :authorize_read_actual_head_pipeline!, only: [:test_reports, :exposed_artifacts, :coverage_reports, :terraform_reports] + before_action :authorize_read_actual_head_pipeline!, only: [ + :test_reports, + :exposed_artifacts, + :coverage_reports, + :terraform_reports, + :accessibility_reports + ] before_action :set_issuables_index, only: [:index] before_action :authenticate_user!, only: [:assign_related_issues] before_action :check_user_can_push_to_source_branch!, only: [:rebase] @@ -136,6 +142,14 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo reports_response(@merge_request.compare_test_reports) end + def accessibility_reports + if @merge_request.has_accessibility_reports? + reports_response(@merge_request.compare_accessibility_reports) + else + head :no_content + end + end + def coverage_reports if @merge_request.has_coverage_reports? reports_response(@merge_request.find_coverage_reports) diff --git a/app/graphql/types/snippets/blob_type.rb b/app/graphql/types/snippets/blob_type.rb index e4300dcb367..dcde1e5a73b 100644 --- a/app/graphql/types/snippets/blob_type.rb +++ b/app/graphql/types/snippets/blob_type.rb @@ -49,6 +49,15 @@ module Types field :mode, type: GraphQL::STRING_TYPE, description: 'Blob mode', null: true + + field :external_storage, type: GraphQL::STRING_TYPE, + description: 'Blob external storage', + null: true + + field :rendered_as_text, type: GraphQL::BOOLEAN_TYPE, + description: 'Shows whether the blob is rendered as text', + method: :rendered_as_text?, + null: false end # rubocop: enable Graphql/AuthorizeTypes end diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index ab8142746ed..e93aeba6dfd 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -4,12 +4,14 @@ module EventsHelper ICON_NAMES_BY_EVENT_TYPE = { 'pushed to' => 'commit', 'pushed new' => 'commit', + 'updated' => 'commit', 'created' => 'status_open', 'opened' => 'status_open', 'closed' => 'status_closed', 'accepted' => 'fork', 'commented on' => 'comment', 'deleted' => 'remove', + 'destroyed' => 'remove', 'imported' => 'import', 'joined' => 'users' }.freeze diff --git a/app/models/project.rb b/app/models/project.rb index d8ccea3e37f..a16bf3c9fb0 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -525,12 +525,14 @@ class Project < ApplicationRecord def self.public_or_visible_to_user(user = nil, min_access_level = nil) min_access_level = nil if user&.admin? - if user + return public_to_user unless user + + if user.is_a?(DeployToken) + user.projects + else where('EXISTS (?) OR projects.visibility_level IN (?)', user.authorizations_for_projects(min_access_level: min_access_level), Gitlab::VisibilityLevel.levels_for_user(user)) - else - public_to_user end end diff --git a/app/models/user.rb b/app/models/user.rb index 942bcf75a88..365e3cd713d 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -249,15 +249,12 @@ class User < ApplicationRecord enum layout: { fixed: 0, fluid: 1 } # User's Dashboard preference - # Note: When adding an option, it MUST go on the end of the array. enum dashboard: { projects: 0, stars: 1, project_activity: 2, starred_project_activity: 3, groups: 4, todos: 5, issues: 6, merge_requests: 7, operations: 8 } # User's Project preference - # Note: When adding an option, it MUST go on the end of the array. enum project_view: { readme: 0, activity: 1, files: 2 } # User's role - # Note: When adding an option, it MUST go on the end of the array. enum role: { software_developer: 0, development_team_lead: 1, devops_engineer: 2, systems_administrator: 3, security_analyst: 4, data_analyst: 5, product_manager: 6, product_designer: 7, other: 8 }, _suffix: true delegate :path, to: :namespace, allow_nil: true, prefix: true diff --git a/app/policies/concerns/policy_actor.rb b/app/policies/concerns/policy_actor.rb index 406677d7b56..f910e04d015 100644 --- a/app/policies/concerns/policy_actor.rb +++ b/app/policies/concerns/policy_actor.rb @@ -1,8 +1,15 @@ # frozen_string_literal: true -# Include this module if we want to pass something else than the user to -# check policies. This defines several methods which the policy checker -# would call and check. +# Include this module to have an object respond to user messages without being +# a user. +# +# Use Case 1: +# Pass something else than the user to check policies. This defines several +# methods which the policy checker would call and check. +# +# Use Case 2: +# Access the API with non-user object such as deploy tokens. This defines +# several methods which the API auth flow would call. module PolicyActor extend ActiveSupport::Concern @@ -37,6 +44,30 @@ module PolicyActor def alert_bot? false end + + def deactivated? + false + end + + def confirmation_required_on_sign_in? + false + end + + def can?(action, subject = :global) + Ability.allowed?(self, action, subject) + end + + def preferred_language + nil + end + + def requires_ldap_check? + false + end + + def try_obtain_ldap_lease + nil + end end PolicyActor.prepend_if_ee('EE::PolicyActor') diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index a5813124bb1..1529ff07005 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -84,6 +84,16 @@ class ProjectPolicy < BasePolicy project.merge_requests_allowing_push_to_user(user).any? end + desc "Deploy token with read_package_registry scope" + condition(:read_package_registry_deploy_token) do + user.is_a?(DeployToken) && user.has_access_to?(project) && user.read_package_registry + end + + desc "Deploy token with write_package_registry scope" + condition(:write_package_registry_deploy_token) do + user.is_a?(DeployToken) && user.has_access_to?(project) && user.write_package_registry + end + with_scope :subject condition(:forking_allowed) do @subject.feature_available?(:forking, @user) @@ -532,6 +542,16 @@ class ProjectPolicy < BasePolicy prevent :destroy_design end + rule { read_package_registry_deploy_token }.policy do + enable :read_package + enable :read_project + end + + rule { write_package_registry_deploy_token }.policy do + enable :create_package + enable :read_project + end + private def team_member? diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb index 4a699fe3213..d84b687937f 100644 --- a/app/services/auth/container_registry_authentication_service.rb +++ b/app/services/auth/container_registry_authentication_service.rb @@ -103,17 +103,19 @@ module Auth return unless requested_project - actions = actions.select do |action| + authorized_actions = actions.select do |action| can_access?(requested_project, action) end - return unless actions.present? + log_if_actions_denied(type, requested_project, actions, authorized_actions) + + return unless authorized_actions.present? # At this point user/build is already authenticated. # - ensure_container_repository!(path, actions) + ensure_container_repository!(path, authorized_actions) - { type: type, name: path.to_s, actions: actions } + { type: type, name: path.to_s, actions: authorized_actions } end ## @@ -222,5 +224,22 @@ module Auth REGISTRY_LOGIN_ABILITIES.include?(ability) end end + + def log_if_actions_denied(type, requested_project, requested_actions, authorized_actions) + return if requested_actions == authorized_actions + + log_info = { + message: "Denied container registry permissions", + scope_type: type, + requested_project_path: requested_project.full_path, + requested_actions: requested_actions, + authorized_actions: authorized_actions, + username: current_user&.username, + user_id: current_user&.id, + project_path: project&.full_path + }.compact + + Gitlab::AuthLogger.warn(log_info) + end end end diff --git a/app/views/ci/variables/_index.html.haml b/app/views/ci/variables/_index.html.haml index 4d8df4cc12a..26051261715 100644 --- a/app/views/ci/variables/_index.html.haml +++ b/app/views/ci/variables/_index.html.haml @@ -8,7 +8,7 @@ - if Feature.enabled?(:new_variables_ui, @project || @group, default_enabled: true) - is_group = !@group.nil? - #js-ci-project-variables{ data: { endpoint: save_endpoint, project_id: @project&.id || '', group: is_group.to_s, maskable_regex: ci_variable_maskable_regex} } + #js-ci-project-variables{ data: { endpoint: save_endpoint, project_id: @project&.id || '', group: is_group.to_s, maskable_regex: ci_variable_maskable_regex, protected_by_default: ci_variable_protected_by_default?.to_s} } - else .row diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml index 2633a3899f7..be41afa66c7 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -6,6 +6,11 @@ = content_for :meta_tags do = auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{@project.name} issues") +- if @project.jira_issues_import_feature_flag_enabled? + .js-projects-issues-root{ data: { can_edit: can?(current_user, :admin_project, @project).to_s, + is_jira_configured: @project.jira_service.present?.to_s, + project_path: @project.full_path } } + - if project_issues(@project).exists? .top-area = render 'shared/issuable/nav', type: :issues |