Commit da2e2b9f authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab-ce master

parents 322c6da5 e4c61726
......@@ -2,6 +2,70 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
## 11.7.2 (2019-01-29)
### Security (24 changes)
- Make potentially malicious links more visible in the UI and scrub RTLO chars from links. !2770
- Don't process MR refs for guests in the notes. !2771
- Sanitize user full name to clean up any URL to prevent mail clients from auto-linking URLs. !2828
- Fixed XSS content in KaTex links.
- Disallows unauthorized users from accessing the pipelines section.
- Verify that LFS upload requests are genuine.
- Extract GitLab Pages using RubyZip.
- Prevent awarding emojis to notes whose parent is not visible to user.
- Prevent unauthorized replies when discussion is locked or confidential.
- Disable git v2 protocol temporarily.
- Fix showing ci status for guest users when public pipline are not set.
- Fix contributed projects info still visible when user enable private profile.
- Add subresources removal to member destroy service.
- Add more LFS validations to prevent forgery.
- Use common error for unauthenticated users when creating issues.
- Fix slow regex in project reference pattern.
- Fix private user email being visible in push (and tag push) webhooks.
- Fix wiki access rights when external wiki is enabled.
- Group guests are no longer able to see merge requests they don't have access to at group level.
- Fix path disclosure on project import error.
- Restrict project import visibility based on its group.
- Expose CI/CD trigger token only to the trigger owner.
- Notify only users who can access the project on project move.
- Alias GitHub and BitBucket OAuth2 callback URLs.
### Fixed (1 change)
- Fix uninitialized constant with GitLab Pages.
## 11.7.1 (2019-01-28)
### Security (24 changes)
- Make potentially malicious links more visible in the UI and scrub RTLO chars from links. !2770
- Don't process MR refs for guests in the notes. !2771
- Sanitize user full name to clean up any URL to prevent mail clients from auto-linking URLs. !2828
- Fixed XSS content in KaTex links.
- Disallows unauthorized users from accessing the pipelines section.
- Verify that LFS upload requests are genuine.
- Extract GitLab Pages using RubyZip.
- Prevent awarding emojis to notes whose parent is not visible to user.
- Prevent unauthorized replies when discussion is locked or confidential.
- Disable git v2 protocol temporarily.
- Fix showing ci status for guest users when public pipline are not set.
- Fix contributed projects info still visible when user enable private profile.
- Add subresources removal to member destroy service.
- Add more LFS validations to prevent forgery.
- Use common error for unauthenticated users when creating issues.
- Fix slow regex in project reference pattern.
- Fix private user email being visible in push (and tag push) webhooks.
- Fix wiki access rights when external wiki is enabled.
- Group guests are no longer able to see merge requests they don't have access to at group level.
- Fix path disclosure on project import error.
- Restrict project import visibility based on its group.
- Expose CI/CD trigger token only to the trigger owner.
- Notify only users who can access the project on project move.
- Alias GitHub and BitBucket OAuth2 callback URLs.
## 11.7.0 (2019-01-22)
### Security (14 changes, 1 of them is from the community)
......@@ -188,6 +252,10 @@ entry.
- Update url placeholder for the sentry configuration page. !24338
## 11.6.8 (2019-01-30)
- No changes.
## 11.6.5 (2019-01-17)
### Fixed (5 changes)
......@@ -528,6 +596,33 @@ entry.
- Enable Rubocop on lib/gitlab. (gfyoung)
## 11.5.8 (2019-01-28)
### Security (21 changes)
- Make potentially malicious links more visible in the UI and scrub RTLO chars from links. !2770
- Don't process MR refs for guests in the notes. !2771
- Fixed XSS content in KaTex links.
- Verify that LFS upload requests are genuine.
- Extract GitLab Pages using RubyZip.
- Prevent awarding emojis to notes whose parent is not visible to user.
- Prevent unauthorized replies when discussion is locked or confidential.
- Disable git v2 protocol temporarily.
- Fix showing ci status for guest users when public pipline are not set.
- Fix contributed projects info still visible when user enable private profile.
- Disallows unauthorized users from accessing the pipelines section.
- Add more LFS validations to prevent forgery.
- Use common error for unauthenticated users when creating issues.
- Fix slow regex in project reference pattern.
- Fix private user email being visible in push (and tag push) webhooks.
- Fix wiki access rights when external wiki is enabled.
- Fix path disclosure on project import error.
- Restrict project import visibility based on its group.
- Expose CI/CD trigger token only to the trigger owner.
- Notify only users who can access the project on project move.
- Alias GitHub and BitBucket OAuth2 callback URLs.
## 11.5.5 (2018-12-20)
### Security (1 change)
......
1.14.0
\ No newline at end of file
1.14.1
8.1.0
\ No newline at end of file
8.1.1
......@@ -60,6 +60,7 @@ gem 'u2f', '~> 0.2.1'
# GitLab Pages
gem 'validates_hostname', '~> 1.0.6'
gem 'rubyzip', '~> 1.2.2', require: 'zip'
# Browser detection
gem 'browser', '~> 2.5'
......
......@@ -1177,6 +1177,7 @@ DEPENDENCIES
ruby-prof (~> 0.17.0)
ruby-progressbar
ruby_parser (~> 3.8)
rubyzip (~> 1.2.2)
rugged (~> 0.27)
sanitize (~> 4.6)
sass (~> 3.5)
......
......@@ -28,10 +28,10 @@ export default {
},
computed: {
statusHtml() {
if (this.user.status.emoji && this.user.status.message) {
return `${glEmojiTag(this.user.status.emoji)} ${this.user.status.message}`;
} else if (this.user.status.message) {
return this.user.status.message;
if (this.user.status.emoji && this.user.status.message_html) {
return `${glEmojiTag(this.user.status.emoji)} ${this.user.status.message_html}`;
} else if (this.user.status.message_html) {
return this.user.status.message_html;
}
return '';
},
......
......@@ -35,7 +35,9 @@ module MembershipActions
respond_to do |format|
format.html do
message = "User was successfully removed from #{source_type}."
source = source_type == 'group' ? 'group and any subresources' : source_type
message = "User was successfully removed from #{source}."
redirect_to members_page_url, notice: message
end
......
......@@ -8,7 +8,7 @@ class Import::BitbucketController < Import::BaseController
rescue_from Bitbucket::Error::Unauthorized, with: :bitbucket_unauthorized
def callback
response = client.auth_code.get_token(params[:code], redirect_uri: callback_import_bitbucket_url)
response = client.auth_code.get_token(params[:code], redirect_uri: users_import_bitbucket_callback_url)
session[:bitbucket_token] = response.token
session[:bitbucket_expires_at] = response.expires_at
......@@ -89,7 +89,7 @@ class Import::BitbucketController < Import::BaseController
end
def go_to_bitbucket_for_permissions
redirect_to client.auth_code.authorize_url(redirect_uri: callback_import_bitbucket_url)
redirect_to client.auth_code.authorize_url(redirect_uri: users_import_bitbucket_callback_url)
end
def bitbucket_unauthorized
......
......@@ -83,7 +83,7 @@ class Import::GithubController < Import::BaseController
end
def callback_import_url
public_send("callback_import_#{provider}_url", extra_import_params) # rubocop:disable GitlabSecurity/PublicSend
public_send("users_import_#{provider}_callback_url", extra_import_params) # rubocop:disable GitlabSecurity/PublicSend
end
def provider_unauthorized
......
......@@ -19,7 +19,7 @@ class Projects::IssuesController < Projects::ApplicationController
prepend_before_action(only: [:index]) { authenticate_sessionless_user!(:rss) }
prepend_before_action(only: [:calendar]) { authenticate_sessionless_user!(:ics) }
prepend_before_action :authenticate_new_issue!, only: [:new]
prepend_before_action :authenticate_user!, only: [:new]
prepend_before_action :store_uri, only: [:new, :show]
before_action :whitelist_query_limiting, only: [:create, :create_merge_request, :move, :bulk_update]
......@@ -249,14 +249,6 @@ class Projects::IssuesController < Projects::ApplicationController
] + [{ label_ids: [], assignee_ids: [] }]
end
def authenticate_new_issue!
return if current_user
notice = "Please sign in to create the new issue."
redirect_to new_user_session_path, notice: notice
end
def store_uri
if request.get? && !request.xhr?
store_location_for :user, request.fullpath
......
......@@ -5,7 +5,7 @@ class Projects::LfsStorageController < Projects::GitHttpClientController
include WorkhorseRequest
include SendFileUpload
skip_before_action :verify_workhorse_api!, only: [:download, :upload_finalize]
skip_before_action :verify_workhorse_api!, only: :download
def download
lfs_object = LfsObject.find_by_oid(oid)
......
......@@ -39,9 +39,12 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont
end
def set_pipeline_variables
@pipelines = @merge_request.all_pipelines
@pipeline = @merge_request.head_pipeline
@statuses_count = @pipeline.present? ? @pipeline.statuses.relevant.count : 0
@pipelines =
if can?(current_user, :read_pipeline, @project)
@merge_request.all_pipelines
else
Ci::Pipeline.none
end
end
end
......
......@@ -4,6 +4,7 @@ class Projects::PipelinesController < Projects::ApplicationController
before_action :whitelist_query_limiting, only: [:create, :retry]
before_action :pipeline, except: [:index, :new, :create, :charts]
before_action :authorize_read_pipeline!
before_action :authorize_read_build!, only: [:index]
before_action :authorize_create_pipeline!, only: [:new, :create]
before_action :authorize_update_pipeline!, only: [:retry, :cancel]
......
......@@ -99,7 +99,9 @@ module Projects
def define_triggers_variables
@triggers = @project.triggers
.present(current_user: current_user)
@trigger = ::Ci::Trigger.new
.present(current_user: current_user)
end
def define_badges_variables
......
......@@ -66,12 +66,11 @@ class Projects::TriggersController < Projects::ApplicationController
end
def trigger
@trigger ||= project.triggers.find(params[:id]) || render_404
@trigger ||= project.triggers.find(params[:id])
.present(current_user: current_user)
end
def trigger_params
params.require(:trigger).permit(
:description
)
params.require(:trigger).permit(:description)
end
end
......@@ -14,6 +14,9 @@ class ContributedProjectsFinder < UnionFinder
# Returns an ActiveRecord::Relation.
# rubocop: disable CodeReuse/ActiveRecord
def execute(current_user = nil)
# Do not show contributed projects if the user profile is private.
return Project.none unless can_read_profile?(current_user)
segments = all_projects(current_user)
find_union(segments, Project).includes(:namespace).order_id_desc
......@@ -22,6 +25,10 @@ class ContributedProjectsFinder < UnionFinder
private
def can_read_profile?(current_user)
Ability.allowed?(current_user, :read_user_profile, @user)
end
def all_projects(current_user)
projects = []
......
......@@ -36,6 +36,14 @@ module EmailsHelper
nil
end
def sanitize_name(name)
if name =~ URI::DEFAULT_PARSER.regexp[:URI_REF]
name.tr('.', '_')
else
name
end
end
def password_reset_token_valid_time
valid_hours = Devise.reset_password_within / 60 / 60
if valid_hours >= 24
......
# frozen_string_literal: true
module ExternalWikiHelper
def get_project_wiki_path(project)
external_wiki_service = project.external_wiki
if external_wiki_service
external_wiki_service.properties['external_wiki_url']
else
project_wiki_path(project, :home)
end
end
end
......@@ -18,12 +18,13 @@ module MembersHelper
"remove #{member.user.name} from"
end
"#{text} #{action} the #{member.source.human_name} #{member.real_source_type.humanize(capitalize: false)}?"
"#{text} #{action} the #{member.source.human_name} #{source_text(member)}?"
end
def remove_member_title(member)
action = member.request? ? 'Deny access request' : 'Remove user'
"#{action} from #{member.real_source_type.humanize(capitalize: false)}"
"#{action} from #{source_text(member)}"
end
def leave_confirmation_message(member_source)
......@@ -35,4 +36,14 @@ module MembersHelper
options = params.slice(:search, :sort).merge(options).permit!
"#{request.path}?#{options.to_param}"
end
private
def source_text(member)
type = member.real_source_type.humanize(capitalize: false)
return type if member.request? || member.invite? || type != 'group'
'group and any subresources'
end
end
......@@ -307,7 +307,8 @@ module ProjectsHelper
nav_tabs << :container_registry
end
if project.builds_enabled? && can?(current_user, :read_pipeline, project)
# Pipelines feature is tied to presence of builds
if can?(current_user, :read_build, project)
nav_tabs << :pipelines
end
......@@ -315,19 +316,24 @@ module ProjectsHelper
nav_tabs << :operations
end
if project.external_issue_tracker
nav_tabs << :external_issue_tracker
end
tab_ability_map.each do |tab, ability|
if can?(current_user, ability, project)
nav_tabs << tab
end
end
nav_tabs << external_nav_tabs(project)
nav_tabs.flatten
end
def external_nav_tabs(project)
[].tap do |tabs|
tabs << :external_issue_tracker if project.external_issue_tracker
tabs << :external_wiki if project.has_external_wiki?
end
end
def tab_ability_map
{
environments: :read_environment,
......
......@@ -4,6 +4,7 @@ module Ci
class Trigger < ActiveRecord::Base
extend Gitlab::Ci::Model
include IgnorableColumn
include Presentable
ignore_column :deleted_at
......@@ -29,7 +30,7 @@ module Ci
end
def short_token
token[0...4]
token[0...4] if token.present?
end
def legacy?
......
......@@ -11,6 +11,7 @@ class Commit
include Mentionable
include Referable
include StaticModel
include Presentable
include ::Gitlab::Utils::StrongMemoize
attr_mentionable :safe_message, pipeline: :single_line
......@@ -304,7 +305,9 @@ class Commit
end
def last_pipeline
@last_pipeline ||= pipelines.last
strong_memoize(:last_pipeline) do
pipelines.last
end
end
def status(ref = nil)
......
......@@ -15,7 +15,7 @@ module CacheMarkdownField
# Increment this number every time the renderer changes its output
CACHE_REDCARPET_VERSION = 3
CACHE_COMMONMARK_VERSION_START = 10
CACHE_COMMONMARK_VERSION = 13
CACHE_COMMONMARK_VERSION = 14
# changes to these attributes cause the cache to be invalidates
INVALIDATED_BY = %w[author project].freeze
......
# frozen_string_literal: true
class LfsDownloadObject
include ActiveModel::Validations
attr_accessor :oid, :size, :link
delegate :sanitized_url, :credentials, to: :sanitized_uri
validates :oid, format: { with: /\A\h{64}\z/ }
validates :size, numericality: { greater_than_or_equal_to: 0 }
validates :link, public_url: { protocols: %w(http https) }
def initialize(oid:, size:, link:)
@oid = oid
@size = size
@link = link
end
def sanitized_uri
@sanitized_uri ||= Gitlab::UrlSanitizer.new(link)
end
end
......@@ -78,12 +78,15 @@ class Member < ActiveRecord::Base
scope :owners, -> { active.where(access_level: OWNER) }
scope :owners_and_maintainers, -> { active.where(access_level: [OWNER, MAINTAINER]) }
scope :owners_and_masters, -> { owners_and_maintainers } # @deprecated
scope :with_user, -> (user) { where(user: user) }
scope :order_name_asc, -> { left_join_users.reorder(Gitlab::Database.nulls_last_order('users.name', 'ASC')) }
scope :order_name_desc, -> { left_join_users.reorder(Gitlab::Database.nulls_last_order('users.name', 'DESC')) }
scope :order_recent_sign_in, -> { left_join_users.reorder(Gitlab::Database.nulls_last_order('users.last_sign_in_at', 'DESC')) }
scope :order_oldest_sign_in, -> { left_join_users.reorder(Gitlab::Database.nulls_last_order('users.last_sign_in_at', 'ASC')) }
scope :on_project_and_ancestors, ->(project) { where(source: [project] + project.ancestors) }
before_validation :generate_invite_token, on: :create, if: -> (member) { member.invite_email.present? }
after_create :send_invite, if: :invite?, unless: :importing?
......
......@@ -12,6 +12,8 @@ class GroupMember < Member
validates :source_type, format: { with: /\ANamespace\z/ }
default_scope { where(source_type: SOURCE_TYPE) }
scope :in_groups, ->(groups) { where(source_id: groups.select(:id)) }
after_create :update_two_factor_requirement, unless: :invite?
after_destroy :update_two_factor_requirement, unless: :invite?
......
......@@ -12,6 +12,10 @@ class ProjectMember < Member
default_scope { where(source_type: SOURCE_TYPE) }
scope :in_project, ->(project) { where(source_id: project.id) }
scope :in_namespaces, ->(groups) do
joins('INNER JOIN projects ON projects.id = members.source_id')
.where('projects.namespace_id in (?)', groups.select(:id))
end
class << self
# Add users to projects with passed access option
......
......@@ -377,8 +377,10 @@ class Project < ActiveRecord::Base
# "enabled" here means "not disabled". It includes private features!
scope :with_feature_enabled, ->(feature) {
access_level_attribute = ProjectFeature.access_level_attribute(feature)
with_project_feature.where(project_features: { access_level_attribute => [nil, ProjectFeature::PRIVATE, ProjectFeature::ENABLED, ProjectFeature::PUBLIC] })
access_level_attribute = ProjectFeature.arel_table[ProjectFeature.access_level_attribute(feature)]
enabled_feature = access_level_attribute.gt(ProjectFeature::DISABLED).or(access_level_attribute.eq(nil))
with_project_feature.where(enabled_feature)
}
# Picks a feature where the level is exactly that given.
......@@ -465,7 +467,8 @@ class Project < ActiveRecord::Base
# logged in users to more efficiently get private projects with the given
# feature.
def self.with_feature_available_for_user(feature, user)
visible = [nil, ProjectFeature::ENABLED, ProjectFeature::PUBLIC]
visible = [ProjectFeature::ENABLED, ProjectFeature::PUBLIC]
min_access_level = ProjectFeature.required_minimum_access_level(feature)
if user&.admin?
with_feature_enabled(feature)
......@@ -473,10 +476,15 @@ class Project < ActiveRecord::Base
column = ProjectFeature.quoted_access_level_column(feature)
with_project_feature
.where("#{column} IN (?) OR (#{column} = ? AND EXISTS (?))",
visible,
ProjectFeature::PRIVATE,
user.authorizations_for_projects)
.where(
"(projects.visibility_level > :private AND (#{column} IS NULL OR #{column} >= (:public_visible) OR (#{column} = :private_visible AND EXISTS(:authorizations))))"\
" OR (projects.visibility_level = :private AND (#{column} IS NULL OR #{column} >= :private_visible) AND EXISTS(:authorizations))",
{
private: Gitlab::VisibilityLevel::PRIVATE,
public_visible: ProjectFeature::ENABLED,
private_visible: ProjectFeature::PRIVATE,
authorizations: user.authorizations_for_projects(min_access_level: min_access_level)
})
else
with_feature_access_level(feature, visible)
end
......@@ -530,6 +538,7 @@ class Project < ActiveRecord::Base
def reference_pattern
%r{
(?<!#{Gitlab::PathRegex::PATH_START_CHAR})
((?<namespace>#{Gitlab::PathRegex::FULL_NAMESPACE_FORMAT_REGEX})\/)?
(?<project>#{Gitlab::PathRegex::PROJECT_PATH_FORMAT_REGEX})
}x
......@@ -569,6 +578,14 @@ class Project < ActiveRecord::Base
end
end
def all_pipelines
if builds_enabled?
super
else
super.external
end
end
# returns all ancestor-groups upto but excluding the given namespace
# when no namespace is given, all ancestors upto the top are returned
def ancestors_upto(top = nil, hierarchy_order: nil)
......
......@@ -23,11 +23,11 @@ class ProjectFeature < ActiveRecord::Base
PUBLIC = 30
FEATURES = %i(issues merge_requests wiki snippets builds repository pages).freeze
PRIVATE_FEATURES_MIN_ACCESS_LEVEL = { merge_requests: Gitlab::Access::REPORTER }.freeze
class << self
def access_level_attribute(feature)
feature = feature.model_name.plural.to_sym if feature.respond_to?(:model_name)
raise ArgumentError, "invalid project feature: #{feature}" unless FEATURES.include?(feature)
feature = ensure_feature!(feature)
"#{feature}_access_level".to_sym
end
......@@ -38,6 +38,21 @@ class ProjectFeature < ActiveRecord::Base
"#{table}.#{attribute}"
end
def required_minimum_access_level(feature)
feature = ensure_feature!(feature)
PRIVATE_FEATURES_MIN_ACCESS_LEVEL.fetch(feature, Gitlab::Access::GUEST)
end
private
def ensure_feature!(feature)
feature = feature.model_name.plural.to_sym if feature.respond_to?(:model_name)
raise ArgumentError, "invalid project feature: #{feature}" unless FEATURES.include?(feature)
feature
end
end
# Default scopes force us to unscope here since a service may need to check
......
......@@ -74,6 +74,14 @@ class ProjectTeam
end
alias_method :users, :members
# `members` method uses project_authorizations table which
# is updated asynchronously, on project move it still contains
# old members who may not have access to the new location,
# so we filter out only members of project or project's group
def members_in_project_and_ancestors
members.where(id: member_user_ids)
end
def guests
@guests ||= fetch_members(Gitlab::Access::GUEST)
end
......@@ -191,6 +199,10 @@ class ProjectTeam
def group
project.group
end
def member_user_ids
Member.on_project_and_ancestors(project).select(:user_id)
end
end
ProjectTeam.prepend(EE::ProjectTeam)
......@@ -754,8 +754,12 @@ class User < ApplicationRecord
#
# Example use:
# `Project.where('EXISTS(?)', user.authorizations_for_projects)`
def authorizations_for_projects
project_authorizations.select(1).where('project_authorizations.project_id = projects.id')
def authorizations_for_projects(min_access_level: nil)
authorizations = project_authorizations.select(1).where('project_authorizations.project_id = projects.id')
return authorizations unless min_access_level.present?
authorizations.where('project_authorizations.access_level >= ?', min_access_level)
end
# Returns the projects this user has reporter (or greater) access to, limited
......
......@@ -10,6 +10,15 @@ module Ci
@subject.project.branch_allows_collaboration?(@user, @subject.ref)
end
condition(:external_pipeline, scope: :subject, score: 0) do
@subject.external?
end
# Disallow users without permissions from accessing internal pipelines
rule { ~can?(:read_build) & ~external_pipeline }.policy do
prevent :read_pipeline
end
rule { protected_ref }.prevent :update_pipeline
rule { can?(:public_access) & branch_allows_collaboration }.policy do
......
......@@ -18,6 +18,7 @@ class IssuePolicy < IssuablePolicy
prevent :read_issue_iid
prevent :update_issue
prevent :admin_issue
prevent :create_note
end
rule { locked }.policy do
......
......@@ -18,6 +18,7 @@ class NotePolicy < BasePolicy
prevent :read_note
prevent :admin_note
prevent :resolve_note
prevent :award_emoji
end
rule { is_author }.policy do
......
......@@ -28,7 +28,10 @@ class PersonalSnippetPolicy < BasePolicy
rule { anonymous }.prevent :comment_personal_snippet
rule { can?(:comment_personal_snippet) }.enable :award_emoji
rule { can?(:comment_personal_snippet) }.policy do
enable :create_note
enable :award_emoji
end
rule { full_private_access }.enable :read_personal_snippet
end
......@@ -108,6 +108,10 @@ class ProjectPolicy < BasePolicy
condition(:has_clusters, scope: :subject) { clusterable_has_clusters? }
condition(:can_have_multiple_clusters) { multiple_clusters_available? }
condition(:internal_builds_disabled) do
!@subject.builds_enabled?
end
features = %w[
merge_requests
issues
......@@ -196,7 +200,6 @@ class ProjectPolicy < BasePolicy
enable :read_build
enable :read_container_image
enable :read_pipeline
enable :read_pipeline_schedule
enable :read_environment
enable :read_deployment
enable :read_merge_request
......@@ -235,6 +238,7 @@ class ProjectPolicy < BasePolicy
enable :update_build
enable :create_pipeline
enable :update_pipeline
enable :read_pipeline_schedule
enable :create_pipeline_schedule
enable :create_merge_request_from
enable :create_wiki
......@@ -314,13 +318,12 @@ class ProjectPolicy < BasePolicy
prevent(*create_read_update_admin_destroy(:project_snippet))
end
rule { wiki_disabled & ~has_external_wiki }.policy do
rule { wiki_disabled }.policy do
prevent(*create_read_update_admin_destroy(:wiki))
prevent(:download_wiki_code)
end
rule { builds_disabled | repository_disabled }.policy do
prevent(*create_update_admin_destroy(:pipeline))
prevent(*create_read_update_admin_destroy(:build))
prevent(*create_read_update_admin_destroy(:pipeline_schedule))
prevent(*create_read_update_admin_destroy(:environment))
......@@ -328,11 +331,22 @@ class ProjectPolicy < BasePolicy
prevent(*create_read_update_admin_destroy(:deployment))
end
# There's two separate cases when builds_disabled is true:
# 1. When internal CI is disabled - builds_disabled && internal_builds_disabled
# - We do not prevent the user from accessing Pipelines to allow him to access external CI
# 2. When the user is not allowed to access CI - builds_disabled && ~internal_builds_disabled
# - We prevent the user from accessing Pipelines
rule { (builds_disabled & ~internal_builds_disabled) | repository_disabled }.policy do
prevent(*create_read_update_admin_destroy(:pipeline))
prevent(*create_read_update_admin_destroy(:commit_status))
end
rule { repository_disabled }.policy do
prevent :push_code
prevent :download_code
prevent :fork_project
prevent :read_commit_status
prevent :read_pipeline
prevent(*create_read_update_admin_destroy(:release))
end
......@@ -359,7 +373,6 @@ class ProjectPolicy < BasePolicy
enable :read_merge_request
enable :read_note
enable :read_pipeline
enable :read_pipeline_schedule
enable :read_commit_status
enable :read_container_image
enable :download_code
......@@ -378,7 +391,6 @@ class ProjectPolicy < BasePolicy
rule { public_builds & can?(:guest_access) }.policy do
enable :read_pipeline
enable :read_pipeline_schedule
end
# These rules are included to allow maintainers of projects to push to certain
......@@ -393,7 +405,7 @@ class ProjectPolicy < BasePolicy
end.enable :read_issue_iid
rule do
(can?(:read_project_for_iids) & merge_requests_visible_to_user) | can?(:read_merge_request)
(~guest & can?(:read_project_for_iids) & merge_requests_visible_to_user) | can?(:read_merge_request)
end.enable :read_merge_request_iid
rule { ~can_have_multiple_clusters & has_clusters }.prevent :add_cluster
......
......@@ -43,6 +43,8 @@ class ProjectSnippetPolicy < BasePolicy
enable :update_project_snippet
enable :admin_project_snippet
end
rule { ~can?(:read_project_snippet) }.prevent :create_note
end
ProjectSnippetPolicy.prepend(EE::ProjectSnippetPolicy)
# frozen_string_literal: true
module Ci
class TriggerPresenter < Gitlab::View::Presenter::Delegated
presents :trigger
def has_token_exposed?
can?(current_user, :admin_trigger, trigger)
end
def token
if has_token_exposed?
trigger.token
else
trigger.short_token
end
end
end
end
# frozen_string_literal: true
class CommitPresenter < Gitlab::View::Presenter::Simple
presents :commit
def status_for(ref)
can?(current_user, :read_commit_status, commit.project) && commit.status(ref)
end
def any_pipelines?
can?(current_user, :read_pipeline, commit.project) && commit.pipelines.any?
end
end
......@@ -170,6 +170,10 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
source_branch_exists? && merge_request.can_remove_source_branch?(current_user)
end
def can_read_pipeline?
pipeline && can?(current_user, :read_pipeline, pipeline)
end
def mergeable_discussions_state
# This avoids calling MergeRequest#mergeable_discussions_state without
# considering the state of the MR first. If a MR isn't mergeable, we can
......
......@@ -59,7 +59,7 @@ class MergeRequestWidgetEntity < IssuableEntity
end
expose :merge_commit_message
expose :actual_head_pipeline, with: PipelineDetailsEntity, as: :pipeline
expose :actual_head_pipeline, with: PipelineDetailsEntity, as: :pipeline, if: -> (mr, _) { presenter(mr).can_read_pipeline? }
expose :merge_pipeline, with: PipelineDetailsEntity, if: ->(mr, _) { mr.merged? && can?(request.current_user, :read_pipeline, mr.target_project)}
# Booleans
......
......@@ -2,9 +2,11 @@
module Members
class DestroyService < Members::BaseService
def execute(member, skip_authorization: false)
def execute(member, skip_authorization: false, skip_subresources: false)
raise Gitlab::Access::AccessDeniedError unless skip_authorization || can_destroy_member?(member)
@skip_auth = skip_authorization
return member if member.is_a?(GroupMember) && member.source.last_owner?(member.user)
member.destroy
......@@ -15,6 +17,7 @@ module Members
notification_service.decline_access_request(member)
end
delete_subresources(member) unless skip_subresources
enqueue_delete_todos(member)
after_execute(member: member)
......@@ -24,6 +27,29 @@ module Members
private
def delete_subresources(member)
return unless member.is_a?(GroupMember) && member.user && member.group
delete_project_members(member)
delete_subgroup_members(member) if Group.supports_nested_objects?
end
def delete_project_members(member)
groups = member.group.self_and_descendants
ProjectMember.in_namespaces(groups).with_user(member.user).each do |project_member|
self.class.new(current_user).execute(project_member, skip_authorization: @skip_auth)
end
end
def delete_subgroup_members(member)
groups = member.group.descendants
GroupMember.in_groups(groups).with_user(member.user).each do |group_member|
self.class.new(current_user).execute(group_member, skip_authorization: @skip_auth, skip_subresources: true)
end
end
def can_destroy_member?(member)
can?(current_user, destroy_member_permission(member), member)
end
......
......@@ -11,7 +11,7 @@ module Notes
if in_reply_to_discussion_id.present?
discussion = find_discussion(in_reply_to_discussion_id)
unless discussion
unless discussion && can?(current_user, :create_note, discussion.noteable)
note = Note.new
note.errors.add(:base, 'Discussion to reply to cannot be found')
return note
......@@ -36,19 +36,8 @@ module Notes
if project
project.notes.find_discussion(discussion_id)
else
discussion = Note.find_discussion(discussion_id)
noteable = discussion.noteable
return nil unless noteable_without_project?(noteable)
discussion
end
Note.find_discussion(discussion_id)
end
def noteable_without_project?(noteable)
return true if noteable.is_a?(PersonalSnippet) && can?(current_user, :comment_personal_snippet, noteable)
false
end
end
end
......@@ -373,7 +373,8 @@ class NotificationService
end
def project_was_moved(project, old_path_with_namespace)
recipients = notifiable_users(project.team.members, :mention, project: project)
recipients = project.private? ? project.team.members_in_project_and_ancestors : project.team.members
recipients = notifiable_users(recipients, :mention, project: project)
recipients.each do |recipient|
mailer.project_was_moved_email(
......
# frozen_string_literal: true
module Projects
# Used by project imports, it removes any potential paths
# included in an error message that could be stored in the DB
class ImportErrorFilter
ERROR_MESSAGE_FILTER = /[^\s]*#{File::SEPARATOR}[^\s]*(?=(\s|\z))/
FILTER_MESSAGE = '[FILTERED]'
def self.filter_message(message)
message.gsub(ERROR_MESSAGE_FILTER, FILTER_MESSAGE)
end
end
end
......@@ -24,8 +24,16 @@ module Projects
import_data
success
rescue => e
rescue Gitlab::UrlBlocker::BlockedUrlError => e
Gitlab::Sentry.track_acceptable_exception(e, extra: { project_path: project.full_path, importer: project.import_type })
error("Error importing repository #{project.safe_import_url} into #{project.full_path} - #{e.message}")
rescue => e
message = Projects::ImportErrorFilter.filter_message(e.message)
Gitlab::Sentry.track_acceptable_exception(e, extra: { project_path: project.full_path, importer: project.import_type })
error("Error importing repository #{project.safe_import_url} into #{project.full_path} - #{message}")
end
private
......@@ -35,7 +43,7 @@ module Projects
begin
Gitlab::UrlBlocker.validate!(project.import_url, ports: Project::VALID_IMPORT_PORTS)
rescue Gitlab::UrlBlocker::BlockedUrlError => e
raise Error, "Blocked import URL: #{e.message}"
raise e, "Blocked import URL: #{e.message}"
end
end
......@@ -86,11 +94,11 @@ module Projects
return unless project.lfs_enabled?
oids_to_download = Projects::LfsPointers::LfsImportService.new(project).execute
download_service = Projects::LfsPointers::LfsDownloadService.new(project)
lfs_objects_to_download = Projects::LfsPointers::LfsImportService.new(project).execute
oids_to_download.each do |oid, link|
download_service.execute(oid, link)
lfs_objects_to_download.each do |lfs_download_object|
Projects::LfsPointers::LfsDownloadService.new(project, lfs_download_object)
.execute
end
rescue => e
# Right now, to avoid aborting the importing process, we silently fail
......
......@@ -41,16 +41,17 @@ module Projects
end
def parse_response_links(objects_response)
objects_response.each_with_object({}) do |entry, link_list|
objects_response.each_with_object([]) do |entry, link_list|
begin
oid = entry['oid']
link = entry.dig('actions', DOWNLOAD_ACTION, 'href')
raise DownloadLinkNotFound unless link
link_list[oid] = add_credentials(link)
rescue DownloadLinkNotFound, URI::InvalidURIError
Rails.logger.error("Link for Lfs Object with oid #{oid} not found or invalid.")
link_list << LfsDownloadObject.new(oid: entry['oid'],
size: entry['size'],
link: add_credentials(link))
rescue DownloadLinkNotFound, Addressable::URI::InvalidURIError
log_error("Link for Lfs Object with oid #{entry['oid']} not found or invalid.")
end
end
end
......@@ -70,7 +71,7 @@ module Projects
end
def add_credentials(link)
uri = URI.parse(link)
uri = Addressable::URI.parse(link)
if should_add_credentials?(uri)
uri.user = remote_uri.user
......
......@@ -4,68 +4,93 @@
module Projects
module LfsPointers
class LfsDownloadService < BaseService
VALID_PROTOCOLS = %w[http https].freeze
SizeError = Class.new(StandardError)
OidError = Class.new(StandardError)
# rubocop: disable CodeReuse/ActiveRecord
def execute(oid, url)
return unless project&.lfs_enabled? && oid.present? && url.present?
attr_reader :lfs_download_object
delegate :oid, :size, :credentials, :sanitized_url, to: :lfs_download_object, prefix: :lfs
return if LfsObject.exists?(oid: oid)
def initialize(project, lfs_download_object)
super(project)
sanitized_uri = sanitize_url!(url)
@lfs_download_object = lfs_download_object
end
with_tmp_file(oid) do |file|
download_and_save_file(file, sanitized_uri)
lfs_object = LfsObject.new(oid: oid, size: file.size, file: file)
# rubocop: disable CodeReuse/ActiveRecord
def execute
return unless project&.lfs_enabled? && lfs_download_object
return error("LFS file with oid #{lfs_oid} has invalid attributes") unless lfs_download_object.valid?
return if LfsObject.exists?(oid: lfs_oid)
project.all_lfs_objects << lfs_object
wrap_download_errors do
download_lfs_file!
end
rescue Gitlab::UrlBlocker::BlockedUrlError => e
Rails.logger.error("LFS file with oid #{oid} couldn't be downloaded: #{e.message}")
rescue StandardError => e
Rails.logger.error("LFS file with oid #{oid} couldn't be downloaded from #{sanitized_uri.sanitized_url}: #{e.message}")
end
# rubocop: enable CodeReuse/ActiveRecord
private
def sanitize_url!(url)
Gitlab::UrlSanitizer.new(url).tap do |sanitized_uri|
# Just validate that HTTP/HTTPS protocols are used. The
# subsequent Gitlab::HTTP.get call will do network checks
# based on the settings.
Gitlab::UrlBlocker.validate!(sanitized_uri.sanitized_url,
protocols: VALID_PROTOCOLS)
def wrap_download_errors(&block)
yield
rescue SizeError, OidError, StandardError => e
error("LFS file with oid #{lfs_oid} could't be downloaded from #{lfs_sanitized_url}: #{e.message}")
end
def download_lfs_file!
with_tmp_file do |tmp_file|
download_and_save_file!(tmp_file)
project.all_lfs_objects << LfsObject.new(oid: lfs_oid,
size: lfs_size,
file: tmp_file)
success
end
end
def download_and_save_file(file, sanitized_uri)
response = Gitlab::HTTP.get(sanitized_uri.sanitized_url, headers(sanitized_uri)) do |fragment|
def download_and_save_file!(file)
digester = Digest::SHA256.new
response = Gitlab::HTTP.get(lfs_sanitized_url, download_headers) do |fragment|
digester << fragment
file.write(fragment)
raise_size_error! if file.size > lfs_size
end
raise StandardError, "Received error code #{response.code}" unless response.success?
end
def headers(sanitized_uri)
query_options.tap do |headers|
credentials = sanitized_uri.credentials
raise_size_error! if file.size != lfs_size
raise_oid_error! if digester.hexdigest != lfs_oid
end
if credentials[:user].present? || credentials[:password].present?
def download_headers
{ stream_body: true }.tap do |headers|
if lfs_credentials[:user].present? || lfs_credentials[:password].present?
# Using authentication headers in the request
headers[:http_basic_authentication] = [credentials[:user], credentials[:password]]
headers[:basic_auth] = { username: lfs_credentials[:user], password: lfs_credentials[:password] }
end
end
end
def query_options
{ stream_body: true }
end
def with_tmp_file(oid)
def with_tmp_file
create_tmp_storage_dir
File.open(File.join(tmp_storage_dir, oid), 'wb') { |file| yield file }
File.open(tmp_filename, 'wb') do |file|
begin
yield file
rescue StandardError => e
# If the lfs file is successfully downloaded it will be removed
# when it is added to the project's lfs files.
# Nevertheless if any excetion raises the file would remain
# in the file system. Here we ensure to remove it
File.unlink(file) if File.exist?(file)
raise e
end
end
end
def tmp_filename
File.join(tmp_storage_dir, lfs_oid)
end
def create_tmp_storage_dir
......@@ -79,6 +104,20 @@ module Projects
def storage_dir
@storage_dir ||= Gitlab.config.lfs.storage_path
end
def raise_size_error!
raise SizeError, 'Size mistmatch'
end
def raise_oid_error!
raise OidError, 'Oid mismatch'
end
def error(message, http_status = nil)
log_error(message)
super
end
end
end
end
......@@ -7,7 +7,11 @@ module Projects
BLOCK_SIZE = 32.kilobytes
MAX_SIZE = 1.terabyte
SITE_PATH = 'public/'.freeze
PUBLIC_DIR = 'public'.freeze
# this has to be invalid group name,
# as it shares the namespace with groups
TMP_EXTRACT_PATH = '@pages.tmp'.freeze
attr_reader :build
......@@ -27,12 +31,11 @@ module Projects
raise InvalidStateError, 'pages are outdated' unless latest?
# Create temporary directory in which we will extract the artifacts
FileUtils.mkdir_p(tmp_path)
Dir.mktmpdir(nil, tmp_path) do |archive_path|
make_secure_tmp_dir(tmp_path) do |archive_path|
extract_archive!(archive_path)
# Check if we did extract public directory
archive_public_path = File.join(archive_path, 'public')
archive_public_path = File.join(archive_path, PUBLIC_DIR)
raise InvalidStateError, 'pages miss the public folder' unless Dir.exist?(archive_public_path)
raise InvalidStateError, 'pages are outdated' unless latest?
......@@ -85,22 +88,18 @@ module Projects
raise InvalidStateError, 'missing artifacts metadata' unless build.artifacts_metadata?
# Calculate page size after extract
public_entry = build.artifacts_metadata_entry(SITE_PATH, recursive: true)
public_entry = build.artifacts_metadata_entry(PUBLIC_DIR + '/', recursive: true)
if public_entry.total_size > max_size
raise InvalidStateError, "artifacts for pages are too large: #{public_entry.total_size}"
end
# Requires UnZip at least 6.00 Info-ZIP.
# -qq be (very) quiet
# -n never overwrite existing files
# We add * to end of SITE_PATH, because we want to extract SITE_PATH and all subdirectories
site_path = File.join(SITE_PATH, '*')
build.artifacts_file.use_file do |artifacts_path|
unless system(*%W(unzip -n #{artifacts_path} #{site_path} -d #{temp_path}))
raise FailedToExtractError, 'pages failed to extract'
end
SafeZip::Extract.new(artifacts_path)
.extract(directories: [PUBLIC_DIR], to: temp_path)
end
rescue SafeZip::Extract::Error => e
raise FailedToExtractError, e.message
end
def deploy_page!(archive_public_path)
......@@ -139,7 +138,7 @@ module Projects
end
def tmp_path
@tmp_path ||= File.join(::Settings.pages.path, 'tmp')
@tmp_path ||= File.join(::Settings.pages.path, TMP_EXTRACT_PATH)
end
def pages_path
......@@ -147,11 +146,11 @@ module Projects
end
def public_path
@public_path ||= File.join(pages_path, 'public')
@public_path ||= File.join(pages_path, PUBLIC_DIR)
end
def previous_public_path
@previous_public_path ||= File.join(pages_path, "public.#{SecureRandom.hex}")
@previous_public_path ||= File.join(pages_path, "#{PUBLIC_DIR}.#{SecureRandom.hex}")
end
def ref
......@@ -188,5 +187,15 @@ module Projects
def pages_deployments_failed_total_counter
@pages_deployments_failed_total_counter ||= Gitlab::Metrics.counter(:pages_deployments_failed_total, "Counter of GitLab Pages deployments which failed")
end
def make_secure_tmp_dir(tmp_path)
FileUtils.mkdir_p(tmp_path)
path = Dir.mktmpdir(nil, tmp_path)
begin
yield(path)
ensure
FileUtils.remove_entry_secure(path)
end
end
end
end
......@@ -7,8 +7,6 @@ module ProtectedBranches
@merge_params = AccessLevelParams.new(:merge, params)
@unprotect_params = AccessLevelParams.new(:unprotect, params)
verify_params!
protected_branch_params = {
name: params[:name],
push_access_levels_attributes: @push_params.access_levels,
......@@ -18,12 +16,6 @@ module ProtectedBranches
::ProtectedBranches::CreateService.new(@project, @current_user, protected_branch_params).execute
end
private
def verify_params!
# EE-only
end
end
end
......
#content
= email_default_heading("#{@resource.user.name}, you've added an additional email!")
= email_default_heading("#{sanitize_name(@resource.user.name)}, you've added an additional email!")
%p Click the link below to confirm your email address (#{@resource.email})
#cta
= link_to 'Confirm your email address', confirmation_url(@resource, confirmation_token: @token)
......
......@@ -302,17 +302,31 @@
- if project_nav_tab? :wiki
= nav_link(controller: :wikis) do
= link_to get_project_wiki_path(@project), class: 'shortcuts-wiki qa-wiki-link' do
= link_to wiki_url, class: 'shortcuts-wiki qa-wiki-link' do
.nav-icon-container
= sprite_icon('book')
%span.nav-item-name
= _('Wiki')
%ul.sidebar-sub-level-items.is-fly-out-only
= nav_link(controller: :wikis, html_options: { class: "fly-out-top-item" } ) do
= link_to get_project_wiki_path(@project) do
= link_to wiki_url do
%strong.fly-out-top-item-name
= _('Wiki')
- if project_nav_tab?(:external_wiki)
- external_wiki_url = @project.external_wiki.external_wiki_url
= nav_link do
= link_to external_wiki_url, class: 'shortcuts-external_wiki' do
.nav-icon-container
= sprite_icon('issue-external')
%span.nav-item-name
= _('External Wiki')
%ul.sidebar-sub-level-items.is-fly-out-only
= nav_link(html_options: { class: "fly-out-top-item" } ) do
= link_to external_wiki_url do
%strong.fly-out-top-item-name
= _('External Wiki')
- if project_nav_tab? :snippets
= nav_link(controller: :snippets) do
= link_to project_snippets_path(@project), class: 'shortcuts-snippets' do
......
......@@ -3,7 +3,7 @@
<% discussion = note.discussion if note.part_of_discussion? -%>
<% if discussion && !discussion.individual_note? -%>
<%= note.author_name -%>
<%= sanitize_name(note.author_name) -%>
<% if discussion.new_discussion? -%>
<%= " started a new discussion" -%>
<% else -%>
......@@ -16,7 +16,7 @@
<% elsif Gitlab::CurrentSettings.email_author_in_body -%>
<%= "#{note.author_name} commented:" -%>
<%= "#{sanitize_name(note.author_name)} commented:" -%>
<% end -%>
......
......@@ -3,7 +3,7 @@ Auto DevOps pipeline was disabled for <%= @project.name %>
The Auto DevOps pipeline failed for pipeline <%= @pipeline.iid %> (<%= pipeline_url(@pipeline) %>) and has been disabled for <%= @project.name %>. In order to use the Auto DevOps pipeline with your project, please review the currently supported languagues (https://docs.gitlab.com/ee/topics/autodevops/#currently-supported-languages), adjust your project accordingly, and turn on the Auto DevOps pipeline within your CI/CD project settings (<%= project_settings_ci_cd_url(@project) %>).
<% if @pipeline.user -%>
Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) triggered by <%= @pipeline.user.name %> ( <%= user_url(@pipeline.user) %> )
Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) triggered by <%= sanitize_name(@pipeline.user.name) %> ( <%= user_url(@pipeline.user) %> )
<% else -%>
Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) triggered by API
<% end -%>
......
%p
Issue was closed by #{@updated_by.name}
Issue was closed by #{sanitize_name(@updated_by.name)}
Issue was closed by #{@updated_by.name}
Issue was closed by #{sanitize_name(@updated_by.name)}
Issue ##{@issue.iid}: #{project_issue_url(@issue.project, @issue)}
%p
Merge Request #{@merge_request.to_reference} was closed by #{@updated_by.name}
Merge Request #{@merge_request.to_reference} was closed by #{sanitize_name(@updated_by.name)}
Merge Request #{@merge_request.to_reference} was closed by #{@updated_by.name}
Merge Request #{@merge_request.to_reference} was closed by #{sanitize_name(@updated_by.name)}
Merge Request url: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
= merge_path_description(@merge_request, 'to')
Author: #{@merge_request.author_name}
Assignee: #{@merge_request.assignee_name}
Author: #{sanitize_name(@merge_request.author_name)}
Assignee: #{sanitize_name(@merge_request.assignee_name)}
%p
Issue was #{@issue_status} by #{@updated_by.name}
Issue was #{@issue_status} by #{sanitize_name(@updated_by.name)}
Issue was <%= @issue_status %> by <%= @updated_by.name %>
Issue was <%= @issue_status %> by <%= sanitize_name(@updated_by.name) %>
Issue <%= @issue.iid %>: <%= url_for(project_issue_url(@issue.project, @issue)) %>
<%= member.user.name %> (<%= user_url(member.user) %>) requested <%= member.human_access %> access to the <%= member_source.human_name %> <%= member_source.model_name.singular %>.
<%= sanitize_name(member.user.name) %> (<%= user_url(member.user) %>) requested <%= member.human_access %> access to the <%= member_source.human_name %> <%= member_source.model_name.singular %>.
<%= polymorphic_url([member_source, :members]) %>
<%= member.invite_email %>, now known as <%= member.user.name %>, has accepted your invitation to join the <%= member_source.human_name %> <%= member_source.model_name.singular %>.
<%= member.invite_email %>, now known as <%= sanitize_name(member.user.name) %>, has accepted your invitation to join the <%= member_source.human_name %> <%= member_source.model_name.singular %>.
<%= member_source.web_url %>
You have been invited <%= "by #{member.created_by.name} " if member.created_by %>to join the <%= member_source.human_name %> <%= member_source.model_name.singular %> as <%= member.human_access %>.
You have been invited <%= "by #{sanitize_name(member.created_by.name)} " if member.created_by %>to join the <%= member_source.human_name %> <%= member_source.model_name.singular %> as <%= member.human_access %>.
Accept invitation: <%= invite_url(@token) %>
Decline invitation: <%= decline_invite_url(@token) %>
%p
Merge Request #{@merge_request.to_reference} was #{@mr_status} by #{@updated_by.name}
Merge Request #{@merge_request.to_reference} was #{@mr_status} by #{sanitize_name(@updated_by.name)}
Merge Request #{@merge_request.to_reference} was #{@mr_status} by #{@updated_by.name}
Merge Request #{@merge_request.to_reference} was #{@mr_status} by #{sanitize_name(@updated_by.name)}
Merge Request url: #{project_merge_request_url(@merge_request.target_project, @merge_request)}
= merge_path_description(@merge_request, 'to')
Author: #{@merge_request.author_name}
Assignee: #{@merge_request.assignee_name}
Author: #{sanitize_name(@merge_request.author_name)}
Assignee: #{sanitize_name(@merge_request.assignee_name)}
......@@ -4,5 +4,5 @@ Merge Request url: #{project_merge_request_url(@merge_request.target_project, @m
= merge_path_description(@merge_request, 'to')
Author: #{@merge_request.author_name}
Assignee: #{@merge_request.assignee_name}
Author: #{sanitize_name(@merge_request.author_name)}
Assignee: #{sanitize_name(@merge_request.assignee_name)}
......@@ -4,5 +4,5 @@ Merge Request url: #{project_merge_request_url(@merge_request.target_project, @m
= merge_path_description(@merge_request, 'to')
Author: #{@merge_request.author_name}
Assignee: #{@merge_request.assignee_name}
Author: #{sanitize_name(@merge_request.author_name)}
Assignee: #{sanitize_name(@merge_request.assignee_name)}
%p
Hi #{@user.name}!
Hi #{sanitize_name(@user.name)}!
%p
A new GPG key was added to your account:
%p
......
Hi <%= @user.name %>!
Hi <%= sanitize_name(@user.name) %>!
A new GPG key was added to your account:
......
New Issue was created.
Issue <%= @issue.iid %>: <%= url_for(project_issue_url(@issue.project, @issue)) %>
Author: <%= @issue.author_name %>
Author: <%= sanitize_name(@issue.author_name) %>
Assignee: <%= @issue.assignee_list %>
<%= @issue.description %>
You have been mentioned in an issue.
Issue <%= @issue.iid %>: <%= url_for(project_issue_url(@issue.project, @issue)) %>
Author: <%= @issue.author_name %>
Assignee: <%= @issue.assignee_list %>
Author: <%= sanitize_name(@issue.author_name) %>
Assignee: <%= sanitize_name(@issue.assignee_list) %>
<%= @issue.description %>
......@@ -3,7 +3,7 @@ You have been mentioned in Merge Request <%= @merge_request.to_reference %>
<%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request)) %>
<%= merge_path_description(@merge_request, 'to') %>
Author: <%= @merge_request.author_name %>
Assignee: <%= @merge_request.assignee_name %>
Author: <%= sanitize_name(@merge_request.author_name) %>
Assignee: <%= sanitize_name(@merge_request.assignee_name) %>
<%= @merge_request.description %>
......@@ -7,7 +7,7 @@
- if @merge_request.assignee_id.present?
%p
Assignee: #{@merge_request.assignee_name}
Assignee: #{sanitize_name(@merge_request.assignee_name)}
= render_if_exists 'notify/merge_request_approvers', presenter: @mr_presenter
......
%p
Hi #{@user.name}!
Hi #{sanitize_name(@user.name)}!
%p
A new public key was added to your account:
%p
......
Hi <%= @user.name %>!
Hi <%= sanitize_name(@user.name) %>!
A new public key was added to your account:
......
%p
Hi #{@user['name']}!
Hi #{sanitize_name(@user['name'])}!
%p
- if Gitlab::CurrentSettings.allow_signup?
Your account has been created successfully.
......
Hi <%= @user.name %>!
Hi <%= sanitize_name(@user.name) %>!
The Administrator created an account for you. Now you are a member of the company GitLab application.
......
......@@ -10,20 +10,20 @@ Commit: <%= @pipeline.short_sha %> ( <%= commit_url(@pipeline) %> )
Commit Message: <%= @pipeline.git_commit_message.truncate(50) %>
<% commit = @pipeline.commit -%>
<% if commit.author -%>
Commit Author: <%= commit.author.name %> ( <%= user_url(commit.author) %> )
Commit Author: <%= sanitize_name(commit.author.name) %> ( <%= user_url(commit.author) %> )
<% else -%>
Commit Author: <%= commit.author_name %>
<% end -%>
<% if commit.different_committer? -%>
<% if commit.committer -%>
Committed by: <%= commit.committer.name %> ( <%= user_url(commit.committer) %> )
Committed by: <%= sanitize_name(commit.committer.name) %> ( <%= user_url(commit.committer) %> )
<% else -%>
Committed by: <%= commit.committer_name %>
<% end -%>
<% end -%>
<% if @pipeline.user -%>
Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) triggered by <%= @pipeline.user.name %> ( <%= user_url(@pipeline.user) %> )
Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) triggered by <%= sanitize_name(@pipeline.user.name) %> ( <%= user_url(@pipeline.user) %> )
<% else -%>
Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) triggered by API
<% end -%>
......
......@@ -10,13 +10,13 @@ Commit: <%= @pipeline.short_sha %> ( <%= commit_url(@pipeline) %> )
Commit Message: <%= @pipeline.git_commit_message.truncate(50) %>
<% commit = @pipeline.commit -%>
<% if commit.author -%>
Commit Author: <%= commit.author.name %> ( <%= user_url(commit.author) %> )
Commit Author: <%= sanitize_name(commit.author.name) %> ( <%= user_url(commit.author) %> )
<% else -%>
Commit Author: <%= commit.author_name %>
<% end -%>
<% if commit.different_committer? -%>
<% if commit.committer -%>
Committed by: <%= commit.committer.name %> ( <%= user_url(commit.committer) %> )
Committed by: <%= sanitize_name(commit.committer.name) %> ( <%= user_url(commit.committer) %> )
<% else -%>
Committed by: <%= commit.committer_name %>
<% end -%>
......@@ -25,7 +25,7 @@ Committed by: <%= commit.committer_name %>
<% job_count = @pipeline.total_size -%>
<% stage_count = @pipeline.stages_count -%>
<% if @pipeline.user -%>
Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) triggered by <%= @pipeline.user.name %> ( <%= user_url(@pipeline.user) %> )
Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) triggered by <%= sanitize_name(@pipeline.user.name) %> ( <%= user_url(@pipeline.user) %> )
<% else -%>
Pipeline #<%= @pipeline.id %> ( <%= pipeline_url(@pipeline) %> ) triggered by API
<% end -%>
......
%h3
= @updated_by_user.name
= sanitize_name(@updated_by_user.name)
pushed new commits to merge request
= link_to(@merge_request.to_reference, project_merge_request_url(@merge_request.target_project, @merge_request))
......
#{@updated_by_user.name} pushed new commits to merge request #{@merge_request.to_reference}
#{sanitize_name(@updated_by_user.name)} pushed new commits to merge request #{@merge_request.to_reference}
\
#{url_for(project_merge_request_url(@merge_request.target_project, @merge_request))}
\
......
......@@ -2,7 +2,7 @@
Assignee changed
- if @previous_assignees.any?
from
%strong= @previous_assignees.map(&:name).to_sentence
%strong= sanitize_name(@previous_assignees.map(&:name).to_sentence)
to
- if @issue.assignees.any?
%strong= @issue.assignee_list
......
......@@ -2,5 +2,5 @@ Reassigned Issue <%= @issue.iid %>
<%= url_for([@issue.project.namespace.becomes(Namespace), @issue.project, @issue, { only_path: false }]) %>
Assignee changed <%= "from #{@previous_assignees.map(&:name).to_sentence}" if @previous_assignees.any? -%>
Assignee changed <%= "from #{sanitize_name(@previous_assignees.map(&:name).to_sentence)}" if @previous_assignees.any? -%>
to <%= "#{@issue.assignees.any? ? @issue.assignee_list : 'Unassigned'}" %>
......@@ -2,9 +2,9 @@
Assignee changed
- if @previous_assignee
from
%strong= @previous_assignee.name
%strong= sanitize_name(@previous_assignee.name)
to
- if @merge_request.assignee_id
%strong= @merge_request.assignee_name
%strong= sanitize_name(@merge_request.assignee_name)
- else
%strong Unassigned
......@@ -2,5 +2,5 @@ Reassigned Merge Request <%= @merge_request.iid %>
<%= url_for([@merge_request.project.namespace.becomes(Namespace), @merge_request.project, @merge_request, { only_path: false }]) %>
Assignee changed <%= "from #{@previous_assignee.name}" if @previous_assignee -%>
to <%= "#{@merge_request.assignee_id ? @merge_request.assignee_name : 'Unassigned'}" %>
Assignee changed <%= "from #{sanitize_name(@previous_assignee.name)}" if @previous_assignee -%>
to <%= "#{@merge_request.assignee_id ? sanitize_name(@merge_request.assignee_name) : 'Unassigned'}" %>
%p
All discussions on Merge Request #{@merge_request.to_reference} were resolved by #{@resolved_by.name}
All discussions on Merge Request #{@merge_request.to_reference} were resolved by #{sanitize_name(@resolved_by.name)}
All discussions on Merge Request <%= @merge_request.to_reference %> were resolved by <%= @resolved_by.name %>
All discussions on Merge Request <%= @merge_request.to_reference %> were resolved by <%= sanitize_name(@resolved_by.name) %>
<%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request)) %>
= icon('info-circle fw')
= succeed '.' do
To learn more about this project, read
= link_to "the wiki", get_project_wiki_path(viewer.project)
= link_to "the wiki", project_wiki_path(viewer.project, :home)
- any_pipelines = @commit.present(current_user: current_user).any_pipelines?
%ul.nav-links.no-top.no-bottom.commit-ci-menu.nav.nav-tabs
= nav_link(path: 'commit#show') do
= link_to project_commit_path(@project, @commit.id) do
Changes
%span.badge.badge-pill= @diffs.size
- if can?(current_user, :read_pipeline, @project)
- if any_pipelines
= nav_link(path: 'commit#pipelines') do
= link_to pipelines_project_commit_path(@project, @commit.id) do
Pipelines
......
......@@ -74,8 +74,8 @@
%span.commit-info.merge-requests{ 'data-project-commit-path' => merge_requests_project_commit_path(@project, @commit.id, format: :json) }
= icon('spinner spin')
- if @commit.last_pipeline
- last_pipeline = @commit.last_pipeline
- if can?(current_user, :read_pipeline, last_pipeline)
.well-segment.pipeline-info
.status-icon-container
= link_to project_pipeline_path(@project, last_pipeline.id), class: "ci-status-icon-#{last_pipeline.status}" do
......
......@@ -9,10 +9,7 @@
.container-fluid{ class: [limited_container_width, container_class] }
= render "commit_box"
- if @commit.status
= render "ci_menu"
- else
.block-connector
= render "projects/diffs/diffs", diffs: @diffs, environment: @environment, is_commit: true
.limited-width-notes
......
......@@ -6,6 +6,7 @@
- merge_request = local_assigns.fetch(:merge_request, nil)
- project = local_assigns.fetch(:project) { merge_request&.project }
- ref = local_assigns.fetch(:ref) { merge_request&.source_branch }
- commit_status = commit.present(current_user: current_user).status_for(ref)
- link = commit_path(project, commit, merge_request: merge_request)
......@@ -26,7 +27,7 @@
%span.commit-row-message.d-block.d-sm-none
&middot;
= commit.short_id
- if commit.status(ref)
- if commit_status
.d-block.d-sm-none
= render_commit_status(commit, ref: ref)
- if commit.description?
......@@ -52,7 +53,7 @@
- else
= render partial: 'projects/commit/ajax_signature', locals: { commit: commit }
- if commit.status(ref)
- if commit_status
= render_commit_status(commit, ref: ref)
.js-commit-pipeline-status{ data: { endpoint: pipelines_project_commit_path(project, commit.id, ref: ref) } }
......
......@@ -12,6 +12,7 @@
%ul.content-list.related-items-list
- has_any_head_pipeline = @merge_requests.any?(&:head_pipeline_id)
- @merge_requests.each do |merge_request|
- merge_request = merge_request.present(current_user: current_user)
%li.list-item.py-0.px-0
.item-body.issuable-info-container.py-lg-3.px-lg-3.pl-md-3
.item-contents
......@@ -25,7 +26,7 @@
= merge_request.target_project.full_path
= merge_request.to_reference
%span.mr-ci-status.flex-md-grow-1.justify-content-end.d-flex.ml-md-2
- if merge_request.head_pipeline
- if merge_request.can_read_pipeline?
= render_pipeline_status(merge_request.head_pipeline, tooltip_placement: 'bottom')
- elsif has_any_head_pipeline
= icon('blank fw')
......
......@@ -6,7 +6,7 @@
%li
- target = @project.repository.find_branch(branch).dereferenced_target
- pipeline = @project.pipeline_for(branch, target.sha) if target
- if pipeline
- if can?(current_user, :read_pipeline, pipeline)
%span.related-branch-ci-status
= render_pipeline_status(pipeline)
%span.related-branch-info
......
......@@ -46,7 +46,7 @@
%li.issuable-status.d-none.d-sm-inline-block
= icon('ban')
CLOSED
- if merge_request.head_pipeline
- 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)
- if merge_request.open? && merge_request.broken?
......
......@@ -6,7 +6,6 @@
= preserve(markdown(commit.description, pipeline: :single_line))
.info-well
- if commit.status
.well-segment.pipeline-info
.icon-container
= icon('clock-o')
......
%tr
%td
- if can?(current_user, :admin_trigger, trigger)
- if trigger.has_token_exposed?
%span= trigger.token
= clipboard_button(text: trigger.token, title: "Copy trigger token to clipboard")
- else
......
- @no_container = true
- add_to_breadcrumbs "Wiki", get_project_wiki_path(@project)
- add_to_breadcrumbs "Wiki", project_wiki_path(@project, :home)
- breadcrumb_title s_("Wiki|Pages")
- page_title s_("Wiki|Pages"), _("Wiki")
......
......@@ -2,7 +2,7 @@
- breadcrumb_title @page.human_title
- wiki_breadcrumb_dropdown_links(@page.slug)
- page_title @page.human_title, _("Wiki")
- add_to_breadcrumbs _("Wiki"), get_project_wiki_path(@project)
- add_to_breadcrumbs _("Wiki"), project_wiki_path(@project, :home)
.wiki-page-header.has-sidebar-toggle
%button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
......
......@@ -84,7 +84,7 @@
title: _('Issues'), data: { container: 'body', placement: 'top' } do
= 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?
- if pipeline_status && can?(current_user, :read_cross_project) && project.pipeline_status.has_status? && can?(current_user, :read_build, project)
%span.icon-wrapper.pipeline-status
= render_project_pipeline_status(project.pipeline_status, tooltip_placement: 'top')
.updated-note
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment