Commit 5dab034c authored by GitLab Bot's avatar GitLab Bot

Merge remote-tracking branch 'upstream/master' into ce-to-ee-2018-11-29

# Conflicts:
#	app/controllers/application_controller.rb
#	app/controllers/projects/issues_controller.rb
#	app/controllers/projects_controller.rb
#	app/views/projects/commits/_commit.html.haml
#	doc/README.md
#	locale/gitlab.pot
#	spec/models/project_spec.rb

[ci skip]
parents 38ba246b a9f5b223
...@@ -7,6 +7,11 @@ gem_versions = {} ...@@ -7,6 +7,11 @@ gem_versions = {}
gem_versions['activerecord_sane_schema_dumper'] = rails5? ? '1.0' : '0.2' gem_versions['activerecord_sane_schema_dumper'] = rails5? ? '1.0' : '0.2'
gem_versions['rails'] = rails5? ? '5.0.7' : '4.2.10' gem_versions['rails'] = rails5? ? '5.0.7' : '4.2.10'
gem_versions['rails-i18n'] = rails5? ? '~> 5.1' : '~> 4.0.9' gem_versions['rails-i18n'] = rails5? ? '~> 5.1' : '~> 4.0.9'
# The 2.0.6 version of rack requires monkeypatch to be present in
# `config.ru`. This can be removed once a new update for Rack
# is available that contains https://github.com/rack/rack/pull/1201.
gem_versions['rack'] = rails5? ? '2.0.6' : '1.6.11'
# --- The end of special code for migrating to Rails 5.0 --- # --- The end of special code for migrating to Rails 5.0 ---
source 'https://rubygems.org' source 'https://rubygems.org'
...@@ -164,6 +169,8 @@ gem 'icalendar' ...@@ -164,6 +169,8 @@ gem 'icalendar'
gem 'diffy', '~> 3.1.0' gem 'diffy', '~> 3.1.0'
# Application server # Application server
gem 'rack', gem_versions['rack']
group :unicorn do group :unicorn do
gem 'unicorn', '~> 5.1.0' gem 'unicorn', '~> 5.1.0'
gem 'unicorn-worker-killer', '~> 0.4.4' gem 'unicorn-worker-killer', '~> 0.4.4'
......
...@@ -1125,6 +1125,7 @@ DEPENDENCIES ...@@ -1125,6 +1125,7 @@ DEPENDENCIES
pry-rails (~> 0.3.4) pry-rails (~> 0.3.4)
puma (~> 3.12) puma (~> 3.12)
puma_worker_killer puma_worker_killer
rack (= 2.0.6)
rack-attack (~> 4.4.1) rack-attack (~> 4.4.1)
rack-cors (~> 1.0.0) rack-cors (~> 1.0.0)
rack-oauth2 (~> 1.2.1) rack-oauth2 (~> 1.2.1)
......
...@@ -1116,6 +1116,7 @@ DEPENDENCIES ...@@ -1116,6 +1116,7 @@ DEPENDENCIES
pry-rails (~> 0.3.4) pry-rails (~> 0.3.4)
puma (~> 3.12) puma (~> 3.12)
puma_worker_killer puma_worker_killer
rack (= 1.6.11)
rack-attack (~> 4.4.1) rack-attack (~> 4.4.1)
rack-cors (~> 1.0.0) rack-cors (~> 1.0.0)
rack-oauth2 (~> 1.2.1) rack-oauth2 (~> 1.2.1)
......
...@@ -26,6 +26,9 @@ export default function renderMermaid($els) { ...@@ -26,6 +26,9 @@ export default function renderMermaid($els) {
}, },
// mermaidAPI options // mermaidAPI options
theme: 'neutral', theme: 'neutral',
flowchart: {
htmlLabels: false,
},
}); });
$els.each((i, el) => { $els.each((i, el) => {
......
...@@ -12,11 +12,11 @@ class ApplicationController < ActionController::Base ...@@ -12,11 +12,11 @@ class ApplicationController < ActionController::Base
include WorkhorseHelper include WorkhorseHelper
include EnforcesTwoFactorAuthentication include EnforcesTwoFactorAuthentication
include WithPerformanceBar include WithPerformanceBar
include SessionlessAuthentication
# this can be removed after switching to rails 5 # this can be removed after switching to rails 5
# https://gitlab.com/gitlab-org/gitlab-ce/issues/51908 # https://gitlab.com/gitlab-org/gitlab-ce/issues/51908
include InvalidUTF8ErrorHandler unless Gitlab.rails5? include InvalidUTF8ErrorHandler unless Gitlab.rails5?
before_action :authenticate_sessionless_user!
before_action :authenticate_user! before_action :authenticate_user!
before_action :enforce_terms!, if: :should_enforce_terms? before_action :enforce_terms!, if: :should_enforce_terms?
before_action :validate_user_service_ticket! before_action :validate_user_service_ticket!
...@@ -153,6 +153,7 @@ class ApplicationController < ActionController::Base ...@@ -153,6 +153,7 @@ class ApplicationController < ActionController::Base
end end
end end
<<<<<<< HEAD
# This filter handles personal access tokens, and atom requests with rss tokens # This filter handles personal access tokens, and atom requests with rss tokens
def authenticate_sessionless_user! def authenticate_sessionless_user!
user = Gitlab::Auth::RequestAuthenticator.new(request).find_sessionless_user user = Gitlab::Auth::RequestAuthenticator.new(request).find_sessionless_user
...@@ -164,6 +165,8 @@ class ApplicationController < ActionController::Base ...@@ -164,6 +165,8 @@ class ApplicationController < ActionController::Base
render_404 unless Gitlab::CurrentSettings.should_check_namespace_plan? render_404 unless Gitlab::CurrentSettings.should_check_namespace_plan?
end end
=======
>>>>>>> upstream/master
def log_exception(exception) def log_exception(exception)
Raven.capture_exception(exception) if sentry_enabled? Raven.capture_exception(exception) if sentry_enabled?
...@@ -434,25 +437,11 @@ class ApplicationController < ActionController::Base ...@@ -434,25 +437,11 @@ class ApplicationController < ActionController::Base
Gitlab::I18n.with_user_locale(current_user, &block) Gitlab::I18n.with_user_locale(current_user, &block)
end end
def sessionless_sign_in(user)
if user && can?(user, :log_in)
# Notice we are passing store false, so the user is not
# actually stored in the session and a token is needed
# for every request. If you want the token to work as a
# sign in token, you can simply remove store: false.
sign_in(user, store: false, message: :sessionless_sign_in)
end
end
def set_page_title_header def set_page_title_header
# Per https://tools.ietf.org/html/rfc5987, headers need to be ISO-8859-1, not UTF-8 # Per https://tools.ietf.org/html/rfc5987, headers need to be ISO-8859-1, not UTF-8
response.headers['Page-Title'] = URI.escape(page_title('GitLab')) response.headers['Page-Title'] = URI.escape(page_title('GitLab'))
end end
def sessionless_user?
current_user && !session.keys.include?('warden.user.user.key')
end
def peek_request? def peek_request?
request.path.start_with?('/-/peek') request.path.start_with?('/-/peek')
end end
......
...@@ -6,6 +6,7 @@ module NotesActions ...@@ -6,6 +6,7 @@ module NotesActions
extend ActiveSupport::Concern extend ActiveSupport::Concern
included do included do
prepend_before_action :normalize_create_params, only: [:create]
before_action :set_polling_interval_header, only: [:index] before_action :set_polling_interval_header, only: [:index]
before_action :require_noteable!, only: [:index, :create] before_action :require_noteable!, only: [:index, :create]
before_action :authorize_admin_note!, only: [:update, :destroy] before_action :authorize_admin_note!, only: [:update, :destroy]
...@@ -247,6 +248,15 @@ module NotesActions ...@@ -247,6 +248,15 @@ module NotesActions
DiscussionSerializer.new(project: project, noteable: noteable, current_user: current_user, note_entity: ProjectNoteEntity) DiscussionSerializer.new(project: project, noteable: noteable, current_user: current_user, note_entity: ProjectNoteEntity)
end end
# Avoids checking permissions in the wrong object - this ensures that the object we checked permissions for
# is the object we're actually creating a note in.
def normalize_create_params
params[:note].try do |note|
note[:noteable_id] = params[:target_id]
note[:noteable_type] = params[:target_type].classify
end
end
def note_project def note_project
strong_memoize(:note_project) do strong_memoize(:note_project) do
next nil unless project next nil unless project
......
# frozen_string_literal: true
# == SessionlessAuthentication
#
# Controller concern to handle PAT and RSS token authentication methods
#
module SessionlessAuthentication
# This filter handles personal access tokens, and atom requests with rss tokens
def authenticate_sessionless_user!(request_format)
user = Gitlab::Auth::RequestAuthenticator.new(request).find_sessionless_user(request_format)
sessionless_sign_in(user) if user
end
def sessionless_user?
current_user && !session.keys.include?('warden.user.user.key')
end
def sessionless_sign_in(user)
if user && can?(user, :log_in)
# Notice we are passing store false, so the user is not
# actually stored in the session and a token is needed
# for every request. If you want the token to work as a
# sign in token, you can simply remove store: false.
sign_in(user, store: false, message: :sessionless_sign_in)
end
end
end
...@@ -4,6 +4,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController ...@@ -4,6 +4,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
include ParamsBackwardCompatibility include ParamsBackwardCompatibility
include RendersMemberAccess include RendersMemberAccess
prepend_before_action(only: [:index]) { authenticate_sessionless_user!(:rss) }
before_action :set_non_archived_param before_action :set_non_archived_param
before_action :default_sorting before_action :default_sorting
skip_cross_project_access_check :index, :starred skip_cross_project_access_check :index, :starred
......
...@@ -4,6 +4,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController ...@@ -4,6 +4,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
include ActionView::Helpers::NumberHelper include ActionView::Helpers::NumberHelper
before_action :authorize_read_project!, only: :index before_action :authorize_read_project!, only: :index
before_action :authorize_read_group!, only: :index
before_action :find_todos, only: [:index, :destroy_all] before_action :find_todos, only: [:index, :destroy_all]
def index def index
...@@ -60,6 +61,15 @@ class Dashboard::TodosController < Dashboard::ApplicationController ...@@ -60,6 +61,15 @@ class Dashboard::TodosController < Dashboard::ApplicationController
end end
end end
def authorize_read_group!
group_id = params[:group_id]
if group_id.present?
group = Group.find(group_id)
render_404 unless can?(current_user, :read_group, group)
end
end
def find_todos def find_todos
@todos ||= TodosFinder.new(current_user, todo_params).execute @todos ||= TodosFinder.new(current_user, todo_params).execute
end end
......
...@@ -4,6 +4,9 @@ class DashboardController < Dashboard::ApplicationController ...@@ -4,6 +4,9 @@ class DashboardController < Dashboard::ApplicationController
include IssuesAction include IssuesAction
include MergeRequestsAction include MergeRequestsAction
prepend_before_action(only: [:issues]) { authenticate_sessionless_user!(:rss) }
prepend_before_action(only: [:issues_calendar]) { authenticate_sessionless_user!(:ics) }
before_action :event_filter, only: :activity before_action :event_filter, only: :activity
before_action :projects, only: [:issues, :merge_requests] before_action :projects, only: [:issues, :merge_requests]
before_action :set_show_full_reference, only: [:issues, :merge_requests] before_action :set_show_full_reference, only: [:issues, :merge_requests]
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
class GraphqlController < ApplicationController class GraphqlController < ApplicationController
# Unauthenticated users have access to the API for public data # Unauthenticated users have access to the API for public data
skip_before_action :authenticate_user! skip_before_action :authenticate_user!
prepend_before_action(only: [:execute]) { authenticate_sessionless_user!(:api) }
before_action :check_graphql_feature_flag! before_action :check_graphql_feature_flag!
......
...@@ -8,6 +8,9 @@ class GroupsController < Groups::ApplicationController ...@@ -8,6 +8,9 @@ class GroupsController < Groups::ApplicationController
include PreviewMarkdown include PreviewMarkdown
respond_to :html respond_to :html
prepend_before_action(only: [:show, :issues]) { authenticate_sessionless_user!(:rss) }
prepend_before_action(only: [:issues_calendar]) { authenticate_sessionless_user!(:ics) }
before_action :authenticate_user!, only: [:new, :create] before_action :authenticate_user!, only: [:new, :create]
before_action :group, except: [:index, :new, :create] before_action :group, except: [:index, :new, :create]
......
...@@ -9,7 +9,7 @@ class Oauth::ApplicationsController < Doorkeeper::ApplicationsController ...@@ -9,7 +9,7 @@ class Oauth::ApplicationsController < Doorkeeper::ApplicationsController
before_action :verify_user_oauth_applications_enabled, except: :index before_action :verify_user_oauth_applications_enabled, except: :index
before_action :authenticate_user! before_action :authenticate_user!
before_action :add_gon_variables before_action :add_gon_variables
before_action :load_scopes, only: [:index, :create, :edit] before_action :load_scopes, only: [:index, :create, :edit, :update]
helper_method :can? helper_method :can?
......
...@@ -6,6 +6,7 @@ class Projects::CommitsController < Projects::ApplicationController ...@@ -6,6 +6,7 @@ class Projects::CommitsController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
include RendersCommits include RendersCommits
prepend_before_action(only: [:show]) { authenticate_sessionless_user!(:rss) }
before_action :whitelist_query_limiting, except: :commits_root before_action :whitelist_query_limiting, except: :commits_root
before_action :require_non_empty_project before_action :require_non_empty_project
before_action :assign_ref_vars, except: :commits_root before_action :assign_ref_vars, except: :commits_root
......
...@@ -9,12 +9,15 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -9,12 +9,15 @@ class Projects::IssuesController < Projects::ApplicationController
include IssuesCalendar include IssuesCalendar
include SpammableActions include SpammableActions
<<<<<<< HEAD
prepend ::EE::Projects::IssuesController prepend ::EE::Projects::IssuesController
def self.authenticate_user_only_actions def self.authenticate_user_only_actions
%i[new] %i[new]
end end
=======
>>>>>>> upstream/master
def self.issue_except_actions def self.issue_except_actions
%i[index calendar new create bulk_update] %i[index calendar new create bulk_update]
end end
...@@ -23,7 +26,10 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -23,7 +26,10 @@ class Projects::IssuesController < Projects::ApplicationController
%i[index calendar] %i[index calendar]
end end
prepend_before_action :authenticate_user!, only: authenticate_user_only_actions 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 :store_uri, only: [:new, :show]
before_action :whitelist_query_limiting, only: [:create, :create_merge_request, :move, :bulk_update] before_action :whitelist_query_limiting, only: [:create, :create_merge_request, :move, :bulk_update]
before_action :check_issues_available! before_action :check_issues_available!
...@@ -234,16 +240,18 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -234,16 +240,18 @@ class Projects::IssuesController < Projects::ApplicationController
] + [{ label_ids: [], assignee_ids: [] }] ] + [{ label_ids: [], assignee_ids: [] }]
end end
def authenticate_user! def authenticate_new_issue!
return if current_user return if current_user
notice = "Please sign in to create the new issue." 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? if request.get? && !request.xhr?
store_location_for :user, request.fullpath store_location_for :user, request.fullpath
end end
redirect_to new_user_session_path, notice: notice
end end
def serializer def serializer
......
...@@ -11,7 +11,10 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -11,7 +11,10 @@ class Projects::MilestonesController < Projects::ApplicationController
before_action :authorize_read_milestone! before_action :authorize_read_milestone!
# Allow admin milestone # Allow admin milestone
before_action :authorize_admin_milestone!, except: [:index, :show, :merge_requests, :participants, :labels, :promote] before_action :authorize_admin_milestone!, except: [:index, :show, :merge_requests, :participants, :labels]
# Allow to promote milestone
before_action :authorize_promote_milestone!, only: :promote
respond_to :html respond_to :html
...@@ -78,7 +81,7 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -78,7 +81,7 @@ class Projects::MilestonesController < Projects::ApplicationController
def promote def promote
promoted_milestone = Milestones::PromoteService.new(project, current_user).execute(milestone) promoted_milestone = Milestones::PromoteService.new(project, current_user).execute(milestone)
flash[:notice] = flash_notice_for(promoted_milestone, project.group) flash[:notice] = flash_notice_for(promoted_milestone, project_group)
respond_to do |format| respond_to do |format|
format.html do format.html do
...@@ -109,6 +112,12 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -109,6 +112,12 @@ class Projects::MilestonesController < Projects::ApplicationController
protected protected
def project_group
strong_memoize(:project_group) do
project.group
end
end
def milestones def milestones
strong_memoize(:milestones) do strong_memoize(:milestones) do
MilestonesFinder.new(search_params).execute MilestonesFinder.new(search_params).execute
...@@ -125,13 +134,17 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -125,13 +134,17 @@ class Projects::MilestonesController < Projects::ApplicationController
return render_404 unless can?(current_user, :admin_milestone, @project) return render_404 unless can?(current_user, :admin_milestone, @project)
end end
def authorize_promote_milestone!
return render_404 unless can?(current_user, :admin_milestone, project_group)
end
def milestone_params def milestone_params
params.require(:milestone).permit(:title, :description, :start_date, :due_date, :state_event) params.require(:milestone).permit(:title, :description, :start_date, :due_date, :state_event)
end end
def search_params def search_params
if request.format.json? && @project.group && can?(current_user, :read_group, @project.group) if request.format.json? && project_group && can?(current_user, :read_group, project_group)
groups = @project.group.self_and_ancestors_ids groups = project_group.self_and_ancestors_ids
end end
params.permit(:state).merge(project_ids: @project.id, group_ids: groups) params.permit(:state).merge(project_ids: @project.id, group_ids: groups)
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
class Projects::TagsController < Projects::ApplicationController class Projects::TagsController < Projects::ApplicationController
include SortingHelper include SortingHelper
prepend_before_action(only: [:index]) { authenticate_sessionless_user!(:rss) }
# Authorize # Authorize
before_action :require_non_empty_project before_action :require_non_empty_project
before_action :authorize_download_code! before_action :authorize_download_code!
......
...@@ -6,6 +6,12 @@ class ProjectsController < Projects::ApplicationController ...@@ -6,6 +6,12 @@ class ProjectsController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
include PreviewMarkdown include PreviewMarkdown
include SendFileUpload include SendFileUpload
<<<<<<< HEAD
=======
prepend_before_action(only: [:show]) { authenticate_sessionless_user!(:rss) }
>>>>>>> upstream/master
before_action :whitelist_query_limiting, only: [:create] before_action :whitelist_query_limiting, only: [:create]
before_action :authenticate_user!, except: [:index, :show, :activity, :refs] before_action :authenticate_user!, except: [:index, :show, :activity, :refs]
before_action :redirect_git_extension, only: [:show] before_action :redirect_git_extension, only: [:show]
......
...@@ -13,6 +13,7 @@ class UsersController < ApplicationController ...@@ -13,6 +13,7 @@ class UsersController < ApplicationController
calendar_activities: true calendar_activities: true
skip_before_action :authenticate_user! skip_before_action :authenticate_user!
prepend_before_action(only: [:show]) { authenticate_sessionless_user!(:rss) }
before_action :user, except: [:exists] before_action :user, except: [:exists]
before_action :authorize_read_user_profile!, before_action :authorize_read_user_profile!,
only: [:calendar, :calendar_activities, :groups, :projects, :contributed_projects, :snippets] only: [:calendar, :calendar_activities, :groups, :projects, :contributed_projects, :snippets]
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
module MilestonesHelper module MilestonesHelper
include EntityDateHelper include EntityDateHelper
include Gitlab::Utils::StrongMemoize
def milestones_filter_path(opts = {}) def milestones_filter_path(opts = {})
if @project if @project
...@@ -243,6 +244,18 @@ module MilestonesHelper ...@@ -243,6 +244,18 @@ module MilestonesHelper
dashboard_milestone_path(milestone.safe_title, title: milestone.title) dashboard_milestone_path(milestone.safe_title, title: milestone.title)
end end
end end
def can_admin_project_milestones?
strong_memoize(:can_admin_project_milestones) do
can?(current_user, :admin_milestone, @project)
end
end
def can_admin_group_milestones?
strong_memoize(:can_admin_group_milestones) do
can?(current_user, :admin_milestone, @project.group)
end
end
end end
MilestonesHelper.prepend(EE::MilestonesHelper) MilestonesHelper.prepend(EE::MilestonesHelper)
...@@ -28,7 +28,7 @@ module Emails ...@@ -28,7 +28,7 @@ module Emails
mail_answer_note_thread(@merge_request, @note, note_thread_options(recipient_id)) mail_answer_note_thread(@merge_request, @note, note_thread_options(recipient_id))
end end
def note_snippet_email(recipient_id, note_id) def note_project_snippet_email(recipient_id, note_id)
setup_note_mail(note_id, recipient_id) setup_note_mail(note_id, recipient_id)
@snippet = @note.noteable @snippet = @note.noteable
......
...@@ -15,7 +15,7 @@ module CacheMarkdownField ...@@ -15,7 +15,7 @@ module CacheMarkdownField
# Increment this number every time the renderer changes its output # Increment this number every time the renderer changes its output
CACHE_REDCARPET_VERSION = 3 CACHE_REDCARPET_VERSION = 3
CACHE_COMMONMARK_VERSION_START = 10 CACHE_COMMONMARK_VERSION_START = 10
CACHE_COMMONMARK_VERSION = 11 CACHE_COMMONMARK_VERSION = 12
# changes to these attributes cause the cache to be invalidates # changes to these attributes cause the cache to be invalidates
INVALIDATED_BY = %w[author project].freeze INVALIDATED_BY = %w[author project].freeze
......
...@@ -324,7 +324,7 @@ class Note < ActiveRecord::Base ...@@ -324,7 +324,7 @@ class Note < ActiveRecord::Base
end end
def to_ability_name def to_ability_name
for_personal_snippet? ? 'personal_snippet' : noteable_type.underscore for_snippet? ? noteable.class.name.underscore : noteable_type.underscore
end end
def can_be_discussion_note? def can_be_discussion_note?
......
...@@ -71,7 +71,7 @@ class PrometheusService < MonitoringService ...@@ -71,7 +71,7 @@ class PrometheusService < MonitoringService
end end
def prometheus_client def prometheus_client
RestClient::Resource.new(api_url) if api_url && manual_configuration? && active? RestClient::Resource.new(api_url, max_redirects: 0) if api_url && manual_configuration? && active?
end end
def prometheus_available? def prometheus_available?
......
...@@ -2,4 +2,6 @@ ...@@ -2,4 +2,6 @@
class CommitPolicy < BasePolicy class CommitPolicy < BasePolicy
delegate { @subject.project } delegate { @subject.project }
rule { can?(:download_code) }.enable :read_commit
end end
...@@ -9,8 +9,17 @@ class NotePolicy < BasePolicy ...@@ -9,8 +9,17 @@ class NotePolicy < BasePolicy
condition(:editable, scope: :subject) { @subject.editable? } condition(:editable, scope: :subject) { @subject.editable? }
condition(:can_read_noteable) { can?(:"read_#{@subject.to_ability_name}") }
rule { ~editable }.prevent :admin_note rule { ~editable }.prevent :admin_note
# If user can't read the issue/MR/etc then they should not be allowed to do anything to their own notes
rule { ~can_read_noteable }.policy do
prevent :read_note
prevent :admin_note
prevent :resolve_note
end
rule { is_author }.policy do rule { is_author }.policy do
enable :read_note enable :read_note
enable :admin_note enable :admin_note
......
= email_default_heading("Hello, #{@resource.name}!")
- if @resource.try(:unconfirmed_email?)
%p
We're contacting you to notify you that your email is being changed to #{@resource.reload.unconfirmed_email}.
- else
%p
We're contacting you to notify you that your email has been changed to #{@resource.email}.
%p
If you did not initiate this change, please contact your administrator
immediately.
Hello, <%= @resource.name %>!
<% if @resource.try(:unconfirmed_email?) %>
We're contacting you to notify you that your email is being changed to <%= @resource.reload.unconfirmed_email %>.
<% else %>
We're contacting you to notify you that your email has been changed to <%= @resource.email %>.
<% end %>
If you did not initiate this change, please contact your administrator
immediately.
- page_title 'Edit', @label.name, 'Labels' - add_to_breadcrumbs _("Labels"), group_labels_path(@group)
- breadcrumb_title _("Edit")
- page_title "Edit", @label.name, _("Labels")
%h3.page-title %h3.page-title
Edit Label Edit Label
......
- breadcrumb_title "Labels" - add_to_breadcrumbs _("Labels"), group_labels_path(@group)
- page_title 'New Label' - breadcrumb_title _("New")
- page_title _("New Label")
%h3.page-title %h3.page-title
New Label New Label
......
- page_title "Milestones" - breadcrumb_title _("Edit")
- page_title _("Milestones")
- render "header_title" - render "header_title"
%h3.page-title %h3.page-title
Edit Milestone Edit Milestone
%hr
= render "form" = render "form"
- breadcrumb_title "Milestones" - @no_container = true
- page_title "Milestones" - add_to_breadcrumbs _("Milestones"), group_milestones_path(@group)
- breadcrumb_title _("New")
- page_title _("Milestones"), @milestone.name, _("Milestones")
%h3.page-title %div{ class: container_class }
%h3.page-title
New Milestone New Milestone
= render "form" %hr
= render "form"
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
- ref = local_assigns.fetch(:ref) { merge_request&.source_branch } - ref = local_assigns.fetch(:ref) { merge_request&.source_branch }
- link = commit_path(project, commit, merge_request: merge_request) - link = commit_path(project, commit, merge_request: merge_request)
<<<<<<< HEAD
- cache_key = [project.full_path, - cache_key = [project.full_path,
ref, ref,
commit.id, commit.id,
...@@ -53,6 +54,34 @@ ...@@ -53,6 +54,34 @@
- if show_project_name - if show_project_name
%span.project_namespace %span.project_namespace
= project.full_name = project.full_name
=======
%li.commit.flex-row.js-toggle-container{ id: "commit-#{commit.short_id}" }
.avatar-cell.d-none.d-sm-block
= author_avatar(commit, size: 36, has_tooltip: false)
.commit-detail.flex-list
.commit-content.qa-commit-content
- if view_details && merge_request
= link_to commit.title, project_commit_path(project, commit.id, merge_request_iid: merge_request.iid), class: "commit-row-message item-title"
- else
= link_to_markdown_field(commit, :title, link, class: "commit-row-message item-title")
%span.commit-row-message.d-block.d-sm-none
&middot;
= commit.short_id
- if commit.status(ref)
.d-block.d-sm-none
= render_commit_status(commit, ref: ref)
- if commit.description?
%button.text-expander.js-toggle-button
= sprite_icon('ellipsis_h', size: 12)
.committer
- commit_author_link = commit_author_link(commit, avatar: false, size: 24)
- commit_timeago = time_ago_with_tooltip(commit.authored_date, placement: 'bottom')
- commit_text = _('%{commit_author_link} authored %{commit_timeago}') % { commit_author_link: commit_author_link, commit_timeago: commit_timeago }
#{ commit_text.html_safe }
>>>>>>> upstream/master
- if commit.description? - if commit.description?
%pre.commit-row-description.js-toggle-content.append-bottom-8 %pre.commit-row-description.js-toggle-content.append-bottom-8
......
- @no_container = true - @no_container = true
- add_to_breadcrumbs "Labels", project_labels_path(@project)
- breadcrumb_title "Edit"
- page_title "Edit", @label.name, "Labels" - page_title "Edit", @label.name, "Labels"
%div{ class: container_class } %div{ class: container_class }
......
- @no_container = true - @no_container = true
- breadcrumb_title "Labels" - add_to_breadcrumbs "Labels", project_labels_path(@project)
- breadcrumb_title "New"
- page_title "New Label" - page_title "New Label"
%div{ class: container_class } %div{ class: container_class }
......
- @no_container = true - @no_container = true
- breadcrumb_title "Edit"
- add_to_breadcrumbs "Milestones", project_milestones_path(@project)
- page_title "Edit", @milestone.title, "Milestones" - page_title "Edit", @milestone.title, "Milestones"
%div{ class: container_class } %div{ class: container_class }
%h3.page-title %h3.page-title
......
- @no_container = true - @no_container = true
- breadcrumb_title "Milestones" - add_to_breadcrumbs "Milestones", project_milestones_path(@project)
- breadcrumb_title "New"
- page_title "New Milestone" - page_title "New Milestone"
%div{ class: container_class } %div{ class: container_class }
......
- @content_class = "limit-container-width" unless fluid_layout - @content_class = "limit-container-width" unless fluid_layout
- page_title _("Edit"), @page.title.capitalize, _("Wiki") - add_to_breadcrumbs _("Wiki"), project_wiki_path(@project, @page)
- breadcrumb_title @page.persisted? ? _("Edit") : _("New")
- page_title @page.persisted? ? _("Edit") : _("New"), @page.title.capitalize, _("Wiki")
= wiki_page_errors(@error) = wiki_page_errors(@error)
......
...@@ -35,8 +35,8 @@ ...@@ -35,8 +35,8 @@
.col-sm-2 .col-sm-2
.milestone-actions.d-flex.justify-content-sm-start.justify-content-md-end .milestone-actions.d-flex.justify-content-sm-start.justify-content-md-end
- if @project - if @project
- if can?(current_user, :admin_milestone, milestone.project) and milestone.active? - if can_admin_project_milestones? and milestone.active?
- if @project.group - if can_admin_group_milestones?
%button.js-promote-project-milestone-button.btn.btn-blank.btn-sm.btn-grouped.has-tooltip{ title: _('Promote to Group Milestone'), %button.js-promote-project-milestone-button.btn.btn-blank.btn-sm.btn-grouped.has-tooltip{ title: _('Promote to Group Milestone'),
disabled: true, disabled: true,
type: 'button', type: 'button',
......
...@@ -3,31 +3,31 @@ ...@@ -3,31 +3,31 @@
.d-none.d-sm-block .d-none.d-sm-block
- if can?(current_user, :update_personal_snippet, @snippet) - if can?(current_user, :update_personal_snippet, @snippet)
= link_to edit_snippet_path(@snippet), class: "btn btn-grouped" do = link_to edit_snippet_path(@snippet), class: "btn btn-grouped" do
Edit = _("Edit")
- if can?(current_user, :admin_personal_snippet, @snippet) - if can?(current_user, :admin_personal_snippet, @snippet)
= link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-inverted btn-remove", title: 'Delete Snippet' do = link_to snippet_path(@snippet), method: :delete, data: { confirm: _("Are you sure?") }, class: "btn btn-grouped btn-inverted btn-remove", title: _('Delete Snippet') do
Delete = _("Delete")
= link_to new_snippet_path, class: "btn btn-grouped btn-inverted btn-success", title: "New snippet" do = link_to new_snippet_path, class: "btn btn-grouped btn-inverted btn-create", title: _("New snippet") do
New snippet = _("New snippet")
- if @snippet.submittable_as_spam_by?(current_user) - if @snippet.submittable_as_spam_by?(current_user)
= link_to 'Submit as spam', mark_as_spam_snippet_path(@snippet), method: :post, class: 'btn btn-grouped btn-spam', title: 'Submit as spam' = link_to _('Submit as spam'), mark_as_spam_snippet_path(@snippet), method: :post, class: 'btn btn-grouped btn-spam', title: _('Submit as spam')
.d-block.d-sm-none.dropdown .d-block.d-sm-none.dropdown
%button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } } %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
Options = _("Options")
= icon('caret-down') = icon('caret-down')
.dropdown-menu.dropdown-menu-full-width .dropdown-menu.dropdown-menu-full-width
%ul %ul
%li %li
= link_to new_snippet_path, title: "New snippet" do = link_to new_snippet_path, title: _("New snippet") do
New snippet = _("New snippet")
- if can?(current_user, :admin_personal_snippet, @snippet) - if can?(current_user, :admin_personal_snippet, @snippet)
%li %li
= link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do = link_to snippet_path(@snippet), method: :delete, data: { confirm: _("Are you sure?") }, title: _('Delete Snippet') do
Delete = _("Delete")
- if can?(current_user, :update_personal_snippet, @snippet) - if can?(current_user, :update_personal_snippet, @snippet)
%li %li
= link_to edit_snippet_path(@snippet) do = link_to edit_snippet_path(@snippet) do
Edit = _("Edit")
- if @snippet.submittable_as_spam_by?(current_user) - if @snippet.submittable_as_spam_by?(current_user)
%li %li
= link_to 'Submit as spam', mark_as_spam_snippet_path(@snippet), method: :post = link_to _('Submit as spam'), mark_as_spam_snippet_path(@snippet), method: :post
...@@ -5,6 +5,6 @@ ...@@ -5,6 +5,6 @@
= render partial: 'shared/snippets/snippet', collection: @snippets, locals: { link_project: link_project } = render partial: 'shared/snippets/snippet', collection: @snippets, locals: { link_project: link_project }
- if @snippets.empty? - if @snippets.empty?
%li %li
.nothing-here-block Nothing here. .nothing-here-block= _("Nothing here.")
= paginate @snippets, theme: 'gitlab' = paginate @snippets, theme: 'gitlab'
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
.nav-links.snippet-scope-menu.mobile-separator.nav.nav-tabs .nav-links.snippet-scope-menu.mobile-separator.nav.nav-tabs
%li{ class: active_when(params[:scope].nil?) } %li{ class: active_when(params[:scope].nil?) }
= link_to subject_snippets_path(subject) do = link_to subject_snippets_path(subject) do
All = _("All")
%span.badge.badge-pill %span.badge.badge-pill
- if include_private - if include_private
= subject.snippets.count = subject.snippets.count
...@@ -14,18 +14,18 @@ ...@@ -14,18 +14,18 @@
- if include_private - if include_private
%li{ class: active_when(params[:scope] == "are_private") } %li{ class: active_when(params[:scope] == "are_private") }
= link_to subject_snippets_path(subject, scope: 'are_private') do = link_to subject_snippets_path(subject, scope: 'are_private') do
Private = _("Private")
%span.badge.badge-pill %span.badge.badge-pill
= subject.snippets.are_private.count = subject.snippets.are_private.count
%li{ class: active_when(params[:scope] == "are_internal") } %li{ class: active_when(params[:scope] == "are_internal") }
= link_to subject_snippets_path(subject, scope: 'are_internal') do = link_to subject_snippets_path(subject, scope: 'are_internal') do
Internal = _("Internal")
%span.badge.badge-pill %span.badge.badge-pill
= subject.snippets.are_internal.count = subject.snippets.are_internal.count
%li{ class: active_when(params[:scope] == "are_public") } %li{ class: active_when(params[:scope] == "are_public") }
= link_to subject_snippets_path(subject, scope: 'are_public') do = link_to subject_snippets_path(subject, scope: 'are_public') do
Public = _("Public")
%span.badge.badge-pill %span.badge.badge-pill
= subject.snippets.are_public.count = subject.snippets.are_public.count
- page_title "Edit", "#{@snippet.title} (#{@snippet.to_reference})", "Snippets" - page_title _("Edit"), "#{@snippet.title} (#{@snippet.to_reference})", _("Snippets")
%h3.page-title %h3.page-title
Edit Snippet = _("Edit Snippet")
%hr %hr
= render 'shared/snippets/form', url: snippet_path(@snippet) = render 'shared/snippets/form', url: snippet_path(@snippet)
- page_title "By #{@user.name}", "Snippets" - page_title _("By %{user_name}") % { user_name: @user.name }, _("Snippets")
%ol.breadcrumb %ol.breadcrumb
%li.breadcrumb-item %li.breadcrumb-item
= link_to snippets_path do = link_to snippets_path do
Snippets = _("Snippets")
%li.breadcrumb-item %li.breadcrumb-item
= @user.name = @user.name
.float-right.d-none.d-sm-block .float-right.d-none.d-sm-block
= link_to user_path(@user) do = link_to user_path(@user) do
#{@user.name} profile page = _("%{user_name} profile page") % { user_name: @user.name }
= render 'snippets' = render 'snippets'
- @hide_top_links = true - @hide_top_links = true
- @hide_breadcrumbs = true - @hide_breadcrumbs = true
- page_title "New Snippet" - page_title _("New Snippet")
.page-title-holder .page-title-holder
%h1.page-title= _('New Snippet') %h1.page-title= _('New Snippet')
......
- if current_user - if current_user
- if note.emoji_awardable? - if note.emoji_awardable?
.note-actions-item .note-actions-item
= link_to '#', title: 'Add reaction', class: "note-action-button note-emoji-button js-add-award js-note-emoji has-tooltip", data: { position: 'right' } do = link_to '#', title: _('Add reaction'), class: "note-action-button note-emoji-button js-add-award js-note-emoji has-tooltip", data: { position: 'right' } do
= icon('spinner spin') = icon('spinner spin')
%span{ class: 'link-highlight award-control-icon-neutral' }= custom_icon('emoji_slightly_smiling_face') %span{ class: 'link-highlight award-control-icon-neutral' }= custom_icon('emoji_slightly_smiling_face')
%span{ class: 'link-highlight award-control-icon-positive' }= custom_icon('emoji_smiley') %span{ class: 'link-highlight award-control-icon-positive' }= custom_icon('emoji_smiley')
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
- if note_editable - if note_editable
.note-actions-item .note-actions-item
= button_tag title: 'Edit comment', class: 'note-action-button js-note-edit has-tooltip btn btn-transparent', data: { container: 'body' } do = button_tag title: _('Edit comment'), class: 'note-action-button js-note-edit has-tooltip btn btn-transparent', data: { container: 'body' } do
%span.link-highlight %span.link-highlight
= custom_icon('icon_pencil') = custom_icon('icon_pencil')
......
- @hide_top_links = true - @hide_top_links = true
- @content_class = "limit-container-width limited-inner-width-container" unless fluid_layout - @content_class = "limit-container-width limited-inner-width-container" unless fluid_layout
- add_to_breadcrumbs "Snippets", dashboard_snippets_path - add_to_breadcrumbs _("Snippets"), dashboard_snippets_path
- breadcrumb_title @snippet.to_reference - breadcrumb_title @snippet.to_reference
- page_title "#{@snippet.title} (#{@snippet.to_reference})", "Snippets" - page_title "#{@snippet.title} (#{@snippet.to_reference})", _("Snippets")
= render 'shared/snippets/header' = render 'shared/snippets/header'
......
---
title: Externalize strings from `/app/views/snippets`
merge_request: 23351
author: Tao Wang
type: other
---
title: Include new link in breadcrumb for issues, merge requests, milestones, and labels
merge_request: 18515
author: George Tsiolis
type: changed
---
title: Redact sensitive information on gitlab-workhorse log
merge_request:
author:
type: security
---
title: Do not follow redirects in Prometheus service when making http requests to the configured api url
merge_request:
author:
type: security
---
title: Don't expose confidential information in commit message list
merge_request:
author:
type: security
---
title: Provide email notification when a user changes their email address
merge_request:
author:
type: security
---
title: Restrict Personal Access Tokens to API scope on web requests
merge_request:
author:
type: security
---
title: Resolve reflected XSS in Ouath authorize window
merge_request:
author:
type: security
---
title: Fix SSRF in project integrations
merge_request:
author:
type: security
---
title: Fix CRLF vulnerability in Project hooks
merge_request:
author:
type: security
---
title: Fixed ability to comment on locked/confidential issues.
merge_request:
author:
type: security
---
title: Fixed ability of guest users to edit/delete comments on locked or confidential issues.
merge_request:
author:
type: security
---
title: Fix milestone promotion authorization check
merge_request:
author:
type: security
---
title: Configure mermaid to not render HTML content in diagrams
merge_request:
author:
type: security
---
title: Fix a possible symlink time of check to time of use race condition in GitLab
Pages
merge_request:
author:
type: security
---
title: Removed ability to see private group names when the group id is entered in
the url.
merge_request:
author:
type: security
---
title: Fix stored XSS for Environments
merge_request:
author:
type: security
---
title: Fix possible XSS attack in Markdown urls with spaces
merge_request: 2599
author:
type: security
---
title: Add monkey patch to unicorn to fix eof? problem
merge_request: 23385
author:
type: fixed
...@@ -13,6 +13,10 @@ if defined?(Unicorn) ...@@ -13,6 +13,10 @@ if defined?(Unicorn)
# Max memory size (RSS) per worker # Max memory size (RSS) per worker
use Unicorn::WorkerKiller::Oom, min, max use Unicorn::WorkerKiller::Oom, min, max
end end
# Monkey patch for fixing Rack 2.0.6 bug:
# https://gitlab.com/gitlab-org/gitlab-ee/issues/8539
Unicorn::StreamInput.send(:public, :eof?) # rubocop:disable GitlabSecurity/PublicSend
end end
require ::File.expand_path('../config/environment', __FILE__) require ::File.expand_path('../config/environment', __FILE__)
......
...@@ -117,6 +117,9 @@ module Gitlab ...@@ -117,6 +117,9 @@ module Gitlab
# - Webhook URLs (:hook) # - Webhook URLs (:hook)
# - Sentry DSN (:sentry_dsn) # - Sentry DSN (:sentry_dsn)
# - File content from Web Editor (:content) # - File content from Web Editor (:content)
#
# NOTE: It is **IMPORTANT** to also update gitlab-workhorse's filter when adding parameters here to not
# introduce another security vulnerability: https://gitlab.com/gitlab-org/gitlab-workhorse/issues/182
config.filter_parameters += [/token$/, /password/, /secret/, /key$/] config.filter_parameters += [/token$/, /password/, /secret/, /key$/]
config.filter_parameters += %i( config.filter_parameters += %i(
certificate certificate
......
...@@ -103,6 +103,9 @@ Devise.setup do |config| ...@@ -103,6 +103,9 @@ Devise.setup do |config|
# Send a notification email when the user's password is changed # Send a notification email when the user's password is changed
config.send_password_change_notification = true config.send_password_change_notification = true
# Send a notification email when the user's email is changed
config.send_email_changed_notification = true
# ==> Configuration for :validatable # ==> Configuration for :validatable
# Range for password length. Default is 6..128. # Range for password length. Default is 6..128.
config.password_length = 8..128 config.password_length = 8..128
......
...@@ -48,6 +48,13 @@ Doorkeeper.configure do ...@@ -48,6 +48,13 @@ Doorkeeper.configure do
# #
force_ssl_in_redirect_uri false force_ssl_in_redirect_uri false
# Specify what redirect URI's you want to block during Application creation.
# Any redirect URI is whitelisted by default.
#
# You can use this option in order to forbid URI's with 'javascript' scheme
# for example.
forbid_redirect_uri { |uri| %w[data vbscript javascript].include?(uri.scheme.to_s.downcase) }
# Provide support for an owner to be assigned to each registered application (disabled by default) # Provide support for an owner to be assigned to each registered application (disabled by default)
# Optional parameter confirmation: true (default false) if you want to enforce ownership of # Optional parameter confirmation: true (default false) if you want to enforce ownership of
# a registered application # a registered application
......
...@@ -33,24 +33,24 @@ class Rack::Attack ...@@ -33,24 +33,24 @@ class Rack::Attack
throttle('throttle_authenticated_api', Gitlab::Throttle.authenticated_api_options) do |req| throttle('throttle_authenticated_api', Gitlab::Throttle.authenticated_api_options) do |req|
Gitlab::Throttle.settings.throttle_authenticated_api_enabled && Gitlab::Throttle.settings.throttle_authenticated_api_enabled &&
req.api_request? && req.api_request? &&
req.authenticated_user_id req.authenticated_user_id([:api])
end end
throttle('throttle_authenticated_web', Gitlab::Throttle.authenticated_web_options) do |req| throttle('throttle_authenticated_web', Gitlab::Throttle.authenticated_web_options) do |req|
Gitlab::Throttle.settings.throttle_authenticated_web_enabled && Gitlab::Throttle.settings.throttle_authenticated_web_enabled &&
req.web_request? && req.web_request? &&
req.authenticated_user_id req.authenticated_user_id([:api, :rss, :ics])
end end
class Request class Request
prepend ::EE::Gitlab::Rack::Attack::Request prepend ::EE::Gitlab::Rack::Attack::Request
def unauthenticated? def unauthenticated?
!authenticated_user_id !authenticated_user_id([:api, :rss, :ics])
end end
def authenticated_user_id def authenticated_user_id(request_formats)
Gitlab::Auth::RequestAuthenticator.new(self).user&.id Gitlab::Auth::RequestAuthenticator.new(self).user(request_formats)&.id
end end
def api_request? def api_request?
......
# frozen_string_literal: true
class CleanupEnvironmentsExternalUrl < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
update_column_in_batches(:environments, :external_url, nil) do |table, query|
query.where(table[:external_url].matches('javascript://%'))
end
end
def down
end
end
# frozen_string_literal: true
class MigrateForbiddenRedirectUris < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
FORBIDDEN_SCHEMES = %w[data:// vbscript:// javascript://]
NEW_URI = 'http://forbidden-scheme-has-been-overwritten'
disable_ddl_transaction!
def up
update_forbidden_uris(:oauth_applications)
update_forbidden_uris(:oauth_access_grants)
end
def down
# noop
end
private
def update_forbidden_uris(table_name)
update_column_in_batches(table_name, :redirect_uri, NEW_URI) do |table, query|
where_clause = FORBIDDEN_SCHEMES.map do |scheme|
table[:redirect_uri].matches("#{scheme}%")
end.inject(&:or)
query.where(where_clause)
end
end
end
This diff is collapsed.
# Merge conflict resolution # Merge request conflict resolution
> [Introduced][ce-5479] in GitLab 8.11. Merge conflicts occur when two branches have different changes that cannot be
merged automatically.
When a merge request has conflicts, GitLab may provide the option to resolve Git is able to automatically merge changes between branches in most cases, but
those conflicts in the GitLab UI. (See there are situations where Git will require your assistance to resolve the
[conflicts available for resolution](#conflicts-available-for-resolution) for conflicts manually. Typically, this is necessary when people change the same
more information on when this is available.) If this is an option, you will see parts of the same files.
a **resolve these conflicts** link in the merge request widget:
GitLab will prevent merge requests from being merged until all conflicts are
resolved. Conflicts can be resolved locally, or in many cases within GitLab
(see [conflicts available for resolution](#conflicts-available-for-resolution)
for information on when this is available).
![Merge request widget](img/merge_request_widget.png) ![Merge request widget](img/merge_request_widget.png)
NOTE: **Note:**
GitLab resolves conflicts by creating a merge commit in the source branch that
is not automatically merged into the target branch. This allows the merge
commit to be reviewed and tested before the changes are merged, preventing
unintended changes entering the target branch without review or breaking the
build.
## Resolve conflicts: interactive mode
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5479) in GitLab 8.11.
Clicking this will show a list of files with conflicts, with conflict sections Clicking this will show a list of files with conflicts, with conflict sections
highlighted: highlighted:
...@@ -21,9 +37,9 @@ request into the source branch, resolving the conflicts using the options ...@@ -21,9 +37,9 @@ request into the source branch, resolving the conflicts using the options
chosen. If the source branch is `feature` and the target branch is `master`, chosen. If the source branch is `feature` and the target branch is `master`,
this is similar to performing `git checkout feature; git merge master` locally. this is similar to performing `git checkout feature; git merge master` locally.
## Merge conflict editor ## Resolve conflicts: inline editor
> Introduced in GitLab 8.13. > [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6374) in GitLab 8.13.
The merge conflict resolution editor allows for more complex merge conflicts, The merge conflict resolution editor allows for more complex merge conflicts,
which require the user to manually modify a file in order to resolve a conflict, which require the user to manually modify a file in order to resolve a conflict,
...@@ -50,5 +66,3 @@ Additionally, GitLab does not detect conflicts in renames away from a path. For ...@@ -50,5 +66,3 @@ Additionally, GitLab does not detect conflicts in renames away from a path. For
example, this will not create a conflict: on branch `a`, doing `git mv file1 example, this will not create a conflict: on branch `a`, doing `git mv file1
file2`; on branch `b`, doing `git mv file1 file3`. Instead, both files will be file2`; on branch `b`, doing `git mv file1 file3`. Instead, both files will be
present in the branch after the merge request is merged. present in the branch after the merge request is merged.
[ce-5479]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5479
...@@ -64,6 +64,8 @@ Below is the table of events users can be notified of: ...@@ -64,6 +64,8 @@ Below is the table of events users can be notified of:
|------------------------------|-------------------------------------------------------------------|------------------------------| |------------------------------|-------------------------------------------------------------------|------------------------------|
| New SSH key added | User | Security email, always sent. | | New SSH key added | User | Security email, always sent. |
| New email added | User | Security email, always sent. | | New email added | User | Security email, always sent. |
| Email changed | User | Security email, always sent. |
| Password changed | User | Security email, always sent. |
| New user created | User | Sent on user creation, except for omniauth (LDAP)| | New user created | User | Sent on user creation, except for omniauth (LDAP)|
| User added to project | User | Sent when user is added to project | | User added to project | User | Sent when user is added to project |
| Project access level changed | User | Sent when user project access level is changed | | Project access level changed | User | Sent when user project access level is changed |
......
...@@ -17,6 +17,9 @@ module Banzai ...@@ -17,6 +17,9 @@ module Banzai
# This is a small extension to the CommonMark spec. If they start allowing # This is a small extension to the CommonMark spec. If they start allowing
# spaces in urls, we could then remove this filter. # spaces in urls, we could then remove this filter.
# #
# Note: Filter::SanitizationFilter should always be run sometime after this filter
# to prevent XSS attacks
#
class SpacedLinkFilter < HTML::Pipeline::Filter class SpacedLinkFilter < HTML::Pipeline::Filter
include ActionView::Helpers::TagHelper include ActionView::Helpers::TagHelper
......
...@@ -14,13 +14,16 @@ module Banzai ...@@ -14,13 +14,16 @@ module Banzai
def self.filters def self.filters
@filters ||= FilterArray[ @filters ||= FilterArray[
Filter::PlantumlFilter, Filter::PlantumlFilter,
# Must always be before the SanitizationFilter to prevent XSS attacks
Filter::SpacedLinkFilter,
Filter::SanitizationFilter, Filter::SanitizationFilter,
Filter::SyntaxHighlightFilter, Filter::SyntaxHighlightFilter,
Filter::MathFilter, Filter::MathFilter,
Filter::ColorFilter, Filter::ColorFilter,
Filter::MermaidFilter, Filter::MermaidFilter,
Filter::SpacedLinkFilter,
Filter::VideoLinkFilter, Filter::VideoLinkFilter,
Filter::ImageLazyLoadFilter, Filter::ImageLazyLoadFilter,
Filter::ImageLinkFilter, Filter::ImageLinkFilter,
......
...@@ -13,12 +13,18 @@ module Gitlab ...@@ -13,12 +13,18 @@ module Gitlab
@request = request @request = request
end end
def user def user(request_formats)
find_sessionless_user || find_user_from_warden request_formats.each do |format|
user = find_sessionless_user(format)
return user if user
end
find_user_from_warden
end end
def find_sessionless_user def find_sessionless_user(request_format)
find_user_from_access_token || find_user_from_feed_token find_user_from_web_access_token(request_format) || find_user_from_feed_token(request_format)
rescue Gitlab::Auth::AuthenticationError rescue Gitlab::Auth::AuthenticationError
nil nil
end end
......
...@@ -29,8 +29,8 @@ module Gitlab ...@@ -29,8 +29,8 @@ module Gitlab
current_request.env['warden']&.authenticate if verified_request? current_request.env['warden']&.authenticate if verified_request?
end end
def find_user_from_feed_token def find_user_from_feed_token(request_format)
return unless rss_request? || ics_request? return unless valid_rss_format?(request_format)
# NOTE: feed_token was renamed from rss_token but both needs to be supported because # NOTE: feed_token was renamed from rss_token but both needs to be supported because
# users might have already added the feed to their RSS reader before the rename # users might have already added the feed to their RSS reader before the rename
...@@ -40,6 +40,17 @@ module Gitlab ...@@ -40,6 +40,17 @@ module Gitlab
User.find_by_feed_token(token) || raise(UnauthorizedError) User.find_by_feed_token(token) || raise(UnauthorizedError)
end end
# We only allow Private Access Tokens with `api` scope to be used by web
# requests on RSS feeds or ICS files for backwards compatibility.
# It is also used by GraphQL/API requests.
def find_user_from_web_access_token(request_format)
return unless access_token && valid_web_access_format?(request_format)
validate_access_token!(scopes: [:api])
access_token.user || raise(UnauthorizedError)
end
def find_user_from_access_token def find_user_from_access_token
return unless access_token return unless access_token
...@@ -111,6 +122,26 @@ module Gitlab ...@@ -111,6 +122,26 @@ module Gitlab
@current_request ||= ensure_action_dispatch_request(request) @current_request ||= ensure_action_dispatch_request(request)
end end
def valid_web_access_format?(request_format)
case request_format
when :rss
rss_request?
when :ics
ics_request?
when :api
api_request?
end
end
def valid_rss_format?(request_format)
case request_format
when :rss
rss_request?
when :ics
ics_request?
end
end
def rss_request? def rss_request?
current_request.path.ends_with?('.atom') || current_request.format.atom? current_request.path.ends_with?('.atom') || current_request.format.atom?
end end
...@@ -118,6 +149,10 @@ module Gitlab ...@@ -118,6 +149,10 @@ module Gitlab
def ics_request? def ics_request?
current_request.path.ends_with?('.ics') || current_request.format.ics? current_request.path.ends_with?('.ics') || current_request.format.ics?
end end
def api_request?
current_request.path.starts_with?("/api/")
end
end end
end end
end end
# frozen_string_literal: true # frozen_string_literal: true
require 'resolv' require 'resolv'
require 'ipaddress'
module Gitlab module Gitlab
class UrlBlocker class UrlBlocker
...@@ -10,11 +11,8 @@ module Gitlab ...@@ -10,11 +11,8 @@ module Gitlab
def validate!(url, allow_localhost: false, allow_local_network: true, enforce_user: false, ports: [], protocols: []) def validate!(url, allow_localhost: false, allow_local_network: true, enforce_user: false, ports: [], protocols: [])
return true if url.nil? return true if url.nil?
begin # Param url can be a string, URI or Addressable::URI
uri = Addressable::URI.parse(url) uri = parse_url(url)
rescue Addressable::URI::InvalidURIError
raise BlockedUrlError, "URI is invalid"
end
# Allow imports from the GitLab instance itself but only from the configured ports # Allow imports from the GitLab instance itself but only from the configured ports
return true if internal?(uri) return true if internal?(uri)
...@@ -26,7 +24,9 @@ module Gitlab ...@@ -26,7 +24,9 @@ module Gitlab
validate_hostname!(uri.hostname) validate_hostname!(uri.hostname)
begin begin
addrs_info = Addrinfo.getaddrinfo(uri.hostname, port, nil, :STREAM) addrs_info = Addrinfo.getaddrinfo(uri.hostname, port, nil, :STREAM).map do |addr|
addr.ipv6_v4mapped? ? addr.ipv6_to_ipv4 : addr
end
rescue SocketError rescue SocketError
return true return true
end end
...@@ -49,6 +49,18 @@ module Gitlab ...@@ -49,6 +49,18 @@ module Gitlab
private private
def parse_url(url)
raise Addressable::URI::InvalidURIError if multiline?(url)
Addressable::URI.parse(url)
rescue Addressable::URI::InvalidURIError, URI::InvalidURIError
raise BlockedUrlError, 'URI is invalid'
end
def multiline?(url)
CGI.unescape(url.to_s) =~ /\n|\r/
end
def validate_port!(port, ports) def validate_port!(port, ports)
return if port.blank? return if port.blank?
# Only ports under 1024 are restricted # Only ports under 1024 are restricted
...@@ -73,13 +85,14 @@ module Gitlab ...@@ -73,13 +85,14 @@ module Gitlab
def validate_hostname!(value) def validate_hostname!(value)
return if value.blank? return if value.blank?
return if IPAddress.valid?(value)
return if value =~ /\A\p{Alnum}/ return if value =~ /\A\p{Alnum}/
raise BlockedUrlError, "Hostname needs to start with an alphanumeric character" raise BlockedUrlError, "Hostname or IP address invalid"
end end
def validate_localhost!(addrs_info) def validate_localhost!(addrs_info)
local_ips = ["127.0.0.1", "::1", "0.0.0.0"] local_ips = ["::", "0.0.0.0"]
local_ips.concat(Socket.ip_address_list.map(&:ip_address)) local_ips.concat(Socket.ip_address_list.map(&:ip_address))
return if (local_ips & addrs_info.map(&:ip_address)).empty? return if (local_ips & addrs_info.map(&:ip_address)).empty?
...@@ -94,7 +107,7 @@ module Gitlab ...@@ -94,7 +107,7 @@ module Gitlab
end end
def validate_local_network!(addrs_info) def validate_local_network!(addrs_info)
return unless addrs_info.any? { |addr| addr.ipv4_private? || addr.ipv6_sitelocal? } return unless addrs_info.any? { |addr| addr.ipv4_private? || addr.ipv6_sitelocal? || addr.ipv6_unique_local? }
raise BlockedUrlError, "Requests to the local network are not allowed" raise BlockedUrlError, "Requests to the local network are not allowed"
end end
...@@ -111,11 +124,13 @@ module Gitlab ...@@ -111,11 +124,13 @@ module Gitlab
end end
def internal_web?(uri) def internal_web?(uri)
uri.scheme == config.gitlab.protocol &&
uri.hostname == config.gitlab.host && uri.hostname == config.gitlab.host &&
(uri.port.blank? || uri.port == config.gitlab.port) (uri.port.blank? || uri.port == config.gitlab.port)
end end
def internal_shell?(uri) def internal_shell?(uri)
uri.scheme == 'ssh' &&
uri.hostname == config.gitlab_shell.ssh_host && uri.hostname == config.gitlab_shell.ssh_host &&
(uri.port.blank? || uri.port == config.gitlab_shell.ssh_port) (uri.port.blank? || uri.port == config.gitlab_shell.ssh_port)
end end
......
...@@ -188,6 +188,9 @@ msgstr "" ...@@ -188,6 +188,9 @@ msgstr ""
msgid "%{usage_ping_link_start}Learn more%{usage_ping_link_end} about what information is shared with GitLab Inc." msgid "%{usage_ping_link_start}Learn more%{usage_ping_link_end} about what information is shared with GitLab Inc."
msgstr "" msgstr ""
msgid "%{user_name} profile page"
msgstr ""
msgid "+ %{count} more" msgid "+ %{count} more"
msgstr "" msgstr ""
...@@ -1363,6 +1366,9 @@ msgstr "" ...@@ -1363,6 +1366,9 @@ msgstr ""
msgid "Business metrics (Custom)" msgid "Business metrics (Custom)"
msgstr "" msgstr ""
msgid "By %{user_name}"
msgstr ""
msgid "ByAuthor|by" msgid "ByAuthor|by"
msgstr "" msgstr ""
...@@ -3016,6 +3022,9 @@ msgstr "" ...@@ -3016,6 +3022,9 @@ msgstr ""
msgid "Edit application" msgid "Edit application"
msgstr "" msgstr ""
msgid "Edit comment"
msgstr ""
msgid "Edit environment" msgid "Edit environment"
msgstr "" msgstr ""
...@@ -4592,6 +4601,9 @@ msgstr "" ...@@ -4592,6 +4601,9 @@ msgstr ""
msgid "Interested parties can even contribute by pushing commits if they want to." msgid "Interested parties can even contribute by pushing commits if they want to."
msgstr "" msgstr ""
msgid "Internal"
msgstr ""
msgid "Internal - The group and any internal projects can be viewed by any logged in user." msgid "Internal - The group and any internal projects can be viewed by any logged in user."
msgstr "" msgstr ""
...@@ -5738,6 +5750,9 @@ msgstr "" ...@@ -5738,6 +5750,9 @@ msgstr ""
msgid "Notes|Show history only" msgid "Notes|Show history only"
msgstr "" msgstr ""
msgid "Nothing here."
msgstr ""
msgid "Notification events" msgid "Notification events"
msgstr "" msgstr ""
...@@ -6256,6 +6271,9 @@ msgstr "" ...@@ -6256,6 +6271,9 @@ msgstr ""
msgid "Prioritized label" msgid "Prioritized label"
msgstr "" msgstr ""
msgid "Private"
msgstr ""
msgid "Private - Project access must be granted explicitly to each user." msgid "Private - Project access must be granted explicitly to each user."
msgstr "" msgstr ""
...@@ -6820,7 +6838,11 @@ msgstr "" ...@@ -6820,7 +6838,11 @@ msgstr ""
msgid "Provider" msgid "Provider"
msgstr "" msgstr ""
<<<<<<< HEAD
msgid "Pseudonymizer data collection" msgid "Pseudonymizer data collection"
=======
msgid "Public"
>>>>>>> upstream/master
msgstr "" msgstr ""
msgid "Public - The group and any public projects can be viewed without any authentication." msgid "Public - The group and any public projects can be viewed without any authentication."
......
...@@ -8,7 +8,7 @@ module RuboCop ...@@ -8,7 +8,7 @@ module RuboCop
class AddReference < RuboCop::Cop::Cop class AddReference < RuboCop::Cop::Cop
include MigrationHelpers include MigrationHelpers
MSG = '`add_reference` requires `index: true`' MSG = '`add_reference` requires `index: true` or `index: { options... }`'
def on_send(node) def on_send(node)
return unless in_migration?(node) return unless in_migration?(node)
...@@ -33,7 +33,12 @@ module RuboCop ...@@ -33,7 +33,12 @@ module RuboCop
private private
def index_enabled?(pair) def index_enabled?(pair)
hash_key_type(pair) == :sym && hash_key_name(pair) == :index && pair.children[1].true_type? return unless hash_key_type(pair) == :sym
return unless hash_key_name(pair) == :index
index = pair.children[1]
index.true_type? || index.hash_type?
end end
def hash_key_type(pair) def hash_key_type(pair)
......
...@@ -107,59 +107,6 @@ describe ApplicationController do ...@@ -107,59 +107,6 @@ describe ApplicationController do
end end
end end
describe "#authenticate_user_from_personal_access_token!" do
before do
stub_authentication_activity_metrics(debug: false)
end
controller(described_class) do
def index
render text: 'authenticated'
end
end
let(:personal_access_token) { create(:personal_access_token, user: user) }
context "when the 'personal_access_token' param is populated with the personal access token" do
it "logs the user in" do
expect(authentication_metrics)
.to increment(:user_authenticated_counter)
.and increment(:user_session_override_counter)
.and increment(:user_sessionless_authentication_counter)
get :index, private_token: personal_access_token.token
expect(response).to have_gitlab_http_status(200)
expect(response.body).to eq('authenticated')
end
end
context "when the 'PERSONAL_ACCESS_TOKEN' header is populated with the personal access token" do
it "logs the user in" do
expect(authentication_metrics)
.to increment(:user_authenticated_counter)
.and increment(:user_session_override_counter)
.and increment(:user_sessionless_authentication_counter)
@request.headers["PRIVATE-TOKEN"] = personal_access_token.token
get :index
expect(response).to have_gitlab_http_status(200)
expect(response.body).to eq('authenticated')
end
end
it "doesn't log the user in otherwise" do
expect(authentication_metrics)
.to increment(:user_unauthenticated_counter)
get :index, private_token: "token"
expect(response.status).not_to eq(200)
expect(response.body).not_to eq('authenticated')
end
end
describe 'session expiration' do describe 'session expiration' do
controller(described_class) do controller(described_class) do
# The anonymous controller will report 401 and fail to run any actions. # The anonymous controller will report 401 and fail to run any actions.
...@@ -224,74 +171,6 @@ describe ApplicationController do ...@@ -224,74 +171,6 @@ describe ApplicationController do
end end
end end
describe '#authenticate_sessionless_user!' do
before do
stub_authentication_activity_metrics(debug: false)
end
describe 'authenticating a user from a feed token' do
controller(described_class) do
def index
render text: 'authenticated'
end
end
context "when the 'feed_token' param is populated with the feed token" do
context 'when the request format is atom' do
it "logs the user in" do
expect(authentication_metrics)
.to increment(:user_authenticated_counter)
.and increment(:user_session_override_counter)
.and increment(:user_sessionless_authentication_counter)
get :index, feed_token: user.feed_token, format: :atom
expect(response).to have_gitlab_http_status 200
expect(response.body).to eq 'authenticated'
end
end
context 'when the request format is ics' do
it "logs the user in" do
expect(authentication_metrics)
.to increment(:user_authenticated_counter)
.and increment(:user_session_override_counter)
.and increment(:user_sessionless_authentication_counter)
get :index, feed_token: user.feed_token, format: :ics
expect(response).to have_gitlab_http_status 200
expect(response.body).to eq 'authenticated'
end
end
context 'when the request format is neither atom nor ics' do
it "doesn't log the user in" do
expect(authentication_metrics)
.to increment(:user_unauthenticated_counter)
get :index, feed_token: user.feed_token
expect(response.status).not_to have_gitlab_http_status 200
expect(response.body).not_to eq 'authenticated'
end
end
end
context "when the 'feed_token' param is populated with an invalid feed token" do
it "doesn't log the user" do
expect(authentication_metrics)
.to increment(:user_unauthenticated_counter)
get :index, feed_token: 'token', format: :atom
expect(response.status).not_to eq 200
expect(response.body).not_to eq 'authenticated'
end
end
end
end
describe '#route_not_found' do describe '#route_not_found' do
it 'renders 404 if authenticated' do it 'renders 404 if authenticated' do
allow(controller).to receive(:current_user).and_return(user) allow(controller).to receive(:current_user).and_return(user)
...@@ -557,36 +436,6 @@ describe ApplicationController do ...@@ -557,36 +436,6 @@ describe ApplicationController do
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
end end
context 'for sessionless users' do
render_views
before do
sign_out user
end
it 'renders a 403 when the sessionless user did not accept the terms' do
get :index, feed_token: user.feed_token, format: :atom
expect(response).to have_gitlab_http_status(403)
end
it 'renders the error message when the format was html' do
get :index,
private_token: create(:personal_access_token, user: user).token,
format: :html
expect(response.body).to have_content /accept the terms of service/i
end
it 'renders a 200 when the sessionless user accepted the terms' do
accept_terms(user)
get :index, feed_token: user.feed_token, format: :atom
expect(response).to have_gitlab_http_status(200)
end
end
end end
end end
......
require 'spec_helper'
describe Dashboard::ProjectsController do
it_behaves_like 'authenticates sessionless user', :index, :atom
end
...@@ -42,6 +42,16 @@ describe Dashboard::TodosController do ...@@ -42,6 +42,16 @@ describe Dashboard::TodosController do
end end
end end
context 'group authorization' do
it 'renders 404 when user does not have read access on given group' do
unauthorized_group = create(:group, :private)
get :index, group_id: unauthorized_group.id
expect(response).to have_gitlab_http_status(404)
end
end
context 'when using pagination' do context 'when using pagination' do
let(:last_page) { user.todos.page.total_pages } let(:last_page) { user.todos.page.total_pages }
let!(:issues) { create_list(:issue, 3, project: project, assignees: [user]) } let!(:issues) { create_list(:issue, 3, project: project, assignees: [user]) }
......
require 'spec_helper' require 'spec_helper'
describe DashboardController do describe DashboardController do
context 'signed in' do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project) } let(:project) { create(:project) }
...@@ -18,4 +19,8 @@ describe DashboardController do ...@@ -18,4 +19,8 @@ describe DashboardController do
it_behaves_like 'issuables list meta-data', :merge_request, :merge_requests it_behaves_like 'issuables list meta-data', :merge_request, :merge_requests
it_behaves_like 'issuables requiring filter', :merge_requests it_behaves_like 'issuables requiring filter', :merge_requests
end end
end
it_behaves_like 'authenticates sessionless user', :issues, :atom, author_id: User.first
it_behaves_like 'authenticates sessionless user', :issues_calendar, :ics
end end
...@@ -52,15 +52,58 @@ describe GraphqlController do ...@@ -52,15 +52,58 @@ describe GraphqlController do
end end
end end
context 'token authentication' do
before do
stub_authentication_activity_metrics(debug: false)
end
let(:user) { create(:user, username: 'Simon') }
let(:personal_access_token) { create(:personal_access_token, user: user) }
context "when the 'personal_access_token' param is populated with the personal access token" do
it 'logs the user in' do
expect(authentication_metrics)
.to increment(:user_authenticated_counter)
.and increment(:user_session_override_counter)
.and increment(:user_sessionless_authentication_counter)
run_test_query!(private_token: personal_access_token.token)
expect(response).to have_gitlab_http_status(200)
expect(query_response).to eq('echo' => '"Simon" says: test success')
end
end
context 'when the personal access token has no api scope' do
it 'does not log the user in' do
personal_access_token.update(scopes: [:read_user])
run_test_query!(private_token: personal_access_token.token)
expect(response).to have_gitlab_http_status(200)
expect(query_response).to eq('echo' => 'nil says: test success')
end
end
context 'without token' do
it 'shows public data' do
run_test_query!
expect(query_response).to eq('echo' => 'nil says: test success')
end
end
end
# Chosen to exercise all the moving parts in GraphqlController#execute # Chosen to exercise all the moving parts in GraphqlController#execute
def run_test_query!(variables: { 'text' => 'test success' }) def run_test_query!(variables: { 'text' => 'test success' }, private_token: nil)
query = <<~QUERY query = <<~QUERY
query Echo($text: String) { query Echo($text: String) {
echo(text: $text) echo(text: $text)
} }
QUERY QUERY
post :execute, query: query, operationName: 'Echo', variables: variables post :execute, query: query, operationName: 'Echo', variables: variables, private_token: private_token
end end
def query_response def query_response
......
...@@ -645,4 +645,24 @@ describe GroupsController do ...@@ -645,4 +645,24 @@ describe GroupsController do
end end
end end
end end
context 'token authentication' do
it_behaves_like 'authenticates sessionless user', :show, :atom, public: true do
before do
default_params.merge!(id: group)
end
end
it_behaves_like 'authenticates sessionless user', :issues, :atom, public: true do
before do
default_params.merge!(id: group, author_id: user.id)
end
end
it_behaves_like 'authenticates sessionless user', :issues_calendar, :ics, public: true do
before do
default_params.merge!(id: group)
end
end
end
end end
...@@ -40,6 +40,23 @@ describe Oauth::ApplicationsController do ...@@ -40,6 +40,23 @@ describe Oauth::ApplicationsController do
expect(response).to have_gitlab_http_status(302) expect(response).to have_gitlab_http_status(302)
expect(response).to redirect_to(profile_path) expect(response).to redirect_to(profile_path)
end end
context 'redirect_uri' do
render_views
it 'shows an error for a forbidden URI' do
invalid_uri_params = {
doorkeeper_application: {
name: 'foo',
redirect_uri: 'javascript://alert()'
}
}
post :create, invalid_uri_params
expect(response.body).to include 'Redirect URI is forbidden by the server'
end
end
end end
end end
......
...@@ -5,10 +5,14 @@ describe Projects::CommitsController do ...@@ -5,10 +5,14 @@ describe Projects::CommitsController do
let(:user) { create(:user) } let(:user) { create(:user) }
before do before do
sign_in(user)
project.add_maintainer(user) project.add_maintainer(user)
end end
context 'signed in' do
before do
sign_in(user)
end
describe "GET commits_root" do describe "GET commits_root" do
context "no ref is provided" do context "no ref is provided" do
it 'should redirect to the default branch of the project' do it 'should redirect to the default branch of the project' do
...@@ -90,4 +94,28 @@ describe Projects::CommitsController do ...@@ -90,4 +94,28 @@ describe Projects::CommitsController do
end end
end end
end end
end
context 'token authentication' do
context 'public project' do
it_behaves_like 'authenticates sessionless user', :show, :atom, public: true do
before do
public_project = create(:project, :repository, :public)
default_params.merge!(namespace_id: public_project.namespace, project_id: public_project, id: "master.atom")
end
end
end
context 'private project' do
it_behaves_like 'authenticates sessionless user', :show, :atom, public: false do
before do
private_project = create(:project, :repository, :private)
private_project.add_maintainer(user)
default_params.merge!(namespace_id: private_project.namespace, project_id: private_project, id: "master.atom")
end
end
end
end
end end
...@@ -1068,4 +1068,40 @@ describe Projects::IssuesController do ...@@ -1068,4 +1068,40 @@ describe Projects::IssuesController do
end end
end end
end end
context 'private project with token authentication' do
let(:private_project) { create(:project, :private) }
it_behaves_like 'authenticates sessionless user', :index, :atom do
before do
default_params.merge!(project_id: private_project, namespace_id: private_project.namespace)
private_project.add_maintainer(user)
end
end
it_behaves_like 'authenticates sessionless user', :calendar, :ics do
before do
default_params.merge!(project_id: private_project, namespace_id: private_project.namespace)
private_project.add_maintainer(user)
end
end
end
context 'public project with token authentication' do
let(:public_project) { create(:project, :public) }
it_behaves_like 'authenticates sessionless user', :index, :atom, public: true do
before do
default_params.merge!(project_id: public_project, namespace_id: public_project.namespace)
end
end
it_behaves_like 'authenticates sessionless user', :calendar, :ics, public: true do
before do
default_params.merge!(project_id: public_project, namespace_id: public_project.namespace)
end
end
end
end end
...@@ -143,11 +143,27 @@ describe Projects::MilestonesController do ...@@ -143,11 +143,27 @@ describe Projects::MilestonesController do
end end
describe '#promote' do describe '#promote' do
let(:group) { create(:group) }
before do
project.update(namespace: group)
end
context 'when user does not have permission to promote milestone' do
before do
group.add_guest(user)
end
it 'renders 404' do
post :promote, namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid
expect(response).to have_gitlab_http_status(404)
end
end
context 'promotion succeeds' do context 'promotion succeeds' do
before do before do
group = create(:group)
group.add_developer(user) group.add_developer(user)
milestone.project.update(namespace: group)
end end
it 'shows group milestone' do it 'shows group milestone' do
...@@ -166,12 +182,17 @@ describe Projects::MilestonesController do ...@@ -166,12 +182,17 @@ describe Projects::MilestonesController do
end end
end end
context 'promotion fails' do context 'when user cannot admin group milestones' do
it 'shows project milestone' do before do
project.add_developer(user)
end
it 'renders 404' do
project.update(namespace: user.namespace)
post :promote, namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid post :promote, namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid
expect(response).to redirect_to(project_milestone_path(project, milestone)) expect(response).to have_gitlab_http_status(404)
expect(flash[:alert]).to eq('Promotion failed - Project does not belong to a group.')
end end
end end
end end
......
...@@ -283,7 +283,7 @@ describe Projects::NotesController do ...@@ -283,7 +283,7 @@ describe Projects::NotesController do
def post_create(extra_params = {}) def post_create(extra_params = {})
post :create, { post :create, {
note: { note: 'some other note' }, note: { note: 'some other note', noteable_id: merge_request.id },
namespace_id: project.namespace, namespace_id: project.namespace,
project_id: project, project_id: project,
target_type: 'merge_request', target_type: 'merge_request',
...@@ -324,6 +324,30 @@ describe Projects::NotesController do ...@@ -324,6 +324,30 @@ describe Projects::NotesController do
end end
end end
context 'when target_id and noteable_id do not match' do
let(:locked_issue) { create(:issue, :locked, project: project) }
let(:issue) {create(:issue, project: project)}
before do
project.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PUBLIC)
project.project_member(user).destroy
end
it 'uses target_id and ignores noteable_id' do
request_params = {
note: { note: 'some note', noteable_type: 'Issue', noteable_id: locked_issue.id },
target_type: 'issue',
target_id: issue.id,
project_id: project,
namespace_id: project.namespace
}
expect { post :create, request_params }.to change { issue.notes.count }.by(1)
.and change { locked_issue.notes.count }.by(0)
expect(response).to have_gitlab_http_status(302)
end
end
context 'when the merge request discussion is locked' do context 'when the merge request discussion is locked' do
before do before do
project.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PUBLIC) project.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PUBLIC)
...@@ -376,6 +400,7 @@ describe Projects::NotesController do ...@@ -376,6 +400,7 @@ describe Projects::NotesController do
end end
describe 'PUT update' do describe 'PUT update' do
context "should update the note with a valid issue" do
let(:request_params) do let(:request_params) do
{ {
namespace_id: project.namespace, namespace_id: project.namespace,
...@@ -397,6 +422,30 @@ describe Projects::NotesController do ...@@ -397,6 +422,30 @@ describe Projects::NotesController do
expect { put :update, request_params }.to change { note.reload.note } expect { put :update, request_params }.to change { note.reload.note }
end end
end end
context "doesnt update the note" do
let(:issue) { create(:issue, :confidential, project: project) }
let(:note) { create(:note, noteable: issue, project: project) }
before do
sign_in(user)
project.add_guest(user)
end
it "disallows edits when the issue is confidential and the user has guest permissions" do
request_params = {
namespace_id: project.namespace,
project_id: project,
id: note,
format: :json,
note: {
note: "New comment"
}
}
expect { put :update, request_params }.not_to change { note.reload.note }
expect(response).to have_gitlab_http_status(404)
end
end
end
describe 'DELETE destroy' do describe 'DELETE destroy' do
let(:request_params) do let(:request_params) do
......
...@@ -35,4 +35,26 @@ describe Projects::TagsController do ...@@ -35,4 +35,26 @@ describe Projects::TagsController do
it { is_expected.to respond_with(:not_found) } it { is_expected.to respond_with(:not_found) }
end end
end end
context 'private project with token authentication' do
let(:private_project) { create(:project, :repository, :private) }
it_behaves_like 'authenticates sessionless user', :index, :atom do
before do
default_params.merge!(project_id: private_project, namespace_id: private_project.namespace)
private_project.add_maintainer(user)
end
end
end
context 'public project with token authentication' do
let(:public_project) { create(:project, :repository, :public) }
it_behaves_like 'authenticates sessionless user', :index, :atom, public: true do
before do
default_params.merge!(project_id: public_project, namespace_id: public_project.namespace)
end
end
end
end end
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