diff --git a/CHANGELOG.md b/CHANGELOG.md index feda5e0835b26f59e7c530433ef92b918b94e01f..8a4a6c9ff13dd89f80e2958d48b81e9ddaf7ef67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -486,6 +486,33 @@ entry. - Update url placeholder for the sentry configuration page. !24338 +## 11.6.10 (2019-02-28) + +### Security (21 changes) + +- Stop linking to unrecognized package sources. !55518 +- Check snippet attached file to be moved is within designated directory. +- Fix potential Addressable::URI::InvalidURIError. +- Do not display impersonated sessions under active sessions and remove ability to revoke session. +- Display only information visible to current user on the Milestone page. +- Show only merge requests visible to user on milestone detail page. +- Disable issue boards API when issues are disabled. +- Don't show new issue link after move when a user does not have permissions. +- Fix git clone revealing private repo's presence. +- Fix blind SSRF in Prometheus integration by checking URL before querying. +- Check if desired milestone for an issue is available. +- Don't allow non-members to see private related MRs. +- Fix arbitrary file read via diffs during import. +- Display the correct number of MRs a user has access to. +- Forbid creating discussions for users with restricted access. +- Do not disclose milestone titles for unauthorized users. +- Validate session key when authorizing with GCP to create a cluster. +- Block local URLs for Kubernetes integration. +- Limit mermaid rendering to 5K characters. +- Remove the possibility to share a project with a group that a user is not a member of. +- Fix leaking private repository information in API. + + ## 11.6.8 (2019-01-30) - No changes. diff --git a/Gemfile b/Gemfile index 11fdff8a094b0ac749f90a46b1bb5a70a6659e31..2d769284f91f9d0789f9b9b31dff4062f13c812c 100644 --- a/Gemfile +++ b/Gemfile @@ -68,7 +68,7 @@ gem 'gpgme', '~> 2.0.18' # LDAP Auth # GitLab fork with several improvements to original library. For full list of changes # see https://github.com/intridea/omniauth-ldap/compare/master...gitlabhq:master -gem 'gitlab_omniauth-ldap', '~> 2.0.4', require: 'omniauth-ldap' +gem 'gitlab_omniauth-ldap', '~> 2.1.1', require: 'omniauth-ldap' gem 'net-ldap' # API diff --git a/Gemfile.lock b/Gemfile.lock index 452546b547d89bfc50482d8cdd2c29e871871de0..e4791a98f2fe436c941881da381c4c906df229b3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -291,7 +291,7 @@ GEM rubocop (~> 0.54.0) rubocop-gitlab-security (~> 0.1.0) rubocop-rspec (~> 1.19) - gitlab_omniauth-ldap (2.0.4) + gitlab_omniauth-ldap (2.1.1) net-ldap (~> 0.16) omniauth (~> 1.3) pyu-ruby-sasl (>= 0.0.3.3, < 0.1) @@ -1024,7 +1024,7 @@ DEPENDENCIES gitlab-markup (~> 1.6.5) gitlab-sidekiq-fetcher (~> 0.4.0) gitlab-styles (~> 2.4) - gitlab_omniauth-ldap (~> 2.0.4) + gitlab_omniauth-ldap (~> 2.1.1) gon (~> 6.2) google-api-client (~> 0.23) google-protobuf (~> 3.6) diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js index 85eb08cc97d76249f7a628bdccdeff8cd62f7a0e..8754c253881f92e67b2bb660e1ac86ee8ca42221 100644 --- a/app/assets/javascripts/api.js +++ b/app/assets/javascripts/api.js @@ -12,6 +12,7 @@ const Api = { projectsPath: '/api/:version/projects.json', projectPath: '/api/:version/projects/:id', projectLabelsPath: '/:namespace_path/:project_path/labels', + projectMergeRequestsPath: '/api/:version/projects/:id/merge_requests', projectMergeRequestPath: '/api/:version/projects/:id/merge_requests/:mrid', projectMergeRequestChangesPath: '/api/:version/projects/:id/merge_requests/:mrid/changes', projectMergeRequestVersionsPath: '/api/:version/projects/:id/merge_requests/:mrid/versions', @@ -111,6 +112,22 @@ const Api = { return axios.get(url); }, + /** + * Get all Merge Requests for a project, eventually filtering based on + * supplied parameters + * @param projectPath + * @param params + * @returns {Promise} + */ + projectMergeRequests(projectPath, params = {}) { + const url = Api.buildUrl(Api.projectMergeRequestsPath).replace( + ':id', + encodeURIComponent(projectPath), + ); + + return axios.get(url, { params }); + }, + // Return Merge Request for project projectMergeRequest(projectPath, mergeRequestId, params = {}) { const url = Api.buildUrl(Api.projectMergeRequestPath) diff --git a/app/assets/javascripts/behaviors/markdown/render_mermaid.js b/app/assets/javascripts/behaviors/markdown/render_mermaid.js index 35380ca49fb6b4313b64dab6e5072e02190f7e23..798114b4b0bfdce4f78c84060250dea68d36ef69 100644 --- a/app/assets/javascripts/behaviors/markdown/render_mermaid.js +++ b/app/assets/javascripts/behaviors/markdown/render_mermaid.js @@ -1,4 +1,5 @@ import flash from '~/flash'; +import { sprintf, __ } from '../../locale'; // Renders diagrams and flowcharts from text using Mermaid in any element with the // `js-render-mermaid` class. @@ -14,6 +15,9 @@ import flash from '~/flash'; // // +// This is an arbitary number; Can be iterated upon when suitable. +const MAX_CHAR_LIMIT = 5000; + export default function renderMermaid($els) { if (!$els.length) return; @@ -34,6 +38,21 @@ export default function renderMermaid($els) { $els.each((i, el) => { const source = el.textContent; + /** + * Restrict the rendering to a certain amount of character to + * prevent mermaidjs from hanging up the entire thread and + * causing a DoS. + */ + if (source && source.length > MAX_CHAR_LIMIT) { + el.textContent = sprintf( + __( + 'Cannot render the image. Maximum character count (%{charLimit}) has been exceeded.', + ), + { charLimit: MAX_CHAR_LIMIT }, + ); + return; + } + // Remove any extra spans added by the backend syntax highlighting. Object.assign(el, { textContent: source }); diff --git a/app/assets/javascripts/ide/services/index.js b/app/assets/javascripts/ide/services/index.js index 13449592e6244d456c8489af3db7ffb744c0f0a7..ba33b6826d672a8ae31a9de5908c69863cbb7e35 100644 --- a/app/assets/javascripts/ide/services/index.js +++ b/app/assets/javascripts/ide/services/index.js @@ -40,6 +40,9 @@ export default { getProjectData(namespace, project) { return Api.project(`${namespace}/${project}`); }, + getProjectMergeRequests(projectId, params = {}) { + return Api.projectMergeRequests(projectId, params); + }, getProjectMergeRequestData(projectId, mergeRequestId, params = {}) { return Api.projectMergeRequest(projectId, mergeRequestId, params); }, diff --git a/app/assets/javascripts/ide/stores/actions/merge_request.js b/app/assets/javascripts/ide/stores/actions/merge_request.js index 18c243699961805b3a6e379c13ba9e683336b639..362ced248a1325f9f1f65f0a3d77e7124af9b062 100644 --- a/app/assets/javascripts/ide/stores/actions/merge_request.js +++ b/app/assets/javascripts/ide/stores/actions/merge_request.js @@ -4,6 +4,38 @@ import service from '../../services'; import * as types from '../mutation_types'; import { activityBarViews } from '../../constants'; +export const getMergeRequestsForBranch = ({ commit }, { projectId, branchId } = {}) => + service + .getProjectMergeRequests(`${projectId}`, { + source_branch: branchId, + order_by: 'created_at', + per_page: 1, + }) + .then(({ data }) => { + if (data.length > 0) { + const currentMR = data[0]; + + commit(types.SET_MERGE_REQUEST, { + projectPath: projectId, + mergeRequestId: currentMR.iid, + mergeRequest: currentMR, + }); + + commit(types.SET_CURRENT_MERGE_REQUEST, `${currentMR.iid}`); + } + }) + .catch(e => { + flash( + __(`Error fetching merge requests for ${branchId}`), + 'alert', + document, + null, + false, + true, + ); + throw e; + }); + export const getMergeRequestData = ( { commit, dispatch, state }, { projectId, mergeRequestId, targetProjectId = null, force = false } = {}, diff --git a/app/assets/javascripts/ide/stores/actions/project.js b/app/assets/javascripts/ide/stores/actions/project.js index b65f631c99cf54708ccb1a5a8bb210f18dfe10c5..06ed5c0b572cb1d81e1cabefc694e7fc353d7f15 100644 --- a/app/assets/javascripts/ide/stores/actions/project.js +++ b/app/assets/javascripts/ide/stores/actions/project.js @@ -136,17 +136,24 @@ export const openBranch = ({ dispatch, state }, { projectId, branchId, basePath return dispatch('getFiles', { projectId, branchId, - }).then(() => { - if (basePath) { - const path = basePath.slice(-1) === '/' ? basePath.slice(0, -1) : basePath; - const treeEntryKey = Object.keys(state.entries).find( - key => key === path && !state.entries[key].pending, - ); - const treeEntry = state.entries[treeEntryKey]; + }) + .then(() => { + if (basePath) { + const path = basePath.slice(-1) === '/' ? basePath.slice(0, -1) : basePath; + const treeEntryKey = Object.keys(state.entries).find( + key => key === path && !state.entries[key].pending, + ); + const treeEntry = state.entries[treeEntryKey]; - if (treeEntry) { - dispatch('handleTreeEntryAction', treeEntry); + if (treeEntry) { + dispatch('handleTreeEntryAction', treeEntry); + } } - } - }); + }) + .then(() => { + dispatch('getMergeRequestsForBranch', { + projectId, + branchId, + }); + }); }; diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index fda7b6d54672374355f94f25736400524aa55948..ba3b0906e2815a650606319af4bd1a1fa4092863 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -26,16 +26,11 @@ */ @mixin markdown-table { width: auto; - display: inline-block; + display: block; overflow-x: auto; border: 0; border-color: $gl-gray-100; - @supports (width: fit-content) { - display: block; - width: fit-content; - } - tr { th { border-bottom: solid 2px $gl-gray-100; diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 3796ef47c32bcc869656f002f4dcdef374e4438e..623fa485ba6bf540f7958cf8e7ce0b281bdce8c7 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -22,6 +22,7 @@ .detail-page-header, .page-content-header, .commit-box, + .info-well, .commit-ci-menu, .files-changed-inner, .limited-header-width, diff --git a/app/controllers/concerns/milestone_actions.rb b/app/controllers/concerns/milestone_actions.rb index eccbe35577b444c4c18d275e8dcb81d067aedb86..c0c0160a827a4964bd8419d49970051d4c25dcde 100644 --- a/app/controllers/concerns/milestone_actions.rb +++ b/app/controllers/concerns/milestone_actions.rb @@ -8,7 +8,7 @@ module MilestoneActions format.html { redirect_to milestone_redirect_path } format.json do render json: tabs_json("shared/milestones/_merge_requests_tab", { - merge_requests: @milestone.sorted_merge_requests, # rubocop:disable Gitlab/ModuleWithInstanceVariables + merge_requests: @milestone.sorted_merge_requests(current_user), # rubocop:disable Gitlab/ModuleWithInstanceVariables show_project_name: true }) end diff --git a/app/controllers/google_api/authorizations_controller.rb b/app/controllers/google_api/authorizations_controller.rb index dd9f5af61b33e594ffcd33a566797d258323a608..ed0995e7ffdda5e4c827a0cb17c8ead7fb5ceb6a 100644 --- a/app/controllers/google_api/authorizations_controller.rb +++ b/app/controllers/google_api/authorizations_controller.rb @@ -2,6 +2,10 @@ module GoogleApi class AuthorizationsController < ApplicationController + include Gitlab::Utils::StrongMemoize + + before_action :validate_session_key! + def callback token, expires_at = GoogleApi::CloudPlatform::Client .new(nil, callback_google_api_auth_url) @@ -11,21 +15,27 @@ module GoogleApi session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at] = expires_at.to_s - state_redirect_uri = redirect_uri_from_session_key(params[:state]) - - if state_redirect_uri - redirect_to state_redirect_uri - else - redirect_to root_path - end + redirect_to redirect_uri_from_session end private - def redirect_uri_from_session_key(state) - key = GoogleApi::CloudPlatform::Client - .session_key_for_redirect_uri(params[:state]) - session[key] if key + def validate_session_key! + access_denied! unless redirect_uri_from_session.present? + end + + def redirect_uri_from_session + strong_memoize(:redirect_uri_from_session) do + if params[:state].present? + session[session_key_for_redirect_uri(params[:state])] + else + nil + end + end + end + + def session_key_for_redirect_uri(state) + GoogleApi::CloudPlatform::Client.session_key_for_redirect_uri(state) end end end diff --git a/app/controllers/profiles/active_sessions_controller.rb b/app/controllers/profiles/active_sessions_controller.rb index efe7ede5efab2f2da620755300cd1cd4f1b12cbf..c473023cacb588a4d46aa8f23775e95a90aaca67 100644 --- a/app/controllers/profiles/active_sessions_controller.rb +++ b/app/controllers/profiles/active_sessions_controller.rb @@ -2,15 +2,6 @@ class Profiles::ActiveSessionsController < Profiles::ApplicationController def index - @sessions = ActiveSession.list(current_user) - end - - def destroy - ActiveSession.destroy(current_user, params[:id]) - - respond_to do |format| - format.html { redirect_to profile_active_sessions_url, status: :found } - format.js { head :ok } - end + @sessions = ActiveSession.list(current_user).reject(&:is_impersonated) end end diff --git a/app/controllers/projects/autocomplete_sources_controller.rb b/app/controllers/projects/autocomplete_sources_controller.rb index 9c130af83944c2263c8bee1940c5da4cfe145fdd..0e3f13045ce56661b8eabdc371ec2bb1569b3a8e 100644 --- a/app/controllers/projects/autocomplete_sources_controller.rb +++ b/app/controllers/projects/autocomplete_sources_controller.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class Projects::AutocompleteSourcesController < Projects::ApplicationController + before_action :authorize_read_milestone!, only: :milestones + def members render json: ::Projects::ParticipantsService.new(@project, current_user).execute(target) end diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index b13c0ae396797a1e0090ae1d0879b5b211120db2..939a09d4fd27a376d785ed7caff5f030b6ff995d 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -65,7 +65,11 @@ class Projects::CommitController < Projects::ApplicationController # rubocop: enable CodeReuse/ActiveRecord def merge_requests - @merge_requests = @commit.merge_requests.map do |mr| + @merge_requests = MergeRequestsFinder.new( + current_user, + project_id: @project.id, + commit_sha: @commit.sha + ).execute.map do |mr| { iid: mr.iid, path: merge_request_path(mr), title: mr.title } end diff --git a/app/controllers/projects/group_links_controller.rb b/app/controllers/projects/group_links_controller.rb index 7c713c197626e79d5c6174c6bb2d67d8525a5426..bc942ba9288107f862c9fa068f30ea883f176e1a 100644 --- a/app/controllers/projects/group_links_controller.rb +++ b/app/controllers/projects/group_links_controller.rb @@ -13,9 +13,10 @@ class Projects::GroupLinksController < Projects::ApplicationController group = Group.find(params[:link_group_id]) if params[:link_group_id].present? if group - return render_404 unless can?(current_user, :read_group, group) + result = Projects::GroupLinks::CreateService.new(project, current_user, group_link_create_params).execute(group) + return render_404 if result[:http_status] == 404 - Projects::GroupLinks::CreateService.new(project, current_user, group_link_create_params).execute(group) + flash[:alert] = result[:message] if result[:http_status] == 409 else flash[:alert] = 'Please select a group.' end diff --git a/app/finders/merge_requests_finder.rb b/app/finders/merge_requests_finder.rb index b645011a3c55382778663c33605e9e6b5922111b..93bee3f14880b8217a8fa9b1f53d243e5bda7a77 100644 --- a/app/finders/merge_requests_finder.rb +++ b/app/finders/merge_requests_finder.rb @@ -37,13 +37,20 @@ class MergeRequestsFinder < IssuableFinder end def filter_items(_items) - items = by_source_branch(super) + items = by_commit(super) + items = by_source_branch(items) items = by_wip(items) by_target_branch(items) end private + def by_commit(items) + return items unless params[:commit_sha].presence + + items.by_commit_sha(params[:commit_sha]) + end + def source_branch @source_branch ||= params[:source_branch].presence end diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb index 3ef0cc5020c9988696426f3d203560b07b188e04..b96c2f3afb286bfdd6910074221a34f110a53984 100644 --- a/app/graphql/types/project_type.rb +++ b/app/graphql/types/project_type.rb @@ -16,7 +16,6 @@ module Types field :description, GraphQL::STRING_TYPE, null: true - field :default_branch, GraphQL::STRING_TYPE, null: true field :tag_list, GraphQL::STRING_TYPE, null: true field :ssh_url_to_repo, GraphQL::STRING_TYPE, null: true @@ -59,7 +58,6 @@ module Types end field :import_status, GraphQL::STRING_TYPE, null: true - field :ci_config_path, GraphQL::STRING_TYPE, null: true field :only_allow_merge_if_pipeline_succeeds, GraphQL::BOOLEAN_TYPE, null: true field :request_access_enabled, GraphQL::BOOLEAN_TYPE, null: true diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index 923a06a0512f408c71c5cef4df6ffdcdbca0e0ce..355b91a86618c98ea48fc1a86e23a20646aa401d 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -100,17 +100,6 @@ module CiStatusHelper "pipeline-status/#{pipeline_status.sha}-#{pipeline_status.status}" end - def render_project_pipeline_status(pipeline_status, tooltip_placement: 'left') - project = pipeline_status.project - path = pipelines_project_commit_path(project, pipeline_status.sha, ref: pipeline_status.ref) - - render_status_with_link( - 'commit', - pipeline_status.status, - path, - tooltip_placement: tooltip_placement) - end - def render_commit_status(commit, ref: nil, tooltip_placement: 'left') project = commit.project path = pipelines_project_commit_path(project, commit, ref: ref) @@ -123,12 +112,6 @@ module CiStatusHelper icon_size: 24) end - def render_pipeline_status(pipeline, tooltip_placement: 'left') - project = pipeline.project - path = project_pipeline_path(project, pipeline) - render_status_with_link('pipeline', pipeline.status, path, tooltip_placement: tooltip_placement) - end - def render_status_with_link(type, status, path = nil, tooltip_placement: 'left', cssclass: '', container: 'body', icon_size: 16) klass = "ci-status-link ci-status-icon-#{status.dasherize} #{cssclass}" title = "#{type.titleize}: #{ci_label_for_status(status)}" diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb index 654ae211310481eddd648db5c5d37778daeac1b9..d2e334fb856efd36b8a3c1953e3f9b082a2e4082 100644 --- a/app/mailers/emails/issues.rb +++ b/app/mailers/emails/issues.rb @@ -74,6 +74,7 @@ module Emails @new_issue = new_issue @new_project = new_issue.project + @can_access_project = recipient.can?(:read_project, @new_project) mail_answer_thread(issue, issue_thread_options(updated_by_user.id, recipient.id, reason)) end diff --git a/app/mailers/emails/pipelines.rb b/app/mailers/emails/pipelines.rb index 31e183640adc4022db38ae350fe341534d992fc8..fb57c0da34d4a7c5d0c489ce06c67a796f7c35e0 100644 --- a/app/mailers/emails/pipelines.rb +++ b/app/mailers/emails/pipelines.rb @@ -15,7 +15,7 @@ module Emails def pipeline_mail(pipeline, recipients, status) @project = pipeline.project @pipeline = pipeline - @merge_request = pipeline.merge_requests.first + @merge_request = pipeline.merge_requests_as_head_pipeline.first add_headers # We use bcc here because we don't want to generate this emails for a diff --git a/app/models/active_session.rb b/app/models/active_session.rb index 0d9c6a4a1f01a4dac40138763a554f0b63599ffb..1e01f1d17e6ca7fa5c92f08192e97ad35b95c60d 100644 --- a/app/models/active_session.rb +++ b/app/models/active_session.rb @@ -5,7 +5,8 @@ class ActiveSession attr_accessor :created_at, :updated_at, :session_id, :ip_address, - :browser, :os, :device_name, :device_type + :browser, :os, :device_name, :device_type, + :is_impersonated def current?(session) return false if session_id.nil? || session.id.nil? @@ -31,7 +32,8 @@ class ActiveSession device_type: client.device_type, created_at: user.current_sign_in_at || timestamp, updated_at: timestamp, - session_id: session_id + session_id: session_id, + is_impersonated: request.session[:impersonator_id].present? ) redis.pipelined do diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 317f965da1c0c46a47ae4467e75a57958df54405..ca9725f7a0435f85397164db1fbc93206b7cef0c 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -39,7 +39,7 @@ module Ci # Merge requests for which the current pipeline is running against # the merge request's latest commit. - has_many :merge_requests, foreign_key: "head_pipeline_id" + has_many :merge_requests_as_head_pipeline, foreign_key: "head_pipeline_id", class_name: 'MergeRequest' has_many :pending_builds, -> { pending }, foreign_key: :commit_id, class_name: 'Ci::Build' has_many :retryable_builds, -> { latest.failed_or_canceled.includes(:project) }, foreign_key: :commit_id, class_name: 'Ci::Build' diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb index 46d0898014e026eab7eecebcd0603d3d288a8a96..814fc591408bb9ed00845ab41df02048108deed9 100644 --- a/app/models/clusters/platforms/kubernetes.rb +++ b/app/models/clusters/platforms/kubernetes.rb @@ -41,7 +41,7 @@ module Clusters validate :no_namespace, unless: :allow_user_defined_namespace? # We expect to be `active?` only when enabled and cluster is created (the api_url is assigned) - validates :api_url, url: true, presence: true + validates :api_url, public_url: true, presence: true validates :token, presence: true validates :ca_cert, certificate: true, allow_blank: true, if: :ca_cert_changed? diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 670103bc3f39ffa03743e8d12b264f0905065a74..c7ad182ab8281fff27d3a42503f477ad3ed11e05 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -75,6 +75,7 @@ module Issuable validates :author, presence: true validates :title, presence: true, length: { maximum: 255 } + validate :milestone_is_valid scope :authored, ->(user) { where(author_id: user) } scope :recent, -> { reorder(id: :desc) } @@ -118,6 +119,16 @@ module Issuable def has_multiple_assignees? assignees.count > 1 end + + def milestone_available? + project_id == milestone&.project_id || project.ancestors_upto.compact.include?(milestone&.group) + end + + private + + def milestone_is_valid + errors.add(:milestone_id, message: "is invalid") if milestone_id.present? && !milestone_available? + end end class_methods do diff --git a/app/models/concerns/milestoneish.rb b/app/models/concerns/milestoneish.rb index 055ffe046469da8a85b6151ff7f0bf13d0d367ce..e65bbb8ca070981152db19d5e5b80ee5d9b0a234 100644 --- a/app/models/concerns/milestoneish.rb +++ b/app/models/concerns/milestoneish.rb @@ -46,12 +46,31 @@ module Milestoneish end end + def issue_participants_visible_by_user(user) + User.joins(:issue_assignees) + .where('issue_assignees.issue_id' => issues_visible_to_user(user).select(:id)) + .distinct + end + + def issue_labels_visible_by_user(user) + Label.joins(:label_links) + .where('label_links.target_id' => issues_visible_to_user(user).select(:id), 'label_links.target_type' => 'Issue') + .distinct + end + def sorted_issues(user) issues_visible_to_user(user).preload_associations.sort_by_attribute('label_priority') end - def sorted_merge_requests - merge_requests.sort_by_attribute('label_priority') + def sorted_merge_requests(user) + merge_requests_visible_to_user(user).sort_by_attribute('label_priority') + end + + def merge_requests_visible_to_user(user) + memoize_per_user(user, :merge_requests_visible_to_user) do + MergeRequestsFinder.new(user, issues_finder_params) + .execute.where(milestone_id: milestoneish_id) + end end def upcoming? diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 1468ae1c34a1b68426e4d62b04caa846732d4395..b67797f5404f0e781fd52724c1d3d923575e1960 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -71,7 +71,7 @@ class MergeRequest < ActiveRecord::Base serialize :merge_params, Hash # rubocop:disable Cop/ActiveRecordSerialize - after_create :ensure_merge_request_diff, unless: :importing? + after_create :ensure_merge_request_diff after_update :clear_memoized_shas after_update :reload_diff_if_branch_changed after_save :ensure_metrics @@ -189,6 +189,9 @@ class MergeRequest < ActiveRecord::Base after_save :keep_around_commit + alias_attribute :project, :target_project + alias_attribute :project_id, :target_project_id + def self.reference_prefix '!' end @@ -847,10 +850,6 @@ class MergeRequest < ActiveRecord::Base target_project != source_project end - def project - target_project - end - # If the merge request closes any issues, save this information in the # `MergeRequestsClosingIssues` model. This is a performance optimization. # Calculating this information for a number of merge requests requires @@ -1154,35 +1153,16 @@ class MergeRequest < ActiveRecord::Base Gitlab::Ci::Variables::Collection.new.tap do |variables| variables.append(key: 'CI_MERGE_REQUEST_ID', value: id.to_s) variables.append(key: 'CI_MERGE_REQUEST_IID', value: iid.to_s) - - variables.append(key: 'CI_MERGE_REQUEST_REF_PATH', - value: ref_path.to_s) - - variables.append(key: 'CI_MERGE_REQUEST_PROJECT_ID', - value: project.id.to_s) - - variables.append(key: 'CI_MERGE_REQUEST_PROJECT_PATH', - value: project.full_path) - - variables.append(key: 'CI_MERGE_REQUEST_PROJECT_URL', - value: project.web_url) - - variables.append(key: 'CI_MERGE_REQUEST_TARGET_BRANCH_NAME', - value: target_branch.to_s) - - if source_project - variables.append(key: 'CI_MERGE_REQUEST_SOURCE_PROJECT_ID', - value: source_project.id.to_s) - - variables.append(key: 'CI_MERGE_REQUEST_SOURCE_PROJECT_PATH', - value: source_project.full_path) - - variables.append(key: 'CI_MERGE_REQUEST_SOURCE_PROJECT_URL', - value: source_project.web_url) - - variables.append(key: 'CI_MERGE_REQUEST_SOURCE_BRANCH_NAME', - value: source_branch.to_s) - end + variables.append(key: 'CI_MERGE_REQUEST_REF_PATH', value: ref_path.to_s) + variables.append(key: 'CI_MERGE_REQUEST_PROJECT_ID', value: project.id.to_s) + variables.append(key: 'CI_MERGE_REQUEST_PROJECT_PATH', value: project.full_path) + variables.append(key: 'CI_MERGE_REQUEST_PROJECT_URL', value: project.web_url) + variables.append(key: 'CI_MERGE_REQUEST_TARGET_BRANCH_NAME', value: target_branch.to_s) + variables.append(key: 'CI_MERGE_REQUEST_TITLE', value: title) + variables.append(key: 'CI_MERGE_REQUEST_ASSIGNEES', value: assignee.username) if assignee + variables.append(key: 'CI_MERGE_REQUEST_MILESTONE', value: milestone.title) if milestone + variables.append(key: 'CI_MERGE_REQUEST_LABELS', value: label_names.join(',')) if labels.present? + variables.concat(source_project_variables) end end @@ -1389,4 +1369,15 @@ class MergeRequest < ActiveRecord::Base source_project&.ci_pipelines &.latest_for_merge_request(self, source_branch, diff_head_sha) end + + def source_project_variables + Gitlab::Ci::Variables::Collection.new.tap do |variables| + break variables unless source_project + + variables.append(key: 'CI_MERGE_REQUEST_SOURCE_PROJECT_ID', value: source_project.id.to_s) + variables.append(key: 'CI_MERGE_REQUEST_SOURCE_PROJECT_PATH', value: source_project.full_path) + variables.append(key: 'CI_MERGE_REQUEST_SOURCE_PROJECT_URL', value: source_project.web_url) + variables.append(key: 'CI_MERGE_REQUEST_SOURCE_BRANCH_NAME', value: source_branch.to_s) + end + end end diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index e286a4e57f25b92edb15b6ba84f5bd8b58d5da09..351a662ae83cbc6c3962528b942056f5efa479c5 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -22,6 +22,8 @@ class MergeRequestDiff < ActiveRecord::Base has_many :merge_request_diff_commits, -> { order(:merge_request_diff_id, :relative_order) } + validates :base_commit_sha, :head_commit_sha, :start_commit_sha, sha: true + state_machine :state, initial: :empty do event :clean do transition any => :without_files diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb index f700090a493cfa181ac9d7fb9e7c542eaa15a06f..e6787236c4ebdb608af7ca888b955b389e7b6934 100644 --- a/app/models/project_feature.rb +++ b/app/models/project_feature.rb @@ -76,7 +76,7 @@ class ProjectFeature < ActiveRecord::Base # This feature might not be behind a feature flag at all, so default to true return false unless ::Feature.enabled?(feature, user, default_enabled: true) - get_permission(user, access_level(feature)) + get_permission(user, feature) end def access_level(feature) @@ -134,12 +134,12 @@ class ProjectFeature < ActiveRecord::Base (FEATURES - %i(pages)).each {|f| validator.call("#{f}_access_level")} end - def get_permission(user, level) - case level + def get_permission(user, feature) + case access_level(feature) when DISABLED false when PRIVATE - user && (project.team.member?(user) || user.full_private_access?) + team_access?(user, feature) when ENABLED true when PUBLIC @@ -148,4 +148,11 @@ class ProjectFeature < ActiveRecord::Base true end end + + def team_access?(user, feature) + return unless user + return true if user.full_private_access? + + project.team.member?(user, ProjectFeature.required_minimum_access_level(feature)) + end end diff --git a/app/models/project_services/prometheus_service.rb b/app/models/project_services/prometheus_service.rb index 60cb2d380d51654e7ff6404ca7c929bfa343c626..c68a9d923c897eea883dff7ee6a5727db5e5cc4c 100644 --- a/app/models/project_services/prometheus_service.rb +++ b/app/models/project_services/prometheus_service.rb @@ -71,7 +71,7 @@ class PrometheusService < MonitoringService end def prometheus_client - RestClient::Resource.new(api_url, max_redirects: 0) if api_url && manual_configuration? && active? + RestClient::Resource.new(api_url, max_redirects: 0) if should_return_client? end def prometheus_available? @@ -83,6 +83,10 @@ class PrometheusService < MonitoringService private + def should_return_client? + api_url && manual_configuration? && active? && valid? + end + def synchronize_service_state self.active = prometheus_available? || manual_configuration? diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb index 298769c0eb88a7dd7025a5e2eb6ca4087f2be415..e74e5f008d7f5c941374397bf964c3ca920dfe43 100644 --- a/app/policies/group_policy.rb +++ b/app/policies/group_policy.rb @@ -53,7 +53,6 @@ class GroupPolicy < BasePolicy rule { admin }.enable :read_group rule { has_projects }.policy do - enable :read_group enable :read_list enable :read_label end diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index 533782104ac3abe47ffa0936f6fe2a8f50fca73a..87749ecf6c0b040e151c779ba5a3db2dbd2c32e4 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -300,6 +300,8 @@ class ProjectPolicy < BasePolicy rule { issues_disabled }.policy do prevent(*create_read_update_admin_destroy(:issue)) + prevent(*create_read_update_admin_destroy(:board)) + prevent(*create_read_update_admin_destroy(:list)) end rule { merge_requests_disabled | repository_disabled }.policy do @@ -463,7 +465,7 @@ class ProjectPolicy < BasePolicy when ProjectFeature::DISABLED false when ProjectFeature::PRIVATE - guest? || admin? + admin? || team_access_level >= ProjectFeature.required_minimum_access_level(feature) else true end diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index ef991eaf234eb3fbe471b8895f382e5b5675b4cc..1e1f2fbd08e82786c63e45c905b4c9a6a1a987f1 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -387,4 +387,10 @@ class IssuableBaseService < BaseService def parent project end + + # we need to check this because milestone from milestone_id param is displayed on "new" page + # where private project milestone could leak without this check + def ensure_milestone_available(issuable) + issuable.milestone_id = nil unless issuable.milestone_available? + end end diff --git a/app/services/issues/build_service.rb b/app/services/issues/build_service.rb index 3fb2c2b300716d35eb21b709854b42764fc9729e..61615ac20581b7eaf87d0b8129584cdc54f4513c 100644 --- a/app/services/issues/build_service.rb +++ b/app/services/issues/build_service.rb @@ -6,7 +6,9 @@ module Issues def execute filter_resolve_discussion_params - @issue = project.issues.new(issue_params) + @issue = project.issues.new(issue_params).tap do |issue| + ensure_milestone_available(issue) + end end def issue_params_with_info_from_discussions diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index 48419da98adeaf83fdd391fddc301ef82f564c38..109c964e577ef754dceb166b7188ef37e478523c 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -19,6 +19,7 @@ module MergeRequests merge_request.target_project = find_target_project merge_request.target_branch = find_target_branch merge_request.can_be_created = projects_and_branches_valid? + ensure_milestone_available(merge_request) # compare branches only if branches are valid, otherwise # compare_branches may raise an error diff --git a/app/services/projects/group_links/create_service.rb b/app/services/projects/group_links/create_service.rb index 1392775f8053c7a84e951437a570494410b8eca2..e3d5bea085226761e4bc882579b18966025c39ea 100644 --- a/app/services/projects/group_links/create_service.rb +++ b/app/services/projects/group_links/create_service.rb @@ -4,13 +4,19 @@ module Projects module GroupLinks class CreateService < BaseService def execute(group) - return false unless group + return error('Not Found', 404) unless group && can?(current_user, :read_namespace, group) - project.project_group_links.create( + link = project.project_group_links.new( group: group, group_access: params[:link_group_access], expires_at: params[:expires_at] ) + + if link.save + success(link: link) + else + error(link.errors.full_messages.to_sentence, 409) + end end end end diff --git a/app/uploaders/file_mover.rb b/app/uploaders/file_mover.rb index a7f8615e9ba8c19359bdde4cd01ddc3866d8a486..236b7ed2b3d43663d70f88043032c834fdc39159 100644 --- a/app/uploaders/file_mover.rb +++ b/app/uploaders/file_mover.rb @@ -11,6 +11,8 @@ class FileMover end def execute + return unless valid? + move if update_markdown @@ -21,6 +23,12 @@ class FileMover private + def valid? + Pathname.new(temp_file_path).realpath.to_path.start_with?( + (Pathname(temp_file_uploader.root) + temp_file_uploader.base_dir).to_path + ) + end + def move FileUtils.mkdir_p(File.dirname(file_path)) FileUtils.move(temp_file_path, file_path) diff --git a/app/validators/sha_validator.rb b/app/validators/sha_validator.rb new file mode 100644 index 0000000000000000000000000000000000000000..085fca4d65d81052e6e7608b71e9a831f2124ebe --- /dev/null +++ b/app/validators/sha_validator.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class ShaValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + return if value.blank? || value.match(/\A\h{40}\z/) + + record.errors.add(attribute, 'is not a valid SHA') + end +end diff --git a/app/views/ci/status/_icon.html.haml b/app/views/ci/status/_icon.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..f38bdb2e5ed0153530577a663c5b6c7de11d3038 --- /dev/null +++ b/app/views/ci/status/_icon.html.haml @@ -0,0 +1,16 @@ +- status = local_assigns.fetch(:status) +- size = local_assigns.fetch(:size, 16) +- type = local_assigns.fetch(:type, 'pipeline') +- tooltip_placement = local_assigns.fetch(:tooltip_placement, "left") +- path = local_assigns.fetch(:path, status.has_details? ? status.details_path : nil) +- css_classes = "ci-status-link ci-status-icon ci-status-icon-#{status.group} has-tooltip" +- title = s_("PipelineStatusTooltip|Pipeline: %{ci_status}") % {ci_status: status.label} +- if type == 'commit' + - title = s_("PipelineStatusTooltip|Commit: %{ci_status}") % {ci_status: status.label} + +- if path + = link_to path, class: css_classes, title: title, data: { placement: tooltip_placement } do + = sprite_icon(status.icon, size: size) +- else + %span{ class: css_classes, title: title, data: { placement: tooltip_placement } } + = sprite_icon(status.icon, size: size) diff --git a/app/views/notify/issue_moved_email.html.haml b/app/views/notify/issue_moved_email.html.haml index 472c31e9a5e5fc162896b1e42a2dc33d37392830..b766cb1a523e4b18b85c293e3b4a5bffb1de6e99 100644 --- a/app/views/notify/issue_moved_email.html.haml +++ b/app/views/notify/issue_moved_email.html.haml @@ -1,6 +1,9 @@ %p Issue was moved to another project. -%p - New issue: - = link_to project_issue_url(@new_project, @new_issue) do - = @new_issue.title +- if @can_access_project + %p + New issue: + = link_to project_issue_url(@new_project, @new_issue) do + = @new_issue.title +- else + You don't have access to the project. diff --git a/app/views/notify/issue_moved_email.text.erb b/app/views/notify/issue_moved_email.text.erb index 66ede43635b9fbc4576258eb17f8ce5366a09625..985e689aa9de747b20cb0d6a4464b1a9674d8b5a 100644 --- a/app/views/notify/issue_moved_email.text.erb +++ b/app/views/notify/issue_moved_email.text.erb @@ -1,4 +1,8 @@ Issue was moved to another project. +<% if @can_access_project %> New issue location: <%= project_issue_url(@new_project, @new_issue) %> +<% else %> +You don't have access to the project. +<% end %> diff --git a/app/views/profiles/active_sessions/_active_session.html.haml b/app/views/profiles/active_sessions/_active_session.html.haml index 23ef31a0c85d7d5a818af59782eaee1f3aea89fe..2bf514d72a5d4e419a6b28b0411467c1ec2bdddc 100644 --- a/app/views/profiles/active_sessions/_active_session.html.haml +++ b/app/views/profiles/active_sessions/_active_session.html.haml @@ -23,9 +23,3 @@ %strong Signed in on = l(active_session.created_at, format: :short) - - - unless is_current_session - .float-right - = link_to profile_active_session_path(active_session.session_id), data: { confirm: 'Are you sure? The device will be signed out of GitLab.' }, method: :delete, class: "btn btn-danger prepend-left-10" do - %span.sr-only Revoke - Revoke diff --git a/app/views/projects/blob/viewers/_dependency_manager.html.haml b/app/views/projects/blob/viewers/_dependency_manager.html.haml index 87aa7c1dbf8deb762423ce544be21752894d26fb..5970d41fdab79d128321581f8ef4ca143455cdbc 100644 --- a/app/views/projects/blob/viewers/_dependency_manager.html.haml +++ b/app/views/projects/blob/viewers/_dependency_manager.html.haml @@ -3,9 +3,4 @@ This project manages its dependencies using %strong= viewer.manager_name - - if viewer.package_name - and defines a #{viewer.package_type} named - %strong< - = link_to_if viewer.package_url.present?, viewer.package_name, viewer.package_url, target: '_blank', rel: 'noopener noreferrer' - = link_to 'Learn more', viewer.manager_url, target: '_blank', rel: 'noopener noreferrer' diff --git a/app/views/projects/issues/_merge_requests.html.haml b/app/views/projects/issues/_merge_requests.html.haml index 310e339ac8d735ced7019f38e0da484ced4a3b0b..6a66c2e57cc39ff16f4b43f7dd5ab3eb7dc6b9a8 100644 --- a/app/views/projects/issues/_merge_requests.html.haml +++ b/app/views/projects/issues/_merge_requests.html.haml @@ -27,7 +27,7 @@ = merge_request.to_reference %span.mr-ci-status.flex-md-grow-1.justify-content-end.d-flex.ml-md-2 - if merge_request.can_read_pipeline? - = render_pipeline_status(merge_request.head_pipeline, tooltip_placement: 'bottom') + = render 'ci/status/icon', status: merge_request.head_pipeline.detailed_status(current_user), tooltip_placement: 'bottom' - elsif has_any_head_pipeline = icon('blank fw') diff --git a/app/views/projects/issues/_related_branches.html.haml b/app/views/projects/issues/_related_branches.html.haml index ffdd96870ef343358f88211e3c3b7e8c015be27b..6da4956a0367ebe268572424216cb09cdd6d78de 100644 --- a/app/views/projects/issues/_related_branches.html.haml +++ b/app/views/projects/issues/_related_branches.html.haml @@ -8,7 +8,7 @@ - pipeline = @project.pipeline_for(branch, target.sha) if target - if can?(current_user, :read_pipeline, pipeline) %span.related-branch-ci-status - = render_pipeline_status(pipeline) + = render 'ci/status/icon', status: pipeline.detailed_status(current_user) %span.related-branch-info %strong = link_to branch, project_compare_path(@project, from: @project.default_branch, to: branch), class: "ref-name" diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index 90916191d97f7bbf4f3ac48e63052e010c2d10f4..b8e0b66e277c2c696d7866a5ee6657ed30dcfb7d 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -48,7 +48,7 @@ CLOSED - if can?(current_user, :read_pipeline, merge_request.head_pipeline) %li.issuable-pipeline-status.d-none.d-sm-inline-block - = render_pipeline_status(merge_request.head_pipeline) + = render 'ci/status/icon', status: merge_request.head_pipeline.detailed_status(current_user) - if merge_request.open? && merge_request.broken? %li.issuable-pipeline-broken.d-none.d-sm-inline-block = link_to merge_request_path(merge_request), class: "has-tooltip", title: _('Cannot be merged automatically') do diff --git a/app/views/shared/milestones/_milestone.html.haml b/app/views/shared/milestones/_milestone.html.haml index 40b8374848e76e3047869fd8ee22f1b6381ded18..e75f0a184ea047ba05cfe6e2344fa297f739f44b 100644 --- a/app/views/shared/milestones/_milestone.html.haml +++ b/app/views/shared/milestones/_milestone.html.haml @@ -32,7 +32,7 @@ = milestone_progress_bar(milestone) = link_to pluralize(milestone.total_issues_count(current_user), 'Issue'), issues_path · - = link_to pluralize(milestone.merge_requests.size, 'Merge Request'), merge_requests_path + = link_to pluralize(milestone.merge_requests_visible_to_user(current_user).size, 'Merge Request'), merge_requests_path .float-lg-right.light #{milestone.percent_complete(current_user)}% complete .col-sm-2 .milestone-actions.d-flex.justify-content-sm-start.justify-content-md-end diff --git a/app/views/shared/milestones/_tabs.html.haml b/app/views/shared/milestones/_tabs.html.haml index 55460acab8f565ff885e28935f5e0e4b92631eaf..b877f66c71ee9458e4e56f283e5cb6f00cc8403f 100644 --- a/app/views/shared/milestones/_tabs.html.haml +++ b/app/views/shared/milestones/_tabs.html.haml @@ -12,7 +12,7 @@ %li.nav-item = link_to '#tab-merge-requests', class: 'nav-link', 'data-toggle' => 'tab', 'data-endpoint': milestone_merge_request_tab_path(milestone) do Merge Requests - %span.badge.badge-pill= milestone.merge_requests.size + %span.badge.badge-pill= milestone.merge_requests_visible_to_user(current_user).size - else %li.nav-item = link_to '#tab-merge-requests', class: 'nav-link active', 'data-toggle' => 'tab', 'data-endpoint': milestone_merge_request_tab_path(milestone) do @@ -21,11 +21,11 @@ %li.nav-item = link_to '#tab-participants', class: 'nav-link', 'data-toggle' => 'tab', 'data-endpoint': milestone_participants_tab_path(milestone) do Participants - %span.badge.badge-pill= milestone.participants.count + %span.badge.badge-pill= milestone.issue_participants_visible_by_user(current_user).count %li.nav-item = link_to '#tab-labels', class: 'nav-link', 'data-toggle' => 'tab', 'data-endpoint': milestone_labels_tab_path(milestone) do Labels - %span.badge.badge-pill= milestone.labels.count + %span.badge.badge-pill= milestone.issue_labels_visible_by_user(current_user).count - issues = milestone.sorted_issues(current_user) - show_project_name = local_assigns.fetch(:show_project_name, false) diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml index f1a87faa7ac1dd4ee801cbff3a00dad2ea884a7a..d2b1be29eb9d529fed4e39f2d482c77f0139dcea 100644 --- a/app/views/shared/projects/_project.html.haml +++ b/app/views/shared/projects/_project.html.haml @@ -85,7 +85,8 @@ = sprite_icon('issues', size: 14, css_class: 'append-right-4') = number_with_delimiter(project.open_issues_count) - if pipeline_status && can?(current_user, :read_cross_project) && project.pipeline_status.has_status? && can?(current_user, :read_build, project) + - pipeline_path = pipelines_project_commit_path(project.pipeline_status.project, project.pipeline_status.sha, ref: project.pipeline_status.ref) %span.icon-wrapper.pipeline-status - = render_project_pipeline_status(project.pipeline_status, tooltip_placement: 'top') + = render 'ci/status/icon', status: project.commit.last_pipeline.detailed_status(current_user), type: 'commit', tooltip_placement: 'top', path: pipeline_path .updated-note %span Updated #{updated_tooltip} diff --git a/app/workers/pipeline_metrics_worker.rb b/app/workers/pipeline_metrics_worker.rb index c2fbfd2b3a5fefc9d8191f7a08b93b550fc86e6b..0ddad43b8d539e16f2cc227432f4945b7afbee86 100644 --- a/app/workers/pipeline_metrics_worker.rb +++ b/app/workers/pipeline_metrics_worker.rb @@ -30,6 +30,6 @@ class PipelineMetricsWorker # rubocop: enable CodeReuse/ActiveRecord def merge_requests(pipeline) - pipeline.merge_requests.map(&:id) + pipeline.merge_requests_as_head_pipeline.map(&:id) end end diff --git a/changelogs/unreleased/2802-security-add-public-internal-groups-as-members-to-your-project-idor.yml b/changelogs/unreleased/2802-security-add-public-internal-groups-as-members-to-your-project-idor.yml new file mode 100644 index 0000000000000000000000000000000000000000..27ad151cd061f49612df1363ba0b3ca8bbc775ee --- /dev/null +++ b/changelogs/unreleased/2802-security-add-public-internal-groups-as-members-to-your-project-idor.yml @@ -0,0 +1,6 @@ +--- +title: Remove the possibility to share a project with a group that a user is not a member + of +merge_request: +author: +type: security diff --git a/changelogs/unreleased/45305-ci-status-icon-mismatch-on-merge-requests-page-and-the-mr-itself.yml b/changelogs/unreleased/45305-ci-status-icon-mismatch-on-merge-requests-page-and-the-mr-itself.yml new file mode 100644 index 0000000000000000000000000000000000000000..64ab76a2b05f5a8be4546874b3eb07d4de0b094c --- /dev/null +++ b/changelogs/unreleased/45305-ci-status-icon-mismatch-on-merge-requests-page-and-the-mr-itself.yml @@ -0,0 +1,5 @@ +--- +title: Fix pipeline status icon mismatch +merge_request: 25407 +author: +type: fixed diff --git a/changelogs/unreleased/49663-branch-to-mr-connection.yml b/changelogs/unreleased/49663-branch-to-mr-connection.yml new file mode 100644 index 0000000000000000000000000000000000000000..d92ed6fd3bfb76988ce54f8c9cb349a7c4f59974 --- /dev/null +++ b/changelogs/unreleased/49663-branch-to-mr-connection.yml @@ -0,0 +1,5 @@ +--- +title: Link to most recent MR from a branch +merge_request: 25689 +author: +type: added diff --git a/changelogs/unreleased/51971-milestones-visibility.yml b/changelogs/unreleased/51971-milestones-visibility.yml new file mode 100644 index 0000000000000000000000000000000000000000..818f0071e6c421be98e329717f6443e533249597 --- /dev/null +++ b/changelogs/unreleased/51971-milestones-visibility.yml @@ -0,0 +1,5 @@ +--- +title: Check if desired milestone for an issue is available +merge_request: +author: +type: security diff --git a/changelogs/unreleased/57534_filter_impersonated_sessions.yml b/changelogs/unreleased/57534_filter_impersonated_sessions.yml new file mode 100644 index 0000000000000000000000000000000000000000..80aea0ab1bcfea868ff8c22b6fa309e115a7c15b --- /dev/null +++ b/changelogs/unreleased/57534_filter_impersonated_sessions.yml @@ -0,0 +1,6 @@ +--- +title: Do not display impersonated sessions under active sessions and remove ability + to revoke session +merge_request: +author: +type: security diff --git a/changelogs/unreleased/add_ldap_tls_options.yml b/changelogs/unreleased/add_ldap_tls_options.yml new file mode 100644 index 0000000000000000000000000000000000000000..c3678cb8fb66bf6c0d300da4607a84d46cd934d6 --- /dev/null +++ b/changelogs/unreleased/add_ldap_tls_options.yml @@ -0,0 +1,5 @@ +--- +title: Allow raw `tls_options` to be passed in LDAP configuration +merge_request: 20678 +author: +type: changed diff --git a/changelogs/unreleased/enable-markup-highlighting.yml b/changelogs/unreleased/enable-markup-highlighting.yml new file mode 100644 index 0000000000000000000000000000000000000000..33fcf784f7dca57bda1d1f968ab866014c5af02f --- /dev/null +++ b/changelogs/unreleased/enable-markup-highlighting.yml @@ -0,0 +1,5 @@ +--- +title: Enable syntax highlighting to other supported markups +merge_request: 25761 +author: +type: other diff --git a/changelogs/unreleased/expose-additional-merge-request-pipeline-variables.yml b/changelogs/unreleased/expose-additional-merge-request-pipeline-variables.yml new file mode 100644 index 0000000000000000000000000000000000000000..399f60ef219dffde0855ee407166ebb78bfcd8ef --- /dev/null +++ b/changelogs/unreleased/expose-additional-merge-request-pipeline-variables.yml @@ -0,0 +1,5 @@ +--- +title: Expose additional merge request pipeline variables +merge_request: 24595 +author: Hiroyuki Sato +type: added diff --git a/changelogs/unreleased/feature-gb-add-serverless-cicd-template.yml b/changelogs/unreleased/feature-gb-add-serverless-cicd-template.yml new file mode 100644 index 0000000000000000000000000000000000000000..ebb71f00c4b5ca35659bf3993da860475b427fa2 --- /dev/null +++ b/changelogs/unreleased/feature-gb-add-serverless-cicd-template.yml @@ -0,0 +1,5 @@ +--- +title: Simplify CI/CD configuration on serverless projects +merge_request: 25523 +author: +type: added diff --git a/changelogs/unreleased/gokhanap-master-patch-03762.yml b/changelogs/unreleased/gokhanap-master-patch-03762.yml new file mode 100644 index 0000000000000000000000000000000000000000..22ab453e3599637deb53c763870f96f2f68eb61b --- /dev/null +++ b/changelogs/unreleased/gokhanap-master-patch-03762.yml @@ -0,0 +1,5 @@ +--- +title: 'commit page info-well overflow fix #56436' +merge_request: 24799 +author: Gokhan Apaydin +type: fixed diff --git a/changelogs/unreleased/security-2774-milestones-detail.yml b/changelogs/unreleased/security-2774-milestones-detail.yml new file mode 100644 index 0000000000000000000000000000000000000000..faf56fee01eb3ed1005f8fb373775586d763e78e --- /dev/null +++ b/changelogs/unreleased/security-2774-milestones-detail.yml @@ -0,0 +1,5 @@ +--- +title: Display only information visible to current user on the Milestone page +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-2797-milestone-mrs.yml b/changelogs/unreleased/security-2797-milestone-mrs.yml new file mode 100644 index 0000000000000000000000000000000000000000..5bb104ec403ae0741ce927000bc67cf60f54530e --- /dev/null +++ b/changelogs/unreleased/security-2797-milestone-mrs.yml @@ -0,0 +1,5 @@ +--- +title: Show only merge requests visible to user on milestone detail page +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-2798-fix-boards-policy.yml b/changelogs/unreleased/security-2798-fix-boards-policy.yml new file mode 100644 index 0000000000000000000000000000000000000000..10e8ac3a78738c96457d45ee4237c1505d81ddd2 --- /dev/null +++ b/changelogs/unreleased/security-2798-fix-boards-policy.yml @@ -0,0 +1,5 @@ +--- +title: Disable issue boards API when issues are disabled +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-2799-emails.yml b/changelogs/unreleased/security-2799-emails.yml new file mode 100644 index 0000000000000000000000000000000000000000..dbf1207810e76b418687753d77c505a78b011cb3 --- /dev/null +++ b/changelogs/unreleased/security-2799-emails.yml @@ -0,0 +1,5 @@ +--- +title: Don't show new issue link after move when a user does not have permissions +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-50334.yml b/changelogs/unreleased/security-50334.yml new file mode 100644 index 0000000000000000000000000000000000000000..828ef82b517d04e3adbcc4751911b0716b5ca20a --- /dev/null +++ b/changelogs/unreleased/security-50334.yml @@ -0,0 +1,5 @@ +--- +title: Fix git clone revealing private repo's presence +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-55468-check-validity-before-querying.yml b/changelogs/unreleased/security-55468-check-validity-before-querying.yml new file mode 100644 index 0000000000000000000000000000000000000000..8bb11a97f525d0004daa8c57d43592e4ab2458d7 --- /dev/null +++ b/changelogs/unreleased/security-55468-check-validity-before-querying.yml @@ -0,0 +1,5 @@ +--- +title: Fix blind SSRF in Prometheus integration by checking URL before querying +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-56348.yml b/changelogs/unreleased/security-56348.yml new file mode 100644 index 0000000000000000000000000000000000000000..a289e4e90772d0b3c99abef455906ffa0204808d --- /dev/null +++ b/changelogs/unreleased/security-56348.yml @@ -0,0 +1,5 @@ +--- +title: Check snippet attached file to be moved is within designated directory +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-commit-private-related-mr.yml b/changelogs/unreleased/security-commit-private-related-mr.yml new file mode 100644 index 0000000000000000000000000000000000000000..c4de200b0d8156e96a65720b94cf29a460eb2d05 --- /dev/null +++ b/changelogs/unreleased/security-commit-private-related-mr.yml @@ -0,0 +1,5 @@ +--- +title: Don't allow non-members to see private related MRs. +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-fj-diff-import-file-read-fix.yml b/changelogs/unreleased/security-fj-diff-import-file-read-fix.yml new file mode 100644 index 0000000000000000000000000000000000000000..e98d4e89712e98049de37562e8f9c5e7f431de6d --- /dev/null +++ b/changelogs/unreleased/security-fj-diff-import-file-read-fix.yml @@ -0,0 +1,5 @@ +--- +title: Fix arbitrary file read via diffs during import +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-id-fix-mr-visibility.yml b/changelogs/unreleased/security-id-fix-mr-visibility.yml new file mode 100644 index 0000000000000000000000000000000000000000..8f41d191acce8916d718df55b59a1c3b70456b57 --- /dev/null +++ b/changelogs/unreleased/security-id-fix-mr-visibility.yml @@ -0,0 +1,5 @@ +--- +title: Display the correct number of MRs a user has access to +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-id-restricted-access-to-private-repo.yml b/changelogs/unreleased/security-id-restricted-access-to-private-repo.yml new file mode 100644 index 0000000000000000000000000000000000000000..7d7478d297b8322b8c6efaf92c4cdd29f00066bc --- /dev/null +++ b/changelogs/unreleased/security-id-restricted-access-to-private-repo.yml @@ -0,0 +1,5 @@ +--- +title: Forbid creating discussions for users with restricted access +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-issue_54789_2.yml b/changelogs/unreleased/security-issue_54789_2.yml new file mode 100644 index 0000000000000000000000000000000000000000..8ecb72a2ae31f5180f499b9de7b2c720f568f603 --- /dev/null +++ b/changelogs/unreleased/security-issue_54789_2.yml @@ -0,0 +1,5 @@ +--- +title: Do not disclose milestone titles for unauthorized users +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-kubernetes-google-login-csrf.yml b/changelogs/unreleased/security-kubernetes-google-login-csrf.yml new file mode 100644 index 0000000000000000000000000000000000000000..2f87100a8ddf133fdd233e5c2a9d79b9c7b561d6 --- /dev/null +++ b/changelogs/unreleased/security-kubernetes-google-login-csrf.yml @@ -0,0 +1,5 @@ +--- +title: Validate session key when authorizing with GCP to create a cluster +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-kubernetes-local-ssrf.yml b/changelogs/unreleased/security-kubernetes-local-ssrf.yml new file mode 100644 index 0000000000000000000000000000000000000000..7a2ad09233950da7085876ec9950d2e6b48e75da --- /dev/null +++ b/changelogs/unreleased/security-kubernetes-local-ssrf.yml @@ -0,0 +1,5 @@ +--- +title: Block local URLs for Kubernetes integration +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-mermaid.yml b/changelogs/unreleased/security-mermaid.yml new file mode 100644 index 0000000000000000000000000000000000000000..ec42b5a1615070bb52f308b6093d186807444806 --- /dev/null +++ b/changelogs/unreleased/security-mermaid.yml @@ -0,0 +1,5 @@ +--- +title: Limit mermaid rendering to 5K characters +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-osw-stop-linking-to-packages.yml b/changelogs/unreleased/security-osw-stop-linking-to-packages.yml new file mode 100644 index 0000000000000000000000000000000000000000..078f06140feed6fc76f584ea38cf3ff97b0ed74d --- /dev/null +++ b/changelogs/unreleased/security-osw-stop-linking-to-packages.yml @@ -0,0 +1,5 @@ +--- +title: Stop linking to unrecognized package sources +merge_request: 55518 +author: +type: security diff --git a/changelogs/unreleased/security-protect-private-repo-information.yml b/changelogs/unreleased/security-protect-private-repo-information.yml new file mode 100644 index 0000000000000000000000000000000000000000..8b1a528206d79ba6776eb3a4145086a12592593e --- /dev/null +++ b/changelogs/unreleased/security-protect-private-repo-information.yml @@ -0,0 +1,5 @@ +--- +title: Fix leaking private repository information in API +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-shared-project-private-group.yml b/changelogs/unreleased/security-shared-project-private-group.yml new file mode 100644 index 0000000000000000000000000000000000000000..3b21daa5491286f1b71f673ef07b663dd55bbaad --- /dev/null +++ b/changelogs/unreleased/security-shared-project-private-group.yml @@ -0,0 +1,5 @@ +--- +title: Fixed ability to see private groups by users not belonging to given group +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-tags-oracle.yml b/changelogs/unreleased/security-tags-oracle.yml new file mode 100644 index 0000000000000000000000000000000000000000..eb8ad6f646cdd7963b931cbc24bbdeb04fcd26d4 --- /dev/null +++ b/changelogs/unreleased/security-tags-oracle.yml @@ -0,0 +1,5 @@ +--- +title: Prevent releases links API to leak tag existance +merge_request: +author: +type: security diff --git a/changelogs/unreleased/table-fix-scroll-and-block.yml b/changelogs/unreleased/table-fix-scroll-and-block.yml new file mode 100644 index 0000000000000000000000000000000000000000..e6def0468b8677a2594badc5fc731a2b8fca0709 --- /dev/null +++ b/changelogs/unreleased/table-fix-scroll-and-block.yml @@ -0,0 +1,5 @@ +--- +title: Fix large table horizontal scroll and prevent side-by-side tables +merge_request: 25520 +author: Dany Jupille +type: fixed diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 1a5b9ec3f02384c66c6e85185832032aa5ab333a..2ad992a059cfccc04d242928e800228616f105e7 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -379,19 +379,54 @@ production: &base # "start_tls" or "simple_tls". Defaults to true. verify_certificates: true - # Specifies the path to a file containing a PEM-format CA certificate, - # e.g. if you need to use an internal CA. - # - # Example: '/etc/ca.pem' - # - ca_file: '' - - # Specifies the SSL version for OpenSSL to use, if the OpenSSL default - # is not appropriate. - # - # Example: 'TLSv1_1' - # - ssl_version: '' + # OpenSSL::SSL::SSLContext options. + tls_options: + # Specifies the path to a file containing a PEM-format CA certificate, + # e.g. if you need to use an internal CA. + # + # Example: '/etc/ca.pem' + # + ca_file: '' + + # Specifies the SSL version for OpenSSL to use, if the OpenSSL default + # is not appropriate. + # + # Example: 'TLSv1_1' + # + ssl_version: '' + + # Specific SSL ciphers to use in communication with LDAP servers. + # + # Example: 'ALL:!EXPORT:!LOW:!aNULL:!eNULL:!SSLv2' + ciphers: '' + + # Client certificate + # + # Example: + # cert: | + # -----BEGIN CERTIFICATE----- + # MIIDbDCCAlSgAwIBAgIGAWkJxLmKMA0GCSqGSIb3DQEBCwUAMHcxFDASBgNVBAoTC0dvb2dsZSBJ + # bmMuMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQDEwtMREFQIENsaWVudDEPMA0GA1UE + # CxMGR1N1aXRlMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTAeFw0xOTAyMjAwNzE4 + # rntnF4d+0dd7zP3jrWkbdtoqjLDT/5D7NYRmVCD5vizV98FJ5//PIHbD1gL3a9b2MPAc6k7NV8tl + # ... + # 4SbuJPAiJxC1LQ0t39dR6oMCAMab3hXQqhL56LrR6cRBp6Mtlphv7alu9xb/x51y2x+g2zWtsf80 + # Jrv/vKMsIh/sAyuogb7hqMtp55ecnKxceg== + # -----END CERTIFICATE ----- + cert: '' + + # Client private key + # key: | + # -----BEGIN PRIVATE KEY----- + # MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC3DmJtLRmJGY4xU1QtI3yjvxO6 + # bNuyE4z1NF6Xn7VSbcAaQtavWQ6GZi5uukMo+W5DHVtEkgDwh92ySZMuJdJogFbNvJvHAayheCdN + # 7mCQ2UUT9jGXIbmksUn9QMeJVXTZjgJWJzPXToeUdinx9G7+lpVa62UATEd1gaI3oyL72WmpDy/C + # rntnF4d+0dd7zP3jrWkbdtoqjLDT/5D7NYRmVCD5vizV98FJ5//PIHbD1gL3a9b2MPAc6k7NV8tl + # ... + # +9IhSYX+XIg7BZOVDeYqlPfxRvQh8vy3qjt/KUihmEPioAjLaGiihs1Fk5ctLk9A2hIUyP+sEQv9 + # l6RG+a/mW+0rCWn8JAd464Ps9hE= + # -----END PRIVATE KEY----- + key: '' # Set a timeout, in seconds, for LDAP queries. This helps avoid blocking # a request if the LDAP server becomes unresponsive. @@ -653,8 +688,8 @@ production: &base # # Turns on AWS Server-Side Encryption with Amazon S3-Managed Keys for backups, this is optional # # encryption: 'AES256' # # Turns on AWS Server-Side Encryption with Amazon Customer-Provided Encryption Keys for backups, this is optional - # # This should be set to the 256-bit, base64-encoded encryption key for Amazon S3 to use to encrypt or decrypt your data. - # # 'encryption' must also be set in order for this to have any effect. + # # This should be set to the 256-bit, base64-encoded encryption key for Amazon S3 to use to encrypt or decrypt your data. + # # 'encryption' must also be set in order for this to have any effect. # # encryption_key: '' # # Specifies Amazon S3 storage class to use for backups, this is optional # # storage_class: 'STANDARD' diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 1344b3cb1f6f2e97be35b408e3449c0134017fe1..03800f3d9d298aa7013c85e14f8027cb5426c104 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -40,6 +40,24 @@ if Settings.ldap['enabled'] || Rails.env.test? # Since GitLab 10.0, verify_certificates defaults to true for security. server['verify_certificates'] = true if server['verify_certificates'].nil? + # Expose ability to set `tls_options` directly. Deprecate `ca_file` and + # `ssl_version` in favor of `tls_options` hash option. + server['tls_options'] ||= {} + + if server['ssl_version'] || server['ca_file'] + Rails.logger.warn 'DEPRECATED: LDAP options `ssl_version` and `ca_file` should be nested within `tls_options`' + end + + if server['ssl_version'] + server['tls_options']['ssl_version'] ||= server['ssl_version'] + server.delete('ssl_version') + end + + if server['ca_file'] + server['tls_options']['ca_file'] ||= server['ca_file'] + server.delete('ca_file') + end + Settings.ldap['servers'][key] = server end end diff --git a/config/routes/git_http.rb b/config/routes/git_http.rb index ec5c68f81df068f1ea8775af3901a5cc68ee649f..a959d40881b1309b0361fb9a2d7476baadfc409a 100644 --- a/config/routes/git_http.rb +++ b/config/routes/git_http.rb @@ -40,7 +40,7 @@ scope(path: '*namespace_id/:project_id', # /info/refs?service=git-receive-pack, but nothing else. # git_http_handshake = lambda do |request| - ::Constraints::ProjectUrlConstrainer.new.matches?(request) && + ::Constraints::ProjectUrlConstrainer.new.matches?(request, existence_check: false) && (request.query_string.blank? || request.query_string.match(/\Aservice=git-(upload|receive)-pack\z/)) end diff --git a/doc/administration/auth/ldap.md b/doc/administration/auth/ldap.md index d5d0d99ac24e96ff971f51b362671f9fe52b7fcb..440c2b1285afcbe041123eccf0af6fe154a34542 100644 --- a/doc/administration/auth/ldap.md +++ b/doc/administration/auth/ldap.md @@ -81,6 +81,9 @@ library. `tls` corresponds to StartTLS, not to be confused with regular TLS. Normally, if you specify `ssl` it will be on port 636, while `tls` (StartTLS) would be on port 389. `plain` also operates on port 389. +NOTE: **Note:** +LDAP users must have an email address set, regardless of whether it is used to log in. + **Omnibus configuration** ```ruby @@ -136,14 +139,54 @@ main: ## verify_certificates: true - ## - ## Specifies the SSL version for OpenSSL to use, if the OpenSSL default - ## is not appropriate. - ## - ## Example: 'TLSv1_1' - ## - ## - ssl_version: '' + # OpenSSL::SSL::SSLContext options. + tls_options: + # Specifies the path to a file containing a PEM-format CA certificate, + # e.g. if you need to use an internal CA. + # + # Example: '/etc/ca.pem' + # + ca_file: '' + + # Specifies the SSL version for OpenSSL to use, if the OpenSSL default + # is not appropriate. + # + # Example: 'TLSv1_1' + # + ssl_version: '' + + # Specific SSL ciphers to use in communication with LDAP servers. + # + # Example: 'ALL:!EXPORT:!LOW:!aNULL:!eNULL:!SSLv2' + ciphers: '' + + # Client certificate + # + # Example: + # cert: | + # -----BEGIN CERTIFICATE----- + # MIIDbDCCAlSgAwIBAgIGAWkJxLmKMA0GCSqGSIb3DQEBCwUAMHcxFDASBgNVBAoTC0dvb2dsZSBJ + # bmMuMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQDEwtMREFQIENsaWVudDEPMA0GA1UE + # CxMGR1N1aXRlMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTAeFw0xOTAyMjAwNzE4 + # rntnF4d+0dd7zP3jrWkbdtoqjLDT/5D7NYRmVCD5vizV98FJ5//PIHbD1gL3a9b2MPAc6k7NV8tl + # ... + # 4SbuJPAiJxC1LQ0t39dR6oMCAMab3hXQqhL56LrR6cRBp6Mtlphv7alu9xb/x51y2x+g2zWtsf80 + # Jrv/vKMsIh/sAyuogb7hqMtp55ecnKxceg== + # -----END CERTIFICATE ----- + cert: '' + + # Client private key + # key: | + # -----BEGIN PRIVATE KEY----- + # MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC3DmJtLRmJGY4xU1QtI3yjvxO6 + # bNuyE4z1NF6Xn7VSbcAaQtavWQ6GZi5uukMo+W5DHVtEkgDwh92ySZMuJdJogFbNvJvHAayheCdN + # 7mCQ2UUT9jGXIbmksUn9QMeJVXTZjgJWJzPXToeUdinx9G7+lpVa62UATEd1gaI3oyL72WmpDy/C + # rntnF4d+0dd7zP3jrWkbdtoqjLDT/5D7NYRmVCD5vizV98FJ5//PIHbD1gL3a9b2MPAc6k7NV8tl + # ... + # +9IhSYX+XIg7BZOVDeYqlPfxRvQh8vy3qjt/KUihmEPioAjLaGiihs1Fk5ctLk9A2hIUyP+sEQv9 + # l6RG+a/mW+0rCWn8JAd464Ps9hE= + # -----END PRIVATE KEY----- + key: '' ## ## Set a timeout, in seconds, for LDAP queries. This helps avoid blocking diff --git a/doc/articles/index.md b/doc/articles/index.md index 87ee17bb6de61763e60bdeb214b05165609f23ca..162db11d6ac59e102353c3c523b6136e1bb48c75 100644 --- a/doc/articles/index.md +++ b/doc/articles/index.md @@ -4,8 +4,8 @@ comments: false # Technical articles list (deprecated) -[Technical articles](../development/documentation/index.md#technical-articles) are -topic-related documentation, written with an user-friendly approach and language, aiming +Technical articles are +topic-related documentation, written with a user-friendly approach and language, aiming to provide the community with guidance on specific processes to achieve certain objectives. The list of technical articles was [deprecated](https://gitlab.com/gitlab-org/gitlab-ce/issues/41138) in favor of having them linked from their topic-related documentation: diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index 6fe352df48aa89932276cf94052d158bb93aa428..08db89124de2a2b2fe654d8229d6d5c69102d871 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -79,19 +79,23 @@ future GitLab releases.** | **CI_JOB_STAGE** | 9.0 | 0.5 | The name of the stage as defined in `.gitlab-ci.yml` | | **CI_JOB_TOKEN** | 9.0 | 1.2 | Token used for authenticating with the [GitLab Container Registry][registry] and downloading [dependent repositories][dependent-repositories] | | **CI_JOB_URL** | 11.1 | 0.5 | Job details URL | -| **CI_MERGE_REQUEST_ID** | 11.6 | all | The ID of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) | -| **CI_MERGE_REQUEST_IID** | 11.6 | all | The IID of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) | -| **CI_MERGE_REQUEST_PROJECT_ID** | 11.6 | all | The ID of the project of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) | -| **CI_MERGE_REQUEST_PROJECT_PATH** | 11.6 | all | The path of the project of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) (e.g. `namespace/awesome-project`) | -| **CI_MERGE_REQUEST_PROJECT_URL** | 11.6 | all | The URL of the project of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) (e.g. `http://192.168.10.15:3000/namespace/awesome-project`) | -| **CI_MERGE_REQUEST_REF_PATH** | 11.6 | all | The ref path of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md). (e.g. `refs/merge-requests/1/head`) | -| **CI_MERGE_REQUEST_SOURCE_BRANCH_NAME** | 11.6 | all | The source branch name of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) | -| **CI_MERGE_REQUEST_SOURCE_BRANCH_SHA** | 11.9 | all | The HEAD sha of the source branch of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) | -| **CI_MERGE_REQUEST_SOURCE_PROJECT_ID** | 11.6 | all | The ID of the source project of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) | -| **CI_MERGE_REQUEST_SOURCE_PROJECT_PATH** | 11.6 | all | The path of the source project of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) | -| **CI_MERGE_REQUEST_SOURCE_PROJECT_URL** | 11.6 | all | The URL of the source project of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) | -| **CI_MERGE_REQUEST_TARGET_BRANCH_NAME** | 11.6 | all | The target branch name of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) | -| **CI_MERGE_REQUEST_TARGET_BRANCH_SHA** | 11.9 | all | The HEAD sha of the target branch of the merge request if it's [pipelines for merge requests](../merge_request_pipelines/index.md) | +| **CI_MERGE_REQUEST_ID** | 11.6 | all | The ID of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md) | +| **CI_MERGE_REQUEST_IID** | 11.6 | all | The IID of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md) | +| **CI_MERGE_REQUEST_PROJECT_ID** | 11.6 | all | The ID of the project of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md) | +| **CI_MERGE_REQUEST_PROJECT_PATH** | 11.6 | all | The path of the project of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md) (e.g. `namespace/awesome-project`) | +| **CI_MERGE_REQUEST_PROJECT_URL** | 11.6 | all | The URL of the project of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md) (e.g. `http://192.168.10.15:3000/namespace/awesome-project`) | +| **CI_MERGE_REQUEST_REF_PATH** | 11.6 | all | The ref path of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). (e.g. `refs/merge-requests/1/head`) | +| **CI_MERGE_REQUEST_SOURCE_BRANCH_NAME** | 11.6 | all | The source branch name of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md) | +| **CI_MERGE_REQUEST_SOURCE_BRANCH_SHA** | 11.9 | all | The HEAD sha of the source branch of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md) | +| **CI_MERGE_REQUEST_SOURCE_PROJECT_ID** | 11.6 | all | The ID of the source project of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md) | +| **CI_MERGE_REQUEST_SOURCE_PROJECT_PATH** | 11.6 | all | The path of the source project of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md) | +| **CI_MERGE_REQUEST_SOURCE_PROJECT_URL** | 11.6 | all | The URL of the source project of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md) | +| **CI_MERGE_REQUEST_TARGET_BRANCH_NAME** | 11.6 | all | The target branch name of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md) | +| **CI_MERGE_REQUEST_TARGET_BRANCH_SHA** | 11.9 | all | The HEAD sha of the target branch of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md) | +| **CI_MERGE_REQUEST_TITLE** | 11.9 | all | The title of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md) | +| **CI_MERGE_REQUEST_ASSIGNEES** | 11.9 | all | Comma-separated usernames of the assignees of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md). **Comming soon**: [Multitle assignees for merge requests](https://gitlab.com/gitlab-org/gitlab-ee/issues/2004) | +| **CI_MERGE_REQUEST_MILESTONE** | 11.9 | all | The milestone title of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md) | +| **CI_MERGE_REQUEST_LABELS** | 11.9 | all | Comma-separated label names of the merge request if [the pipelines are for merge requests](../merge_request_pipelines/index.md) | | **CI_NODE_INDEX** | 11.5 | all | Index of the job in the job set. If the job is not parallelized, this variable is not set. | | **CI_NODE_TOTAL** | 11.5 | all | Total number of instances of this job running in parallel. If the job is not parallelized, this variable is set to `1`. | | **CI_API_V4_URL** | 11.7 | all | The GitLab API v4 root URL | diff --git a/doc/development/changelog.md b/doc/development/changelog.md index cd0a1f46d276a009b34269272837ddeb84a724a1..6efed36edf0400102c6d689a7cdfce225f8f670d 100644 --- a/doc/development/changelog.md +++ b/doc/development/changelog.md @@ -135,21 +135,13 @@ If you're working on the GitLab EE repository, the entry will be added to | Argument | Shorthand | Purpose | | ----------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------- | -| [`--amend`] | | Amend the previous commit | -| [`--force`] | `-f` | Overwrite an existing entry | -| [`--merge-request`] | `-m` | Set merge request ID | -| [`--dry-run`] | `-n` | Don't actually write anything, just print | -| [`--git-username`] | `-u` | Use Git user.name configuration as the author | -| [`--type`] | `-t` | The category of the change, valid options are: `added`, `fixed`, `changed`, `deprecated`, `removed`, `security`, `performance`, `other` | -| [`--help`] | `-h` | Print help message | - -[`--amend`]: #-amend -[`--force`]: #-force-or-f -[`--merge-request`]: #-merge-request-or-m -[`--dry-run`]: #-dry-run-or-n -[`--git-username`]: #-git-username-or-u -[`--type`]: #-type-or-t -[`--help`]: #-help +| [`--amend`](#--amend) | | Amend the previous commit | +| [`--force`](#--force-or--f) | `-f` | Overwrite an existing entry | +| [`--merge-request`](#--merge-request-or--m) | `-m` | Set merge request ID | +| [`--dry-run`](#--dry-run-or--n) | `-n` | Don't actually write anything, just print | +| [`--git-username`](#--git-username-or--u) | `-u` | Use Git user.name configuration as the author | +| [`--type`](#--type-or--t) | `-t` | The category of the change, valid options are: `added`, `fixed`, `changed`, `deprecated`, `removed`, `security`, `performance`, `other` | +| `--help` | `-h` | Print help message | ##### `--amend` diff --git a/doc/development/contributing/merge_request_workflow.md b/doc/development/contributing/merge_request_workflow.md index 4e766f378712448209842884f90af3528152dda2..8b14c3b20ea20d8246d4f771daf178166cf743ea 100644 --- a/doc/development/contributing/merge_request_workflow.md +++ b/doc/development/contributing/merge_request_workflow.md @@ -179,7 +179,7 @@ the feature you contribute through all of these steps. 1. Added to [the website](https://gitlab.com/gitlab-com/www-gitlab-com/), if relevant 1. Community questions answered 1. Answers to questions radiated (in docs/wiki/support etc.) -1. [Black-box tests/end-to-end tests](../testing_guide/testing_levels.md#black-box-tests-or-end-to-end-tests) added if required. Please contact [the quality team](https://about.gitlab.com/handbook/engineering/quality/#teams) with any questions +1. [Black-box tests/end-to-end tests](../testing_guide/testing_levels.md#black-box-tests-at-the-system-level-aka-end-to-end-tests) added if required. Please contact [the quality team](https://about.gitlab.com/handbook/engineering/quality/#teams) with any questions If you add a dependency in GitLab (such as an operating system package) please consider updating the following and note the applicability of each in your diff --git a/doc/development/documentation/index.md b/doc/development/documentation/index.md index 652fe3ea7111127f8c991b292d1ffc55431ccc40..a4da34a50ce7623c2ce8161e18d67a7588f7b413 100644 --- a/doc/development/documentation/index.md +++ b/doc/development/documentation/index.md @@ -511,10 +511,10 @@ Currently, the following tests are in place: 1. `docs lint`: Check that all internal (relative) links work correctly and that all cURL examples in API docs use the full switches. It's recommended - to [check locally](#previewing-locally) before pushing to GitLab by executing the command + to [check locally](#previewing-the-changes-live) before pushing to GitLab by executing the command `bundle exec nanoc check internal_links` on your local [`gitlab-docs`](https://gitlab.com/gitlab-com/gitlab-docs) directory. -1. [`ee_compat_check`](../automatic_ce_ee_merge.md#avoiding-ce-gt-ee-merge-conflicts-beforehand) (runs on CE only): +1. [`ee_compat_check`](../automatic_ce_ee_merge.md#avoiding-ce-ee-merge-conflicts-beforehand) (runs on CE only): When you submit a merge request to GitLab Community Edition (CE), there is this additional job that runs against Enterprise Edition (EE) and checks if your changes can apply cleanly to the EE codebase. diff --git a/doc/development/documentation/site_architecture/global_nav.md b/doc/development/documentation/site_architecture/global_nav.md index 0aa3c41a225029472b94dc708d5680315c6e40a4..f2f4f5f0e1c5d283fd30064baa6d8d3757de4f6a 100644 --- a/doc/development/documentation/site_architecture/global_nav.md +++ b/doc/development/documentation/site_architecture/global_nav.md @@ -62,7 +62,7 @@ the consent of one of the technical writers. The global nav is built from two files: - [Data](#data-file) -- [Layout](#layout-file) +- [Layout](#layout-file-logic) The data file feeds the layout with the links to the docs. The layout organizes the data among the nav in containers properly [styled](#css-classes). diff --git a/doc/development/documentation/styleguide.md b/doc/development/documentation/styleguide.md index cd38721e3ab43d30e65ac509a74d9a87dcf3ce62..0c51d3832aaa801dc71d010386779a03f1581853 100644 --- a/doc/development/documentation/styleguide.md +++ b/doc/development/documentation/styleguide.md @@ -36,7 +36,7 @@ gem will support all [GFM markup](../../user/markdown.md) in the future. For now use regular markdown markup, following the rules on this style guide. For a complete Kramdown reference, check the [GitLab Markdown Kramdown Guide](https://about.gitlab.com/handbook/product/technical-writing/markdown-guide/). Use Kramdown markup wisely: do not overuse its specific markup (e.g., `{:.class}`) as it will not render properly in -[`/help`](#gitlab-help). +[`/help`](index.md#gitlab-help). ## Content @@ -630,7 +630,7 @@ In this case: - The code blocks are indented one or more spaces under the list item to render correctly. - Different highlighting languages are used for each config in the code block. -- The [references](#references) guide is used for reconfigure/restart. +- The [GitLab Restart](#gitlab-restart) section is used to explain a required restart/reconfigure of GitLab. ## API diff --git a/doc/development/ee_features.md b/doc/development/ee_features.md index 17da4c510332f8d5f2ea4e4db9e1c12e39b41d3f..41a64044c68f4a4cb44f9674d37fb2a9ae4874ca 100644 --- a/doc/development/ee_features.md +++ b/doc/development/ee_features.md @@ -161,7 +161,7 @@ still having access the class's implementation with `super`. There are a few gotchas with it: -- you should always [`extend ::Gitlab::Utils::Override`] and use `override` to +- you should always [`extend ::Gitlab::Utils::Override`](utilities.md#overridehttpsgitlabcomgitlab-orggitlab-ceblobmasterlibgitlabutilsoverriderb) and use `override` to guard the "overrider" method to ensure that if the method gets renamed in CE, the EE override won't be silently forgotten. - when the "overrider" would add a line in the middle of the CE @@ -273,8 +273,6 @@ module EE end ``` -[`extend ::Gitlab::Utils::Override`]: utilities.md#override - ##### Overriding CE class methods The same applies to class methods, except we want to use @@ -977,7 +975,7 @@ if (renderIfEE) { To separate EE-specific styles in SCSS files, if a component you're adding styles for is limited to only EE, it is better to have a separate SCSS file in appropriate directory within `app/assets/stylesheets`. -See [backporting changes](#backporting-changes) for instructions on how to merge changes safely. +See [backporting changes](#backporting-changes-from-EE-to-CE) for instructions on how to merge changes safely. In some cases, this is not entirely possible or creating dedicated SCSS file is an overkill, e.g. a text style of some component is different for EE. In such cases, diff --git a/doc/development/fe_guide/architecture.md b/doc/development/fe_guide/architecture.md index aebb22caa1523433ff0dc623239d0fba3edcda3a..c67389b169e760aabd4a6ab7ee6343cb6c76c602 100644 --- a/doc/development/fe_guide/architecture.md +++ b/doc/development/fe_guide/architecture.md @@ -11,12 +11,9 @@ Architectural decisions should be accessible to everyone, so please document them in the relevant Merge Request discussion or by updating our documentation when appropriate. -You can find the Frontend Architecture experts on the [team page][team-page]. +You can find the Frontend Architecture experts on the [team page](https://about.gitlab.com/team). ## Examples You can find documentation about the desired architecture for a new feature -built with Vue.js [here][vue-section]. - -[team-page]: https://about.gitlab.com/team -[vue-section]: vue.md#frontend.html#how-to-build-a-new-feature-with-vue-js +built with Vue.js [here](vue.md). diff --git a/doc/development/fe_guide/vue.md b/doc/development/fe_guide/vue.md index 3cd70bd63fa8a32459a03fb63a18eef1d319e355..435fdf39fb4e9c74725b3ea3050bf4d7367af430 100644 --- a/doc/development/fe_guide/vue.md +++ b/doc/development/fe_guide/vue.md @@ -122,7 +122,7 @@ Check this [page](vuex.md) for more details. ## Style guide -Please refer to the Vue section of our [style guide](style_guide_js.md#vue-js) +Please refer to the Vue section of our [style guide](style_guide_js.md#vuejs) for best practices while writing your Vue components and templates. ## Testing Vue Components @@ -132,7 +132,7 @@ Each Vue component has a unique output. This output is always present in the ren Although we can test each method of a Vue component individually, our goal must be to test the output of the render/template function, which represents the state at all times. -Make use of the [axios mock adapter](axios.md#mock-axios-response-on-tests) to mock data returned. +Make use of the [axios mock adapter](axios.md#mock-axios-response-in-tests) to mock data returned. Here's how we would test the Todo App above: diff --git a/doc/development/testing_guide/best_practices.md b/doc/development/testing_guide/best_practices.md index 2bd8332bf9306574a9a8f9bd4dba0cd505222cb0..cfe0e6f70fc5039a34aea6f02f73f9bf7ff59928 100644 --- a/doc/development/testing_guide/best_practices.md +++ b/doc/development/testing_guide/best_practices.md @@ -40,7 +40,7 @@ bundle exec rspec spec/[path]/[to]/[spec].rb to separate phases. - Use `Gitlab.config.gitlab.host` rather than hard coding `'localhost'` - Don't assert against the absolute value of a sequence-generated attribute (see - [Gotchas](../gotchas.md#dont-assert-against-the-absolute-value-of-a-sequence-generated-attribute)). + [Gotchas](../gotchas.md#do-not-assert-against-the-absolute-value-of-a-sequence-generated-attribute)). - Don't supply the `:each` argument to hooks since it's the default. - On `before` and `after` hooks, prefer it scoped to `:context` over `:all` - When using `evaluate_script("$('.js-foo').testSomething()")` (or `execute_script`) which acts on a given element, diff --git a/doc/development/testing_guide/testing_levels.md b/doc/development/testing_guide/testing_levels.md index a7a3459719b7371ef0ca5bc6037732db142fadda..5d46833a1e230ec296c7ea2ce622b30e322b2d86 100644 --- a/doc/development/testing_guide/testing_levels.md +++ b/doc/development/testing_guide/testing_levels.md @@ -46,7 +46,7 @@ They're useful to test permissions, redirections, what view is rendered etc. | `app/mailers/` | `spec/mailers/` | RSpec | | | `lib/api/` | `spec/requests/api/` | RSpec | | | `lib/ci/api/` | `spec/requests/ci/api/` | RSpec | | -| `app/assets/javascripts/` | `spec/javascripts/` | Karma | More details in the [JavaScript](#javascript) section. | +| `app/assets/javascripts/` | `spec/javascripts/` | Karma | More details in the [Karma JavaScript test suite](frontend_testing.md#karma-test-suite) section. | ### About controller tests @@ -210,7 +210,7 @@ trade-off: - Integration tests are a bit more expensive, but don't abuse them. A system test is often better than an integration test that is stubbing a lot of internals. - System tests are expensive (compared to unit tests), even more if they require - a JavaScript driver. Make sure to follow the guidelines in the [Speed](#test-speed) + a JavaScript driver. Make sure to follow the guidelines in the [Speed](best_practices.md#test-speed) section. Another way to see it is to think about the "cost of tests", this is well diff --git a/doc/user/admin_area/monitoring/health_check.md b/doc/user/admin_area/monitoring/health_check.md index 91e2aafc682e7eab11b0ea7cd79cfe53ab4a7d9a..e183898dfb190414578ab5950a9a4258b8bc163f 100644 --- a/doc/user/admin_area/monitoring/health_check.md +++ b/doc/user/admin_area/monitoring/health_check.md @@ -1,6 +1,7 @@ # Health Check > **Notes:** + > - Liveness and readiness probes were [introduced][ce-10416] in GitLab 9.1. > - The `health_check` endpoint was [introduced][ce-3888] in GitLab 8.8 and was > be deprecated in GitLab 9.1. @@ -15,21 +16,19 @@ traffic until the system is ready or restart the container as needed. ## IP whitelist -To access monitoring resources, the client IP needs to be included in a whitelist. +To access monitoring resources, the requesting client IP needs to be included in a whitelist. [Read how to add IPs to a whitelist for the monitoring endpoints][admin]. ## Using the endpoints -With default whitelist settings, the probes can be accessed from localhost: +With default whitelist settings, the probes can be accessed from localhost using the following URLs: - `http://localhost/-/health` - `http://localhost/-/readiness` - `http://localhost/-/liveness` -The first endpoint, `/-/health/`, only checks whether the application server is running. It does --not verify the database or other services are running. A successful response will return -a 200 status code with the following message: +The first endpoint, `health`, only checks whether the application server is running. It does not verify the database or other services are running. A successful response will return a 200 status code with the following message: ``` GitLab OK @@ -37,9 +36,9 @@ GitLab OK The readiness and liveness probes will provide a report of system health in JSON format. -Readiness example output: +`readiness` probe example output: -``` +```json { "queues_check" : { "status" : "ok" @@ -59,9 +58,9 @@ Readiness example output: } ``` -Liveness example output: +`liveness` probe example output: -``` +```json { "cache_check" : { "status" : "ok" diff --git a/doc/user/group/clusters/index.md b/doc/user/group/clusters/index.md index 7422a30ff3cf71346d721bf7da8dc880a6fe09fa..8cdfb13a97b18c75d3eeca04496b705425d25ff4 100644 --- a/doc/user/group/clusters/index.md +++ b/doc/user/group/clusters/index.md @@ -56,7 +56,7 @@ group. That way you can have different clusters for different environments, like dev, staging, production, etc. Add another cluster similar to the first one and make sure to -[set an environment scope](#environment-scopes) that will +[set an environment scope](#environment-scopes-premium) that will differentiate the new cluster from the rest. ## Base domain diff --git a/doc/user/group/index.md b/doc/user/group/index.md index 300e0115c60da489f7476a7007c1c9a6b7726bf0..67b93ed66666b1f260761727b26cbd41125ccdbf 100644 --- a/doc/user/group/index.md +++ b/doc/user/group/index.md @@ -154,7 +154,7 @@ There are two different ways to add a new project to a group: ## Transfer projects into groups -Learn how to [transfer a project into a group](../project/index.md#transfer-an-existing-project-into-a-group). +Learn how to [transfer a project into a group](../project/settings/index.md#transferring-an-existing-project-into-another-namespace). ## Sharing a project with a group diff --git a/doc/user/group/subgroups/index.md b/doc/user/group/subgroups/index.md index b6f8f55978b838d7f58724fbe15d89ad5b3427d2..3cecefe11f5efcaba41740e3192fada3bacd7792 100644 --- a/doc/user/group/subgroups/index.md +++ b/doc/user/group/subgroups/index.md @@ -1,8 +1,8 @@ # Subgroups NOTE: **Note:** -[Introduced][ce-2772] in GitLab 9.0. Not available when using MySQL as external -database (support removed in GitLab 9.3 [due to performance reasons][issue]). +[Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/2772) in GitLab 9.0. Not available when using MySQL as external +database (support removed in GitLab 9.3 [due to performance reasons](https://gitlab.com/gitlab-org/gitlab-ce/issues/30472#note_27747600)). With subgroups (aka nested groups or hierarchical groups) you can have up to 20 levels of nested groups, which among other things can help you to: @@ -13,7 +13,7 @@ up to 20 levels of nested groups, which among other things can help you to: - **Organize large projects.** For large projects, subgroups makes it potentially easier to separate permissions on parts of the source code. - **Make it easier to manage people and control visibility.** Give people - different [permissions][] depending on their group [membership](#membership). + different [permissions](../../permissions.md#group-members-permissions) depending on their group [membership](#membership). ## Database Requirements @@ -80,9 +80,9 @@ structure. NOTE: **Note:** You need to be an Owner of a group in order to be able to create a subgroup. For -more information check the [permissions table][permissions]. +more information check the [permissions table](../../permissions.md#group-members-permissions). For a list of words that are not allowed to be used as group names see the -[reserved names][reserved]. +[reserved names](../../reserved_names.md). Users can always create subgroups if they are explicitly added as an Owner to a parent group even if group creation is disabled by an administrator in their settings. @@ -176,6 +176,6 @@ Here's a list of what you can't do with subgroups: `group/subgroup01/subgroup03`. [ce-2772]: https://gitlab.com/gitlab-org/gitlab-ce/issues/2772 -[permissions]: ../../permissions.md#group +[permissions]: ../../permissions.md#group-members-permissions [reserved]: ../../reserved_names.md [issue]: https://gitlab.com/gitlab-org/gitlab-ce/issues/30472#note_27747600 diff --git a/doc/user/profile/active_sessions.md b/doc/user/profile/active_sessions.md index 5119c0e30d056a83e5b9a7228fac9e822b880432..28e3f4904a9bbc9682544824e9c9c9e035c4a8d5 100644 --- a/doc/user/profile/active_sessions.md +++ b/doc/user/profile/active_sessions.md @@ -4,7 +4,7 @@ > in GitLab 10.8. GitLab lists all devices that have logged into your account. This allows you to -review the sessions and revoke any of it that you don't recognize. +review the sessions. ## Listing all active sessions @@ -12,9 +12,3 @@ review the sessions and revoke any of it that you don't recognize. 1. Navigate to the **Active Sessions** tab. ![Active sessions list](img/active_sessions_list.png) - -## Revoking a session - -1. Navigate to your [profile's](#profile-settings) **Settings > Active Sessions**. -1. Click on **Revoke** besides a session. The current session cannot be - revoked, as this would sign you out of GitLab. diff --git a/doc/user/profile/img/active_sessions_list.png b/doc/user/profile/img/active_sessions_list.png index 5d94dca69ccfded5543a6d1fc7413230faab0018..1e242ac47103c93d3f857db3ade6a05525317cb9 100644 Binary files a/doc/user/profile/img/active_sessions_list.png and b/doc/user/profile/img/active_sessions_list.png differ diff --git a/doc/user/profile/index.md b/doc/user/profile/index.md index a2b15d058d726b8e55d11ae7f1cc30882227a9f2..b216b9f255c41d98e0733e2c90af44e5b8e8ee08 100644 --- a/doc/user/profile/index.md +++ b/doc/user/profile/index.md @@ -39,7 +39,7 @@ From there, you can: - Manage [personal access tokens](personal_access_tokens.md) to access your account via API and authorized applications - Add and delete emails linked to your account - Choose which email to use for notifications, web-based commits, and display on your public profile -- Manage [SSH keys](../../ssh/README.md#ssh) to access your account via SSH +- Manage [SSH keys](../../ssh/README.md) to access your account via SSH - Manage your [preferences](preferences.md#syntax-highlighting-theme) to customize your own GitLab experience - [View your active sessions](active_sessions.md) and revoke any of them if necessary diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md index 0c514e005b2a865d1ea60e5024a31e144acfde63..6e64053a6ca7bd8745a821275bba70ce20f46b77 100644 --- a/doc/user/project/clusters/index.md +++ b/doc/user/project/clusters/index.md @@ -88,7 +88,7 @@ To add an existing Kubernetes cluster to your project: 1. Click **Add an existing Kubernetes cluster** and fill in the details: - **Kubernetes cluster name** (required) - The name you wish to give the cluster. - **Environment scope** (required) - The - [associated environment](#setting-the-environment-scope) to this cluster. + [associated environment](#setting-the-environment-scope-premium) to this cluster. - **API URL** (required) - It's the URL that GitLab uses to access the Kubernetes API. Kubernetes exposes several APIs, we want the "base" URL that is common to all of them, @@ -473,7 +473,7 @@ project. That way you can have different clusters for different environments, like dev, staging, production, etc. Simply add another cluster, like you did the first time, and make sure to -[set an environment scope](#setting-the-environment-scope) that will +[set an environment scope](#setting-the-environment-scope-premium) that will differentiate the new cluster with the rest. ## Setting the environment scope **[PREMIUM]** diff --git a/doc/user/project/clusters/serverless/index.md b/doc/user/project/clusters/serverless/index.md index 2871510d5edcbc3007ab914d1db5b9e4fbe9052b..856ae03f4bc46ec03ffe7c75d23dc173f9863e47 100644 --- a/doc/user/project/clusters/serverless/index.md +++ b/doc/user/project/clusters/serverless/index.md @@ -114,28 +114,35 @@ Follow these steps to deploy a function using the Node.js runtime to your Knativ - Public, continue to the next step. - Private, you will need to [create a GitLab deploy token](../../deploy_tokens/index.md#creating-a-deploy-token) with `gitlab-deploy-token` as the name and the `read_registry` scope. -1. `.gitlab-ci.yml`: This template allows to define the stage, environment, and - image to be used for your functions. It must be included at the root of your repository: +1. `.gitlab-ci.yml`: this defines a pipeline used to deploy your functions. + It must be included at the root of your repository: ```yaml - stages: - - deploy + include: + template: Serverless.gitlab-ci.yml functions: - stage: deploy - environment: test - image: gcr.io/triggermesh/tm:v0.0.9 - script: - - tm -n "$KUBE_NAMESPACE" set registry-auth gitlab-registry --registry "$CI_REGISTRY" --username "$CI_REGISTRY_USER" --password "$CI_JOB_TOKEN" --push - - tm -n "$KUBE_NAMESPACE" set registry-auth gitlab-registry --registry "$CI_REGISTRY" --username "$CI_DEPLOY_USER" --password "$CI_DEPLOY_PASSWORD" --pull - - tm -n "$KUBE_NAMESPACE" deploy --wait - + extends: .serverless:deploy:functions + environment: production ``` - The `gitlab-ci.yml` template creates a `Deploy` stage with a `functions` job that invokes the `tm` CLI with the required parameters. + This `.gitlab-ci.yml` creates a `functions` job that invokes some + predefined commands to deploy your functions to your cluster. + + `Serverless.gitlab-ci.yml` is a template that allows customization. + You can either import it with `include` parameter and use `extends` to + customize your jobs, or you can inline the entire template by choosing it + from **Apply a template** dropdown when editing the `.gitlab-ci.yml` file through + the user interface. + +2. `serverless.yml`: this file contains the metadata for your functions, + such as name, runtime, and environment. -2. `serverless.yml`: This file contains the metadata for your functions, - such as name, runtime, and environment. It must be included at the root of your repository. The following is a sample `echo` function which shows the required structure for the file. You can find the relevant files for this project in the [functions example project](https://gitlab.com/knative-examples/functions). + It must be included at the root of your repository. + The following is a sample `echo` function which shows the required structure + for the file. + + You can find the relevant files for this project in the [functions example project](https://gitlab.com/knative-examples/functions). ```yaml service: my-functions @@ -234,32 +241,22 @@ Add the following `.gitlab-ci.yml` to the root of your repository (you may skip this step if you've previously cloned the sample [Knative Ruby App](https://gitlab.com/knative-examples/knative-ruby-app) mentioned above): ```yaml -stages: - - build - - deploy +include: + template: Serverless.gitlab-ci.yml build: - stage: build - image: - name: gcr.io/kaniko-project/executor:debug - entrypoint: [""] - only: - - master - script: - - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json - - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination $CI_REGISTRY_IMAGE + extends: .serverless:build:image deploy: - stage: deploy - image: gcr.io/triggermesh/tm@sha256:e3ee74db94d215bd297738d93577481f3e4db38013326c90d57f873df7ab41d5 - only: - - master - environment: production - script: - - echo "$CI_REGISTRY_IMAGE" - - tm -n "$KUBE_NAMESPACE" --config "$KUBECONFIG" deploy service "$CI_PROJECT_NAME" --from-image "$CI_REGISTRY_IMAGE" --wait + extends: .serverless:deploy:image ``` +`Serverless.gitlab-ci.yml` is a template that allows customization. +You can either import it with `include` parameter and use `extends` to +customize your jobs, or you can inline the entire template by choosing it +from **Apply a template** dropdown when editing the `.gitlab-ci.yml` file through +the user interface. + ### Deploy the application with Knative With all the pieces in place, the next time a CI pipeline runs, the Knative application will be deployed. Navigate to diff --git a/doc/user/project/import/svn.md b/doc/user/project/import/svn.md index a5923986292d4e2f1ecbf2344bac60cf27410c0d..4825b005a85e41649f86303136e69ff48e82e798 100644 --- a/doc/user/project/import/svn.md +++ b/doc/user/project/import/svn.md @@ -8,7 +8,7 @@ between the two, for more information consult your favorite search engine. There are two approaches to SVN to Git migration: -1. [Git/SVN Mirror](#smooth-migration-with-a-git-svn-mirror-using-subgit) which: +1. [Git/SVN Mirror](#smooth-migration-with-a-gitsvn-mirror-using-subgit) which: - Makes the GitLab repository to mirror the SVN project. - Git and SVN repositories are kept in sync; you can use either one. - Smoothens the migration process and allows to manage migration risks. diff --git a/doc/user/project/index.md b/doc/user/project/index.md index 0dc50d28cb0ec4dc5a04898409810999d6e5a7c8..4148310dc98e323ad39c268d25d11dc3e0924d38 100644 --- a/doc/user/project/index.md +++ b/doc/user/project/index.md @@ -137,7 +137,7 @@ and Git push/pull redirects. Depending on the situation, different things apply. When [renaming a user](../profile/index.md#changing-your-username), -[changing a group path](../group/index.md#changing-a-group-s-path) or [renaming a repository](settings/index.md#renaming-a-repository): +[changing a group path](../group/index.md#changing-a-groups-path) or [renaming a repository](settings/index.md#renaming-a-repository): - Existing web URLs for the namespace and anything under it (e.g., projects) will redirect to the new URLs. diff --git a/doc/user/project/integrations/mattermost_slash_commands.md b/doc/user/project/integrations/mattermost_slash_commands.md index e031dcad2c30aee26e9baa7797d0d04b8097da7c..9c69437537a48d007cf9cedc597abd9aa89c998c 100644 --- a/doc/user/project/integrations/mattermost_slash_commands.md +++ b/doc/user/project/integrations/mattermost_slash_commands.md @@ -152,7 +152,7 @@ trigger word followed by help. Example: /gitlab help ## Permissions The permissions to run the [available commands](#available-slash-commands) derive from -the [permissions you have on the project](../../permissions.md#project). +the [permissions you have on the project](../../permissions.md#project-members-permissions). ## Further reading diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md index ed289b0c4eb83a992f67c07c11343bae78ca317b..26989e2a8a4f69ebdaa13abae155573c6880b639 100644 --- a/doc/user/project/integrations/prometheus.md +++ b/doc/user/project/integrations/prometheus.md @@ -120,7 +120,7 @@ If the "No data found" screen continues to appear, it could be due to: - No successful deployments have occurred to this environment. - Prometheus does not have performance data for this environment, or the metrics are not labeled correctly. To test this, connect to the Prometheus server and - [run a query](#gitlab-prometheus-queries), replacing `$CI_ENVIRONMENT_SLUG` + [run a query](prometheus_library/kubernetes.html#metrics-supported), replacing `$CI_ENVIRONMENT_SLUG` with the name of your environment. [autodeploy]: ../../../ci/autodeploy/index.md diff --git a/doc/user/project/pages/getting_started_part_one.md b/doc/user/project/pages/getting_started_part_one.md index 9a95fb7096467dbd4f4499017d1ba013d8d0d5cb..f1e2771dcb970a882dfa8314611447cfcf05a74c 100644 --- a/doc/user/project/pages/getting_started_part_one.md +++ b/doc/user/project/pages/getting_started_part_one.md @@ -59,7 +59,7 @@ which is highly recommendable and much faster than hardcoding. If you set up a GitLab Pages project on GitLab.com, it will automatically be accessible under a -[subdomain of `namespace.gitlab.io`](introduction.md#gitlab-pages-on-gitlab-com). +[subdomain of `namespace.gitlab.io`](introduction.md#gitlab-pages-on-gitlabcom). The `namespace` is defined by your username on GitLab.com, or the group name you created this project under. diff --git a/doc/user/project/pages/getting_started_part_three.md b/doc/user/project/pages/getting_started_part_three.md index daae2f4b5a3a11bf51907489c859589cb995dda7..756b8b698c71cb7f99360ecba3babaeec2fa6935 100644 --- a/doc/user/project/pages/getting_started_part_three.md +++ b/doc/user/project/pages/getting_started_part_three.md @@ -20,13 +20,13 @@ you should consider first, which we'll cover in this guide: 1. Either if you're adding a **root domain** or a **subdomain**, for which you'll need to set up [DNS records](#dns-records) -1. Whether you want to add an [SSL/TLS certificate](#ssl-tls-certificates) or not +1. Whether you want to add an [SSL/TLS certificate](#ssltls-certificates) or not To finish the association, you need to [add your domain to your project's Pages settings](#add-your-custom-domain-to-gitlab-pages-settings). Let's start from the beginning with [DNS records](#dns-records). If you already know how they work and want to skip the introduction to DNS, -you may be interested in skipping it until the [TL;DR](#tl-dr) section below. +you may be interested in skipping it until the [TL;DR](#tldr) section below. ### DNS Records @@ -148,7 +148,7 @@ verify your domain's ownership with a TXT record: Once you've set the DNS record, you'll need navigate to your project's **Setting > Pages** and click **+ New domain** to add your custom domain to -GitLab Pages. You can choose whether to add an [SSL/TLS certificate](#ssl-tls-certificates) +GitLab Pages. You can choose whether to add an [SSL/TLS certificate](#ssltls-certificates) to make your website accessible under HTTPS or leave it blank. If don't add a certificate, your site will be accessible only via HTTP: diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md index e0b78753e212d8f8831c2c8a198444f59a56d016..ee471fa6534ae2509046d49d0a543ed8a15bdf94 100644 --- a/doc/user/project/pages/index.md +++ b/doc/user/project/pages/index.md @@ -141,7 +141,7 @@ You can also take some optional further steps: _Advanced options:_ - [Use a custom domain](getting_started_part_three.md#adding-your-custom-domain-to-gitlab-pages) -- Apply [SSL/TLS certification](getting_started_part_three.md#ssl-tls-certificates) to your custom domain +- Apply [SSL/TLS certification](getting_started_part_three.md#ssltls-certificates) to your custom domain ## Explore GitLab Pages diff --git a/doc/user/project/pages/introduction.md b/doc/user/project/pages/introduction.md index 23eb88fd305c2e73f57f7b7cf3eaa273241f4c55..6bb58689f38f670823c9da58e0426129e67d6b54 100644 --- a/doc/user/project/pages/introduction.md +++ b/doc/user/project/pages/introduction.md @@ -11,7 +11,7 @@ With GitLab Pages you can host for free your static websites on GitLab. Combined with the power of [GitLab CI] and the help of [GitLab Runner] you can deploy static pages for your individual projects, your user or your group. -Read [GitLab Pages on GitLab.com](#gitlab-pages-on-gitlab-com) for specific +Read [GitLab Pages on GitLab.com](#gitlab-pages-on-gitlabcom) for specific information, if you are using GitLab.com to host your website. ## Getting started with GitLab Pages domains @@ -410,7 +410,7 @@ file for both the `/data` and `/data/` URL paths. ### Add a custom domain to your Pages website For a complete guide on Pages domains, read through the article -[GitLab Pages from A to Z: Part 3 - Setting Up Custom Domains - DNS Records and SSL/TLS Certificates](getting_started_part_three.md#setting-up-custom-domains-dns-records-and-ssl-tls-certificates) +[GitLab Pages from A to Z: Part 3 - GitLab Pages custom domains and SSL/TLS Certificates](getting_started_part_three.md) If this setting is enabled by your GitLab administrator, you should be able to see the **New Domain** button when visiting your project's settings through the @@ -451,7 +451,7 @@ private key when adding a new domain. ![Pages upload cert](img/pages_upload_cert.png) For a complete guide on Pages domains, read through the article -[GitLab Pages from A to Z: Part 3 - Setting Up Custom Domains - DNS Records and SSL/TLS Certificates](getting_started_part_three.md#setting-up-custom-domains-dns-records-and-ssl-tls-certificates) +[GitLab Pages from A to Z: Part 3 - GitLab Pages custom domains and SSL/TLS Certificates](getting_started_part_three.md) ### Custom error codes pages diff --git a/doc/user/project/pipelines/job_artifacts.md b/doc/user/project/pipelines/job_artifacts.md index bf939dbdaa3601df1c504ece96a2ba4215f94de4..8b57129c9e19d9e0ef1715ae2f458fef10bde63d 100644 --- a/doc/user/project/pipelines/job_artifacts.md +++ b/doc/user/project/pipelines/job_artifacts.md @@ -38,7 +38,7 @@ turn are defined with the `paths` keyword. All paths to files and directories are relative to the repository that was cloned during the build. These uploaded artifacts will be kept in GitLab for 1 week as defined by the `expire_in` definition. You have the option to keep the artifacts from expiring via the -[web interface](#browsing-job-artifacts). If the expiry time is not defined, +[web interface](#browsing-artifacts). If the expiry time is not defined, it defaults to the [instance wide setting](../../admin_area/settings/continuous_integration.md#default-artifacts-expiration-core-only). diff --git a/doc/user/project/repository/branches/index.md b/doc/user/project/repository/branches/index.md index f05554ffc5b68daf06fd3eca78e5f3fc57ea48cc..13e4f2ce16343f85742b9477779f300668421f09 100644 --- a/doc/user/project/repository/branches/index.md +++ b/doc/user/project/repository/branches/index.md @@ -11,7 +11,7 @@ Read through GiLab's branching documentation: See also: - [Branches API](../../../../api/branches.md), for information on operating on repository branches using the GitLab API. -- [GitLab Flow](../../../../university/training/gitlab_flow.md#gitlab-flow). Use the best of GitLab for your branching strategies. +- [GitLab Flow](../../../../university/training/gitlab_flow.md). Use the best of GitLab for your branching strategies. - [Getting started with Git](../../../../topics/git/index.md) and GitLab. ## Default branch diff --git a/doc/user/search/index.md b/doc/user/search/index.md index 770cef42995f7dbf2abf5b5fd3095237a69c5c4f..705983cce2a91cb11151bb2f3d73bd6de7bea7ef 100644 --- a/doc/user/search/index.md +++ b/doc/user/search/index.md @@ -96,7 +96,7 @@ quickly access issues and merge requests created or assigned to you within that Your [todos](../../workflow/todos.md#gitlab-todos) can be searched by "to do" and "done". You can [filter](../../workflow/todos.md#filtering-your-todos) them per project, author, type, and action. Also, you can sort them by -[**Label priority**](../../user/project/labels.md#prioritize-labels), +[**Label priority**](../../user/project/labels.md#label-priority), **Last created** and **Oldest created**. ## Projects diff --git a/jest.config.js b/jest.config.js index 4e346005b8a79df80da0383d552cf2f2aff9d5c9..efbf2e602c148f6ffa065dc9e3a0b3f140610085 100644 --- a/jest.config.js +++ b/jest.config.js @@ -18,7 +18,6 @@ module.exports = { moduleNameMapper: { '^~(.*)$': '/app/assets/javascripts$1', '^ee(.*)$': '/ee/app/assets/javascripts$1', - '^fixtures(.*)$': '/spec/javascripts/fixtures$1', '^helpers(.*)$': '/spec/frontend/helpers$1', '^vendor(.*)$': '/vendor/assets/javascripts$1', '\\.(jpg|jpeg|png|svg)$': '/spec/frontend/__mocks__/file_mock.js', diff --git a/lib/api/commits.rb b/lib/api/commits.rb index 8defc59224d7f32f0581eeb9af84046803a7785a..d0a9debda5b2b7d353e3d7f81f1975a6fa6dca35 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -318,10 +318,18 @@ module API use :pagination end get ':id/repository/commits/:sha/merge_requests', requirements: API::COMMIT_ENDPOINT_REQUIREMENTS do + authorize! :read_merge_request, user_project + commit = user_project.commit(params[:sha]) not_found! 'Commit' unless commit - present paginate(commit.merge_requests), with: Entities::MergeRequestBasic + commit_merge_requests = MergeRequestsFinder.new( + current_user, + project_id: user_project.id, + commit_sha: commit.sha + ).execute + + present paginate(commit_merge_requests), with: Entities::MergeRequestBasic end desc "Get a commit's GPG signature" do diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 18f15632f2b5048ba80da719c0e29c4923182f3f..5176e9713c13cfc61b6fff4de24cb1081b56e0f2 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -156,7 +156,7 @@ module API class BasicProjectDetails < ProjectIdentity include ::API::ProjectsRelationBuilder - expose :default_branch + expose :default_branch, if: -> (project, options) { Ability.allowed?(options[:current_user], :download_code, project) } # Avoids an N+1 query: https://github.com/mbleigh/acts-as-taggable-on/issues/91#issuecomment-168273770 expose :tag_list do |project| # project.tags.order(:name).pluck(:name) is the most suitable option @@ -261,7 +261,7 @@ module API expose :open_issues_count, if: lambda { |project, options| project.feature_available?(:issues, options[:current_user]) } expose :runners_token, if: lambda { |_project, options| options[:user_can_admin_project] } expose :public_builds, as: :public_jobs - expose :ci_config_path + expose :ci_config_path, if: -> (project, options) { Ability.allowed?(options[:current_user], :download_code, project) } expose :shared_with_groups do |project, options| SharedGroup.represent(project.project_group_links, options) end @@ -270,8 +270,9 @@ module API expose :only_allow_merge_if_all_discussions_are_resolved expose :printing_merge_request_link_enabled expose :merge_method - - expose :statistics, using: 'API::Entities::ProjectStatistics', if: :statistics + expose :statistics, using: 'API::Entities::ProjectStatistics', if: -> (project, options) { + options[:statistics] && Ability.allowed?(options[:current_user], :download_code, project) + } # rubocop: disable CodeReuse/ActiveRecord def self.preload_relation(projects_relation, options = {}) diff --git a/lib/api/environments.rb b/lib/api/environments.rb index 0278c6c54a518bee4f438c8f7bbcf9a8c250f581..5b0f3b914cbd3073337efb0cfdc9891afb171ca2 100644 --- a/lib/api/environments.rb +++ b/lib/api/environments.rb @@ -22,7 +22,7 @@ module API get ':id/environments' do authorize! :read_environment, user_project - present paginate(user_project.environments), with: Entities::Environment + present paginate(user_project.environments), with: Entities::Environment, current_user: current_user end desc 'Creates a new environment' do @@ -40,7 +40,7 @@ module API environment = user_project.environments.create(declared_params) if environment.persisted? - present environment, with: Entities::Environment + present environment, with: Entities::Environment, current_user: current_user else render_validation_error!(environment) end @@ -63,7 +63,7 @@ module API update_params = declared_params(include_missing: false).extract!(:name, :external_url) if environment.update(update_params) - present environment, with: Entities::Environment + present environment, with: Entities::Environment, current_user: current_user else render_validation_error!(environment) end @@ -99,7 +99,7 @@ module API environment.stop_with_action!(current_user) status 200 - present environment, with: Entities::Environment + present environment, with: Entities::Environment, current_user: current_user end end end diff --git a/lib/api/helpers/notes_helpers.rb b/lib/api/helpers/notes_helpers.rb index 216b2c457419a338b974e5f9f43b99493f75de3d..795dca5cf0300ddfd8c36ac82796e8aa03e6eb39 100644 --- a/lib/api/helpers/notes_helpers.rb +++ b/lib/api/helpers/notes_helpers.rb @@ -70,14 +70,7 @@ module API def find_noteable(parent, noteables_str, noteable_id) noteable = public_send("find_#{parent}_#{noteables_str.singularize}", noteable_id) # rubocop:disable GitlabSecurity/PublicSend - readable = - if noteable.is_a?(Commit) - # for commits there is not :read_commit policy, check if user - # has :read_note permission on the commit's project - can?(current_user, :read_note, user_project) - else - can?(current_user, noteable_read_ability_name(noteable), noteable) - end + readable = can?(current_user, noteable_read_ability_name(noteable), noteable) return not_found!(noteables_str) unless readable @@ -89,12 +82,11 @@ module API end def create_note(noteable, opts) - policy_object = noteable.is_a?(Commit) ? user_project : noteable - authorize!(:create_note, policy_object) + authorize!(:create_note, noteable) parent = noteable_parent(noteable) - opts.delete(:created_at) unless current_user.can?(:set_note_created_at, policy_object) + opts.delete(:created_at) unless current_user.can?(:set_note_created_at, noteable) opts[:updated_at] = opts[:created_at] if opts[:created_at] diff --git a/lib/api/projects.rb b/lib/api/projects.rb index b23fe6cd4e790e7c3af9eb7b3c3c3d0f0eb06b8f..91501ba4d36df30527958d42a1508db6cfd02dcc 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -184,7 +184,8 @@ module API if project.saved? present project, with: Entities::Project, - user_can_admin_project: can?(current_user, :admin_project, project) + user_can_admin_project: can?(current_user, :admin_project, project), + current_user: current_user else if project.errors[:limit_reached].present? error!(project.errors[:limit_reached], 403) @@ -217,7 +218,8 @@ module API if project.saved? present project, with: Entities::Project, - user_can_admin_project: can?(current_user, :admin_project, project) + user_can_admin_project: can?(current_user, :admin_project, project), + current_user: current_user else render_validation_error!(project) end @@ -281,7 +283,8 @@ module API conflict!(forked_project.errors.messages) else present forked_project, with: Entities::Project, - user_can_admin_project: can?(current_user, :admin_project, forked_project) + user_can_admin_project: can?(current_user, :admin_project, forked_project), + current_user: current_user end end @@ -330,7 +333,8 @@ module API if result[:status] == :success present user_project, with: Entities::Project, - user_can_admin_project: can?(current_user, :admin_project, user_project) + user_can_admin_project: can?(current_user, :admin_project, user_project), + current_user: current_user else render_validation_error!(user_project) end @@ -344,7 +348,7 @@ module API ::Projects::UpdateService.new(user_project, current_user, archived: true).execute - present user_project, with: Entities::Project + present user_project, with: Entities::Project, current_user: current_user end desc 'Unarchive a project' do @@ -355,7 +359,7 @@ module API ::Projects::UpdateService.new(@project, current_user, archived: false).execute - present user_project, with: Entities::Project + present user_project, with: Entities::Project, current_user: current_user end desc 'Star a project' do @@ -368,7 +372,7 @@ module API current_user.toggle_star(user_project) user_project.reload - present user_project, with: Entities::Project + present user_project, with: Entities::Project, current_user: current_user end end @@ -380,7 +384,7 @@ module API current_user.toggle_star(user_project) user_project.reload - present user_project, with: Entities::Project + present user_project, with: Entities::Project, current_user: current_user else not_modified! end @@ -420,7 +424,7 @@ module API result = ::Projects::ForkService.new(fork_from_project, current_user).execute(user_project) if result - present user_project.reload, with: Entities::Project + present user_project.reload, with: Entities::Project, current_user: current_user else render_api_error!("Project already forked", 409) if user_project.forked? end @@ -442,27 +446,24 @@ module API end params do requires :group_id, type: Integer, desc: 'The ID of a group' - requires :group_access, type: Integer, values: Gitlab::Access.values, desc: 'The group access level' + requires :group_access, type: Integer, values: Gitlab::Access.values, as: :link_group_access, desc: 'The group access level' optional :expires_at, type: Date, desc: 'Share expiration date' end post ":id/share" do authorize! :admin_project, user_project group = Group.find_by_id(params[:group_id]) - unless group && can?(current_user, :read_group, group) - not_found!('Group') - end - unless user_project.allowed_to_share_with_group? break render_api_error!("The project sharing with group is disabled", 400) end - link = user_project.project_group_links.new(declared_params(include_missing: false)) + result = ::Projects::GroupLinks::CreateService.new(user_project, current_user, declared_params(include_missing: false)) + .execute(group) - if link.save - present link, with: Entities::ProjectGroupLink + if result[:status] == :success + present result[:link], with: Entities::ProjectGroupLink else - render_api_error!(link.errors.full_messages.first, 409) + render_api_error!(result[:message], result[:http_status]) end end @@ -526,7 +527,7 @@ module API result = ::Projects::TransferService.new(user_project, current_user).execute(namespace) if result - present user_project, with: Entities::Project + present user_project, with: Entities::Project, current_user: current_user else render_api_error!("Failed to transfer project #{user_project.errors.messages}", 400) end diff --git a/lib/api/release/links.rb b/lib/api/release/links.rb index e3072684ef73c1f81be95332ed57a4fcb3a97e8a..5d1b40e3bff5be5df451ae4393870f733db5726b 100644 --- a/lib/api/release/links.rb +++ b/lib/api/release/links.rb @@ -8,6 +8,8 @@ module API RELEASE_ENDPOINT_REQUIREMETS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS .merge(tag_name: API::NO_SLASH_URL_PART_REGEX) + before { authorize! :read_release, user_project } + params do requires :id, type: String, desc: 'The ID of a project' end diff --git a/lib/banzai/pipeline/markup_pipeline.rb b/lib/banzai/pipeline/markup_pipeline.rb index db79a22549c862810698e23744cc07b57273292d..ceba082cd4f82eb544fcd3605ea164a7d3e52125 100644 --- a/lib/banzai/pipeline/markup_pipeline.rb +++ b/lib/banzai/pipeline/markup_pipeline.rb @@ -7,7 +7,8 @@ module Banzai @filters ||= FilterArray[ Filter::SanitizationFilter, Filter::ExternalLinkFilter, - Filter::PlantumlFilter + Filter::PlantumlFilter, + Filter::SyntaxHighlightFilter ] end end diff --git a/lib/constraints/project_url_constrainer.rb b/lib/constraints/project_url_constrainer.rb index eadfbf7bc0102db90fdbc2b4f15d0f5a984b4d1c..d41490d2ebde2a37963364c586d487a2906f9d0a 100644 --- a/lib/constraints/project_url_constrainer.rb +++ b/lib/constraints/project_url_constrainer.rb @@ -2,12 +2,13 @@ module Constraints class ProjectUrlConstrainer - def matches?(request) + def matches?(request, existence_check: true) namespace_path = request.params[:namespace_id] project_path = request.params[:project_id] || request.params[:id] full_path = [namespace_path, project_path].join('/') return false unless ProjectPathValidator.valid_path?(full_path) + return true unless existence_check # We intentionally allow SELECT(*) here so result of this query can be used # as cache for further Project.find_by_full_path calls within request diff --git a/lib/gitlab/auth/ldap/config.rb b/lib/gitlab/auth/ldap/config.rb index 7ceb96f502bee940581157f8562cac076022ec6b..dddba85e62971e3c82a0480350f3bca1dd13d709 100644 --- a/lib/gitlab/auth/ldap/config.rb +++ b/lib/gitlab/auth/ldap/config.rb @@ -75,7 +75,8 @@ module Gitlab encryption: options['encryption'], filter: omniauth_user_filter, name_proc: name_proc, - disable_verify_certificates: !options['verify_certificates'] + disable_verify_certificates: !options['verify_certificates'], + tls_options: tls_options ) if has_auth? @@ -85,9 +86,6 @@ module Gitlab ) end - opts[:ca_file] = options['ca_file'] if options['ca_file'].present? - opts[:ssl_version] = options['ssl_version'] if options['ssl_version'].present? - opts end @@ -196,24 +194,28 @@ module Gitlab end def encryption_options - method = translate_method(options['encryption']) + method = translate_method return nil unless method { method: method, - tls_options: tls_options(method) + tls_options: tls_options } end - def translate_method(method_from_config) - NET_LDAP_ENCRYPTION_METHOD[method_from_config.to_sym] + def translate_method + NET_LDAP_ENCRYPTION_METHOD[options['encryption']&.to_sym] end - def tls_options(method) - return { verify_mode: OpenSSL::SSL::VERIFY_NONE } unless method + def tls_options + return @tls_options if defined?(@tls_options) + + method = translate_method + return nil unless method - opts = if options['verify_certificates'] - OpenSSL::SSL::SSLContext::DEFAULT_PARAMS + opts = if options['verify_certificates'] && method != 'plain' + # Dup so we don't accidentally overwrite the constant + OpenSSL::SSL::SSLContext::DEFAULT_PARAMS.dup else # It is important to explicitly set verify_mode for two reasons: # 1. The behavior of OpenSSL is undefined when verify_mode is not set. @@ -222,10 +224,35 @@ module Gitlab { verify_mode: OpenSSL::SSL::VERIFY_NONE } end - opts[:ca_file] = options['ca_file'] if options['ca_file'].present? - opts[:ssl_version] = options['ssl_version'] if options['ssl_version'].present? + opts.merge!(custom_tls_options) - opts + @tls_options = opts + end + + def custom_tls_options + return {} unless options['tls_options'] + + # Dup so we don't overwrite the original value + custom_options = options['tls_options'].dup.delete_if { |_, value| value.nil? || value.blank? } + custom_options.symbolize_keys! + + if custom_options[:cert] + begin + custom_options[:cert] = OpenSSL::X509::Certificate.new(custom_options[:cert]) + rescue OpenSSL::X509::CertificateError => e + Rails.logger.error "LDAP TLS Options 'cert' is invalid for provider #{provider}: #{e.message}" + end + end + + if custom_options[:key] + begin + custom_options[:key] = OpenSSL::PKey.read(custom_options[:key]) + rescue OpenSSL::PKey::PKeyError => e + Rails.logger.error "LDAP TLS Options 'key' is invalid for provider #{provider}: #{e.message}" + end + end + + custom_options end def auth_options diff --git a/lib/gitlab/auth/omniauth_identity_linker_base.rb b/lib/gitlab/auth/omniauth_identity_linker_base.rb index 253445570f2bc7d9723b019338c641c23bdcf34b..c620fc5d6bd7464378688b37aacbd6aff6d61911 100644 --- a/lib/gitlab/auth/omniauth_identity_linker_base.rb +++ b/lib/gitlab/auth/omniauth_identity_linker_base.rb @@ -12,7 +12,7 @@ module Gitlab end def link - save if identity.new_record? + save if unlinked? end def changed? @@ -35,6 +35,10 @@ module Gitlab @changed = identity.save end + def unlinked? + identity.new_record? + end + # rubocop: disable CodeReuse/ActiveRecord def identity @identity ||= current_user.identities diff --git a/lib/gitlab/ci/templates/Serverless.gitlab-ci.yml b/lib/gitlab/ci/templates/Serverless.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..4f3d08d98fe96b0ffc3c2fd75e78ec294164ad0d --- /dev/null +++ b/lib/gitlab/ci/templates/Serverless.gitlab-ci.yml @@ -0,0 +1,41 @@ +# GitLab Serverless template + +image: alpine:latest + +stages: + - build + - deploy + +.serverless:build:image: + variables: + DOCKERFILE: "Dockerfile" + stage: build + image: + name: gcr.io/kaniko-project/executor:debug + entrypoint: [""] + only: + refs: + - master + script: + - echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json + - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/$DOCKERFILE --destination $CI_REGISTRY_IMAGE + +.serverless:deploy:image: + stage: deploy + image: gcr.io/triggermesh/tm@sha256:e3ee74db94d215bd297738d93577481f3e4db38013326c90d57f873df7ab41d5 + only: + refs: + - master + environment: development + script: + - echo "$CI_REGISTRY_IMAGE" + - tm -n "$KUBE_NAMESPACE" --config "$KUBECONFIG" deploy service "$CI_PROJECT_NAME" --from-image "$CI_REGISTRY_IMAGE" --wait + +.serverless:deploy:functions: + stage: deploy + environment: development + image: gcr.io/triggermesh/tm:v0.0.9 + script: + - tm -n "$KUBE_NAMESPACE" set registry-auth gitlab-registry --registry "$CI_REGISTRY" --username "$CI_REGISTRY_USER" --password "$CI_JOB_TOKEN" --push + - tm -n "$KUBE_NAMESPACE" set registry-auth gitlab-registry --registry "$CI_REGISTRY" --username "$CI_DEPLOY_USER" --password "$CI_DEPLOY_PASSWORD" --pull + - tm -n "$KUBE_NAMESPACE" deploy --wait diff --git a/lib/gitlab/dependency_linker/base_linker.rb b/lib/gitlab/dependency_linker/base_linker.rb index ac2efe598b43d4147d4adf29967c6d2234bb724d..ffad00fa7d74d5054fdc6c303a8ca737ed2ef997 100644 --- a/lib/gitlab/dependency_linker/base_linker.rb +++ b/lib/gitlab/dependency_linker/base_linker.rb @@ -4,6 +4,7 @@ module Gitlab module DependencyLinker class BaseLinker URL_REGEX = %r{https?://[^'" ]+}.freeze + GIT_INVALID_URL_REGEX = /^git\+#{URL_REGEX}/.freeze REPO_REGEX = %r{[^/'" ]+/[^/'" ]+}.freeze class_attribute :file_type @@ -29,8 +30,25 @@ module Gitlab highlighted_lines.join.html_safe end + def external_url(name, external_ref) + return if external_ref =~ GIT_INVALID_URL_REGEX + + case external_ref + when /\A#{URL_REGEX}\z/ + external_ref + when /\A#{REPO_REGEX}\z/ + github_url(external_ref) + else + package_url(name) + end + end + private + def package_url(_name) + raise NotImplementedError + end + def link_dependencies raise NotImplementedError end diff --git a/lib/gitlab/dependency_linker/composer_json_linker.rb b/lib/gitlab/dependency_linker/composer_json_linker.rb index 22d2bead891ab5684e30cc03b8ba0d5d25f107ca..4b8862b31ee6ecdf37820d258c44cd9518b3984d 100644 --- a/lib/gitlab/dependency_linker/composer_json_linker.rb +++ b/lib/gitlab/dependency_linker/composer_json_linker.rb @@ -8,8 +8,8 @@ module Gitlab private def link_packages - link_packages_at_key("require", &method(:package_url)) - link_packages_at_key("require-dev", &method(:package_url)) + link_packages_at_key("require") + link_packages_at_key("require-dev") end def package_url(name) diff --git a/lib/gitlab/dependency_linker/gemfile_linker.rb b/lib/gitlab/dependency_linker/gemfile_linker.rb index 8ab219c49624bf7a676d7ea8aee8c20354d8a4bb..c6e02248b0a05ebed81769bfc97cb458e1f0681c 100644 --- a/lib/gitlab/dependency_linker/gemfile_linker.rb +++ b/lib/gitlab/dependency_linker/gemfile_linker.rb @@ -3,8 +3,14 @@ module Gitlab module DependencyLinker class GemfileLinker < MethodLinker + class_attribute :package_keyword + + self.package_keyword = :gem self.file_type = :gemfile + GITHUB_REGEX = /(github:|:github\s*=>)\s*['"](?[^'"]+)['"]/.freeze + GIT_REGEX = /(git:|:git\s*=>)\s*['"](?#{URL_REGEX})['"]/.freeze + private def link_dependencies @@ -14,21 +20,35 @@ module Gitlab def link_urls # Link `github: "user/repo"` to https://github.com/user/repo - link_regex(/(github:|:github\s*=>)\s*['"](?[^'"]+)['"]/, &method(:github_url)) + link_regex(GITHUB_REGEX, &method(:github_url)) # Link `git: "https://gitlab.example.com/user/repo"` to https://gitlab.example.com/user/repo - link_regex(/(git:|:git\s*=>)\s*['"](?#{URL_REGEX})['"]/, &:itself) + link_regex(GIT_REGEX, &:itself) # Link `source "https://rubygems.org"` to https://rubygems.org link_method_call('source', URL_REGEX, &:itself) end def link_packages - # Link `gem "package_name"` to https://rubygems.org/gems/package_name - link_method_call('gem') do |name| - "https://rubygems.org/gems/#{name}" + packages = parse_packages + + return if packages.blank? + + packages.each do |package| + link_method_call('gem', package.name) do + external_url(package.name, package.external_ref) + end end end + + def package_url(name) + "https://rubygems.org/gems/#{name}" + end + + def parse_packages + parser = Gitlab::DependencyLinker::Parser::Gemfile.new(plain_text) + parser.parse(keyword: self.class.package_keyword) + end end end end diff --git a/lib/gitlab/dependency_linker/gemspec_linker.rb b/lib/gitlab/dependency_linker/gemspec_linker.rb index b924ea86d892a6daa18c84e8282a850633cb5692..94c2b375cf9f8f94b5ff272fa93d8a5e6d436ef0 100644 --- a/lib/gitlab/dependency_linker/gemspec_linker.rb +++ b/lib/gitlab/dependency_linker/gemspec_linker.rb @@ -11,7 +11,7 @@ module Gitlab link_method_call('homepage', URL_REGEX, &:itself) link_method_call('license', &method(:license_url)) - link_method_call(%w[name add_dependency add_runtime_dependency add_development_dependency]) do |name| + link_method_call(%w[add_dependency add_runtime_dependency add_development_dependency]) do |name| "https://rubygems.org/gems/#{name}" end end diff --git a/lib/gitlab/dependency_linker/method_linker.rb b/lib/gitlab/dependency_linker/method_linker.rb index d4d85bb33905af7470281cc2d3a7175a53dc77eb..33899a931c6ea095dca291a6cb8e611728d59668 100644 --- a/lib/gitlab/dependency_linker/method_linker.rb +++ b/lib/gitlab/dependency_linker/method_linker.rb @@ -23,18 +23,22 @@ module Gitlab # link_method_call('name') # # Will link `package` in `self.name = "package"` def link_method_call(method_name, value = nil, &url_proc) + regex = method_call_regex(method_name, value) + + link_regex(regex, &url_proc) + end + + def method_call_regex(method_name, value = nil) method_name = regexp_for_value(method_name) value = regexp_for_value(value) - regex = %r{ + %r{ #{method_name} # Method name \s* # Whitespace [(=]? # Opening brace or equals sign \s* # Whitespace ['"](?#{value})['"] # Package name in quotes }x - - link_regex(regex, &url_proc) end end end diff --git a/lib/gitlab/dependency_linker/package.rb b/lib/gitlab/dependency_linker/package.rb new file mode 100644 index 0000000000000000000000000000000000000000..8a509bbd562140faa9a8379eebf9b3d29dbfa737 --- /dev/null +++ b/lib/gitlab/dependency_linker/package.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Gitlab + module DependencyLinker + class Package + attr_reader :name, :git_ref, :github_ref + + def initialize(name, git_ref, github_ref) + @name = name + @git_ref = git_ref + @github_ref = github_ref + end + + def external_ref + @git_ref || @github_ref + end + end + end +end diff --git a/lib/gitlab/dependency_linker/package_json_linker.rb b/lib/gitlab/dependency_linker/package_json_linker.rb index 578e25f806a958eece7341de7250b71eaa4e659a..6857f2a4fa208352f457f01b35d5e5f95fef7a30 100644 --- a/lib/gitlab/dependency_linker/package_json_linker.rb +++ b/lib/gitlab/dependency_linker/package_json_linker.rb @@ -8,7 +8,6 @@ module Gitlab private def link_dependencies - link_json('name', json["name"], &method(:package_url)) link_json('license', &method(:license_url)) link_json(%w[homepage url], URL_REGEX, &:itself) @@ -16,25 +15,19 @@ module Gitlab end def link_packages - link_packages_at_key("dependencies", &method(:package_url)) - link_packages_at_key("devDependencies", &method(:package_url)) + link_packages_at_key("dependencies") + link_packages_at_key("devDependencies") end - def link_packages_at_key(key, &url_proc) + def link_packages_at_key(key) dependencies = json[key] return unless dependencies dependencies.each do |name, version| - link_json(name, version, link: :key, &url_proc) - - link_json(name) do |value| - case value - when /\A#{URL_REGEX}\z/ - value - when /\A#{REPO_REGEX}\z/ - github_url(value) - end - end + external_url = external_url(name, version) + + link_json(name, version, link: :key) { external_url } + link_json(name) { external_url } end end diff --git a/lib/gitlab/dependency_linker/parser/gemfile.rb b/lib/gitlab/dependency_linker/parser/gemfile.rb new file mode 100644 index 0000000000000000000000000000000000000000..7f755375cea5b29f31a82cee431b18807a353879 --- /dev/null +++ b/lib/gitlab/dependency_linker/parser/gemfile.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module Gitlab + module DependencyLinker + module Parser + class Gemfile < MethodLinker + GIT_REGEX = Gitlab::DependencyLinker::GemfileLinker::GIT_REGEX + GITHUB_REGEX = Gitlab::DependencyLinker::GemfileLinker::GITHUB_REGEX + + def initialize(plain_text) + @plain_text = plain_text + end + + # Returns a list of Gitlab::DependencyLinker::Package + # + # keyword - The package definition keyword, e.g. `:gem` for + # Gemfile parsing, `:pod` for Podfile. + def parse(keyword:) + plain_lines.each_with_object([]) do |line, packages| + name = fetch(line, method_call_regex(keyword)) + + next unless name + + git_ref = fetch(line, GIT_REGEX) + github_ref = fetch(line, GITHUB_REGEX) + + packages << Gitlab::DependencyLinker::Package.new(name, git_ref, github_ref) + end + end + + private + + def fetch(line, regex, group: :name) + match = line.match(regex) + match[group] if match + end + end + end + end +end diff --git a/lib/gitlab/dependency_linker/podfile_linker.rb b/lib/gitlab/dependency_linker/podfile_linker.rb index def9b04cca97f06dd4d39f573458d77cdbc956b4..a20d285da79db38ce65e80800855f656d0be9b32 100644 --- a/lib/gitlab/dependency_linker/podfile_linker.rb +++ b/lib/gitlab/dependency_linker/podfile_linker.rb @@ -5,12 +5,21 @@ module Gitlab class PodfileLinker < GemfileLinker include Cocoapods + self.package_keyword = :pod self.file_type = :podfile private def link_packages - link_method_call('pod', &method(:package_url)) + packages = parse_packages + + return unless packages + + packages.each do |package| + link_method_call('pod', package.name) do + external_url(package.name, package.external_ref) + end + end end end end diff --git a/lib/gitlab/dependency_linker/podspec_linker.rb b/lib/gitlab/dependency_linker/podspec_linker.rb index 6b1758c5a4309d6cf44280143a12094b55ff9124..14abd3999c425d62bda97d2184f11175796f7478 100644 --- a/lib/gitlab/dependency_linker/podspec_linker.rb +++ b/lib/gitlab/dependency_linker/podspec_linker.rb @@ -19,7 +19,7 @@ module Gitlab link_method_call('license', &method(:license_url)) link_regex(/license\s*=\s*\{\s*(type:|:type\s*=>)\s*#{STRING_REGEX}/, &method(:license_url)) - link_method_call(%w[name dependency], &method(:package_url)) + link_method_call('dependency', &method(:package_url)) end end end diff --git a/lib/gitlab/import_export/merge_request_parser.rb b/lib/gitlab/import_export/merge_request_parser.rb index 040a70d6775c24ef6df7b36853b44b71a629d183..deb2f59f05fb668b154619811838875bccc870e6 100644 --- a/lib/gitlab/import_export/merge_request_parser.rb +++ b/lib/gitlab/import_export/merge_request_parser.rb @@ -20,6 +20,17 @@ module Gitlab create_target_branch unless branch_exists?(@merge_request.target_branch) end + # The merge_request_diff associated with the current @merge_request might + # be invalid. Than means, when the @merge_request object is saved, the + # @merge_request.merge_request_diff won't. This can leave the merge request + # in an invalid state, because a merge request must have an associated + # merge request diff. + # In this change, if the associated merge request diff is invalid, we set + # it to nil. This change, in association with the after callback + # :ensure_merge_request_diff in the MergeRequest class, makes that + # when the merge request is going to be created and it doesn't have + # one, a default one will be generated. + @merge_request.merge_request_diff = nil unless @merge_request.merge_request_diff&.valid? @merge_request end diff --git a/lib/gitlab/kubernetes/kube_client.rb b/lib/gitlab/kubernetes/kube_client.rb index 624c2c67551d1d6eb52e2ea0b6f282d5c708dc7f..de14df56555f9b6abdb79f70e82b6c983c7b6660 100644 --- a/lib/gitlab/kubernetes/kube_client.rb +++ b/lib/gitlab/kubernetes/kube_client.rb @@ -82,6 +82,8 @@ module Gitlab def initialize(api_prefix, **kubeclient_options) @api_prefix = api_prefix @kubeclient_options = kubeclient_options.merge(http_max_redirects: 0) + + validate_url! end def create_or_update_cluster_role_binding(resource) @@ -118,6 +120,12 @@ module Gitlab private + def validate_url! + return if Gitlab::CurrentSettings.allow_local_requests_from_hooks_and_services? + + Gitlab::UrlBlocker.validate!(api_prefix, allow_local_network: false) + end + def cluster_role_binding_exists?(resource) get_cluster_role_binding(resource.metadata.name) rescue ::Kubeclient::ResourceNotFoundError diff --git a/locale/gitlab.pot b/locale/gitlab.pot index ec36341be61f04fbbcc5ad9a7473fa163e316693..3abd570175edd8c49e2dc16968bbe399cd45b414 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -1341,6 +1341,9 @@ msgstr "" msgid "Cannot modify managed Kubernetes cluster" msgstr "" +msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded." +msgstr "" + msgid "Certificate" msgstr "" @@ -5431,6 +5434,12 @@ msgstr "" msgid "PipelineSheduleIntervalPattern|Custom" msgstr "" +msgid "PipelineStatusTooltip|Commit: %{ci_status}" +msgstr "" + +msgid "PipelineStatusTooltip|Pipeline: %{ci_status}" +msgstr "" + msgid "Pipelines" msgstr "" diff --git a/qa/.gitignore b/qa/.gitignore index 19ec17d0005c3bbb121f3ae1742e3b731bb47d68..102f7e5e54d2d3c290407f508b8527cc27939a6a 100644 --- a/qa/.gitignore +++ b/qa/.gitignore @@ -1,2 +1,3 @@ tmp/ .ruby-version +urls.txt diff --git a/qa/Gemfile b/qa/Gemfile index f29006617edc8cca8f223929efed1828adc811a1..38e95ba2d6564fb7d9b2301bd712d80e7a348b1a 100644 --- a/qa/Gemfile +++ b/qa/Gemfile @@ -9,3 +9,4 @@ gem 'selenium-webdriver', '~> 3.12' gem 'airborne', '~> 0.2.13' gem 'nokogiri', '~> 1.10.1' gem 'rspec-retry', '~> 0.6.1' +gem 'faker', '~> 1.6', '>= 1.6.6' diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock index c3d9f558c23d0c269e561d6a4298cd05a450c836..c9b0db6a272d335b5696a95fd966e9db92ae200e 100644 --- a/qa/Gemfile.lock +++ b/qa/Gemfile.lock @@ -32,6 +32,8 @@ GEM diff-lcs (1.3) domain_name (0.5.20170404) unf (>= 0.0.5, < 1.0.0) + faker (1.9.3) + i18n (>= 0.7) ffi (1.9.25) http-cookie (1.0.3) domain_name (~> 0.5) @@ -99,6 +101,7 @@ DEPENDENCIES airborne (~> 0.2.13) capybara (~> 2.16.1) capybara-screenshot (~> 1.0.18) + faker (~> 1.6, >= 1.6.6) nokogiri (~> 1.10.1) pry-byebug (~> 3.5.1) rake (~> 12.3.0) diff --git a/qa/Rakefile b/qa/Rakefile index 9a7b9c6bb3598f5f0b6dec68ee1a97aa866f7b93..b6ad09f9b000d06c8f5f8b82cec5599d9c48cd48 100644 --- a/qa/Rakefile +++ b/qa/Rakefile @@ -1,5 +1,6 @@ require_relative 'qa/tools/revoke_all_personal_access_tokens' require_relative 'qa/tools/delete_subgroups' +require_relative 'qa/tools/generate_perf_testdata' desc "Revokes all personal access tokens" task :revoke_personal_access_tokens do @@ -10,3 +11,8 @@ desc "Deletes subgroups within a provided group" task :delete_subgroups do QA::Tools::DeleteSubgroups.new.run end + +desc "Generate Performance Testdata" +task :generate_perf_testdata do + QA::Tools::GeneratePerfTestdata.new.run +end diff --git a/qa/qa/support/api.rb b/qa/qa/support/api.rb index 8aa7d6812ac5fbea2e9f7cd727a826295f699d0f..229bfb44fa5a398d480d300495601d172325afa2 100644 --- a/qa/qa/support/api.rb +++ b/qa/qa/support/api.rb @@ -20,6 +20,16 @@ module QA e.response end + def put(url, payload) + RestClient::Request.execute( + method: :put, + url: url, + payload: payload, + verify_ssl: false) + rescue RestClient::ExceptionWithResponse => e + e.response + end + def delete(url) RestClient::Request.execute( method: :delete, diff --git a/qa/qa/tools/generate_perf_testdata.rb b/qa/qa/tools/generate_perf_testdata.rb new file mode 100644 index 0000000000000000000000000000000000000000..ad515014794396a9cee43daae01bf8687c8d9294 --- /dev/null +++ b/qa/qa/tools/generate_perf_testdata.rb @@ -0,0 +1,141 @@ +# frozen_string_literal: true + +require 'securerandom' +require 'faker' +require_relative '../../qa' +# This script generates testdata for Performance Testing. +# Required environment variables: PERSONAL_ACCESS_TOKEN and GITLAB_ADDRESS +# This job creates a urls.txt which contains a hash of all the URLs needed for Performance Testing +# Run `rake generate_perf_testdata` + +module QA + module Tools + class GeneratePerfTestdata + include Support::Api + + def initialize + raise ArgumentError, "Please provide GITLAB_ADDRESS" unless ENV['GITLAB_ADDRESS'] + raise ArgumentError, "Please provide PERSONAL_ACCESS_TOKEN" unless ENV['PERSONAL_ACCESS_TOKEN'] + + @api_client = Runtime::API::Client.new(ENV['GITLAB_ADDRESS'], personal_access_token: ENV['PERSONAL_ACCESS_TOKEN']) + @group_name = "gitlab-qa-perf-sandbox-#{SecureRandom.hex(8)}" + @project_name = "my-test-project-#{SecureRandom.hex(8)}" + @urls = {} + end + + def run + STDOUT.puts 'Running...' + group_id = create_group + create_project(group_id) + create_branch + add_new_file + methods_arr = [ + method(:create_issues), + method(:create_todos), + method(:create_merge_requests), + method(:create_issue_with_500_discussions), + method(:create_mr_with_large_files) + ] + threads_arr = [] + + methods_arr.each do |m| + threads_arr << Thread.new {m.call} + end + + threads_arr.each(&:join) + STDOUT.puts "\nURLs: #{@urls}" + File.open("urls.txt", "w") { |file| file.puts @urls.to_s} + STDOUT.puts "\nDone" + end + + private + + def create_group + group_search_response = post Runtime::API::Request.new(@api_client, "/groups").url, "name=#{@group_name}&path=#{@group_name}" + group = JSON.parse(group_search_response.body) + @urls[:group_page] = group["web_url"] + group["id"] + end + + def create_project(group_id) + create_project_response = post Runtime::API::Request.new(@api_client, "/projects").url, "name=#{@project_name}&namespace_id=#{group_id}" + @urls[:project_page] = JSON.parse(create_project_response.body)["web_url"] + end + + def create_issues + 30.times do |i| + post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/issues").url, "title=issue#{i}&description=desc#{i}" + end + @urls[:issues_list_page] = @urls[:project_page] + "/issues" + STDOUT.puts "Created Issues" + end + + def create_todos + 30.times do |i| + post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/issues/#{i + 1}/todo").url, nil + end + @urls[:todos_page] = ENV['GITLAB_ADDRESS'] + "/dashboard/todos" + STDOUT.puts "Created todos" + end + + def create_merge_requests + 30.times do |i| + post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/merge_requests").url, "source_branch=branch#{i}&target_branch=master&title=MR#{i}" + end + @urls[:mr_list_page] = @urls[:project_page] + "/merge_requests" + STDOUT.puts "Created MRs" + end + + def add_new_file + post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/repository/files/hello.txt").url, "branch=master&commit_message=\"hello\"&content=\"my new content\"" + 30.times do |i| + post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/repository/files/hello#{i}.txt").url, "branch=branch#{i}&commit_message=\"hello\"&content=\"my new content\"" + end + STDOUT.puts "Added Files" + end + + def create_branch + 30.times do |i| + post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/repository/branches").url, "branch=branch#{i}&ref=master" + end + STDOUT.puts "Created branches" + end + + def create_issue_with_500_discussions + issue_id = 1 + 500.times do + post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/issues/#{issue_id}/discussions").url, "body=\"Let us discuss\"" + end + @urls[:large_issue] = @urls[:project_page] + "/issues/#{issue_id}" + STDOUT.puts "Created Issue with 500 Discussions" + end + + def create_mr_with_large_files + content_arr = [] + 20.times do |i| + faker_line_arr = Faker::Lorem.sentences(1500) + content = faker_line_arr.join("\n\r") + post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/repository/files/hello#{i}.txt").url, "branch=master&commit_message=\"Add hello#{i}.txt\"&content=#{content}" + content_arr[i] = faker_line_arr + end + + post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/repository/branches").url, "branch=performance&ref=master" + + 20.times do |i| + missed_line_array = content_arr[i].each_slice(2).map(&:first) + content = missed_line_array.join("\n\rIm new!:D \n\r ") + put Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/repository/files/hello#{i}.txt").url, "branch=performance&commit_message=\"Update hello#{i}.txt\"&content=#{content}" + end + + create_mr_response = post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/merge_requests").url, "source_branch=performance&target_branch=master&title=Large_MR" + + iid = JSON.parse(create_mr_response.body)["iid"] + 500.times do + post Runtime::API::Request.new(@api_client, "/projects/#{@group_name}%2F#{@project_name}/merge_requests/#{iid}/discussions").url, "body=\"Let us discuss\"" + end + @urls[:large_mr] = JSON.parse(create_mr_response.body)["web_url"] + STDOUT.puts "Created MR with 500 Discussions and 20 Very Large Files" + end + end + end +end diff --git a/spec/controllers/dashboard/milestones_controller_spec.rb b/spec/controllers/dashboard/milestones_controller_spec.rb index 4b164d0aa6bd5e6641583745dedb77c35f9e03a7..ab40b4eb1782013fb75140a6fed304c0f2634285 100644 --- a/spec/controllers/dashboard/milestones_controller_spec.rb +++ b/spec/controllers/dashboard/milestones_controller_spec.rb @@ -13,7 +13,7 @@ describe Dashboard::MilestonesController do ) end let(:issue) { create(:issue, project: project, milestone: project_milestone) } - let(:group_issue) { create(:issue, milestone: group_milestone) } + let(:group_issue) { create(:issue, milestone: group_milestone, project: create(:project, group: group)) } let!(:label) { create(:label, project: project, title: 'Issue Label', issues: [issue]) } let!(:group_label) { create(:group_label, group: group, title: 'Group Issue Label', issues: [group_issue]) } diff --git a/spec/controllers/google_api/authorizations_controller_spec.rb b/spec/controllers/google_api/authorizations_controller_spec.rb index 1e8e82da4f3183a2f1f6723d043645e8f2ab310b..d9ba85cf56ae7892fdfece8b7951a1b9a1520d99 100644 --- a/spec/controllers/google_api/authorizations_controller_spec.rb +++ b/spec/controllers/google_api/authorizations_controller_spec.rb @@ -6,7 +6,7 @@ describe GoogleApi::AuthorizationsController do let(:token) { 'token' } let(:expires_at) { 1.hour.since.strftime('%s') } - subject { get :callback, params: { code: 'xxx', state: @state } } + subject { get :callback, params: { code: 'xxx', state: state } } before do sign_in(user) @@ -15,35 +15,57 @@ describe GoogleApi::AuthorizationsController do .to receive(:get_token).and_return([token, expires_at]) end - it 'sets token and expires_at in session' do - subject + shared_examples_for 'access denied' do + it 'returns a 404' do + subject - expect(session[GoogleApi::CloudPlatform::Client.session_key_for_token]) - .to eq(token) - expect(session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at]) - .to eq(expires_at) + expect(session[GoogleApi::CloudPlatform::Client.session_key_for_token]).to be_nil + expect(response).to have_http_status(:not_found) + end end - context 'when redirect uri key is stored in state' do - set(:project) { create(:project) } - let(:redirect_uri) { project_clusters_url(project).to_s } + context 'session key is present' do + let(:session_key) { 'session-key' } + let(:redirect_uri) { 'example.com' } before do - @state = GoogleApi::CloudPlatform::Client - .new_session_key_for_redirect_uri do |key| - session[key] = redirect_uri + session[GoogleApi::CloudPlatform::Client.session_key_for_redirect_uri(session_key)] = redirect_uri + end + + context 'session key matches state param' do + let(:state) { session_key } + + it 'sets token and expires_at in session' do + subject + + expect(session[GoogleApi::CloudPlatform::Client.session_key_for_token]) + .to eq(token) + expect(session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at]) + .to eq(expires_at) + end + + it 'redirects to the URL stored in state param' do + expect(subject).to redirect_to(redirect_uri) end end - it 'redirects to the URL stored in state param' do - expect(subject).to redirect_to(redirect_uri) + context 'session key does not match state param' do + let(:state) { 'bad-key' } + + it_behaves_like 'access denied' end - end - context 'when redirection url is not stored in state' do - it 'redirects to root_path' do - expect(subject).to redirect_to(root_path) + context 'state param is blank' do + let(:state) { '' } + + it_behaves_like 'access denied' end end + + context 'state param is present, but session key is blank' do + let(:state) { 'session-key' } + + it_behaves_like 'access denied' + end end end diff --git a/spec/controllers/groups/shared_projects_controller_spec.rb b/spec/controllers/groups/shared_projects_controller_spec.rb index dab7700cf64ba7d169a574ef1d53d4306d692cda..b0c20fb5a90c2b4f1e51a44aa748116eab1ff6f9 100644 --- a/spec/controllers/groups/shared_projects_controller_spec.rb +++ b/spec/controllers/groups/shared_projects_controller_spec.rb @@ -6,6 +6,8 @@ describe Groups::SharedProjectsController do end def share_project(project) + group.add_developer(user) + Projects::GroupLinks::CreateService.new( project, user, diff --git a/spec/controllers/omniauth_callbacks_controller_spec.rb b/spec/controllers/omniauth_callbacks_controller_spec.rb index 232a5e2793b73bb487c8ed58df40b46df82de123..e0da23ca0b83f8647dc0d147e174d3df753521e4 100644 --- a/spec/controllers/omniauth_callbacks_controller_spec.rb +++ b/spec/controllers/omniauth_callbacks_controller_spec.rb @@ -193,7 +193,7 @@ describe OmniauthCallbacksController, type: :controller do before do stub_omniauth_saml_config({ enabled: true, auto_link_saml_user: true, allow_single_sign_on: ['saml'], providers: [saml_config] }) - mock_auth_hash('saml', 'my-uid', user.email, mock_saml_response) + mock_auth_hash_with_saml_xml('saml', 'my-uid', user.email, mock_saml_response) request.env["devise.mapping"] = Devise.mappings[:user] request.env['omniauth.auth'] = Rails.application.env_config['omniauth.auth'] post :saml, params: { SAMLResponse: mock_saml_response } diff --git a/spec/controllers/projects/autocomplete_sources_controller_spec.rb b/spec/controllers/projects/autocomplete_sources_controller_spec.rb index 4bc72042710ca8cc65cefe202e5c6d029011a9b9..a9a058e7e17310bcd2552275561e1e5c30f9aa3e 100644 --- a/spec/controllers/projects/autocomplete_sources_controller_spec.rb +++ b/spec/controllers/projects/autocomplete_sources_controller_spec.rb @@ -35,4 +35,35 @@ describe Projects::AutocompleteSourcesController do avatar_url: user.avatar_url) end end + + describe 'GET milestones' do + let(:group) { create(:group, :public) } + let(:project) { create(:project, :public, namespace: group) } + let!(:project_milestone) { create(:milestone, project: project) } + let!(:group_milestone) { create(:milestone, group: group) } + + before do + sign_in(user) + end + + it 'lists milestones' do + group.add_owner(user) + + get :milestones, format: :json, params: { namespace_id: group.path, project_id: project.path } + + milestone_titles = json_response.map { |milestone| milestone["title"] } + expect(milestone_titles).to match_array([project_milestone.title, group_milestone.title]) + end + + context 'when user cannot read project issues and merge requests' do + it 'renders 404' do + project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE) + project.project_feature.update!(merge_requests_access_level: ProjectFeature::PRIVATE) + + get :milestones, format: :json, params: { namespace_id: group.path, project_id: project.path } + + expect(response).to have_gitlab_http_status(404) + end + end + end end diff --git a/spec/controllers/projects/group_links_controller_spec.rb b/spec/controllers/projects/group_links_controller_spec.rb index 675eeff8d12995d1f8c353cbdc5a4d1c2c9b00af..ce021b2f085d74a0f4620ee5e1832e515252d65e 100644 --- a/spec/controllers/projects/group_links_controller_spec.rb +++ b/spec/controllers/projects/group_links_controller_spec.rb @@ -65,8 +65,24 @@ describe Projects::GroupLinksController do end end + context 'when user does not have access to the public group' do + let(:group) { create(:group, :public) } + + include_context 'link project to group' + + it 'renders 404' do + expect(response.status).to eq 404 + end + + it 'does not share project with that group' do + expect(group.shared_projects).not_to include project + end + end + context 'when project group id equal link group id' do before do + group2.add_developer(user) + post(:create, params: { namespace_id: project.namespace, project_id: project, @@ -102,5 +118,26 @@ describe Projects::GroupLinksController do expect(flash[:alert]).to eq('Please select a group.') end end + + context 'when link is not persisted in the database' do + before do + allow(::Projects::GroupLinks::CreateService).to receive_message_chain(:new, :execute) + .and_return({ status: :error, http_status: 409, message: 'error' }) + + post(:create, params: { + namespace_id: project.namespace, + project_id: project, + link_group_id: group.id, + link_group_access: ProjectGroupLink.default_access + }) + end + + it 'redirects to project group links page' do + expect(response).to redirect_to( + project_project_members_path(project) + ) + expect(flash[:alert]).to eq('error') + end + end end end diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb index 5c6858dc7b2be5fbbfe394a273181bdfa3ead2c5..77a94f26d8c1759db0e3ca4cdb345ba799df0d0e 100644 --- a/spec/controllers/snippets_controller_spec.rb +++ b/spec/controllers/snippets_controller_spec.rb @@ -205,6 +205,8 @@ describe SnippetsController do end context 'when the snippet description contains a file' do + include FileMoverHelpers + let(:picture_file) { '/-/system/temp/secret56/picture.jpg' } let(:text_file) { '/-/system/temp/secret78/text.txt' } let(:description) do @@ -215,6 +217,8 @@ describe SnippetsController do before do allow(FileUtils).to receive(:mkdir_p) allow(FileUtils).to receive(:move) + stub_file_mover(text_file) + stub_file_mover(picture_file) end subject { create_snippet({ description: description }, { files: [picture_file, text_file] }) } diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb index 8eb413bdd8dc022001cbe5753491dcc70e118784..986f3823275a51d760066581cc9eeff80211c847 100644 --- a/spec/features/issues/gfm_autocomplete_spec.rb +++ b/spec/features/issues/gfm_autocomplete_spec.rb @@ -278,7 +278,12 @@ describe 'GFM autocomplete', :js do end end - context 'labels' do + # This context has just one example in each contexts in order to improve spec performance. + context 'labels', :quarantine do + let!(:backend) { create(:label, project: project, title: 'backend') } + let!(:bug) { create(:label, project: project, title: 'bug') } + let!(:feature_proposal) { create(:label, project: project, title: 'feature proposal') } + it 'opens autocomplete menu for Labels when field starts with text with item escaping HTML characters' do create(:label, project: project, title: label_xss_title) @@ -293,6 +298,83 @@ describe 'GFM autocomplete', :js do expect(find('.atwho-view-ul').text).to have_content('alert label') end end + + context 'when no labels are assigned' do + it 'shows labels' do + note = find('#note-body') + + # It should show all the labels on "~". + type(note, '~') + wait_for_requests + expect_labels(shown: [backend, bug, feature_proposal]) + + # It should show all the labels on "/label ~". + type(note, '/label ~') + expect_labels(shown: [backend, bug, feature_proposal]) + + # It should show all the labels on "/relabel ~". + type(note, '/relabel ~') + expect_labels(shown: [backend, bug, feature_proposal]) + + # It should show no labels on "/unlabel ~". + type(note, '/unlabel ~') + expect_labels(not_shown: [backend, bug, feature_proposal]) + end + end + + context 'when some labels are assigned' do + before do + issue.labels << [backend] + end + + it 'shows labels' do + note = find('#note-body') + + # It should show all the labels on "~". + type(note, '~') + wait_for_requests + expect_labels(shown: [backend, bug, feature_proposal]) + + # It should show only unset labels on "/label ~". + type(note, '/label ~') + expect_labels(shown: [bug, feature_proposal], not_shown: [backend]) + + # It should show all the labels on "/relabel ~". + type(note, '/relabel ~') + expect_labels(shown: [backend, bug, feature_proposal]) + + # It should show only set labels on "/unlabel ~". + type(note, '/unlabel ~') + expect_labels(shown: [backend], not_shown: [bug, feature_proposal]) + end + end + + context 'when all labels are assigned' do + before do + issue.labels << [backend, bug, feature_proposal] + end + + it 'shows labels' do + note = find('#note-body') + + # It should show all the labels on "~". + type(note, '~') + wait_for_requests + expect_labels(shown: [backend, bug, feature_proposal]) + + # It should show no labels on "/label ~". + type(note, '/label ~') + expect_labels(not_shown: [backend, bug, feature_proposal]) + + # It should show all the labels on "/relabel ~". + type(note, '/relabel ~') + expect_labels(shown: [backend, bug, feature_proposal]) + + # It should show all the labels on "/unlabel ~". + type(note, '/unlabel ~') + expect_labels(shown: [backend, bug, feature_proposal]) + end + end end shared_examples 'autocomplete suggestions' do diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index 406e80e91aa8b87f42f40c5be288f5123a9eea8d..9bc340ed4bb1f4432bcab8faccaa1b09b932b247 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -233,8 +233,8 @@ describe 'Issues' do created_at: Time.now - (index * 60)) end end - let(:newer_due_milestone) { create(:milestone, due_date: '2013-12-11') } - let(:later_due_milestone) { create(:milestone, due_date: '2013-12-12') } + let(:newer_due_milestone) { create(:milestone, project: project, due_date: '2013-12-11') } + let(:later_due_milestone) { create(:milestone, project: project, due_date: '2013-12-12') } it 'sorts by newest' do visit project_issues_path(project, sort: sort_value_created_date) diff --git a/spec/features/merge_request/user_sees_versions_spec.rb b/spec/features/merge_request/user_sees_versions_spec.rb index aa91ade46cafa5995d3cc6e365efc1da91ff09c8..5c45e36399797016cb8031ce3feaa48cd8296221 100644 --- a/spec/features/merge_request/user_sees_versions_spec.rb +++ b/spec/features/merge_request/user_sees_versions_spec.rb @@ -1,7 +1,11 @@ require 'rails_helper' describe 'Merge request > User sees versions', :js do - let(:merge_request) { create(:merge_request, importing: true) } + let(:merge_request) do + create(:merge_request).tap do |mr| + mr.merge_request_diff.destroy + end + end let(:project) { merge_request.source_project } let(:user) { project.creator } let!(:merge_request_diff1) { merge_request.merge_request_diffs.create(head_commit_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') } diff --git a/spec/features/merge_requests/user_lists_merge_requests_spec.rb b/spec/features/merge_requests/user_lists_merge_requests_spec.rb index ef7ae490b0feadb277a0b1b71112cec3b7455ace..c691011b9ca078c3d9502c79ce6d1c2ec85aa855 100644 --- a/spec/features/merge_requests/user_lists_merge_requests_spec.rb +++ b/spec/features/merge_requests/user_lists_merge_requests_spec.rb @@ -13,7 +13,7 @@ describe 'Merge requests > User lists merge requests' do source_project: project, source_branch: 'fix', assignee: user, - milestone: create(:milestone, due_date: '2013-12-11'), + milestone: create(:milestone, project: project, due_date: '2013-12-11'), created_at: 1.minute.ago, updated_at: 1.minute.ago) create(:merge_request, @@ -21,7 +21,7 @@ describe 'Merge requests > User lists merge requests' do source_project: project, source_branch: 'markdown', assignee: user, - milestone: create(:milestone, due_date: '2013-12-12'), + milestone: create(:milestone, project: project, due_date: '2013-12-12'), created_at: 2.minutes.ago, updated_at: 2.minutes.ago) create(:merge_request, diff --git a/spec/features/profiles/active_sessions_spec.rb b/spec/features/profiles/active_sessions_spec.rb index d3050760c062227e622ac7827414b447f7908b4f..2aa0177af5d171985863f2d7c7af6c76d4e3573b 100644 --- a/spec/features/profiles/active_sessions_spec.rb +++ b/spec/features/profiles/active_sessions_spec.rb @@ -7,6 +7,8 @@ describe 'Profile > Active Sessions', :clean_gitlab_redis_shared_state do end end + let(:admin) { create(:admin) } + around do |example| Timecop.freeze(Time.zone.parse('2018-03-12 09:06')) do example.run @@ -16,6 +18,7 @@ describe 'Profile > Active Sessions', :clean_gitlab_redis_shared_state do it 'User sees their active sessions' do Capybara::Session.new(:session1) Capybara::Session.new(:session2) + Capybara::Session.new(:session3) # note: headers can only be set on the non-js (aka. rack-test) driver using_session :session1 do @@ -37,9 +40,27 @@ describe 'Profile > Active Sessions', :clean_gitlab_redis_shared_state do gitlab_sign_in(user) end + # set an admin session impersonating the user + using_session :session3 do + Capybara.page.driver.header( + 'User-Agent', + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36' + ) + + gitlab_sign_in(admin) + + visit admin_user_path(user) + + click_link 'Impersonate' + end + using_session :session1 do visit profile_active_sessions_path + expect(page).to( + have_selector('ul.list-group li.list-group-item', { text: 'Signed in on', + count: 2 })) + expect(page).to have_content( '127.0.0.1 ' \ 'This is your current session ' \ @@ -57,33 +78,8 @@ describe 'Profile > Active Sessions', :clean_gitlab_redis_shared_state do ) expect(page).to have_selector '[title="Smartphone"]', count: 1 - end - end - - it 'User can revoke a session', :js, :redis_session_store do - Capybara::Session.new(:session1) - Capybara::Session.new(:session2) - - # set an additional session in another browser - using_session :session2 do - gitlab_sign_in(user) - end - - using_session :session1 do - gitlab_sign_in(user) - visit profile_active_sessions_path - - expect(page).to have_link('Revoke', count: 1) - - accept_confirm { click_on 'Revoke' } - - expect(page).not_to have_link('Revoke') - end - - using_session :session2 do - visit profile_active_sessions_path - expect(page).to have_content('You need to sign in or sign up before continuing.') + expect(page).not_to have_content('Chrome on Windows') end end end diff --git a/spec/features/projects/blobs/blob_show_spec.rb b/spec/features/projects/blobs/blob_show_spec.rb index 3edcc7ac2cd6dd9e1b53e89a49d7e40ee6641ded..a7aa63018fda7817b9c1b43bba5b66b9f6bb534d 100644 --- a/spec/features/projects/blobs/blob_show_spec.rb +++ b/spec/features/projects/blobs/blob_show_spec.rb @@ -548,10 +548,7 @@ describe 'File blob', :js do it 'displays an auxiliary viewer' do aggregate_failures do # shows names of dependency manager and package - expect(page).to have_content('This project manages its dependencies using RubyGems and defines a gem named activerecord.') - - # shows a link to the gem - expect(page).to have_link('activerecord', href: 'https://rubygems.org/gems/activerecord') + expect(page).to have_content('This project manages its dependencies using RubyGems.') # shows a learn more link expect(page).to have_link('Learn more', href: 'https://rubygems.org/') diff --git a/spec/features/projects/members/invite_group_spec.rb b/spec/features/projects/members/invite_group_spec.rb index fceead0b45eb2f5f36fca9209018e958dab9f8f6..b2d2dba55f16b3ed84d6947b40dc093377419f7c 100644 --- a/spec/features/projects/members/invite_group_spec.rb +++ b/spec/features/projects/members/invite_group_spec.rb @@ -27,6 +27,7 @@ describe 'Project > Members > Invite group', :js do before do project.add_maintainer(maintainer) + group_to_share_with.add_guest(maintainer) sign_in(maintainer) end @@ -112,6 +113,7 @@ describe 'Project > Members > Invite group', :js do before do project.add_maintainer(maintainer) + group.add_guest(maintainer) sign_in(maintainer) visit project_settings_members_path(project) diff --git a/spec/features/projects/settings/user_manages_group_links_spec.rb b/spec/features/projects/settings/user_manages_group_links_spec.rb index 676659b90c318a483c5a5bc2f82501de15c08cc1..e5a58c44e4125bd5a81d0843129827470c6ef2d2 100644 --- a/spec/features/projects/settings/user_manages_group_links_spec.rb +++ b/spec/features/projects/settings/user_manages_group_links_spec.rb @@ -10,6 +10,7 @@ describe 'Projects > Settings > User manages group links' do before do project.add_maintainer(user) + group_market.add_guest(user) sign_in(user) share_link = project.project_group_links.new(group_access: Gitlab::Access::MAINTAINER) diff --git a/spec/features/security/group/private_access_spec.rb b/spec/features/security/group/private_access_spec.rb index 4705cd12d236270420584c838fec8dd1bde5cd25..3238e07fe157833489f6020aa52168baef0770c2 100644 --- a/spec/features/security/group/private_access_spec.rb +++ b/spec/features/security/group/private_access_spec.rb @@ -27,7 +27,7 @@ describe 'Private Group access' do it { is_expected.to be_allowed_for(:developer).of(group) } it { is_expected.to be_allowed_for(:reporter).of(group) } it { is_expected.to be_allowed_for(:guest).of(group) } - it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_denied_for(project_guest) } it { is_expected.to be_denied_for(:user) } it { is_expected.to be_denied_for(:external) } it { is_expected.to be_denied_for(:visitor) } @@ -42,7 +42,7 @@ describe 'Private Group access' do it { is_expected.to be_allowed_for(:developer).of(group) } it { is_expected.to be_allowed_for(:reporter).of(group) } it { is_expected.to be_allowed_for(:guest).of(group) } - it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_denied_for(project_guest) } it { is_expected.to be_denied_for(:user) } it { is_expected.to be_denied_for(:external) } it { is_expected.to be_denied_for(:visitor) } @@ -58,7 +58,7 @@ describe 'Private Group access' do it { is_expected.to be_allowed_for(:developer).of(group) } it { is_expected.to be_allowed_for(:reporter).of(group) } it { is_expected.to be_allowed_for(:guest).of(group) } - it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_denied_for(project_guest) } it { is_expected.to be_denied_for(:user) } it { is_expected.to be_denied_for(:external) } it { is_expected.to be_denied_for(:visitor) } @@ -73,7 +73,7 @@ describe 'Private Group access' do it { is_expected.to be_allowed_for(:developer).of(group) } it { is_expected.to be_allowed_for(:reporter).of(group) } it { is_expected.to be_allowed_for(:guest).of(group) } - it { is_expected.to be_allowed_for(project_guest) } + it { is_expected.to be_denied_for(project_guest) } it { is_expected.to be_denied_for(:user) } it { is_expected.to be_denied_for(:external) } it { is_expected.to be_denied_for(:visitor) } @@ -93,4 +93,28 @@ describe 'Private Group access' do it { is_expected.to be_denied_for(:visitor) } it { is_expected.to be_denied_for(:external) } end + + describe 'GET /groups/:path for shared projects' do + let(:project) { create(:project, :public) } + before do + Projects::GroupLinks::CreateService.new( + project, + create(:user), + link_group_access: ProjectGroupLink::DEVELOPER + ).execute(group) + end + + subject { group_path(group) } + + it { is_expected.to be_allowed_for(:admin) } + it { is_expected.to be_allowed_for(:owner).of(group) } + it { is_expected.to be_allowed_for(:maintainer).of(group) } + it { is_expected.to be_allowed_for(:developer).of(group) } + it { is_expected.to be_allowed_for(:reporter).of(group) } + it { is_expected.to be_allowed_for(:guest).of(group) } + it { is_expected.to be_denied_for(project_guest) } + it { is_expected.to be_denied_for(:user) } + it { is_expected.to be_denied_for(:external) } + it { is_expected.to be_denied_for(:visitor) } + end end diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb index 107da08a0a905fcf3d103d197bd134acf3c79ecd..503b88fcbadab826233ce4beb871474f8c3e97b3 100644 --- a/spec/finders/merge_requests_finder_spec.rb +++ b/spec/finders/merge_requests_finder_spec.rb @@ -13,275 +13,409 @@ describe MergeRequestsFinder do end end - let(:user) { create :user } - let(:user2) { create :user } - - let(:group) { create(:group) } - let(:subgroup) { create(:group, parent: group) } - let(:project1) { create_project_without_n_plus_1(group: group) } - let(:project2) do - Gitlab::GitalyClient.allow_n_plus_1_calls do - fork_project(project1, user) + context "multiple projects with merge requests" do + let(:user) { create :user } + let(:user2) { create :user } + + let(:group) { create(:group) } + let(:subgroup) { create(:group, parent: group) } + let(:project1) { create_project_without_n_plus_1(group: group) } + let(:project2) do + Gitlab::GitalyClient.allow_n_plus_1_calls do + fork_project(project1, user) + end end - end - let(:project3) do - Gitlab::GitalyClient.allow_n_plus_1_calls do - p = fork_project(project1, user) - p.update!(archived: true) - p + let(:project3) do + Gitlab::GitalyClient.allow_n_plus_1_calls do + p = fork_project(project1, user) + p.update!(archived: true) + p + end end - end - let(:project4) { create_project_without_n_plus_1(group: subgroup) } - let(:project5) { create_project_without_n_plus_1(group: subgroup) } - let(:project6) { create_project_without_n_plus_1(group: subgroup) } - - let!(:merge_request1) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1) } - let!(:merge_request2) { create(:merge_request, :conflict, author: user, source_project: project2, target_project: project1, state: 'closed') } - let!(:merge_request3) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project2, state: 'locked', title: 'thing WIP thing') } - let!(:merge_request4) { create(:merge_request, :simple, author: user, source_project: project3, target_project: project3, title: 'WIP thing') } - let!(:merge_request5) { create(:merge_request, :simple, author: user, source_project: project4, target_project: project4, title: '[WIP]') } - let!(:merge_request6) { create(:merge_request, :simple, author: user, source_project: project5, target_project: project5, title: 'WIP: thing') } - let!(:merge_request7) { create(:merge_request, :simple, author: user, source_project: project6, target_project: project6, title: 'wip thing') } - let!(:merge_request8) { create(:merge_request, :simple, author: user, source_project: project1, target_project: project1, title: '[wip] thing') } - let!(:merge_request9) { create(:merge_request, :simple, author: user, source_project: project1, target_project: project2, title: 'wip: thing') } - - before do - project1.add_maintainer(user) - project2.add_developer(user) - project3.add_developer(user) - project2.add_developer(user2) - project4.add_developer(user) - project5.add_developer(user) - project6.add_developer(user) - end - - describe "#execute" do - it 'filters by scope' do - params = { scope: 'authored', state: 'opened' } - merge_requests = described_class.new(user, params).execute - expect(merge_requests.size).to eq(7) + let(:project4) { create_project_without_n_plus_1(:repository, group: subgroup) } + let(:project5) { create_project_without_n_plus_1(group: subgroup) } + let(:project6) { create_project_without_n_plus_1(group: subgroup) } + + let!(:merge_request1) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1) } + let!(:merge_request2) { create(:merge_request, :conflict, author: user, source_project: project2, target_project: project1, state: 'closed') } + let!(:merge_request3) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project2, state: 'locked', title: 'thing WIP thing') } + let!(:merge_request4) { create(:merge_request, :simple, author: user, source_project: project3, target_project: project3, title: 'WIP thing') } + let!(:merge_request5) { create(:merge_request, :simple, author: user, source_project: project4, target_project: project4, title: '[WIP]') } + let!(:merge_request6) { create(:merge_request, :simple, author: user, source_project: project5, target_project: project5, title: 'WIP: thing') } + let!(:merge_request7) { create(:merge_request, :simple, author: user, source_project: project6, target_project: project6, title: 'wip thing') } + let!(:merge_request8) { create(:merge_request, :simple, author: user, source_project: project1, target_project: project1, title: '[wip] thing') } + let!(:merge_request9) { create(:merge_request, :simple, author: user, source_project: project1, target_project: project2, title: 'wip: thing') } + + before do + project1.add_maintainer(user) + project2.add_developer(user) + project3.add_developer(user) + project2.add_developer(user2) + project4.add_developer(user) + project5.add_developer(user) + project6.add_developer(user) end - it 'filters by project' do - params = { project_id: project1.id, scope: 'authored', state: 'opened' } - merge_requests = described_class.new(user, params).execute - expect(merge_requests.size).to eq(2) - end + describe '#execute' do + it 'filters by scope' do + params = { scope: 'authored', state: 'opened' } + merge_requests = described_class.new(user, params).execute + expect(merge_requests.size).to eq(7) + end + + it 'filters by project' do + params = { project_id: project1.id, scope: 'authored', state: 'opened' } + merge_requests = described_class.new(user, params).execute + expect(merge_requests.size).to eq(2) + end + + it 'filters by commit sha' do + merge_requests = described_class.new( + user, + commit_sha: merge_request5.merge_request_diff.last_commit_sha + ).execute + + expect(merge_requests).to contain_exactly(merge_request5) + end + + context 'filtering by group' do + it 'includes all merge requests when user has access' do + params = { group_id: group.id } + + merge_requests = described_class.new(user, params).execute + + expect(merge_requests.size).to eq(3) + end - context 'filtering by group' do - it 'includes all merge requests when user has access' do - params = { group_id: group.id } + it 'excludes merge requests from projects the user does not have access to' do + private_project = create_project_without_n_plus_1(:private, group: group) + private_mr = create(:merge_request, :simple, author: user, source_project: private_project, target_project: private_project) + params = { group_id: group.id } + private_project.add_guest(user) + merge_requests = described_class.new(user, params).execute + + expect(merge_requests.size).to eq(3) + expect(merge_requests).not_to include(private_mr) + end + + it 'filters by group including subgroups', :nested_groups do + params = { group_id: group.id, include_subgroups: true } + + merge_requests = described_class.new(user, params).execute + + expect(merge_requests.size).to eq(6) + end + end + + it 'filters by non_archived' do + params = { non_archived: true } merge_requests = described_class.new(user, params).execute + expect(merge_requests.size).to eq(8) + end + + it 'filters by iid' do + params = { project_id: project1.id, iids: merge_request1.iid } - expect(merge_requests.size).to eq(3) + merge_requests = described_class.new(user, params).execute + + expect(merge_requests).to contain_exactly(merge_request1) end - it 'excludes merge requests from projects the user does not have access to' do - private_project = create_project_without_n_plus_1(:private, group: group) - private_mr = create(:merge_request, :simple, author: user, source_project: private_project, target_project: private_project) - params = { group_id: group.id } + it 'filters by source branch' do + params = { source_branch: merge_request2.source_branch } - private_project.add_guest(user) merge_requests = described_class.new(user, params).execute - expect(merge_requests.size).to eq(3) - expect(merge_requests).not_to include(private_mr) + expect(merge_requests).to contain_exactly(merge_request2) end - it 'filters by group including subgroups', :nested_groups do - params = { group_id: group.id, include_subgroups: true } + it 'filters by target branch' do + params = { target_branch: merge_request2.target_branch } merge_requests = described_class.new(user, params).execute - expect(merge_requests.size).to eq(6) + expect(merge_requests).to contain_exactly(merge_request2) end - end - it 'filters by non_archived' do - params = { non_archived: true } - merge_requests = described_class.new(user, params).execute - expect(merge_requests.size).to eq(8) - end + it 'filters by state' do + params = { state: 'locked' } - it 'filters by iid' do - params = { project_id: project1.id, iids: merge_request1.iid } + merge_requests = described_class.new(user, params).execute - merge_requests = described_class.new(user, params).execute + expect(merge_requests).to contain_exactly(merge_request3) + end - expect(merge_requests).to contain_exactly(merge_request1) - end + it 'filters by wip' do + params = { wip: 'yes' } - it 'filters by source branch' do - params = { source_branch: merge_request2.source_branch } + merge_requests = described_class.new(user, params).execute - merge_requests = described_class.new(user, params).execute + expect(merge_requests).to contain_exactly(merge_request4, merge_request5, merge_request6, merge_request7, merge_request8, merge_request9) + end - expect(merge_requests).to contain_exactly(merge_request2) - end + it 'filters by not wip' do + params = { wip: 'no' } - it 'filters by target branch' do - params = { target_branch: merge_request2.target_branch } + merge_requests = described_class.new(user, params).execute - merge_requests = described_class.new(user, params).execute + expect(merge_requests).to contain_exactly(merge_request1, merge_request2, merge_request3) + end - expect(merge_requests).to contain_exactly(merge_request2) - end + it 'returns all items if no valid wip param exists' do + params = { wip: '' } - it 'filters by state' do - params = { state: 'locked' } + merge_requests = described_class.new(user, params).execute - merge_requests = described_class.new(user, params).execute + expect(merge_requests).to contain_exactly(merge_request1, merge_request2, merge_request3, merge_request4, merge_request5, merge_request6, merge_request7, merge_request8, merge_request9) + end - expect(merge_requests).to contain_exactly(merge_request3) - end + it 'adds wip to scalar params' do + scalar_params = described_class.scalar_params - it 'filters by wip' do - params = { wip: 'yes' } + expect(scalar_params).to include(:wip, :assignee_id) + end - merge_requests = described_class.new(user, params).execute + context 'filtering by group milestone' do + let!(:group) { create(:group, :public) } + let(:group_milestone) { create(:milestone, group: group) } + let!(:group_member) { create(:group_member, group: group, user: user) } + let(:params) { { milestone_title: group_milestone.title } } - expect(merge_requests).to contain_exactly(merge_request4, merge_request5, merge_request6, merge_request7, merge_request8, merge_request9) - end + before do + project2.update(namespace: group) + merge_request2.update(milestone: group_milestone) + merge_request3.update(milestone: group_milestone) + end - it 'filters by not wip' do - params = { wip: 'no' } + it 'returns issues assigned to that group milestone' do + merge_requests = described_class.new(user, params).execute - merge_requests = described_class.new(user, params).execute + expect(merge_requests).to contain_exactly(merge_request2, merge_request3) + end + end - expect(merge_requests).to contain_exactly(merge_request1, merge_request2, merge_request3) - end + context 'filtering by created_at/updated_at' do + let(:new_project) { create(:project, forked_from_project: project1) } - it 'returns all items if no valid wip param exists' do - params = { wip: '' } + let!(:new_merge_request) do + create(:merge_request, + :simple, + author: user, + created_at: 1.week.from_now, + updated_at: 1.week.from_now, + source_project: new_project, + target_project: new_project) + end - merge_requests = described_class.new(user, params).execute + let!(:old_merge_request) do + create(:merge_request, + :simple, + author: user, + source_branch: 'feature_1', + created_at: 1.week.ago, + updated_at: 1.week.ago, + source_project: new_project, + target_project: new_project) + end - expect(merge_requests).to contain_exactly(merge_request1, merge_request2, merge_request3, merge_request4, merge_request5, merge_request6, merge_request7, merge_request8, merge_request9) - end + before do + new_project.add_maintainer(user) + end - it 'adds wip to scalar params' do - scalar_params = described_class.scalar_params + it 'filters by created_after' do + params = { project_id: new_project.id, created_after: new_merge_request.created_at } - expect(scalar_params).to include(:wip, :assignee_id) - end + merge_requests = described_class.new(user, params).execute - context 'filtering by group milestone' do - let!(:group) { create(:group, :public) } - let(:group_milestone) { create(:milestone, group: group) } - let!(:group_member) { create(:group_member, group: group, user: user) } - let(:params) { { milestone_title: group_milestone.title } } + expect(merge_requests).to contain_exactly(new_merge_request) + end - before do - project2.update(namespace: group) - merge_request2.update(milestone: group_milestone) - merge_request3.update(milestone: group_milestone) - end + it 'filters by created_before' do + params = { project_id: new_project.id, created_before: old_merge_request.created_at } - it 'returns issues assigned to that group milestone' do - merge_requests = described_class.new(user, params).execute + merge_requests = described_class.new(user, params).execute - expect(merge_requests).to contain_exactly(merge_request2, merge_request3) - end - end + expect(merge_requests).to contain_exactly(old_merge_request) + end - context 'filtering by created_at/updated_at' do - let(:new_project) { create(:project, forked_from_project: project1) } - - let!(:new_merge_request) do - create(:merge_request, - :simple, - author: user, - created_at: 1.week.from_now, - updated_at: 1.week.from_now, - source_project: new_project, - target_project: new_project) - end + it 'filters by created_after and created_before' do + params = { + project_id: new_project.id, + created_after: old_merge_request.created_at, + created_before: new_merge_request.created_at + } - let!(:old_merge_request) do - create(:merge_request, - :simple, - author: user, - source_branch: 'feature_1', - created_at: 1.week.ago, - updated_at: 1.week.ago, - source_project: new_project, - target_project: new_project) - end + merge_requests = described_class.new(user, params).execute - before do - new_project.add_maintainer(user) - end + expect(merge_requests).to contain_exactly(old_merge_request, new_merge_request) + end - it 'filters by created_after' do - params = { project_id: new_project.id, created_after: new_merge_request.created_at } + it 'filters by updated_after' do + params = { project_id: new_project.id, updated_after: new_merge_request.updated_at } - merge_requests = described_class.new(user, params).execute + merge_requests = described_class.new(user, params).execute - expect(merge_requests).to contain_exactly(new_merge_request) - end + expect(merge_requests).to contain_exactly(new_merge_request) + end - it 'filters by created_before' do - params = { project_id: new_project.id, created_before: old_merge_request.created_at } + it 'filters by updated_before' do + params = { project_id: new_project.id, updated_before: old_merge_request.updated_at } - merge_requests = described_class.new(user, params).execute + merge_requests = described_class.new(user, params).execute - expect(merge_requests).to contain_exactly(old_merge_request) - end + expect(merge_requests).to contain_exactly(old_merge_request) + end - it 'filters by created_after and created_before' do - params = { - project_id: new_project.id, - created_after: old_merge_request.created_at, - created_before: new_merge_request.created_at - } + it 'filters by updated_after and updated_before' do + params = { + project_id: new_project.id, + updated_after: old_merge_request.updated_at, + updated_before: new_merge_request.updated_at + } - merge_requests = described_class.new(user, params).execute + merge_requests = described_class.new(user, params).execute - expect(merge_requests).to contain_exactly(old_merge_request, new_merge_request) + expect(merge_requests).to contain_exactly(old_merge_request, new_merge_request) + end end + end - it 'filters by updated_after' do - params = { project_id: new_project.id, updated_after: new_merge_request.updated_at } + describe '#row_count', :request_store do + it 'returns the number of rows for the default state' do + finder = described_class.new(user) - merge_requests = described_class.new(user, params).execute + expect(finder.row_count).to eq(7) + end - expect(merge_requests).to contain_exactly(new_merge_request) + it 'returns the number of rows for a given state' do + finder = described_class.new(user, state: 'closed') + + expect(finder.row_count).to eq(1) end + end + end - it 'filters by updated_before' do - params = { project_id: new_project.id, updated_before: old_merge_request.updated_at } + context 'when projects require different access levels for merge requests' do + let(:user) { create(:user) } - merge_requests = described_class.new(user, params).execute + let(:public_project) { create(:project, :public) } + let(:internal) { create(:project, :internal) } + let(:private_project) { create(:project, :private) } + let(:public_with_private_repo) { create(:project, :public, :repository, :repository_private) } + let(:internal_with_private_repo) { create(:project, :internal, :repository, :repository_private) } + + let(:merge_requests) { described_class.new(user, {}).execute } + + let!(:mr_public) { create(:merge_request, source_project: public_project) } + let!(:mr_private) { create(:merge_request, source_project: private_project) } + let!(:mr_internal) { create(:merge_request, source_project: internal) } + let!(:mr_private_repo_access) { create(:merge_request, source_project: public_with_private_repo) } + let!(:mr_internal_private_repo_access) { create(:merge_request, source_project: internal_with_private_repo) } + + context 'with admin user' do + let(:user) { create(:user, :admin) } - expect(merge_requests).to contain_exactly(old_merge_request) + it 'returns all merge requests' do + expect(merge_requests).to eq( + [mr_internal_private_repo_access, mr_private_repo_access, mr_internal, mr_private, mr_public] + ) end + end - it 'filters by updated_after and updated_before' do - params = { - project_id: new_project.id, - updated_after: old_merge_request.updated_at, - updated_before: new_merge_request.updated_at - } + context 'when project restricts merge requests' do + let(:non_member) { create(:user) } + let(:project) { create(:project, :repository, :public, :merge_requests_private) } + let!(:merge_request) { create(:merge_request, source_project: project) } - merge_requests = described_class.new(user, params).execute + it "returns nothing to to non members" do + merge_requests = described_class.new( + non_member, + project_id: project.id + ).execute - expect(merge_requests).to contain_exactly(old_merge_request, new_merge_request) + expect(merge_requests).to be_empty end end - end - describe '#row_count', :request_store do - it 'returns the number of rows for the default state' do - finder = described_class.new(user) + context 'with external user' do + let(:user) { create(:user, :external) } - expect(finder.row_count).to eq(7) + it 'returns only public merge requests' do + expect(merge_requests).to eq([mr_public]) + end end - it 'returns the number of rows for a given state' do - finder = described_class.new(user, state: 'closed') + context 'with authenticated user' do + it 'returns public and internal merge requests' do + expect(merge_requests).to eq([mr_internal, mr_public]) + end + + context 'being added to the private project' do + context 'as a guest' do + before do + private_project.add_guest(user) + end + + it 'does not return merge requests from the private project' do + expect(merge_requests).to eq([mr_internal, mr_public]) + end + end + + context 'as a developer' do + before do + private_project.add_developer(user) + end + + it 'returns merge requests from the private project' do + expect(merge_requests).to eq([mr_internal, mr_private, mr_public]) + end + end + end - expect(finder.row_count).to eq(1) + context 'being added to the public project with private repo access' do + context 'as a guest' do + before do + public_with_private_repo.add_guest(user) + end + + it 'returns merge requests from the project' do + expect(merge_requests).to eq([mr_internal, mr_public]) + end + end + + context 'as a reporter' do + before do + public_with_private_repo.add_reporter(user) + end + + it 'returns merge requests from the project' do + expect(merge_requests).to eq([mr_private_repo_access, mr_internal, mr_public]) + end + end + end + + context 'being added to the internal project with private repo access' do + context 'as a guest' do + before do + internal_with_private_repo.add_guest(user) + end + + it 'returns merge requests from the project' do + expect(merge_requests).to eq([mr_internal, mr_public]) + end + end + + context 'as a reporter' do + before do + internal_with_private_repo.add_reporter(user) + end + + it 'returns merge requests from the project' do + expect(merge_requests).to eq([mr_internal_private_repo_access, mr_internal, mr_public]) + end + end + end end end end diff --git a/spec/frontend/gfm_auto_complete_spec.js b/spec/frontend/gfm_auto_complete_spec.js index b79e6e0fe7bd89337f0ebfff0a753b677c1e874b..c7008c780d67e1e6476d087e409ae40b78054029 100644 --- a/spec/frontend/gfm_auto_complete_spec.js +++ b/spec/frontend/gfm_auto_complete_spec.js @@ -6,21 +6,17 @@ import GfmAutoComplete from '~/gfm_auto_complete'; import 'vendor/jquery.caret'; import 'vendor/jquery.atwho'; -import { TEST_HOST } from 'helpers/test_constants'; -import labelsFixture from 'fixtures/autocomplete_sources/labels.json'; // eslint-disable-line import/no-unresolved - describe('GfmAutoComplete', () => { const gfmAutoCompleteCallbacks = GfmAutoComplete.prototype.getDefaultCallbacks.call({ fetchData: () => {}, }); let atwhoInstance; + let items; let sorterValue; describe('DefaultOptions.sorter', () => { describe('assets loading', () => { - let items; - beforeEach(() => { jest.spyOn(GfmAutoComplete, 'isLoading').mockReturnValue(true); @@ -65,7 +61,7 @@ describe('GfmAutoComplete', () => { atwhoInstance = { setting: {} }; const query = 'query'; - const items = []; + items = []; const searchKey = 'searchKey'; gfmAutoCompleteCallbacks.sorter.call(atwhoInstance, query, items, searchKey); @@ -254,90 +250,4 @@ describe('GfmAutoComplete', () => { ).toBe('
  • grp/proj#5 Some Issue
  • '); }); }); - - describe('labels', () => { - const dataSources = { - labels: `${TEST_HOST}/autocomplete_sources/labels`, - }; - - const allLabels = labelsFixture; - const assignedLabels = allLabels.filter(label => label.set); - const unassignedLabels = allLabels.filter(label => !label.set); - - let autocomplete; - let $textarea; - - beforeEach(() => { - autocomplete = new GfmAutoComplete(dataSources); - $textarea = $(''); - autocomplete.setup($textarea, { labels: true }); - }); - - afterEach(() => { - autocomplete.destroy(); - }); - - const triggerDropdown = text => { - $textarea - .trigger('focus') - .val(text) - .caret('pos', -1); - $textarea.trigger('keyup'); - - return new Promise(window.requestAnimationFrame); - }; - - const getDropdownItems = () => { - const dropdown = document.getElementById('at-view-labels'); - const items = dropdown.getElementsByTagName('li'); - return [].map.call(items, item => item.textContent.trim()); - }; - - const expectLabels = ({ input, output }) => - triggerDropdown(input).then(() => { - expect(getDropdownItems()).toEqual(output.map(label => label.title)); - }); - - describe('with no labels assigned', () => { - beforeEach(() => { - autocomplete.cachedData['~'] = [...unassignedLabels]; - }); - - it.each` - input | output - ${'~'} | ${unassignedLabels} - ${'/label ~'} | ${unassignedLabels} - ${'/relabel ~'} | ${unassignedLabels} - ${'/unlabel ~'} | ${[]} - `('$input shows $output.length labels', expectLabels); - }); - - describe('with some labels assigned', () => { - beforeEach(() => { - autocomplete.cachedData['~'] = allLabels; - }); - - it.each` - input | output - ${'~'} | ${allLabels} - ${'/label ~'} | ${unassignedLabels} - ${'/relabel ~'} | ${allLabels} - ${'/unlabel ~'} | ${assignedLabels} - `('$input shows $output.length labels', expectLabels); - }); - - describe('with all labels assigned', () => { - beforeEach(() => { - autocomplete.cachedData['~'] = [...assignedLabels]; - }); - - it.each` - input | output - ${'~'} | ${assignedLabels} - ${'/label ~'} | ${[]} - ${'/relabel ~'} | ${assignedLabels} - ${'/unlabel ~'} | ${assignedLabels} - `('$input shows $output.length labels', expectLabels); - }); - }); }); diff --git a/spec/javascripts/api_spec.js b/spec/javascripts/api_spec.js index 1e9470970ff244718892d0aaf25554c51f223cf9..e537e0e8afcfdea5c3c25a977a8cc3f969696097 100644 --- a/spec/javascripts/api_spec.js +++ b/spec/javascripts/api_spec.js @@ -139,6 +139,40 @@ describe('Api', () => { }); }); + describe('projectMergeRequests', () => { + const projectPath = 'abc'; + const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests`; + + it('fetches all merge requests for a project', done => { + const mockData = [{ source_branch: 'foo' }, { source_branch: 'bar' }]; + mock.onGet(expectedUrl).reply(200, mockData); + Api.projectMergeRequests(projectPath) + .then(({ data }) => { + expect(data.length).toEqual(2); + expect(data[0].source_branch).toBe('foo'); + expect(data[1].source_branch).toBe('bar'); + }) + .then(done) + .catch(done.fail); + }); + + it('fetches merge requests filtered with passed params', done => { + const params = { + source_branch: 'bar', + }; + const mockData = [{ source_branch: 'bar' }]; + mock.onGet(expectedUrl, { params }).reply(200, mockData); + + Api.projectMergeRequests(projectPath, params) + .then(({ data }) => { + expect(data.length).toEqual(1); + expect(data[0].source_branch).toBe('bar'); + }) + .then(done) + .catch(done.fail); + }); + }); + describe('projectMergeRequest', () => { it('fetches a merge request', done => { const projectPath = 'abc'; diff --git a/spec/javascripts/fixtures/autocomplete_sources.rb b/spec/javascripts/fixtures/autocomplete_sources.rb deleted file mode 100644 index c117fb7cd24681850cdb9ad72dec5d96518e4888..0000000000000000000000000000000000000000 --- a/spec/javascripts/fixtures/autocomplete_sources.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -describe Projects::AutocompleteSourcesController, '(JavaScript fixtures)', type: :controller do - include JavaScriptFixturesHelpers - - set(:admin) { create(:admin) } - set(:group) { create(:group, name: 'frontend-fixtures') } - set(:project) { create(:project, namespace: group, path: 'autocomplete-sources-project') } - set(:issue) { create(:issue, project: project) } - - before(:all) do - clean_frontend_fixtures('autocomplete_sources/') - end - - before do - sign_in(admin) - end - - it 'autocomplete_sources/labels.json' do |example| - issue.labels << create(:label, project: project, title: 'bug') - issue.labels << create(:label, project: project, title: 'critical') - - create(:label, project: project, title: 'feature') - create(:label, project: project, title: 'documentation') - - get :labels, - format: :json, - params: { - namespace_id: group.path, - project_id: project.path, - type: issue.class.name, - type_id: issue.id - } - - expect(response).to be_success - store_frontend_fixture(response, example.description) - end -end diff --git a/spec/javascripts/ide/stores/actions/merge_request_spec.js b/spec/javascripts/ide/stores/actions/merge_request_spec.js index 9bfc7c397b80e03eb978bd1981166a7be3f34cdc..a5839630657bb1e7e8499d33726b5b74b86de304 100644 --- a/spec/javascripts/ide/stores/actions/merge_request_spec.js +++ b/spec/javascripts/ide/stores/actions/merge_request_spec.js @@ -2,6 +2,7 @@ import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; import store from '~/ide/stores'; import actions, { + getMergeRequestsForBranch, getMergeRequestData, getMergeRequestChanges, getMergeRequestVersions, @@ -27,6 +28,98 @@ describe('IDE store merge request actions', () => { resetStore(store); }); + describe('getMergeRequestsForBranch', () => { + describe('success', () => { + const mrData = { iid: 2, source_branch: 'bar' }; + const mockData = [mrData]; + + describe('base case', () => { + beforeEach(() => { + spyOn(service, 'getProjectMergeRequests').and.callThrough(); + mock.onGet(/api\/(.*)\/projects\/abcproject\/merge_requests/).reply(200, mockData); + }); + + it('calls getProjectMergeRequests service method', done => { + store + .dispatch('getMergeRequestsForBranch', { projectId: 'abcproject', branchId: 'bar' }) + .then(() => { + expect(service.getProjectMergeRequests).toHaveBeenCalledWith('abcproject', { + source_branch: 'bar', + order_by: 'created_at', + per_page: 1, + }); + + done(); + }) + .catch(done.fail); + }); + + it('sets the "Merge Request" Object', done => { + store + .dispatch('getMergeRequestsForBranch', { projectId: 'abcproject', branchId: 'bar' }) + .then(() => { + expect(Object.keys(store.state.projects.abcproject.mergeRequests).length).toEqual(1); + expect(Object.keys(store.state.projects.abcproject.mergeRequests)[0]).toEqual('2'); + expect(store.state.projects.abcproject.mergeRequests[2]).toEqual( + jasmine.objectContaining(mrData), + ); + done(); + }) + .catch(done.fail); + }); + + it('sets "Current Merge Request" object to the most recent MR', done => { + store + .dispatch('getMergeRequestsForBranch', { projectId: 'abcproject', branchId: 'bar' }) + .then(() => { + expect(store.state.currentMergeRequestId).toEqual('2'); + done(); + }) + .catch(done.fail); + }); + }); + + describe('no merge requests for branch available case', () => { + beforeEach(() => { + spyOn(service, 'getProjectMergeRequests').and.callThrough(); + mock.onGet(/api\/(.*)\/projects\/abcproject\/merge_requests/).reply(200, []); + }); + + it('does not fail if there are no merge requests for current branch', done => { + store + .dispatch('getMergeRequestsForBranch', { projectId: 'abcproject', branchId: 'foo' }) + .then(() => { + expect(Object.keys(store.state.projects.abcproject.mergeRequests).length).toEqual(0); + expect(store.state.currentMergeRequestId).toEqual(''); + done(); + }) + .catch(done.fail); + }); + }); + }); + + describe('error', () => { + beforeEach(() => { + mock.onGet(/api\/(.*)\/projects\/abcproject\/merge_requests/).networkError(); + }); + + it('flashes message, if error', done => { + const flashSpy = spyOnDependency(actions, 'flash'); + + getMergeRequestsForBranch({ commit() {} }, { projectId: 'abcproject', branchId: 'bar' }) + .then(() => { + fail('Expected getMergeRequestsForBranch to throw an error'); + }) + .catch(() => { + expect(flashSpy).toHaveBeenCalled(); + expect(flashSpy.calls.argsFor(0)[0]).toEqual('Error fetching merge requests for bar'); + }) + .then(done) + .catch(done.fail); + }); + }); + }); + describe('getMergeRequestData', () => { describe('success', () => { beforeEach(() => { diff --git a/spec/javascripts/ide/stores/actions/project_spec.js b/spec/javascripts/ide/stores/actions/project_spec.js index 7d8c9edd96568bacdbe3b7d692c9b59ae36866ff..7b0963713fbaa7f47d113caa36b01de44bceedd3 100644 --- a/spec/javascripts/ide/stores/actions/project_spec.js +++ b/spec/javascripts/ide/stores/actions/project_spec.js @@ -249,6 +249,7 @@ describe('IDE store project actions', () => { ['setCurrentBranchId', branch.branchId], ['getBranchData', branch], ['getFiles', branch], + ['getMergeRequestsForBranch', branch], ]); }) .then(done) diff --git a/spec/lib/constraints/project_url_constrainer_spec.rb b/spec/lib/constraints/project_url_constrainer_spec.rb index c96e7ab8495d04d81d6f6e87b3f5e03adee8710a..3496b01ebcc5b3b391fefed478d7b499789bd30b 100644 --- a/spec/lib/constraints/project_url_constrainer_spec.rb +++ b/spec/lib/constraints/project_url_constrainer_spec.rb @@ -16,6 +16,10 @@ describe Constraints::ProjectUrlConstrainer do let(:request) { build_request('foo', 'bar') } it { expect(subject.matches?(request)).to be_falsey } + + context 'existence_check is false' do + it { expect(subject.matches?(request, existence_check: false)).to be_truthy } + end end context "project id ending with .git" do diff --git a/spec/lib/gitlab/auth/ldap/config_spec.rb b/spec/lib/gitlab/auth/ldap/config_spec.rb index d3ab599d5a06989e3861650b6cdc93e20fa947cc..b91a09e3137ab1d0c404c51f53d671b54d9dffcd 100644 --- a/spec/lib/gitlab/auth/ldap/config_spec.rb +++ b/spec/lib/gitlab/auth/ldap/config_spec.rb @@ -5,6 +5,65 @@ describe Gitlab::Auth::LDAP::Config do let(:config) { described_class.new('ldapmain') } + def raw_cert + <<-EOS +-----BEGIN CERTIFICATE----- +MIIDZjCCAk4CCQDX+u/9fICksDANBgkqhkiG9w0BAQsFADB1MQswCQYDVQQGEwJV +UzEMMAoGA1UECAwDRm9vMQwwCgYDVQQHDANCYXIxDDAKBgNVBAoMA0JhejEMMAoG +A1UECwwDUXV4MQ0wCwYDVQQDDARsZGFwMR8wHQYJKoZIhvcNAQkBFhBsZGFwQGV4 +YW1wbGUuY29tMB4XDTE5MDIyNzE1NTUxNFoXDTE5MDMyOTE1NTUxNFowdTELMAkG +A1UEBhMCVVMxDDAKBgNVBAgMA0ZvbzEMMAoGA1UEBwwDQmFyMQwwCgYDVQQKDANC +YXoxDDAKBgNVBAsMA1F1eDENMAsGA1UEAwwEbGRhcDEfMB0GCSqGSIb3DQEJARYQ +bGRhcEBleGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +APuDB/4/AUmTEmhYzN13no4Kt8hkRbLQuENRHlOeQw05/MVdoB1AWLOPzIXn4kex +GD9tHkoJl8S0QPmAAcPHn5O97e+gd0ze5dRQZl/cSd2/j5zeaMvZ1mCrPN/dOluM +94Oj+wQU4bEcOlrqIMSh0ezJw10R3IHXCQFeGtIZU57WmKcrryQX4kP7KTOgRw/t +CYp+NivQHtLbBEj1MU0l10qMS2+w8Qpqov4MdW4gx4wTgId2j1ZZ56+n6Jsc9qoI +wBWBNL4XU5a3kwhYZDOJoOvI9po33KLdT1dXS81uOFXClp3LGmKDgLTwQ1w+RmQG ++JG4EvTfDIShdcTDXEaOfCECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAJM9Btu5g +k8qDiz5TilvpyoGuI4viCwusARFAFmOB/my/cHlVvkuq4bbfV1KJoWWGJg8GcklL +cnIdxc35uYM5icr6xXQyrW0GqAO+LEXyUxVQqYETxrQ/LJ03xhBnuF7hvZJIBiky +GwUy0clJxGfaCeEM8zXwePawLgGjuUawDDQOwigysoWqoMu3VFW8zl8UPa84bow9 +Kn2QmPAkLw4EcqYSCNSSvnyzu5SM64jwLWRXFsmlqD7773oT29vTkqM1EQANFEfT +7gQomLyPqoPBoFph5oSNn6Rf31QX1Sie92EAKVnZ1XmD68hKzjv6ChCtzTv4jABg +XrDwnLkORIAF/Q== +-----END CERTIFICATE----- + EOS + end + + def raw_key + <<-EOS +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQD7gwf+PwFJkxJo +WMzdd56OCrfIZEWy0LhDUR5TnkMNOfzFXaAdQFizj8yF5+JHsRg/bR5KCZfEtED5 +gAHDx5+Tve3voHdM3uXUUGZf3Endv4+c3mjL2dZgqzzf3TpbjPeDo/sEFOGxHDpa +6iDEodHsycNdEdyB1wkBXhrSGVOe1pinK68kF+JD+ykzoEcP7QmKfjYr0B7S2wRI +9TFNJddKjEtvsPEKaqL+DHVuIMeME4CHdo9WWeevp+ibHPaqCMAVgTS+F1OWt5MI +WGQziaDryPaaN9yi3U9XV0vNbjhVwpadyxpig4C08ENcPkZkBviRuBL03wyEoXXE +w1xGjnwhAgMBAAECggEAbw82GVui6uUpjLAhjm3CssAi1TcJ2+L0aq1IMe5Bd3ay +mkg0apY+VNPboQl6zuNxbJh3doPz42UhB8sxfE0Ktwd4KIb4Bxap7+2stwmkCGoN +NVy0c8d2NWuHzuZ2XXTK2vMu5Wd/HWD0l66o14sJEoEpZlB7yU216UevmjSayxjh +aBTSaYyyrf24haTaCuqwph/V73ZlMpFdSALGny0uiP/5inxciMCkMpHfX6BflSb4 +EGKsIYt9BJ0kY4GNG5bCP7971UCxp2eEJhU2fV8HuFGCOD12IqSpUqPxHxjsWpfx +T7FZ3V2kM/58Ca+5LB2y3atcPIdY0/g7/43V4VD+7QKBgQD/PO4/0cmZuuLU1LPT +C/C596kPK0JLlvvRqhbz4byRAkW/n7uQFG7TMtFNle3UmT7rk7pjtbHnByqzEd+9 +jMhBysjHOMg0+DWm7fEtSg/tJ3qLVO3nbdA4qmXYobLcLoG+PCYRLskEHHqTG/Bv +QZLbavOU6rrTqckNr1TMpNBmXwKBgQD8Q0C2YTOpwgjRUe8i6Chnc3o4x8a1i98y +9la6c7y7acWHSbEczMkNfEBrbM73rTb+bBA0Zqw+Z1gkv8bGpvGxX8kbSfJJ2YKW +9koxpLNTVNVapqBa9ImiaozV285dz9Ukx8bnMOJlTELpOl7RRV7iF0smYjfHIl3D +Yxyda/MtfwKBgHb9l/Dmw77IkqE4PFFimqqIHCe3OiP1UpavXh36midcUNoCBLYp +4HTTlyI9iG/5tYysBVQgy7xx6eUrqww6Ss3pVOsTvLp9EL4u5aYAhiZApm+4e2TO +HCmevvZcg/8EK3Zdoj2Wex5QjJBykQe9IVLrrH07ZTfySon3uGfjWkivAoGAGvqS +VC8HGHOw/7n0ilYr5Ax8mM/813OzFj80PVKdb6m7P2HJOFxKcE/Gj/aeF+0FgaZL +AV+tsirZSWzdNGesV5z35Bw/dlh11/FVNAP6TcI34y8I3VFj2uPsVf7hDjVpBTr8 +ccNPoyfJzCm69ESoBiQZnGxKrNhnELtr1wYxhr8CgYApWwf4hVrTWV1zs+pEJenh +AtlErSqafbECNDSwS5BX8yDpu5yRBJ4xegO/rNlmb8ICRYkuJapD1xXicFOsmfUK +0Ff8afd2Q/OfBeUdq9KA4JO9fNqzEwOWvv8Ryn4ZSYcAuLP7IVJKjjI6R7rYaO/G +3OWJdizbykGOi0BFDu+3dw== +-----END PRIVATE KEY----- + EOS + end + describe '.servers' do it 'returns empty array if no server information is available' do allow(Gitlab.config).to receive(:ldap).and_return('enabled' => false) @@ -89,6 +148,42 @@ describe Gitlab::Auth::LDAP::Config do expect(config.adapter_options[:encryption]).to include({ method: :start_tls }) end + it 'transforms SSL cert and key to OpenSSL objects' do + stub_ldap_config( + options: { + 'host' => 'ldap.example.com', + 'port' => 686, + 'encryption' => 'start_tls', + 'tls_options' => { + 'cert' => raw_cert, + 'key' => raw_key + } + } + ) + + expect(config.adapter_options[:encryption][:tls_options][:cert]).to be_a(OpenSSL::X509::Certificate) + expect(config.adapter_options[:encryption][:tls_options][:key]).to be_a(OpenSSL::PKey::RSA) + end + + it 'logs an error when an invalid key or cert are configured' do + allow(Rails.logger).to receive(:error) + stub_ldap_config( + options: { + 'host' => 'ldap.example.com', + 'port' => 686, + 'encryption' => 'start_tls', + 'tls_options' => { + 'cert' => 'invalid cert', + 'key' => 'invalid_key' + } + } + ) + + config.adapter_options + + expect(Rails.logger).to have_received(:error).with(/LDAP TLS Options/).twice + end + context 'when verify_certificates is enabled' do it 'sets tls_options to OpenSSL defaults' do stub_ldap_config( @@ -130,7 +225,9 @@ describe Gitlab::Auth::LDAP::Config do 'host' => 'ldap.example.com', 'port' => 686, 'encryption' => 'simple_tls', - 'ca_file' => '/etc/ca.pem' + 'tls_options' => { + 'ca_file' => '/etc/ca.pem' + } } ) @@ -145,7 +242,9 @@ describe Gitlab::Auth::LDAP::Config do 'host' => 'ldap.example.com', 'port' => 686, 'encryption' => 'simple_tls', - 'ca_file' => ' ' + 'tls_options' => { + 'ca_file' => ' ' + } } ) @@ -160,7 +259,9 @@ describe Gitlab::Auth::LDAP::Config do 'host' => 'ldap.example.com', 'port' => 686, 'encryption' => 'simple_tls', - 'ssl_version' => 'TLSv1_2' + 'tls_options' => { + 'ssl_version' => 'TLSv1_2' + } } ) @@ -175,7 +276,9 @@ describe Gitlab::Auth::LDAP::Config do 'host' => 'ldap.example.com', 'port' => 686, 'encryption' => 'simple_tls', - 'ssl_version' => ' ' + 'tls_options' => { + 'ssl_version' => ' ' + } } ) @@ -223,6 +326,23 @@ describe Gitlab::Auth::LDAP::Config do ) end + it 'transforms SSL cert and key to OpenSSL objects' do + stub_ldap_config( + options: { + 'host' => 'ldap.example.com', + 'port' => 686, + 'encryption' => 'start_tls', + 'tls_options' => { + 'cert' => raw_cert, + 'key' => raw_key + } + } + ) + + expect(config.omniauth_options[:tls_options][:cert]).to be_a(OpenSSL::X509::Certificate) + expect(config.omniauth_options[:tls_options][:key]).to be_a(OpenSSL::PKey::RSA) + end + context 'when verify_certificates is enabled' do it 'specifies disable_verify_certificates as false' do stub_ldap_config( @@ -261,11 +381,13 @@ describe Gitlab::Auth::LDAP::Config do 'port' => 686, 'encryption' => 'simple_tls', 'verify_certificates' => true, - 'ca_file' => '/etc/ca.pem' + 'tls_options' => { + 'ca_file' => '/etc/ca.pem' + } } ) - expect(config.omniauth_options).to include({ ca_file: '/etc/ca.pem' }) + expect(config.omniauth_options[:tls_options]).to include({ ca_file: '/etc/ca.pem' }) end end @@ -277,11 +399,13 @@ describe Gitlab::Auth::LDAP::Config do 'port' => 686, 'encryption' => 'simple_tls', 'verify_certificates' => true, - 'ca_file' => ' ' + 'tls_options' => { + 'ca_file' => ' ' + } } ) - expect(config.omniauth_options).not_to have_key(:ca_file) + expect(config.omniauth_options[:tls_options]).not_to have_key(:ca_file) end end @@ -293,11 +417,13 @@ describe Gitlab::Auth::LDAP::Config do 'port' => 686, 'encryption' => 'simple_tls', 'verify_certificates' => true, - 'ssl_version' => 'TLSv1_2' + 'tls_options' => { + 'ssl_version' => 'TLSv1_2' + } } ) - expect(config.omniauth_options).to include({ ssl_version: 'TLSv1_2' }) + expect(config.omniauth_options[:tls_options]).to include({ ssl_version: 'TLSv1_2' }) end end @@ -309,11 +435,14 @@ describe Gitlab::Auth::LDAP::Config do 'port' => 686, 'encryption' => 'simple_tls', 'verify_certificates' => true, - 'ssl_version' => ' ' + 'tls_options' => { + 'ssl_version' => ' ' + } } ) - expect(config.omniauth_options).not_to have_key(:ssl_version) + # OpenSSL default params includes `ssl_version` so we just check that it's not blank + expect(config.omniauth_options[:tls_options]).not_to include({ ssl_version: ' ' }) end end end diff --git a/spec/lib/gitlab/ci/templates/templates_spec.rb b/spec/lib/gitlab/ci/templates/templates_spec.rb index 0dd74399a47540e5da22d971dd1c424c3e1f0e84..fbbd58280a9d9d7071650659c6e3f95b629018e9 100644 --- a/spec/lib/gitlab/ci/templates/templates_spec.rb +++ b/spec/lib/gitlab/ci/templates/templates_spec.rb @@ -3,9 +3,40 @@ require 'spec_helper' describe "CI YML Templates" do - Gitlab::Template::GitlabCiYmlTemplate.all.each do |template| - it "#{template.name} should be valid" do - expect { Gitlab::Ci::YamlProcessor.new(template.content) }.not_to raise_error + ABSTRACT_TEMPLATES = %w[Serverless].freeze + + def self.concrete_templates + Gitlab::Template::GitlabCiYmlTemplate.all.reject do |template| + ABSTRACT_TEMPLATES.include?(template.name) + end + end + + def self.abstract_templates + Gitlab::Template::GitlabCiYmlTemplate.all.select do |template| + ABSTRACT_TEMPLATES.include?(template.name) + end + end + + describe 'concrete templates with CI/CD jobs' do + concrete_templates.each do |template| + it "#{template.name} template should be valid" do + expect { Gitlab::Ci::YamlProcessor.new(template.content) } + .not_to raise_error + end + end + end + + describe 'abstract templates without concrete jobs defined' do + abstract_templates.each do |template| + it "#{template.name} template should be valid after being implemented" do + content = template.content + <<~EOS + concrete_build_implemented_by_a_user: + stage: build + script: do something + EOS + + expect { Gitlab::Ci::YamlProcessor.new(content) }.not_to raise_error + end end end end diff --git a/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb index 4d222564fd0b52b9ff4c5f7084a2bf4d73656259..0ebd8994636935b41b8f9a699264377c2e373905 100644 --- a/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb +++ b/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb @@ -50,8 +50,8 @@ describe Gitlab::DependencyLinker::ComposerJsonLinker do %{#{name}} end - it 'links the module name' do - expect(subject).to include(link('laravel/laravel', 'https://packagist.org/packages/laravel/laravel')) + it 'does not link the module name' do + expect(subject).not_to include(link('laravel/laravel', 'https://packagist.org/packages/laravel/laravel')) end it 'links the homepage' do diff --git a/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb index a97803b119ebdb7cdc454c701a6a655b7484cd25..f00f6b47b9466008398ce33ed54ecb86cf37175f 100644 --- a/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb +++ b/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb @@ -41,13 +41,16 @@ describe Gitlab::DependencyLinker::GemfileLinker do end it 'links dependencies' do - expect(subject).to include(link('rails', 'https://rubygems.org/gems/rails')) expect(subject).to include(link('rails-deprecated_sanitizer', 'https://rubygems.org/gems/rails-deprecated_sanitizer')) - expect(subject).to include(link('responders', 'https://rubygems.org/gems/responders')) - expect(subject).to include(link('sprockets', 'https://rubygems.org/gems/sprockets')) expect(subject).to include(link('default_value_for', 'https://rubygems.org/gems/default_value_for')) end + it 'links to external dependencies' do + expect(subject).to include(link('rails', 'https://github.com/rails/rails')) + expect(subject).to include(link('responders', 'https://github.com/rails/responders')) + expect(subject).to include(link('sprockets', 'https://gitlab.example.com/gems/sprockets')) + end + it 'links GitHub repos' do expect(subject).to include(link('rails/rails', 'https://github.com/rails/rails')) expect(subject).to include(link('rails/responders', 'https://github.com/rails/responders')) diff --git a/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb b/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb index 24ad7d12f4c0b68b8b1921edda1db02f61aa74a7..6c6a5d70576a0433a3ef6d8629cdd397c0aaebde 100644 --- a/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb +++ b/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb @@ -43,8 +43,8 @@ describe Gitlab::DependencyLinker::GemspecLinker do %{#{name}} end - it 'links the gem name' do - expect(subject).to include(link('gitlab_git', 'https://rubygems.org/gems/gitlab_git')) + it 'does not link the gem name' do + expect(subject).not_to include(link('gitlab_git', 'https://rubygems.org/gems/gitlab_git')) end it 'links the license' do diff --git a/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb index 1e8b72afb7be458149d4fef24bfa4d1e285a306c..9050127af7f28c8cc2991ea50dca6b6a28d4d3c5 100644 --- a/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb +++ b/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb @@ -33,7 +33,8 @@ describe Gitlab::DependencyLinker::PackageJsonLinker do "express": "4.2.x", "bigpipe": "bigpipe/pagelet", "plates": "https://github.com/flatiron/plates/tarball/master", - "karma": "^1.4.1" + "karma": "^1.4.1", + "random": "git+https://EdOverflow@github.com/example/example.git" }, "devDependencies": { "vows": "^0.7.0", @@ -51,8 +52,8 @@ describe Gitlab::DependencyLinker::PackageJsonLinker do %{#{name}} end - it 'links the module name' do - expect(subject).to include(link('module-name', 'https://npmjs.com/package/module-name')) + it 'does not link the module name' do + expect(subject).not_to include(link('module-name', 'https://npmjs.com/package/module-name')) end it 'links the homepage' do @@ -71,14 +72,21 @@ describe Gitlab::DependencyLinker::PackageJsonLinker do expect(subject).to include(link('primus', 'https://npmjs.com/package/primus')) expect(subject).to include(link('async', 'https://npmjs.com/package/async')) expect(subject).to include(link('express', 'https://npmjs.com/package/express')) - expect(subject).to include(link('bigpipe', 'https://npmjs.com/package/bigpipe')) - expect(subject).to include(link('plates', 'https://npmjs.com/package/plates')) expect(subject).to include(link('karma', 'https://npmjs.com/package/karma')) expect(subject).to include(link('vows', 'https://npmjs.com/package/vows')) expect(subject).to include(link('assume', 'https://npmjs.com/package/assume')) expect(subject).to include(link('pre-commit', 'https://npmjs.com/package/pre-commit')) end + it 'links dependencies to URL detected on value' do + expect(subject).to include(link('bigpipe', 'https://github.com/bigpipe/pagelet')) + expect(subject).to include(link('plates', 'https://github.com/flatiron/plates/tarball/master')) + end + + it 'does not link to NPM when invalid git URL' do + expect(subject).not_to include(link('random', 'https://npmjs.com/package/random')) + end + it 'links GitHub repos' do expect(subject).to include(link('bigpipe/pagelet', 'https://github.com/bigpipe/pagelet')) end diff --git a/spec/lib/gitlab/dependency_linker/parser/gemfile_spec.rb b/spec/lib/gitlab/dependency_linker/parser/gemfile_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..9bfb1b13a2b5eaac191ee1948847350f11efd80a --- /dev/null +++ b/spec/lib/gitlab/dependency_linker/parser/gemfile_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Gitlab::DependencyLinker::Parser::Gemfile do + describe '#parse' do + let(:file_content) do + <<-CONTENT.strip_heredoc + source 'https://rubygems.org' + + gem "rails", '4.2.6', github: "rails/rails" + gem 'rails-deprecated_sanitizer', '~> 1.0.3' + gem 'responders', '~> 2.0', :github => 'rails/responders' + gem 'sprockets', '~> 3.6.0', git: 'https://gitlab.example.com/gems/sprockets' + gem 'default_value_for', '~> 3.0.0' + CONTENT + end + + subject { described_class.new(file_content).parse(keyword: 'gem') } + + def fetch_package(name) + subject.find { |package| package.name == name } + end + + it 'returns parsed packages' do + expect(subject.size).to eq(5) + expect(subject).to all(be_a(Gitlab::DependencyLinker::Package)) + end + + it 'packages respond to name and external_ref accordingly' do + expect(fetch_package('rails')).to have_attributes(name: 'rails', + github_ref: 'rails/rails', + git_ref: nil) + + expect(fetch_package('sprockets')).to have_attributes(name: 'sprockets', + github_ref: nil, + git_ref: 'https://gitlab.example.com/gems/sprockets') + + expect(fetch_package('default_value_for')).to have_attributes(name: 'default_value_for', + github_ref: nil, + git_ref: nil) + end + end +end diff --git a/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb index cdfd7ad98261ccc70e7332e50a4e743a2c7ee8b7..8f1b523653e63bf827ba33ab941345e25475333c 100644 --- a/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb +++ b/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb @@ -43,7 +43,10 @@ describe Gitlab::DependencyLinker::PodfileLinker do it 'links packages' do expect(subject).to include(link('AFNetworking', 'https://cocoapods.org/pods/AFNetworking')) - expect(subject).to include(link('Interstellar/Core', 'https://cocoapods.org/pods/Interstellar')) + end + + it 'links external packages' do + expect(subject).to include(link('Interstellar/Core', 'https://github.com/ashfurrow/Interstellar.git')) end it 'links Git repos' do diff --git a/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb b/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb index ed60ab459551b398bd4fc2d51890dfaa4fa33d3a..bacec830103cdc037cab4bade679117442587d99 100644 --- a/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb +++ b/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb @@ -42,8 +42,8 @@ describe Gitlab::DependencyLinker::PodspecLinker do %{#{name}} end - it 'links the gem name' do - expect(subject).to include(link('Reachability', 'https://cocoapods.org/pods/Reachability')) + it 'does not link the pod name' do + expect(subject).not_to include(link('Reachability', 'https://cocoapods.org/pods/Reachability')) end it 'links the license' do diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 018a5d3dd3ddbfbb8e0d7dac1cdfdef1fca840ae..01da3ea70815e8c612538feb00d4ac514c6d1fec 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -127,7 +127,7 @@ ci_pipelines: - scheduled_actions - artifacts - pipeline_schedule -- merge_requests +- merge_requests_as_head_pipeline - merge_request - deployments - environments diff --git a/spec/lib/gitlab/import_export/merge_request_parser_spec.rb b/spec/lib/gitlab/import_export/merge_request_parser_spec.rb index 68eaa70e6b61c0d3f1a3051788aadc82e47cedf3..4b234411a4468a06cafa0c910d7c8c118182830f 100644 --- a/spec/lib/gitlab/import_export/merge_request_parser_spec.rb +++ b/spec/lib/gitlab/import_export/merge_request_parser_spec.rb @@ -41,4 +41,20 @@ describe Gitlab::ImportExport::MergeRequestParser do expect(parsed_merge_request).to eq(merge_request) end + + context 'when the merge request has diffs' do + let(:merge_request) do + build(:merge_request, source_project: forked_project, target_project: project) + end + + context 'when the diff is invalid' do + let(:merge_request_diff) { build(:merge_request_diff, merge_request: merge_request, base_commit_sha: 'foobar') } + + it 'sets the diff to nil' do + expect(merge_request_diff).to be_invalid + expect(merge_request_diff.merge_request).to eq merge_request + expect(parsed_merge_request.merge_request_diff).to be_nil + end + end + end end diff --git a/spec/lib/gitlab/kubernetes/kube_client_spec.rb b/spec/lib/gitlab/kubernetes/kube_client_spec.rb index 02364e921499171d8910de511da9b1d3a97c5fb5..978e64c4407885bf6ebd17683409f5fab0f2c8ee 100644 --- a/spec/lib/gitlab/kubernetes/kube_client_spec.rb +++ b/spec/lib/gitlab/kubernetes/kube_client_spec.rb @@ -50,6 +50,36 @@ describe Gitlab::Kubernetes::KubeClient do end end + describe '#initialize' do + shared_examples 'local address' do + it 'blocks local addresses' do + expect { client }.to raise_error(Gitlab::UrlBlocker::BlockedUrlError) + end + + context 'when local requests are allowed' do + before do + stub_application_setting(allow_local_requests_from_hooks_and_services: true) + end + + it 'allows local addresses' do + expect { client }.not_to raise_error + end + end + end + + context 'localhost address' do + let(:api_url) { 'http://localhost:22' } + + it_behaves_like 'local address' + end + + context 'private network address' do + let(:api_url) { 'http://192.168.1.2:3003' } + + it_behaves_like 'local address' + end + end + describe '#core_client' do subject { client.core_client } diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 15b04c9d0666499cdba15d8cb596afefd0c05a12..3c8897ed37c5956cc70d92ef3dcb08c4610c0e88 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -208,25 +208,53 @@ describe Notify do let(:new_issue) { create(:issue) } subject { described_class.issue_moved_email(recipient, issue, new_issue, current_user) } - it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do - let(:model) { issue } - end - it_behaves_like 'it should show Gmail Actions View Issue link' - it_behaves_like 'an unsubscribeable thread' - it_behaves_like 'appearance header and footer enabled' - it_behaves_like 'appearance header and footer not enabled' + context 'when a user has permissions to access the new issue' do + before do + new_issue.project.add_developer(recipient) + end + + it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do + let(:model) { issue } + end + it_behaves_like 'it should show Gmail Actions View Issue link' + it_behaves_like 'an unsubscribeable thread' + + it 'contains description about action taken' do + is_expected.to have_body_text 'Issue was moved to another project' + end - it 'contains description about action taken' do - is_expected.to have_body_text 'Issue was moved to another project' + it 'has the correct subject and body' do + new_issue_url = project_issue_path(new_issue.project, new_issue) + + aggregate_failures do + is_expected.to have_referable_subject(issue, reply: true) + is_expected.to have_body_text(new_issue_url) + is_expected.to have_body_text(project_issue_path(project, issue)) + end + end + + it 'contains the issue title' do + is_expected.to have_body_text new_issue.title + end end - it 'has the correct subject and body' do - new_issue_url = project_issue_path(new_issue.project, new_issue) + context 'when a user does not permissions to access the new issue' do + it 'has the correct subject and body' do + new_issue_url = project_issue_path(new_issue.project, new_issue) - aggregate_failures do - is_expected.to have_referable_subject(issue, reply: true) - is_expected.to have_body_text(new_issue_url) - is_expected.to have_body_text(project_issue_path(project, issue)) + aggregate_failures do + is_expected.to have_referable_subject(issue, reply: true) + is_expected.not_to have_body_text(new_issue_url) + is_expected.to have_body_text(project_issue_path(project, issue)) + end + end + + it 'does not contain the issue title' do + is_expected.not_to have_body_text new_issue.title + end + + it 'contains information about missing permissions' do + is_expected.to have_body_text "You don't have access to the project." end end end diff --git a/spec/models/active_session_spec.rb b/spec/models/active_session_spec.rb index 129b2f926831beec01bfee0aad8f9386fe710aa5..e128fe8a4b727e405a19f490dac3dedc30ee61e8 100644 --- a/spec/models/active_session_spec.rb +++ b/spec/models/active_session_spec.rb @@ -7,7 +7,10 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_shared_state do end end - let(:session) { double(:session, id: '6919a6f1bb119dd7396fadc38fd18d0d') } + let(:session) do + double(:session, { id: '6919a6f1bb119dd7396fadc38fd18d0d', + '[]': {} }) + end let(:request) do double(:request, { diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 462793b259bb992f9977a64a3d95444cc701be1b..d0b42d103a5b703553ae3525aeb132f56871c4e5 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -513,9 +513,16 @@ describe Ci::Pipeline, :mailer do source_project: project, source_branch: 'feature', target_project: project, - target_branch: 'master') + target_branch: 'master', + assignee: assignee, + milestone: milestone, + labels: labels) end + let(:assignee) { create(:user) } + let(:milestone) { create(:milestone, project: project) } + let(:labels) { create_list(:label, 2) } + it 'exposes merge request pipeline variables' do expect(subject.to_hash) .to include( @@ -531,7 +538,11 @@ describe Ci::Pipeline, :mailer do 'CI_MERGE_REQUEST_SOURCE_PROJECT_PATH' => merge_request.source_project.full_path, 'CI_MERGE_REQUEST_SOURCE_PROJECT_URL' => merge_request.source_project.web_url, 'CI_MERGE_REQUEST_SOURCE_BRANCH_NAME' => merge_request.source_branch.to_s, - 'CI_MERGE_REQUEST_SOURCE_BRANCH_SHA' => pipeline.source_sha.to_s) + 'CI_MERGE_REQUEST_SOURCE_BRANCH_SHA' => pipeline.source_sha.to_s, + 'CI_MERGE_REQUEST_TITLE' => merge_request.title, + 'CI_MERGE_REQUEST_ASSIGNEES' => assignee.username, + 'CI_MERGE_REQUEST_MILESTONE' => milestone.title, + 'CI_MERGE_REQUEST_LABELS' => labels.map(&:title).join(',')) end context 'when source project does not exist' do @@ -547,6 +558,30 @@ describe Ci::Pipeline, :mailer do CI_MERGE_REQUEST_SOURCE_BRANCH_NAME]) end end + + context 'without assignee' do + let(:assignee) { nil } + + it 'does not expose assignee variable' do + expect(subject.to_hash.keys).not_to include('CI_MERGE_REQUEST_ASSIGNEES') + end + end + + context 'without milestone' do + let(:milestone) { nil } + + it 'does not expose milestone variable' do + expect(subject.to_hash.keys).not_to include('CI_MERGE_REQUEST_MILESTONE') + end + end + + context 'without labels' do + let(:labels) { [] } + + it 'does not expose labels variable' do + expect(subject.to_hash.keys).not_to include('CI_MERGE_REQUEST_LABELS') + end + end end end @@ -2217,7 +2252,7 @@ describe Ci::Pipeline, :mailer do end end - describe "#merge_requests" do + describe "#merge_requests_as_head_pipeline" do let(:project) { create(:project) } let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master', sha: 'a288a022a53a5a944fae87bcec6efc87b7061808') } @@ -2225,20 +2260,20 @@ describe Ci::Pipeline, :mailer do allow_any_instance_of(MergeRequest).to receive(:diff_head_sha) { 'a288a022a53a5a944fae87bcec6efc87b7061808' } merge_request = create(:merge_request, source_project: project, head_pipeline: pipeline, source_branch: pipeline.ref) - expect(pipeline.merge_requests).to eq([merge_request]) + expect(pipeline.merge_requests_as_head_pipeline).to eq([merge_request]) end it "doesn't return merge requests whose source branch doesn't match the pipeline's ref" do create(:merge_request, source_project: project, source_branch: 'feature', target_branch: 'master') - expect(pipeline.merge_requests).to be_empty + expect(pipeline.merge_requests_as_head_pipeline).to be_empty end it "doesn't return merge requests whose `diff_head_sha` doesn't match the pipeline's SHA" do create(:merge_request, source_project: project, source_branch: pipeline.ref) allow_any_instance_of(MergeRequest).to receive(:diff_head_sha) { '97de212e80737a608d939f648d959671fb0a0142b' } - expect(pipeline.merge_requests).to be_empty + expect(pipeline.merge_requests_as_head_pipeline).to be_empty end end diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb index 4068d98d8f7688025e9136d67e5fe77a10b589ad..3b32ca8df05d067265a371f8b1d8f7be684987fa 100644 --- a/spec/models/clusters/platforms/kubernetes_spec.rb +++ b/spec/models/clusters/platforms/kubernetes_spec.rb @@ -98,6 +98,22 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching it { expect(kubernetes.save).to be_truthy } end + + context 'when api_url is localhost' do + let(:api_url) { 'http://localhost:22' } + + it { expect(kubernetes.save).to be_falsey } + + context 'Application settings allows local requests' do + before do + allow(ApplicationSetting) + .to receive(:current) + .and_return(ApplicationSetting.build_from_defaults(allow_local_requests_from_hooks_and_services: true)) + end + + it { expect(kubernetes.save).to be_truthy } + end + end end context 'when validates token' do diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index 41159348e04f80b188188cd6717a29b4c532ae9f..72c6161424b4237a07186766c34f4d0977cca65c 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -32,17 +32,56 @@ describe Issuable do end describe "Validation" do - subject { build(:issue) } + context 'general validations' do + subject { build(:issue) } - before do - allow(InternalId).to receive(:generate_next).and_return(nil) + before do + allow(InternalId).to receive(:generate_next).and_return(nil) + end + + it { is_expected.to validate_presence_of(:project) } + it { is_expected.to validate_presence_of(:iid) } + it { is_expected.to validate_presence_of(:author) } + it { is_expected.to validate_presence_of(:title) } + it { is_expected.to validate_length_of(:title).is_at_most(255) } end - it { is_expected.to validate_presence_of(:project) } - it { is_expected.to validate_presence_of(:iid) } - it { is_expected.to validate_presence_of(:author) } - it { is_expected.to validate_presence_of(:title) } - it { is_expected.to validate_length_of(:title).is_at_most(255) } + describe 'milestone' do + let(:project) { create(:project) } + let(:milestone_id) { create(:milestone, project: project).id } + let(:params) do + { + title: 'something', + project: project, + author: build(:user), + milestone_id: milestone_id + } + end + + subject { issuable_class.new(params) } + + context 'with correct params' do + it { is_expected.to be_valid } + end + + context 'with empty string milestone' do + let(:milestone_id) { '' } + + it { is_expected.to be_valid } + end + + context 'with nil milestone id' do + let(:milestone_id) { nil } + + it { is_expected.to be_valid } + end + + context 'with a milestone id from another project' do + let(:milestone_id) { create(:milestone).id } + + it { is_expected.to be_invalid } + end + end end describe "Scope" do @@ -66,6 +105,48 @@ describe Issuable do end end + describe '#milestone_available?' do + let(:group) { create(:group) } + let(:project) { create(:project, group: group) } + let(:issue) { create(:issue, project: project) } + + def build_issuable(milestone_id) + issuable_class.new(project: project, milestone_id: milestone_id) + end + + it 'returns true with a milestone from the issue project' do + milestone = create(:milestone, project: project) + + expect(build_issuable(milestone.id).milestone_available?).to be_truthy + end + + it 'returns true with a milestone from the issue project group' do + milestone = create(:milestone, group: group) + + expect(build_issuable(milestone.id).milestone_available?).to be_truthy + end + + it 'returns true with a milestone from the the parent of the issue project group', :nested_groups do + parent = create(:group) + group.update(parent: parent) + milestone = create(:milestone, group: parent) + + expect(build_issuable(milestone.id).milestone_available?).to be_truthy + end + + it 'returns false with a milestone from another project' do + milestone = create(:milestone) + + expect(build_issuable(milestone.id).milestone_available?).to be_falsey + end + + it 'returns false with a milestone from another group' do + milestone = create(:milestone, group: create(:group)) + + expect(build_issuable(milestone.id).milestone_available?).to be_falsey + end + end + describe ".search" do let!(:searchable_issue) { create(:issue, title: "Searchable awesome issue") } let!(:searchable_issue2) { create(:issue, title: 'Aw') } diff --git a/spec/models/concerns/milestoneish_spec.rb b/spec/models/concerns/milestoneish_spec.rb index 87bf731340f58e652eaedc3824ccc99ddb138a36..81ca5b638fec0ecdce42b66c154da2f59048290a 100644 --- a/spec/models/concerns/milestoneish_spec.rb +++ b/spec/models/concerns/milestoneish_spec.rb @@ -9,8 +9,10 @@ describe Milestone, 'Milestoneish' do let(:admin) { create(:admin) } let(:project) { create(:project, :public) } let(:milestone) { create(:milestone, project: project) } - let!(:issue) { create(:issue, project: project, milestone: milestone) } - let!(:security_issue_1) { create(:issue, :confidential, project: project, author: author, milestone: milestone) } + let(:label1) { create(:label, project: project) } + let(:label2) { create(:label, project: project) } + let!(:issue) { create(:issue, project: project, milestone: milestone, assignees: [member], labels: [label1]) } + let!(:security_issue_1) { create(:issue, :confidential, project: project, author: author, milestone: milestone, labels: [label2]) } let!(:security_issue_2) { create(:issue, :confidential, project: project, assignees: [assignee], milestone: milestone) } let!(:closed_issue_1) { create(:issue, :closed, project: project, milestone: milestone) } let!(:closed_issue_2) { create(:issue, :closed, project: project, milestone: milestone) } @@ -42,13 +44,102 @@ describe Milestone, 'Milestoneish' do end end + context 'attributes visibility' do + using RSpec::Parameterized::TableSyntax + + let(:users) do + { + anonymous: nil, + non_member: non_member, + guest: guest, + member: member, + assignee: assignee + } + end + + let(:project_visibility_levels) do + { + public: Gitlab::VisibilityLevel::PUBLIC, + internal: Gitlab::VisibilityLevel::INTERNAL, + private: Gitlab::VisibilityLevel::PRIVATE + } + end + + describe '#issue_participants_visible_by_user' do + where(:visibility, :user_role, :result) do + :public | nil | [:member] + :public | :non_member | [:member] + :public | :guest | [:member] + :public | :member | [:member, :assignee] + :internal | nil | [] + :internal | :non_member | [:member] + :internal | :guest | [:member] + :internal | :member | [:member, :assignee] + :private | nil | [] + :private | :non_member | [] + :private | :guest | [:member] + :private | :member | [:member, :assignee] + end + + with_them do + before do + project.update(visibility_level: project_visibility_levels[visibility]) + end + + it 'returns the proper participants' do + user = users[user_role] + participants = result.map { |role| users[role] } + + expect(milestone.issue_participants_visible_by_user(user)).to match_array(participants) + end + end + end + + describe '#issue_labels_visible_by_user' do + let(:labels) do + { + label1: label1, + label2: label2 + } + end + + where(:visibility, :user_role, :result) do + :public | nil | [:label1] + :public | :non_member | [:label1] + :public | :guest | [:label1] + :public | :member | [:label1, :label2] + :internal | nil | [] + :internal | :non_member | [:label1] + :internal | :guest | [:label1] + :internal | :member | [:label1, :label2] + :private | nil | [] + :private | :non_member | [] + :private | :guest | [:label1] + :private | :member | [:label1, :label2] + end + + with_them do + before do + project.update(visibility_level: project_visibility_levels[visibility]) + end + + it 'returns the proper participants' do + user = users[user_role] + expected_labels = result.map { |label| labels[label] } + + expect(milestone.issue_labels_visible_by_user(user)).to match_array(expected_labels) + end + end + end + end + describe '#sorted_merge_requests' do it 'sorts merge requests by label priority' do merge_request_1 = create(:labeled_merge_request, labels: [label_2], source_project: project, source_branch: 'branch_1', milestone: milestone) merge_request_2 = create(:labeled_merge_request, labels: [label_1], source_project: project, source_branch: 'branch_2', milestone: milestone) merge_request_3 = create(:labeled_merge_request, labels: [label_3], source_project: project, source_branch: 'branch_3', milestone: milestone) - merge_requests = milestone.sorted_merge_requests + merge_requests = milestone.sorted_merge_requests(member) expect(merge_requests.first).to eq(merge_request_2) expect(merge_requests.second).to eq(merge_request_1) @@ -56,6 +147,51 @@ describe Milestone, 'Milestoneish' do end end + describe '#merge_requests_visible_to_user' do + let(:merge_request) { create(:merge_request, source_project: project, milestone: milestone) } + + context 'when project is private' do + before do + project.update(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + end + + it 'does not return any merge request for a non member' do + merge_requests = milestone.merge_requests_visible_to_user(non_member) + expect(merge_requests).to be_empty + end + + it 'returns milestone merge requests for a member' do + merge_requests = milestone.merge_requests_visible_to_user(member) + expect(merge_requests).to contain_exactly(merge_request) + end + end + + context 'when project is public' do + context 'when merge requests are available to anyone' do + it 'returns milestone merge requests for a non member' do + merge_requests = milestone.merge_requests_visible_to_user(non_member) + expect(merge_requests).to contain_exactly(merge_request) + end + end + + context 'when merge requests are available to project members' do + before do + project.project_feature.update(merge_requests_access_level: ProjectFeature::PRIVATE) + end + + it 'does not return any merge request for a non member' do + merge_requests = milestone.merge_requests_visible_to_user(non_member) + expect(merge_requests).to be_empty + end + + it 'returns milestone merge requests for a member' do + merge_requests = milestone.merge_requests_visible_to_user(member) + expect(merge_requests).to contain_exactly(merge_request) + end + end + end + end + describe '#closed_items_count' do it 'does not count confidential issues for non project members' do expect(milestone.closed_items_count(non_member)).to eq 2 diff --git a/spec/models/issue/metrics_spec.rb b/spec/models/issue/metrics_spec.rb index 1bf0ecb98adf46b74fffa638206722f3fb47b305..b7291eebe64620fe9b54f985299d6f1479ef392f 100644 --- a/spec/models/issue/metrics_spec.rb +++ b/spec/models/issue/metrics_spec.rb @@ -9,7 +9,7 @@ describe Issue::Metrics do context "milestones" do it "records the first time an issue is associated with a milestone" do time = Time.now - Timecop.freeze(time) { subject.update(milestone: create(:milestone)) } + Timecop.freeze(time) { subject.update(milestone: create(:milestone, project: project)) } metrics = subject.metrics expect(metrics).to be_present @@ -18,9 +18,9 @@ describe Issue::Metrics do it "does not record the second time an issue is associated with a milestone" do time = Time.now - Timecop.freeze(time) { subject.update(milestone: create(:milestone)) } + Timecop.freeze(time) { subject.update(milestone: create(:milestone, project: project)) } Timecop.freeze(time + 2.hours) { subject.update(milestone: nil) } - Timecop.freeze(time + 6.hours) { subject.update(milestone: create(:milestone)) } + Timecop.freeze(time + 6.hours) { subject.update(milestone: create(:milestone, project: project)) } metrics = subject.metrics expect(metrics).to be_present diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb index 1849d3bac12d1ec2dfc5b9d8be01a005d52ea417..e530e0302f5b44c40b1cc714b6c6d57d58424afd 100644 --- a/spec/models/merge_request_diff_spec.rb +++ b/spec/models/merge_request_diff_spec.rb @@ -3,6 +3,18 @@ require 'spec_helper' describe MergeRequestDiff do let(:diff_with_commits) { create(:merge_request).merge_request_diff } + describe 'validations' do + subject { diff_with_commits } + + it 'checks sha format of base_commit_sha, head_commit_sha and start_commit_sha' do + subject.base_commit_sha = subject.head_commit_sha = subject.start_commit_sha = 'foobar' + + expect(subject.valid?).to be false + expect(subject.errors.count).to eq 3 + expect(subject.errors).to all(include('is not a valid SHA')) + end + end + describe 'create new record' do subject { diff_with_commits } @@ -78,7 +90,7 @@ describe MergeRequestDiff do it 'returns persisted diffs if cannot compare with diff refs' do expect(diff).to receive(:load_diffs).and_call_original - diff.update!(head_commit_sha: 'invalid-sha') + diff.update!(head_commit_sha: Digest::SHA1.hexdigest(SecureRandom.hex)) diff.diffs.diff_files end diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index af7e3d3a6c904aca2552f983be3b3c793579340c..77b7042424cd6039106f0ac9181b8154fbd7123e 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -182,7 +182,7 @@ describe Milestone do describe '#total_items_count' do before do create :closed_issue, milestone: milestone, project: project - create :merge_request, milestone: milestone + create :merge_request, milestone: milestone, source_project: project end it 'returns total count of issues and merge requests assigned to milestone' do @@ -192,10 +192,10 @@ describe Milestone do describe '#can_be_closed?' do before do - milestone = create :milestone - create :closed_issue, milestone: milestone + milestone = create :milestone, project: project + create :closed_issue, milestone: milestone, project: project - create :issue + create :issue, project: project end it 'returns true if milestone active and all nested issues closed' do diff --git a/spec/models/project_services/prometheus_service_spec.rb b/spec/models/project_services/prometheus_service_spec.rb index b6cf4c7245054f01396d5888db24721fc1d5873c..e9c7c94ad7049e1b70ea98e148df866558201b58 100644 --- a/spec/models/project_services/prometheus_service_spec.rb +++ b/spec/models/project_services/prometheus_service_spec.rb @@ -33,18 +33,38 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do describe 'Validations' do context 'when manual_configuration is enabled' do before do - subject.manual_configuration = true + service.manual_configuration = true end - it { is_expected.to validate_presence_of(:api_url) } + it 'validates presence of api_url' do + expect(service).to validate_presence_of(:api_url) + end end context 'when manual configuration is disabled' do before do - subject.manual_configuration = false + service.manual_configuration = false end - it { is_expected.not_to validate_presence_of(:api_url) } + it 'does not validate presence of api_url' do + expect(service).not_to validate_presence_of(:api_url) + end + end + + context 'when the api_url domain points to localhost or local network' do + let(:domain) { Addressable::URI.parse(service.api_url).hostname } + + it 'cannot query' do + expect(service.can_query?).to be true + + aggregate_failures do + ['127.0.0.1', '192.168.2.3'].each do |url| + allow(Addrinfo).to receive(:getaddrinfo).with(domain, any_args).and_return([Addrinfo.tcp(url, 80)]) + + expect(service.can_query?).to be false + end + end + end end end @@ -74,30 +94,35 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do end describe '#prometheus_client' do + let(:api_url) { 'http://some_url' } + + before do + service.active = true + service.api_url = api_url + service.manual_configuration = manual_configuration + end + context 'manual configuration is enabled' do - let(:api_url) { 'http://some_url' } + let(:manual_configuration) { true } - before do - subject.active = true - subject.manual_configuration = true - subject.api_url = api_url + it 'returns rest client from api_url' do + expect(service.prometheus_client.url).to eq(api_url) end - it 'returns rest client from api_url' do - expect(subject.prometheus_client.url).to eq(api_url) + it 'calls valid?' do + allow(service).to receive(:valid?).and_call_original + + expect(service.prometheus_client).not_to be_nil + + expect(service).to have_received(:valid?) end end context 'manual configuration is disabled' do - let(:api_url) { 'http://some_url' } - - before do - subject.manual_configuration = false - subject.api_url = api_url - end + let(:manual_configuration) { false } it 'no client provided' do - expect(subject.prometheus_client).to be_nil + expect(service.prometheus_client).to be_nil end end end diff --git a/spec/policies/commit_policy_spec.rb b/spec/policies/commit_policy_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..41f6fb0842620190e8bcf7e003f5e946a9ad7639 --- /dev/null +++ b/spec/policies/commit_policy_spec.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe CommitPolicy do + describe '#rules' do + let(:user) { create(:user) } + let(:commit) { project.repository.head_commit } + let(:policy) { described_class.new(user, commit) } + + context 'when project is public' do + let(:project) { create(:project, :public, :repository) } + + it 'can read commit and create a note' do + expect(policy).to be_allowed(:read_commit) + end + + context 'when repository access level is private' do + let(:project) { create(:project, :public, :repository, :repository_private) } + + it 'can not read commit and create a note' do + expect(policy).to be_disallowed(:read_commit) + end + + context 'when the user is a project member' do + before do + project.add_developer(user) + end + + it 'can read commit and create a note' do + expect(policy).to be_allowed(:read_commit) + end + end + end + end + + context 'when project is private' do + let(:project) { create(:project, :private, :repository) } + + it 'can not read commit and create a note' do + expect(policy).to be_disallowed(:read_commit) + end + + context 'when the user is a project member' do + before do + project.add_developer(user) + end + + it 'can read commit and create a note' do + expect(policy).to be_allowed(:read_commit) + end + end + end + end +end diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb index af6d6f084a95ad66e0494b78015858d7046b8727..0ad50c6f91f4978d38332fd3259f2bfe142ad360 100644 --- a/spec/policies/group_policy_spec.rb +++ b/spec/policies/group_policy_spec.rb @@ -74,6 +74,38 @@ describe GroupPolicy do end end + context 'with no user and public project' do + let(:project) { create(:project, :public) } + let(:user) { create(:user) } + let(:current_user) { nil } + + before do + Projects::GroupLinks::CreateService.new( + project, + user, + link_group_access: ProjectGroupLink::DEVELOPER + ).execute(group) + end + + it { expect_disallowed(:read_group) } + end + + context 'with foreign user and public project' do + let(:project) { create(:project, :public) } + let(:user) { create(:user) } + let(:current_user) { create(:user) } + + before do + Projects::GroupLinks::CreateService.new( + project, + user, + link_group_access: ProjectGroupLink::DEVELOPER + ).execute(group) + end + + it { expect_disallowed(:read_group) } + end + context 'has projects' do let(:current_user) { create(:user) } let(:project) { create(:project, namespace: group) } @@ -82,17 +114,13 @@ describe GroupPolicy do project.add_developer(current_user) end - it do - expect_allowed(:read_group, :read_list, :read_label) - end + it { expect_allowed(:read_label, :read_list) } context 'in subgroups', :nested_groups do let(:subgroup) { create(:group, :private, parent: group) } let(:project) { create(:project, namespace: subgroup) } - it do - expect_allowed(:read_group, :read_list, :read_label) - end + it { expect_allowed(:read_label, :read_list) } end end diff --git a/spec/policies/note_policy_spec.rb b/spec/policies/note_policy_spec.rb index 0e848c74659f75f8c8e5efc2eefa86ca0a25de21..4be7a0266d1a318e6351a8343d99bcdab05a392a 100644 --- a/spec/policies/note_policy_spec.rb +++ b/spec/policies/note_policy_spec.rb @@ -1,28 +1,15 @@ require 'spec_helper' -describe NotePolicy, mdoels: true do +describe NotePolicy do describe '#rules' do let(:user) { create(:user) } let(:project) { create(:project, :public) } let(:issue) { create(:issue, project: project) } - - def policies(noteable = nil) - return @policies if @policies - - noteable ||= issue - note = if noteable.is_a?(Commit) - create(:note_on_commit, commit_id: noteable.id, author: user, project: project) - else - create(:note, noteable: noteable, author: user, project: project) - end - - @policies = described_class.new(user, note) - end + let(:noteable) { issue } + let(:policy) { described_class.new(user, note) } + let(:note) { create(:note, noteable: noteable, author: user, project: project) } shared_examples_for 'a discussion with a private noteable' do - let(:noteable) { issue } - let(:policy) { policies(noteable) } - context 'when the note author can no longer see the noteable' do it 'can not edit nor read the note' do expect(policy).to be_disallowed(:admin_note) @@ -46,12 +33,21 @@ describe NotePolicy, mdoels: true do end end - context 'when the project is private' do - let(:project) { create(:project, :private, :repository) } + context 'when the noteable is a commit' do + let(:commit) { project.repository.head_commit } + let(:note) { create(:note_on_commit, commit_id: commit.id, author: user, project: project) } + + context 'when the project is private' do + let(:project) { create(:project, :private, :repository) } + + it_behaves_like 'a discussion with a private noteable' + end - context 'when the noteable is a commit' do - it_behaves_like 'a discussion with a private noteable' do - let(:noteable) { project.repository.head_commit } + context 'when the project is public' do + context 'when repository access level is private' do + let(:project) { create(:project, :public, :repository, :repository_private) } + + it_behaves_like 'a discussion with a private noteable' end end end @@ -59,44 +55,44 @@ describe NotePolicy, mdoels: true do context 'when the project is public' do context 'when the note author is not a project member' do it 'can edit a note' do - expect(policies).to be_allowed(:admin_note) - expect(policies).to be_allowed(:resolve_note) - expect(policies).to be_allowed(:read_note) + expect(policy).to be_allowed(:admin_note) + expect(policy).to be_allowed(:resolve_note) + expect(policy).to be_allowed(:read_note) end end context 'when the noteable is a project snippet' do - it 'can edit note' do - policies = policies(create(:project_snippet, :public, project: project)) + let(:noteable) { create(:project_snippet, :public, project: project) } - expect(policies).to be_allowed(:admin_note) - expect(policies).to be_allowed(:resolve_note) - expect(policies).to be_allowed(:read_note) + it 'can edit note' do + expect(policy).to be_allowed(:admin_note) + expect(policy).to be_allowed(:resolve_note) + expect(policy).to be_allowed(:read_note) end context 'when it is private' do - it_behaves_like 'a discussion with a private noteable' do - let(:noteable) { create(:project_snippet, :private, project: project) } - end + let(:noteable) { create(:project_snippet, :private, project: project) } + + it_behaves_like 'a discussion with a private noteable' end end context 'when the noteable is a personal snippet' do - it 'can edit note' do - policies = policies(create(:personal_snippet, :public)) + let(:noteable) { create(:personal_snippet, :public) } - expect(policies).to be_allowed(:admin_note) - expect(policies).to be_allowed(:resolve_note) - expect(policies).to be_allowed(:read_note) + it 'can edit note' do + expect(policy).to be_allowed(:admin_note) + expect(policy).to be_allowed(:resolve_note) + expect(policy).to be_allowed(:read_note) end context 'when it is private' do - it 'can not edit nor read the note' do - policies = policies(create(:personal_snippet, :private)) + let(:noteable) { create(:personal_snippet, :private) } - expect(policies).to be_disallowed(:admin_note) - expect(policies).to be_disallowed(:resolve_note) - expect(policies).to be_disallowed(:read_note) + it 'can not edit nor read the note' do + expect(policy).to be_disallowed(:admin_note) + expect(policy).to be_disallowed(:resolve_note) + expect(policy).to be_disallowed(:read_note) end end end @@ -120,20 +116,20 @@ describe NotePolicy, mdoels: true do end it 'can edit a note' do - expect(policies).to be_allowed(:admin_note) - expect(policies).to be_allowed(:resolve_note) - expect(policies).to be_allowed(:read_note) + expect(policy).to be_allowed(:admin_note) + expect(policy).to be_allowed(:resolve_note) + expect(policy).to be_allowed(:read_note) end end context 'when the note author is not a project member' do it 'can not edit a note' do - expect(policies).to be_disallowed(:admin_note) - expect(policies).to be_disallowed(:resolve_note) + expect(policy).to be_disallowed(:admin_note) + expect(policy).to be_disallowed(:resolve_note) end it 'can read a note' do - expect(policies).to be_allowed(:read_note) + expect(policy).to be_allowed(:read_note) end end end diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index 997bdc82af6b58f3f1892ef09e526770d65d58fa..47491f708e906301f0ab48ad477784d4e920c3b5 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -131,22 +131,26 @@ describe ProjectPolicy do subject { described_class.new(owner, project) } context 'when the feature is disabled' do - it 'does not include the issues permissions' do + before do project.issues_enabled = false project.save! + end + it 'does not include the issues permissions' do expect_disallowed :read_issue, :read_issue_iid, :create_issue, :update_issue, :admin_issue end - end - context 'when the feature is disabled and external tracker configured' do - it 'does not include the issues permissions' do - create(:jira_service, project: project) + it 'disables boards and lists permissions' do + expect_disallowed :read_board, :create_board, :update_board, :admin_board + expect_disallowed :read_list, :create_list, :update_list, :admin_list + end - project.issues_enabled = false - project.save! + context 'when external tracker configured' do + it 'does not include the issues permissions' do + create(:jira_service, project: project) - expect_disallowed :read_issue, :read_issue_iid, :create_issue, :update_issue, :admin_issue + expect_disallowed :read_issue, :read_issue_iid, :create_issue, :update_issue, :admin_issue + end end end end diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index 066f1d6862aa8651f2e660a386d9dcedca0e8d79..a132b85b87815820c36fbcaaf839bc99649cb301 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -1430,8 +1430,8 @@ describe API::Commits do end describe 'GET /projects/:id/repository/commits/:sha/merge_requests' do - let!(:project) { create(:project, :repository, :private) } - let!(:merged_mr) { create(:merge_request, source_project: project, source_branch: 'master', target_branch: 'feature') } + let(:project) { create(:project, :repository, :private) } + let(:merged_mr) { create(:merge_request, source_project: project, source_branch: 'master', target_branch: 'feature') } let(:commit) { merged_mr.merge_request_diff.commits.last } it 'returns the correct merge request' do @@ -1456,6 +1456,17 @@ describe API::Commits do expect(response).to have_gitlab_http_status(404) end + + context 'public project' do + let(:project) { create(:project, :repository, :public, :merge_requests_private) } + let(:non_member) { create(:user) } + + it 'responds 403 when only members are allowed to read merge requests' do + get api("/projects/#{project.id}/repository/commits/#{commit.id}/merge_requests", non_member) + + expect(response).to have_gitlab_http_status(403) + end + end end describe 'GET /projects/:id/repository/commits/:sha/signature' do diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 01bab2a1361625e3408920b5210dbe373ce6c95a..f35dabf5d0fbbe16b37205552e6a7d208f256baa 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -49,7 +49,7 @@ describe API::Issues do create(:label, title: 'label', color: '#FFAABB', project: project) end let!(:label_link) { create(:label_link, label: label, target: issue) } - set(:milestone) { create(:milestone, title: '1.0.0', project: project) } + let(:milestone) { create(:milestone, title: '1.0.0', project: project) } set(:empty_milestone) do create(:milestone, title: '2.0.0', project: project) end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 856fe1bbe8907c4b8ba8470e219a7face0e9180f..60d9d7fed1354d3b1689800933a9eb51dc573169 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -136,6 +136,7 @@ describe API::Projects do end let!(:public_project) { create(:project, :public, name: 'public_project') } + before do project project2 @@ -968,8 +969,16 @@ describe API::Projects do describe 'GET /projects/:id' do context 'when unauthenticated' do - it 'returns the public projects' do - public_project = create(:project, :public) + it 'does not return private projects' do + private_project = create(:project, :private) + + get api("/projects/#{private_project.id}") + + expect(response).to have_gitlab_http_status(404) + end + + it 'returns public projects' do + public_project = create(:project, :repository, :public) get api("/projects/#{public_project.id}") @@ -977,8 +986,34 @@ describe API::Projects do expect(json_response['id']).to eq(public_project.id) expect(json_response['description']).to eq(public_project.description) expect(json_response['default_branch']).to eq(public_project.default_branch) + expect(json_response['ci_config_path']).to eq(public_project.ci_config_path) expect(json_response.keys).not_to include('permissions') end + + context 'and the project has a private repository' do + let(:project) { create(:project, :repository, :public, :repository_private) } + let(:protected_attributes) { %w(default_branch ci_config_path) } + + it 'hides protected attributes of private repositories if user is not a member' do + get api("/projects/#{project.id}", user) + + expect(response).to have_gitlab_http_status(200) + protected_attributes.each do |attribute| + expect(json_response.keys).not_to include(attribute) + end + end + + it 'exposes protected attributes of private repositories if user is a member' do + project.add_developer(user) + + get api("/projects/#{project.id}", user) + + expect(response).to have_gitlab_http_status(200) + protected_attributes.each do |attribute| + expect(json_response.keys).to include(attribute) + end + end + end end context 'when authenticated' do @@ -1130,6 +1165,26 @@ describe API::Projects do expect(json_response).to include 'statistics' end + context "and the project has a private repository" do + let(:project) { create(:project, :public, :repository, :repository_private) } + + it "does not include statistics if user is not a member" do + get api("/projects/#{project.id}", user), params: { statistics: true } + + expect(response).to have_gitlab_http_status(200) + expect(json_response).not_to include 'statistics' + end + + it "includes statistics if user is a member" do + project.add_developer(user) + + get api("/projects/#{project.id}", user), params: { statistics: true } + + expect(response).to have_gitlab_http_status(200) + expect(json_response).to include 'statistics' + end + end + it "includes import_error if user can admin project" do get api("/projects/#{project.id}", user) @@ -1510,6 +1565,9 @@ describe API::Projects do describe "POST /projects/:id/share" do let(:group) { create(:group) } + before do + group.add_developer(user) + end it "shares project with group" do expires_at = 10.days.from_now.to_date @@ -1560,6 +1618,15 @@ describe API::Projects do expect(response).to have_gitlab_http_status(400) expect(json_response['error']).to eq 'group_access does not have a valid value' end + + it "returns a 409 error when link is not saved" do + allow(::Projects::GroupLinks::CreateService).to receive_message_chain(:new, :execute) + .and_return({ status: :error, http_status: 409, message: 'error' }) + + post api("/projects/#{project.id}/share", user), params: { group_id: group.id, group_access: Gitlab::Access::DEVELOPER } + + expect(response).to have_gitlab_http_status(409) + end end describe 'DELETE /projects/:id/share/:group_id' do diff --git a/spec/requests/api/release/links_spec.rb b/spec/requests/api/release/links_spec.rb index ba948e37e2fef8726b8d37c28c25ccf7dfce13b3..3a59052bb2923a8693a83ee81f9e07c4dc9fbfae 100644 --- a/spec/requests/api/release/links_spec.rb +++ b/spec/requests/api/release/links_spec.rb @@ -73,6 +73,22 @@ describe API::Release::Links do expect(response).to have_gitlab_http_status(:ok) end end + + context 'when project is public and the repository is private' do + let(:project) { create(:project, :repository, :public, :repository_private) } + + it_behaves_like '403 response' do + let(:request) { get api("/projects/#{project.id}/releases/v0.1/assets/links", non_project_member) } + end + + context 'when the release does not exists' do + let!(:release) { } + + it_behaves_like '403 response' do + let(:request) { get api("/projects/#{project.id}/releases/v0.1/assets/links", non_project_member) } + end + end + end end end diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index 5b625fd47bec4947c53a562ecca7f1a3b842101a..bfa178f5cae2bdac45cacd98b1ea3e8f4e9a3d0d 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -104,6 +104,70 @@ describe 'Git HTTP requests' do end end + shared_examples_for 'project path without .git suffix' do + context "GET info/refs" do + let(:path) { "/#{project_path}/info/refs" } + + context "when no params are added" do + before do + get path + end + + it "redirects to the .git suffix version" do + expect(response).to redirect_to("/#{project_path}.git/info/refs") + end + end + + context "when the upload-pack service is requested" do + let(:params) { { service: 'git-upload-pack' } } + + before do + get path, params: params + end + + it "redirects to the .git suffix version" do + expect(response).to redirect_to("/#{project_path}.git/info/refs?service=#{params[:service]}") + end + end + + context "when the receive-pack service is requested" do + let(:params) { { service: 'git-receive-pack' } } + + before do + get path, params: params + end + + it "redirects to the .git suffix version" do + expect(response).to redirect_to("/#{project_path}.git/info/refs?service=#{params[:service]}") + end + end + + context "when the params are anything else" do + let(:params) { { service: 'git-implode-pack' } } + + before do + get path, params: params + end + + it "redirects to the sign-in page" do + expect(response).to redirect_to(new_user_session_path) + end + end + end + + context "POST git-upload-pack" do + it "fails to find a route" do + expect { clone_post(project_path) }.to raise_error(ActionController::RoutingError) + end + end + + context "POST git-receive-pack" do + it "fails to find a route" do + expect { push_post(project_path) }.to raise_error(ActionController::RoutingError) + end + end + end + describe "User with no identities" do let(:user) { create(:user) } @@ -143,6 +207,10 @@ describe 'Git HTTP requests' do expect(response).to have_gitlab_http_status(:unprocessable_entity) end end + + it_behaves_like 'project path without .git suffix' do + let(:project_path) { "#{user.namespace.path}/project.git-project" } + end end end @@ -706,70 +774,8 @@ describe 'Git HTTP requests' do end end - context "when the project path doesn't end in .git" do - let(:project) { create(:project, :repository, :public, path: 'project.git-project') } - - context "GET info/refs" do - let(:path) { "/#{project.full_path}/info/refs" } - - context "when no params are added" do - before do - get path - end - - it "redirects to the .git suffix version" do - expect(response).to redirect_to("/#{project.full_path}.git/info/refs") - end - end - - context "when the upload-pack service is requested" do - let(:params) { { service: 'git-upload-pack' } } - - before do - get path, params: params - end - - it "redirects to the .git suffix version" do - expect(response).to redirect_to("/#{project.full_path}.git/info/refs?service=#{params[:service]}") - end - end - - context "when the receive-pack service is requested" do - let(:params) { { service: 'git-receive-pack' } } - - before do - get path, params: params - end - - it "redirects to the .git suffix version" do - expect(response).to redirect_to("/#{project.full_path}.git/info/refs?service=#{params[:service]}") - end - end - - context "when the params are anything else" do - let(:params) { { service: 'git-implode-pack' } } - - before do - get path, params: params - end - - it "redirects to the sign-in page" do - expect(response).to redirect_to(new_user_session_path) - end - end - end - - context "POST git-upload-pack" do - it "fails to find a route" do - expect { clone_post(project.full_path) }.to raise_error(ActionController::RoutingError) - end - end - - context "POST git-receive-pack" do - it "fails to find a route" do - expect { push_post(project.full_path) }.to raise_error(ActionController::RoutingError) - end - end + it_behaves_like 'project path without .git suffix' do + let(:project_path) { create(:project, :repository, :public, path: 'project.git-project').full_path } end context "retrieving an info/refs file" do diff --git a/spec/services/issuable/common_system_notes_service_spec.rb b/spec/services/issuable/common_system_notes_service_spec.rb index fa5d5ebac5cdfaf4fb4f63e592e381692ac64799..0edc9016c96913cc4b67d1128613c11c22aaa124 100644 --- a/spec/services/issuable/common_system_notes_service_spec.rb +++ b/spec/services/issuable/common_system_notes_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Issuable::CommonSystemNotesService do let(:user) { create(:user) } let(:project) { create(:project) } - let(:issuable) { create(:issue) } + let(:issuable) { create(:issue, project: project) } context 'on issuable update' do it_behaves_like 'system note creation', { title: 'New title' }, 'changed title' @@ -70,7 +70,7 @@ describe Issuable::CommonSystemNotesService do end context 'on issuable create' do - let(:issuable) { build(:issue) } + let(:issuable) { build(:issue, project: project) } subject { described_class.new(project, user).execute(issuable, old_labels: [], is_update: false) } diff --git a/spec/services/issues/build_service_spec.rb b/spec/services/issues/build_service_spec.rb index 248e7d5a389b7eaa9ea207e4ca155547e35d08ca..86e58fe06b950bb62878f18c01e0e68efee3f0df 100644 --- a/spec/services/issues/build_service_spec.rb +++ b/spec/services/issues/build_service_spec.rb @@ -8,29 +8,29 @@ describe Issues::BuildService do project.add_developer(user) end + def build_issue(issue_params = {}) + described_class.new(project, user, issue_params).execute + end + context 'for a single discussion' do describe '#execute' do let(:merge_request) { create(:merge_request, title: "Hello world", source_project: project) } let(:discussion) { create(:diff_note_on_merge_request, project: project, noteable: merge_request, note: "Almost done").to_discussion } - let(:service) { described_class.new(project, user, merge_request_to_resolve_discussions_of: merge_request.iid, discussion_to_resolve: discussion.id) } - it 'references the noteable title in the issue title' do - issue = service.execute + subject { build_issue(merge_request_to_resolve_discussions_of: merge_request.iid, discussion_to_resolve: discussion.id) } - expect(issue.title).to include('Hello world') + it 'references the noteable title in the issue title' do + expect(subject.title).to include('Hello world') end it 'adds the note content to the description' do - issue = service.execute - - expect(issue.description).to include('Almost done') + expect(subject.description).to include('Almost done') end end end context 'for discussions in a merge request' do let(:merge_request) { create(:merge_request_with_diff_notes, source_project: project) } - let(:issue) { described_class.new(project, user, merge_request_to_resolve_discussions_of: merge_request.iid).execute } describe '#items_for_discussions' do it 'has an item for each discussion' do @@ -66,28 +66,30 @@ describe Issues::BuildService do end describe '#execute' do - it 'has the merge request reference in the title' do - expect(issue.title).to include(merge_request.title) - end + let(:base_params) { { merge_request_to_resolve_discussions_of: merge_request.iid } } - it 'has the reference of the merge request in the description' do - expect(issue.description).to include(merge_request.to_reference) + context 'without additional params' do + subject { build_issue(base_params) } + + it 'has the merge request reference in the title' do + expect(subject.title).to include(merge_request.title) + end + + it 'has the reference of the merge request in the description' do + expect(subject.description).to include(merge_request.to_reference) + end end - it 'does not assign title when a title was given' do - issue = described_class.new(project, user, - merge_request_to_resolve_discussions_of: merge_request, - title: 'What an issue').execute + it 'uses provided title if title param given' do + issue = build_issue(base_params.merge(title: 'What an issue')) expect(issue.title).to eq('What an issue') end - it 'does not assign description when a description was given' do - issue = described_class.new(project, user, - merge_request_to_resolve_discussions_of: merge_request, - description: 'Fix at your earliest conveignance').execute + it 'uses provided description if description param given' do + issue = build_issue(base_params.merge(description: 'Fix at your earliest convenience')) - expect(issue.description).to eq('Fix at your earliest conveignance') + expect(issue.description).to eq('Fix at your earliest convenience') end describe 'with multiple discussions' do @@ -96,20 +98,20 @@ describe Issues::BuildService do it 'mentions all the authors in the description' do authors = merge_request.resolvable_discussions.map(&:author) - expect(issue.description).to include(*authors.map(&:to_reference)) + expect(build_issue(base_params).description).to include(*authors.map(&:to_reference)) end it 'has a link for each unresolved discussion in the description' do notes = merge_request.resolvable_discussions.map(&:first_note) links = notes.map { |note| Gitlab::UrlBuilder.build(note) } - expect(issue.description).to include(*links) + expect(build_issue(base_params).description).to include(*links) end it 'mentions additional notes' do create_list(:diff_note_on_merge_request, 2, noteable: merge_request, project: merge_request.target_project, in_reply_to: diff_note) - expect(issue.description).to include('(+2 comments)') + expect(build_issue(base_params).description).to include('(+2 comments)') end end end @@ -120,7 +122,7 @@ describe Issues::BuildService do describe '#execute' do it 'mentions the merge request in the description' do - issue = described_class.new(project, user, merge_request_to_resolve_discussions_of: merge_request.iid).execute + issue = build_issue(merge_request_to_resolve_discussions_of: merge_request.iid) expect(issue.description).to include("Review the conversation in #{merge_request.to_reference}") end @@ -128,20 +130,18 @@ describe Issues::BuildService do end describe '#execute' do - let(:milestone) { create(:milestone, project: project) } - it 'builds a new issues with given params' do - issue = described_class.new( - project, - user, - title: 'Issue #1', - description: 'Issue description', - milestone_id: milestone.id - ).execute - - expect(issue.title).to eq('Issue #1') - expect(issue.description).to eq('Issue description') + milestone = create(:milestone, project: project) + issue = build_issue(milestone_id: milestone.id) + expect(issue.milestone).to eq(milestone) end + + it 'sets milestone to nil if it is not available for the project' do + milestone = create(:milestone, project: create(:project)) + issue = build_issue(milestone_id: milestone.id) + + expect(issue.milestone).to be_nil + end end end diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index 931e47d3a7711dac1bb4886d4f3dc8cdb25d1cbd..f168420972904461f7aed84e23a897367e86bd87 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -356,7 +356,7 @@ describe Issues::UpdateService, :mailer do it_behaves_like 'system notes for milestones' it 'sends notifications for subscribers of changed milestone' do - issue.milestone = create(:milestone) + issue.milestone = create(:milestone, project: project) issue.save @@ -380,7 +380,7 @@ describe Issues::UpdateService, :mailer do end it 'marks todos as done' do - update_issue(milestone: create(:milestone)) + update_issue(milestone: create(:milestone, project: project)) expect(todo.reload.done?).to eq true end @@ -389,7 +389,7 @@ describe Issues::UpdateService, :mailer do it 'sends notifications for subscribers of changed milestone' do perform_enqueued_jobs do - update_issue(milestone: create(:milestone)) + update_issue(milestone: create(:milestone, project: project)) end should_email(subscriber) diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb index 536d0d345a487e46e66342bcbeada603ddb11d5e..057e8137a4ee1b9204ad4c0b4188471b623b5082 100644 --- a/spec/services/merge_requests/build_service_spec.rb +++ b/spec/services/merge_requests/build_service_spec.rb @@ -229,6 +229,15 @@ describe MergeRequests::BuildService do end end end + + context 'when a milestone is from another project' do + let(:milestone) { create(:milestone, project: create(:project)) } + let(:milestone_id) { milestone.id } + + it 'sets milestone to nil' do + expect(merge_request.milestone).to be_nil + end + end end end diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index 20580bf14b973c759860e09e4475a8c64358f021..8e367db031c78e221c29b88e080db5d5a79b2afb 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -328,7 +328,7 @@ describe MergeRequests::UpdateService, :mailer do it_behaves_like 'system notes for milestones' it 'sends notifications for subscribers of changed milestone' do - merge_request.milestone = create(:milestone) + merge_request.milestone = create(:milestone, project: project) merge_request.save @@ -352,7 +352,7 @@ describe MergeRequests::UpdateService, :mailer do end it 'marks pending todos as done' do - update_merge_request({ milestone: create(:milestone) }) + update_merge_request({ milestone: create(:milestone, project: project) }) expect(pending_todo.reload).to be_done end @@ -361,7 +361,7 @@ describe MergeRequests::UpdateService, :mailer do it 'sends notifications for subscribers of changed milestone' do perform_enqueued_jobs do - update_merge_request(milestone: create(:milestone)) + update_merge_request(milestone: create(:milestone, project: project)) end should_email(subscriber) diff --git a/spec/services/projects/group_links/create_service_spec.rb b/spec/services/projects/group_links/create_service_spec.rb index ffb270d277eecd7ed3842fcd07375319c09ec932..68fd82b4cbe6c452a656248cdf5d0c74e3696a4c 100644 --- a/spec/services/projects/group_links/create_service_spec.rb +++ b/spec/services/projects/group_links/create_service_spec.rb @@ -12,6 +12,10 @@ describe Projects::GroupLinks::CreateService, '#execute' do end let(:subject) { described_class.new(project, user, opts) } + before do + group.add_developer(user) + end + it 'adds group to project' do expect { subject.execute(group) }.to change { project.project_group_links.count }.from(0).to(1) end @@ -19,4 +23,8 @@ describe Projects::GroupLinks::CreateService, '#execute' do it 'returns false if group is blank' do expect { subject.execute(nil) }.not_to change { project.project_group_links.count } end + + it 'returns error if user is not allowed to share with a group' do + expect { subject.execute(create :group) }.not_to change { project.project_group_links.count } + end end diff --git a/spec/support/helpers/file_mover_helpers.rb b/spec/support/helpers/file_mover_helpers.rb new file mode 100644 index 0000000000000000000000000000000000000000..1ba7cc033549d38908db83ab30b8f68556bcf0bb --- /dev/null +++ b/spec/support/helpers/file_mover_helpers.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module FileMoverHelpers + def stub_file_mover(file_path, stub_real_path: nil) + file_name = File.basename(file_path) + allow(Pathname).to receive(:new).and_call_original + + expect_next_instance_of(Pathname, a_string_including(file_name)) do |pathname| + allow(pathname).to receive(:realpath) { stub_real_path || pathname.cleanpath } + end + end +end diff --git a/spec/support/helpers/login_helpers.rb b/spec/support/helpers/login_helpers.rb index 3fee6872498eaa51a71800a68a689546234d34e7..4a0cf62a661dd9b520890c7f42b04e9c58f901a1 100644 --- a/spec/support/helpers/login_helpers.rb +++ b/spec/support/helpers/login_helpers.rb @@ -47,7 +47,7 @@ module LoginHelpers end def gitlab_sign_in_via(provider, user, uid, saml_response = nil) - mock_auth_hash(provider, uid, user.email, saml_response) + mock_auth_hash_with_saml_xml(provider, uid, user.email, saml_response) visit new_user_session_path click_link provider end @@ -87,7 +87,12 @@ module LoginHelpers click_link "oauth-login-#{provider}" end - def mock_auth_hash(provider, uid, email, saml_response = nil) + def mock_auth_hash_with_saml_xml(provider, uid, email, saml_response) + response_object = { document: saml_xml(saml_response) } + mock_auth_hash(provider, uid, email, response_object: response_object) + end + + def mock_auth_hash(provider, uid, email, response_object: nil) # The mock_auth configuration allows you to set per-provider (or default) # authentication hashes to return during integration testing. OmniAuth.config.mock_auth[provider.to_sym] = OmniAuth::AuthHash.new({ @@ -110,9 +115,7 @@ module LoginHelpers image: 'mock_user_thumbnail_url' } }, - response_object: { - document: saml_xml(saml_response) - } + response_object: response_object } }) Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[provider.to_sym] diff --git a/spec/support/shared_examples/issuable_shared_examples.rb b/spec/support/shared_examples/issuable_shared_examples.rb index c3d40c5b231ff5edf1cfe640dc0e4542604ab37d..d97b21f71cdd65faee0863e83c18af312a1ae585 100644 --- a/spec/support/shared_examples/issuable_shared_examples.rb +++ b/spec/support/shared_examples/issuable_shared_examples.rb @@ -31,7 +31,7 @@ shared_examples 'system notes for milestones' do context 'project milestones' do it 'creates a system note' do expect do - update_issuable(milestone: create(:milestone)) + update_issuable(milestone: create(:milestone, project: project)) end.to change { Note.system.count }.by(1) end end diff --git a/spec/support/shared_examples/requests/api/discussions.rb b/spec/support/shared_examples/requests/api/discussions.rb index e44da4faa5af10b5951b17a45f37c235d2d336bb..eff8e401bad4c8715c787d907b56d508216ed31e 100644 --- a/spec/support/shared_examples/requests/api/discussions.rb +++ b/spec/support/shared_examples/requests/api/discussions.rb @@ -86,6 +86,37 @@ shared_examples 'discussions API' do |parent_type, noteable_type, id_name| expect(response).to have_gitlab_http_status(404) end end + + context 'when a project is public with private repo access' do + let!(:parent) { create(:project, :public, :repository, :repository_private, :snippets_private) } + let!(:user_without_access) { create(:user) } + + context 'when user is not a team member of private repo' do + before do + project.team.truncate + end + + context "creating a new note" do + before do + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", user_without_access), params: { body: 'hi!' } + end + + it 'raises 404 error' do + expect(response).to have_gitlab_http_status(404) + end + end + + context "fetching a discussion" do + before do + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions/#{note.discussion_id}", user_without_access) + end + + it 'raises 404 error' do + expect(response).to have_gitlab_http_status(404) + end + end + end + end end describe "POST /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions/:discussion_id/notes" do diff --git a/spec/uploaders/file_mover_spec.rb b/spec/uploaders/file_mover_spec.rb index de29d0c943fbf2c52437d81acd7b371ee307b4f6..e474a714b10b8e25c2c6083aa3f0c254f185ec78 100644 --- a/spec/uploaders/file_mover_spec.rb +++ b/spec/uploaders/file_mover_spec.rb @@ -1,8 +1,9 @@ require 'spec_helper' describe FileMover do + include FileMoverHelpers + let(:filename) { 'banana_sample.gif' } - let(:file) { fixture_file_upload(File.join('spec', 'fixtures', filename)) } let(:temp_file_path) { File.join('uploads/-/system/temp', 'secret55', filename) } let(:temp_description) do @@ -12,7 +13,7 @@ describe FileMover do let(:file_path) { File.join('uploads/-/system/personal_snippet', snippet.id.to_s, 'secret55', filename) } let(:snippet) { create(:personal_snippet, description: temp_description) } - subject { described_class.new(file_path, snippet).execute } + subject { described_class.new(temp_file_path, snippet).execute } describe '#execute' do before do @@ -20,6 +21,8 @@ describe FileMover do expect(FileUtils).to receive(:move).with(a_string_including(temp_file_path), a_string_including(file_path)) allow_any_instance_of(CarrierWave::SanitizedFile).to receive(:exists?).and_return(true) allow_any_instance_of(CarrierWave::SanitizedFile).to receive(:size).and_return(10) + + stub_file_mover(temp_file_path) end context 'when move and field update successful' do @@ -66,4 +69,30 @@ describe FileMover do end end end + + context 'security' do + context 'when relative path is involved' do + let(:temp_file_path) { File.join('uploads/-/system/temp', '..', 'another_subdir_of_temp') } + + it 'does not trigger move if path is outside designated directory' do + stub_file_mover('uploads/-/system/another_subdir_of_temp') + expect(FileUtils).not_to receive(:move) + + subject + + expect(snippet.reload.description).to eq(temp_description) + end + end + + context 'when symlink is involved' do + it 'does not trigger move if path is outside designated directory' do + stub_file_mover(temp_file_path, stub_real_path: Pathname('/etc')) + expect(FileUtils).not_to receive(:move) + + subject + + expect(snippet.reload.description).to eq(temp_description) + end + end + end end diff --git a/spec/validators/sha_validator_spec.rb b/spec/validators/sha_validator_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..dc1539cf318611aa1eb1d82db0bdd6f274fd867e --- /dev/null +++ b/spec/validators/sha_validator_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ShaValidator do + let(:validator) { described_class.new(attributes: [:base_commit_sha]) } + let(:merge_diff) { build(:merge_request_diff) } + + subject { validator.validate_each(merge_diff, :base_commit_sha, value) } + + context 'with empty value' do + let(:value) { nil } + + it 'does not add any error if value is empty' do + subject + + expect(merge_diff.errors).to be_empty + end + end + + context 'with valid sha' do + let(:value) { Digest::SHA1.hexdigest(SecureRandom.hex) } + + it 'does not add any error if value is empty' do + subject + + expect(merge_diff.errors).to be_empty + end + end + + context 'with invalid sha' do + let(:value) { 'foo' } + + it 'adds error to the record' do + expect(merge_diff.errors).to be_empty + + subject + + expect(merge_diff.errors).not_to be_empty + end + end +end diff --git a/spec/views/ci/status/_icon.html.haml_spec.rb b/spec/views/ci/status/_icon.html.haml_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..626159fc512c8400b6258856028b04d7d7859cb4 --- /dev/null +++ b/spec/views/ci/status/_icon.html.haml_spec.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe 'ci/status/_icon' do + let(:user) { create(:user) } + let(:project) { create(:project, :private) } + let(:pipeline) { create(:ci_pipeline, project: project) } + + context 'when rendering status for build' do + let(:build) do + create(:ci_build, :success, pipeline: pipeline) + end + + context 'when user has ability to see details' do + before do + project.add_developer(user) + end + + it 'has link to build details page' do + details_path = project_job_path(project, build) + + render_status(build) + + expect(rendered).to have_link(href: details_path) + end + end + + context 'when user do not have ability to see build details' do + before do + render_status(build) + end + + it 'contains build status text' do + expect(rendered).to have_css('.ci-status-icon.ci-status-icon-success') + end + + it 'does not contain links' do + expect(rendered).not_to have_link + end + end + end + + context 'when rendering status for external job' do + context 'when user has ability to see commit status details' do + before do + project.add_developer(user) + end + + context 'status has external target url' do + before do + external_job = create(:generic_commit_status, + status: :running, + pipeline: pipeline, + target_url: 'http://gitlab.com') + + render_status(external_job) + end + + it 'contains valid commit status text' do + expect(rendered).to have_css('.ci-status-icon.ci-status-icon-running') + end + + it 'has link to external status page' do + expect(rendered).to have_link(href: 'http://gitlab.com') + end + end + + context 'status do not have external target url' do + before do + external_job = create(:generic_commit_status, status: :canceled) + + render_status(external_job) + end + + it 'contains valid commit status text' do + expect(rendered).to have_css('.ci-status-icon.ci-status-icon-canceled') + end + + it 'has link to external status page' do + expect(rendered).not_to have_link + end + end + end + end + + def render_status(resource) + render 'ci/status/icon', status: resource.detailed_status(user) + end +end diff --git a/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb b/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb index a13a3046f55b257191ecc2f012f9144f4d53e97a..d20d926f5a064581900960bd3c5cefb7811a65c1 100644 --- a/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb +++ b/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb @@ -18,7 +18,7 @@ describe UpdateHeadPipelineForMergeRequestWorker do context 'when merge request sha does not equal pipeline sha' do before do - merge_request.merge_request_diff.update(head_commit_sha: 'different_sha') + merge_request.merge_request_diff.update(head_commit_sha: Digest::SHA1.hexdigest(SecureRandom.hex)) end it 'does not update head pipeline' do