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 }
New Milestone %h3.page-title
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,24 +54,52 @@ ...@@ -53,24 +54,52 @@
- 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}" }
- if commit.description? .avatar-cell.d-none.d-sm-block
%pre.commit-row-description.js-toggle-content.append-bottom-8 = author_avatar(commit, size: 36, has_tooltip: false)
= preserve(markdown_field(commit, :description))
.commit-actions.flex-row.d-none.d-sm-flex
- if request.xhr?
= render partial: 'projects/commit/signature', object: commit.signature
- else
= render partial: 'projects/commit/ajax_signature', locals: { commit: commit }
- if commit.status(ref) .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) = 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
.js-commit-pipeline-status{ data: { endpoint: pipelines_project_commit_path(project, commit.id, ref: ref) } } - if commit.description?
%pre.commit-row-description.js-toggle-content.append-bottom-8
= preserve(markdown_field(commit, :description))
.commit-sha-group .commit-actions.flex-row.d-none.d-sm-flex
.label.label-monospace - if request.xhr?
= commit.short_id = render partial: 'projects/commit/signature', object: commit.signature
= clipboard_button(text: commit.id, title: _("Copy commit SHA to clipboard"), class: "btn btn-default", container: "body") - else
= link_to_browse_code(project, commit) = render partial: 'projects/commit/ajax_signature', locals: { commit: commit }
- if commit.status(ref)
= render_commit_status(commit, ref: ref)
.js-commit-pipeline-status{ data: { endpoint: pipelines_project_commit_path(project, commit.id, ref: ref) } }
.commit-sha-group
.label.label-monospace
= commit.short_id
= clipboard_button(text: commit.id, title: _("Copy commit SHA to clipboard"), class: "btn btn-default", container: "body")
= link_to_browse_code(project, commit)
- @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
...@@ -95,18 +95,27 @@ The following documentation relates to the DevOps **Plan** stage: ...@@ -95,18 +95,27 @@ The following documentation relates to the DevOps **Plan** stage:
| Plan Topics | Description | | Plan Topics | Description |
|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------| |:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------|
<<<<<<< HEAD
| [Burndown Charts](user/project/milestones/burndown_charts.md) **[STARTER]** | Watch your project's progress throughout a specific milestone. | | [Burndown Charts](user/project/milestones/burndown_charts.md) **[STARTER]** | Watch your project's progress throughout a specific milestone. |
| [Discussions](user/discussions/index.md) | Threads, comments, and resolvable discussions in issues, commits, and merge requests. | | [Discussions](user/discussions/index.md) | Threads, comments, and resolvable discussions in issues, commits, and merge requests. |
| [Due Dates](user/project/issues/due_dates.md) | Keep track of issue deadlines. | | [Due Dates](user/project/issues/due_dates.md) | Keep track of issue deadlines. |
| [Epics](user/group/epics/index.md) **[ULTIMATE]** | Tracking groups of issues that share a theme. | | [Epics](user/group/epics/index.md) **[ULTIMATE]** | Tracking groups of issues that share a theme. |
=======
| [Discussions](user/discussions/index.md) | Threads, comments, and resolvable discussions in issues, commits, and merge requests. |
| [Due Dates](user/project/issues/due_dates.md) | Keep track of issue deadlines. |
| [Quick Actions](user/project/quick_actions.md) | Shortcuts for common actions on issues or merge requests, replacing the need to click buttons or use dropdowns in GitLab's UI. |
>>>>>>> upstream/master
| [Issues](user/project/issues/index.md), including [confidential issues](user/project/issues/confidential_issues.md),<br/>[issue and merge request templates](user/project/description_templates.md),<br/>and [moving issues](user/project/issues/moving_issues.md) | Project issues, restricting access to issues, create templates for submitting new issues and merge requests, and moving issues between projects. | | [Issues](user/project/issues/index.md), including [confidential issues](user/project/issues/confidential_issues.md),<br/>[issue and merge request templates](user/project/description_templates.md),<br/>and [moving issues](user/project/issues/moving_issues.md) | Project issues, restricting access to issues, create templates for submitting new issues and merge requests, and moving issues between projects. |
| [Labels](user/project/labels.md) | Categorize issues or merge requests with descriptive labels. | | [Labels](user/project/labels.md) | Categorize issues or merge requests with descriptive labels. |
| [Milestones](user/project/milestones/index.md) | Set milestones for delivery of issues and merge requests, with optional due date. | | [Milestones](user/project/milestones/index.md) | Set milestones for delivery of issues and merge requests, with optional due date. |
| [Project Issue Board](user/project/issue_board.md) | Display issues on a Scrum or Kanban board. | | [Project Issue Board](user/project/issue_board.md) | Display issues on a Scrum or Kanban board. |
<<<<<<< HEAD
| [Quick Actions](user/project/quick_actions.md) | Shortcuts for common actions on issues or merge requests, replacing the need to click buttons or use dropdowns in GitLab's UI. | | [Quick Actions](user/project/quick_actions.md) | Shortcuts for common actions on issues or merge requests, replacing the need to click buttons or use dropdowns in GitLab's UI. |
| [Related Issues](user/project/issues/related_issues.md) **[STARTER]** | Create a relationship between issues. | | [Related Issues](user/project/issues/related_issues.md) **[STARTER]** | Create a relationship between issues. |
| [Roadmap](user/group/roadmap/index.md) **[ULTIMATE]** | Visualize epic timelines. | | [Roadmap](user/group/roadmap/index.md) **[ULTIMATE]** | Visualize epic timelines. |
| [Service Desk](user/project/service_desk.md) **[PREMIUM]** | A simple way to allow people to create issues in your GitLab instance without needing their own user account. | | [Service Desk](user/project/service_desk.md) **[PREMIUM]** | A simple way to allow people to create issues in your GitLab instance without needing their own user account. |
=======
>>>>>>> upstream/master
| [Time Tracking](workflow/time_tracking.md) | Track time spent on issues and merge requests. | | [Time Tracking](workflow/time_tracking.md) | Track time spent on issues and merge requests. |
| [Todos](workflow/todos.md) | Keep track of work requiring attention with a chronological list displayed on a simple dashboard. | | [Todos](workflow/todos.md) | Keep track of work requiring attention with a chronological list displayed on a simple dashboard. |
...@@ -129,6 +138,7 @@ The following documentation relates to the DevOps **Create** stage: ...@@ -129,6 +138,7 @@ The following documentation relates to the DevOps **Create** stage:
#### Projects and Groups #### Projects and Groups
<<<<<<< HEAD
| Create Topics - Projects and Groups | Description | | Create Topics - Projects and Groups | Description |
|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------| |:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------|
| [Advanced global search](user/search/advanced_global_search.md) **[STARTER]** | Leverage Elasticsearch for faster, more advanced code search across your entire GitLab instance. | | [Advanced global search](user/search/advanced_global_search.md) **[STARTER]** | Leverage Elasticsearch for faster, more advanced code search across your entire GitLab instance. |
...@@ -144,6 +154,18 @@ The following documentation relates to the DevOps **Create** stage: ...@@ -144,6 +154,18 @@ The following documentation relates to the DevOps **Create** stage:
| [Snippets](user/snippets.md) | Snippets allow you to create little bits of code. | | [Snippets](user/snippets.md) | Snippets allow you to create little bits of code. |
| [Web IDE](user/project/web_ide/index.md) | Edit files within GitLab's user interface. | | [Web IDE](user/project/web_ide/index.md) | Edit files within GitLab's user interface. |
| [Wikis](user/project/wiki/index.md) | Enhance your repository documentation with built-in wikis. | | [Wikis](user/project/wiki/index.md) | Enhance your repository documentation with built-in wikis. |
=======
| Create Topics - Projects and Groups | Description |
|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------|
| [Create](gitlab-basics/create-project.md) and [fork](gitlab-basics/fork-project.md) projects, and<br/>[import and export<br/>projects between instances](user/project/settings/import_export.md) | Create, duplicate, and move projects. |
| [GitLab Pages](user/project/pages/index.md) | Build, test, and deploy your static website with GitLab Pages. |
| [Groups](user/group/index.md) and [Subgroups](user/group/subgroups/index.md) | Organize your projects in groups. |
| [Projects](user/project/index.md), including [project access](public_access/public_access.md)<br/>and [settings](user/project/settings/index.md) | Host source code, and control your project's visibility and set configuration. |
| [Search through GitLab](user/search/index.md) | Search for issues, merge requests, projects, groups, and todos. |
| [Snippets](user/snippets.md) | Snippets allow you to create little bits of code. |
| [Web IDE](user/project/web_ide/index.md) | Edit files within GitLab's user interface. |
| [Wikis](user/project/wiki/index.md) | Enhance your repository documentation with built-in wikis. |
>>>>>>> upstream/master
<div align="right"> <div align="right">
<a type="button" class="btn btn-default" href="#overview"> <a type="button" class="btn btn-default" href="#overview">
...@@ -163,9 +185,13 @@ The following documentation relates to the DevOps **Create** stage: ...@@ -163,9 +185,13 @@ The following documentation relates to the DevOps **Create** stage:
| [Files](user/project/repository/index.md#files) | Files management. | | [Files](user/project/repository/index.md#files) | Files management. |
| [Jupyter Notebook files](user/project/repository/index.md#jupyter-notebook-files) | GitLab's support for `.ipynb` files. | | [Jupyter Notebook files](user/project/repository/index.md#jupyter-notebook-files) | GitLab's support for `.ipynb` files. |
| [Protected branches](user/project/protected_branches.md) | Use protected branches. | | [Protected branches](user/project/protected_branches.md) | Use protected branches. |
<<<<<<< HEAD
| [Push rules](push_rules/push_rules.md) **[STARTER]** | Additional control over pushes to your projects. | | [Push rules](push_rules/push_rules.md) **[STARTER]** | Additional control over pushes to your projects. |
| [Repositories](user/project/repository/index.md) | Manage source code repositories in GitLab's user interface. | | [Repositories](user/project/repository/index.md) | Manage source code repositories in GitLab's user interface. |
| [Repository mirroring](workflow/repository_mirroring.md) **[STARTER]** | Push to or pull from repositories outside of GitLab | | [Repository mirroring](workflow/repository_mirroring.md) **[STARTER]** | Push to or pull from repositories outside of GitLab |
=======
| [Repositories](user/project/repository/index.md) | Manage source code repositories in GitLab's user interface. |
>>>>>>> upstream/master
| [Start a merge request](user/project/repository/web_editor.md#tips) | Start merge request when committing via GitLab's user interface. | | [Start a merge request](user/project/repository/web_editor.md#tips) | Start merge request when committing via GitLab's user interface. |
<div align="right"> <div align="right">
...@@ -291,10 +317,15 @@ The following documentation relates to the DevOps **Configure** stage: ...@@ -291,10 +317,15 @@ The following documentation relates to the DevOps **Configure** stage:
| [Auto DevOps](topics/autodevops/index.md) | Automatically employ a complete DevOps lifecycle. | | [Auto DevOps](topics/autodevops/index.md) | Automatically employ a complete DevOps lifecycle. |
| [Easy creation of Kubernetes<br/>clusters on GKE](user/project/clusters/index.md#adding-and-creating-a-new-gke-cluster-via-gitlab) | Use Google Kubernetes Engine and GitLab. | | [Easy creation of Kubernetes<br/>clusters on GKE](user/project/clusters/index.md#adding-and-creating-a-new-gke-cluster-via-gitlab) | Use Google Kubernetes Engine and GitLab. |
| [Executable Runbooks](user/project/clusters/runbooks/index.md) | Documented procedures that explain how to carry out particular processes. | | [Executable Runbooks](user/project/clusters/runbooks/index.md) | Documented procedures that explain how to carry out particular processes. |
<<<<<<< HEAD
| [GitLab ChatOps](ci/chatops/README.md) **[ULTIMATE]** | Interact with CI/CD jobs through chat services. | | [GitLab ChatOps](ci/chatops/README.md) **[ULTIMATE]** | Interact with CI/CD jobs through chat services. |
| [Installing Applications](user/project/clusters/index.md#installing-applications) | Deploy Helm, Ingress, and Prometheus on Kubernetes. | | [Installing Applications](user/project/clusters/index.md#installing-applications) | Deploy Helm, Ingress, and Prometheus on Kubernetes. |
| [Mattermost slash commands](user/project/integrations/mattermost_slash_commands.md) | Enable and use slash commands from within Mattermost. | | [Mattermost slash commands](user/project/integrations/mattermost_slash_commands.md) | Enable and use slash commands from within Mattermost. |
| [Multiple Kubernetes Clusters](user/project/clusters/index.md#multiple-kubernetes-clusters) **[PREMIUM]** | Associate more than one Kubernetes clusters to your project. | | [Multiple Kubernetes Clusters](user/project/clusters/index.md#multiple-kubernetes-clusters) **[PREMIUM]** | Associate more than one Kubernetes clusters to your project. |
=======
| [Installing Applications](user/project/clusters/index.md#installing-applications) | Deploy Helm, Ingress, and Prometheus on Kubernetes. |
| [Mattermost slash commands](user/project/integrations/mattermost_slash_commands.md) | Enable and use slash commands from within Mattermost. |
>>>>>>> upstream/master
| [Protected variables](ci/variables/README.md#protected-variables) | Restrict variables to protected branches and tags. | | [Protected variables](ci/variables/README.md#protected-variables) | Restrict variables to protected branches and tags. |
| [Slack slash commands](user/project/integrations/slack_slash_commands.md) | Enable and use slash commands from within Slack. | | [Slack slash commands](user/project/integrations/slack_slash_commands.md) | Enable and use slash commands from within Slack. |
......
# 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,12 +124,14 @@ module Gitlab ...@@ -111,12 +124,14 @@ module Gitlab
end end
def internal_web?(uri) def internal_web?(uri)
uri.hostname == config.gitlab.host && uri.scheme == config.gitlab.protocol &&
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.hostname == config.gitlab_shell.ssh_host && uri.scheme == 'ssh' &&
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
let(:user) { create(:user) } context 'signed in' do
let(:project) { create(:project) } let(:user) { create(:user) }
let(:project) { create(:project) }
before do before do
project.add_maintainer(user) project.add_maintainer(user)
sign_in(user) sign_in(user)
end end
describe 'GET issues' do describe 'GET issues' do
it_behaves_like 'issuables list meta-data', :issue, :issues it_behaves_like 'issuables list meta-data', :issue, :issues
it_behaves_like 'issuables requiring filter', :issues it_behaves_like 'issuables requiring filter', :issues
end end
describe 'GET merge requests' do describe 'GET merge requests' 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,87 +5,115 @@ describe Projects::CommitsController do ...@@ -5,87 +5,115 @@ 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
describe "GET commits_root" do context 'signed in' do
context "no ref is provided" do before do
it 'should redirect to the default branch of the project' do sign_in(user)
get(:commits_root, end
namespace_id: project.namespace,
project_id: project) describe "GET commits_root" do
context "no ref is provided" do
it 'should redirect to the default branch of the project' do
get(:commits_root,
namespace_id: project.namespace,
project_id: project)
expect(response).to redirect_to project_commits_path(project) expect(response).to redirect_to project_commits_path(project)
end
end end
end end
end
describe "GET show" do describe "GET show" do
render_views render_views
context 'with file path' do context 'with file path' do
before do before do
get(:show, get(:show,
namespace_id: project.namespace, namespace_id: project.namespace,
project_id: project, project_id: project,
id: id) id: id)
end end
context "valid branch, valid file" do context "valid branch, valid file" do
let(:id) { 'master/README.md' } let(:id) { 'master/README.md' }
it { is_expected.to respond_with(:success) } it { is_expected.to respond_with(:success) }
end end
context "valid branch, invalid file" do context "valid branch, invalid file" do
let(:id) { 'master/invalid-path.rb' } let(:id) { 'master/invalid-path.rb' }
it { is_expected.to respond_with(:not_found) } it { is_expected.to respond_with(:not_found) }
end end
context "invalid branch, valid file" do context "invalid branch, valid file" do
let(:id) { 'invalid-branch/README.md' } let(:id) { 'invalid-branch/README.md' }
it { is_expected.to respond_with(:not_found) } it { is_expected.to respond_with(:not_found) }
end
end end
end
context "when the ref name ends in .atom" do context "when the ref name ends in .atom" do
context "when the ref does not exist with the suffix" do context "when the ref does not exist with the suffix" do
before do before do
get(:show, get(:show,
namespace_id: project.namespace, namespace_id: project.namespace,
project_id: project, project_id: project,
id: "master.atom") id: "master.atom")
end
it "renders as atom" do
expect(response).to be_success
expect(response.content_type).to eq('application/atom+xml')
end
it 'renders summary with type=html' do
expect(response.body).to include('<summary type="html">')
end
end end
it "renders as atom" do context "when the ref exists with the suffix" do
expect(response).to be_success before do
expect(response.content_type).to eq('application/atom+xml') commit = project.repository.commit('master')
end
it 'renders summary with type=html' do allow_any_instance_of(Repository).to receive(:commit).and_call_original
expect(response.body).to include('<summary type="html">') allow_any_instance_of(Repository).to receive(:commit).with('master.atom').and_return(commit)
get(:show,
namespace_id: project.namespace,
project_id: project,
id: "master.atom")
end
it "renders as HTML" do
expect(response).to be_success
expect(response.content_type).to eq('text/html')
end
end end
end end
end
end
context "when the ref exists with the suffix" do context 'token authentication' do
context 'public project' do
it_behaves_like 'authenticates sessionless user', :show, :atom, public: true do
before do before do
commit = project.repository.commit('master') public_project = create(:project, :repository, :public)
allow_any_instance_of(Repository).to receive(:commit).and_call_original default_params.merge!(namespace_id: public_project.namespace, project_id: public_project, id: "master.atom")
allow_any_instance_of(Repository).to receive(:commit).with('master.atom').and_return(commit)
get(:show,
namespace_id: project.namespace,
project_id: project,
id: "master.atom")
end 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)
it "renders as HTML" do default_params.merge!(namespace_id: private_project.namespace, project_id: private_project, id: "master.atom")
expect(response).to be_success
expect(response.content_type).to eq('text/html')
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,14 +283,14 @@ describe Projects::NotesController do ...@@ -283,14 +283,14 @@ 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',
target_id: merge_request.id, target_id: merge_request.id,
note_project_id: forked_project.id, note_project_id: forked_project.id,
in_reply_to_discussion_id: existing_comment.discussion_id in_reply_to_discussion_id: existing_comment.discussion_id
}.merge(extra_params) }.merge(extra_params)
end end
context 'when the note_project_id is not correct' do context 'when the note_project_id is not correct' do
...@@ -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,35 +400,60 @@ describe Projects::NotesController do ...@@ -376,35 +400,60 @@ describe Projects::NotesController do
end end
describe 'PUT update' do describe 'PUT update' do
let(:request_params) do context "should update the note with a valid issue" do
{ let(:request_params) do
namespace_id: project.namespace, {
project_id: project, namespace_id: project.namespace,
id: note, project_id: project,
format: :json, id: note,
note: { format: :json,
note: "New comment" note: {
note: "New comment"
}
} }
} end
end
before do before do
sign_in(note.author) sign_in(note.author)
project.add_developer(note.author) project.add_developer(note.author)
end
it "updates the note" do
expect { put :update, request_params }.to change { note.reload.note }
end
end end
context "doesnt update the note" do
let(:issue) { create(:issue, :confidential, project: project) }
let(:note) { create(:note, noteable: issue, project: project) }
it "updates the note" do before do
expect { put :update, request_params }.to change { note.reload.note } 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
end end
describe 'DELETE destroy' do describe 'DELETE destroy' do
let(:request_params) do let(:request_params) do
{ {
namespace_id: project.namespace, namespace_id: project.namespace,
project_id: project, project_id: project,
id: note, id: note,
format: :js format: :js
} }
end end
......
...@@ -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
...@@ -927,6 +927,28 @@ describe ProjectsController do ...@@ -927,6 +927,28 @@ describe ProjectsController do
end end
end end
context 'private project with token authentication' do
let(:private_project) { create(:project, :private) }
it_behaves_like 'authenticates sessionless user', :show, :atom do
before do
default_params.merge!(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', :show, :atom, public: true do
before do
default_params.merge!(id: public_project, namespace_id: public_project.namespace)
end
end
end
def project_moved_message(redirect_route, project) def project_moved_message(redirect_route, project)
"Project '#{redirect_route.path}' was moved to '#{project.full_path}'. Please update any links and bookmarks that may still have the old path." "Project '#{redirect_route.path}' was moved to '#{project.full_path}'. Please update any links and bookmarks that may still have the old path."
end end
......
...@@ -395,6 +395,14 @@ describe UsersController do ...@@ -395,6 +395,14 @@ describe UsersController do
end end
end end
context 'token authentication' do
it_behaves_like 'authenticates sessionless user', :show, :atom, public: true do
before do
default_params.merge!(username: user.username)
end
end
end
def user_moved_message(redirect_route, user) def user_moved_message(redirect_route, user)
"User '#{redirect_route.path}' was moved to '#{user.full_path}'. Please update any links and bookmarks that may still have the old path." "User '#{redirect_route.path}' was moved to '#{user.full_path}'. Please update any links and bookmarks that may still have the old path."
end end
......
...@@ -308,7 +308,7 @@ FactoryBot.define do ...@@ -308,7 +308,7 @@ FactoryBot.define do
trait :with_runner_session do trait :with_runner_session do
after(:build) do |build| after(:build) do |build|
build.build_runner_session(url: 'ws://localhost') build.build_runner_session(url: 'https://localhost')
end end
end end
end end
......
...@@ -40,6 +40,18 @@ describe "User comments on issue", :js do ...@@ -40,6 +40,18 @@ describe "User comments on issue", :js do
expect(page.find('pre code').text).to eq code_block_content expect(page.find('pre code').text).to eq code_block_content
end end
it "does not render html content in mermaid" do
html_content = "<img onerror=location=`javascript\\u003aalert\\u0028document.domain\\u0029` src=x>"
mermaid_content = "graph LR\n B-->D(#{html_content});"
comment = "```mermaid\n#{mermaid_content}\n```"
add_note(comment)
wait_for_requests
expect(page.find('svg.mermaid')).to have_content html_content
end
end end
context "when editing comments" do context "when editing comments" do
......
require 'rails_helper' require 'rails_helper'
describe 'New issue breadcrumbs' do describe 'New issue breadcrumb' do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:user) { project.creator } let(:user) { project.creator }
before do before do
sign_in(user) sign_in(user)
visit new_project_issue_path(project) visit(new_project_issue_path(project))
end end
it 'display a link to project issues and new issue pages' do it 'displays link to project issues and new issue' do
page.within '.breadcrumbs' do page.within '.breadcrumbs' do
expect(find_link('Issues')[:href]).to end_with(project_issues_path(project)) expect(find_link('Issues')[:href]).to end_with(project_issues_path(project))
expect(find_link('New')[:href]).to end_with(new_project_issue_path(project)) expect(find_link('New')[:href]).to end_with(new_project_issue_path(project))
......
...@@ -18,7 +18,7 @@ describe 'Mermaid rendering', :js do ...@@ -18,7 +18,7 @@ describe 'Mermaid rendering', :js do
visit project_issue_path(project, issue) visit project_issue_path(project, issue)
%w[A B C D].each do |label| %w[A B C D].each do |label|
expect(page).to have_selector('svg foreignObject', text: label) expect(page).to have_selector('svg text', text: label)
end end
end end
end end
require 'rails_helper' require 'rails_helper'
describe 'New merge request breadcrumbs' do describe 'New merge request breadcrumb' do
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
let(:user) { project.creator } let(:user) { project.creator }
before do before do
sign_in(user) sign_in(user)
visit project_new_merge_request_path(project) visit(project_new_merge_request_path(project))
end end
it 'display a link to project merge requests and new merge request pages' do it 'displays link to project merge requests and new merge request' do
page.within '.breadcrumbs' do page.within '.breadcrumbs' do
expect(find_link('Merge Requests')[:href]).to end_with(project_merge_requests_path(project)) expect(find_link('Merge Requests')[:href]).to end_with(project_merge_requests_path(project))
expect(find_link('New')[:href]).to end_with(project_new_merge_request_path(project)) expect(find_link('New')[:href]).to end_with(project_new_merge_request_path(project))
......
require 'rails_helper'
describe 'User promotes milestone' do
set(:group) { create(:group) }
set(:user) { create(:user) }
set(:project) { create(:project, namespace: group) }
set(:milestone) { create(:milestone, project: project) }
context 'when user can admin group milestones' do
before do
group.add_developer(user)
sign_in(user)
visit(project_milestones_path(project))
end
it "shows milestone promote button" do
expect(page).to have_selector('.js-promote-project-milestone-button')
end
end
context 'when user cannot admin group milestones' do
before do
project.add_developer(user)
sign_in(user)
visit(project_milestones_path(project))
end
it "does not show milestone promote button" do
expect(page).not_to have_selector('.js-promote-project-milestone-button')
end
end
end
require 'rails_helper'
describe 'New project milestone breadcrumb' do
let(:project) { create(:project) }
let(:milestone) { create(:milestone, project: project) }
let(:user) { project.creator }
before do
sign_in(user)
visit(new_project_milestone_path(project))
end
it 'displays link to project milestones and new project milestone' do
page.within '.breadcrumbs' do
expect(find_link('Milestones')[:href]).to end_with(project_milestones_path(project))
expect(find_link('New')[:href]).to end_with(new_project_milestone_path(project))
end
end
end
...@@ -4,10 +4,9 @@ describe 'User browses commits' do ...@@ -4,10 +4,9 @@ describe 'User browses commits' do
include RepoHelpers include RepoHelpers
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project, :repository, namespace: user.namespace) } let(:project) { create(:project, :public, :repository, namespace: user.namespace) }
before do before do
project.add_maintainer(user)
sign_in(user) sign_in(user)
end end
...@@ -127,6 +126,26 @@ describe 'User browses commits' do ...@@ -127,6 +126,26 @@ describe 'User browses commits' do
.and have_selector('entry summary', text: commit.description[0..10].delete("\r\n")) .and have_selector('entry summary', text: commit.description[0..10].delete("\r\n"))
end end
context 'when a commit links to a confidential issue' do
let(:confidential_issue) { create(:issue, confidential: true, title: 'Secret issue!', project: project) }
before do
project.repository.create_file(user, 'dummy-file', 'dummy content',
branch_name: 'feature',
message: "Linking #{confidential_issue.to_reference}")
end
context 'when the user cannot see confidential issues but was cached with a link', :use_clean_rails_memory_store_fragment_caching do
it 'does not render the confidential issue' do
visit project_commits_path(project, 'feature')
sign_in(create(:user))
visit project_commits_path(project, 'feature')
expect(page).not_to have_link(href: project_issue_path(project, confidential_issue))
end
end
end
context 'master branch' do context 'master branch' do
before do before do
visit_commits_page visit_commits_page
......
require 'rails_helper'
describe 'New project label breadcrumb' do
let(:project) { create(:project) }
let(:user) { project.creator }
before do
sign_in(user)
visit(project_labels_path(project))
end
it 'displays link to project labels and new project label' do
page.within '.breadcrumbs' do
expect(find_link('Labels')[:href]).to end_with(project_labels_path(project))
end
end
end
...@@ -104,5 +104,17 @@ describe Banzai::Pipeline::GfmPipeline do ...@@ -104,5 +104,17 @@ describe Banzai::Pipeline::GfmPipeline do
expect(output).to include("src=\"test%20image.png\"") expect(output).to include("src=\"test%20image.png\"")
end end
it 'sanitizes the fixed link' do
markdown_xss = "[xss](javascript: alert%28document.domain%29)"
output = described_class.to_html(markdown_xss, project: project)
expect(output).not_to include("javascript")
markdown_xss = "<invalidtag>\n[xss](javascript:alert%28document.domain%29)"
output = described_class.to_html(markdown_xss, project: project)
expect(output).not_to include("javascript")
end
end end
end end
...@@ -19,17 +19,17 @@ describe Gitlab::Auth::RequestAuthenticator do ...@@ -19,17 +19,17 @@ describe Gitlab::Auth::RequestAuthenticator do
allow_any_instance_of(described_class).to receive(:find_sessionless_user).and_return(sessionless_user) allow_any_instance_of(described_class).to receive(:find_sessionless_user).and_return(sessionless_user)
allow_any_instance_of(described_class).to receive(:find_user_from_warden).and_return(session_user) allow_any_instance_of(described_class).to receive(:find_user_from_warden).and_return(session_user)
expect(subject.user).to eq sessionless_user expect(subject.user([:api])).to eq sessionless_user
end end
it 'returns session user if no sessionless user found' do it 'returns session user if no sessionless user found' do
allow_any_instance_of(described_class).to receive(:find_user_from_warden).and_return(session_user) allow_any_instance_of(described_class).to receive(:find_user_from_warden).and_return(session_user)
expect(subject.user).to eq session_user expect(subject.user([:api])).to eq session_user
end end
it 'returns nil if no user found' do it 'returns nil if no user found' do
expect(subject.user).to be_blank expect(subject.user([:api])).to be_blank
end end
it 'bubbles up exceptions' do it 'bubbles up exceptions' do
...@@ -42,26 +42,26 @@ describe Gitlab::Auth::RequestAuthenticator do ...@@ -42,26 +42,26 @@ describe Gitlab::Auth::RequestAuthenticator do
let!(:feed_token_user) { build(:user) } let!(:feed_token_user) { build(:user) }
it 'returns access_token user first' do it 'returns access_token user first' do
allow_any_instance_of(described_class).to receive(:find_user_from_access_token).and_return(access_token_user) allow_any_instance_of(described_class).to receive(:find_user_from_web_access_token).and_return(access_token_user)
allow_any_instance_of(described_class).to receive(:find_user_from_feed_token).and_return(feed_token_user) allow_any_instance_of(described_class).to receive(:find_user_from_feed_token).and_return(feed_token_user)
expect(subject.find_sessionless_user).to eq access_token_user expect(subject.find_sessionless_user([:api])).to eq access_token_user
end end
it 'returns feed_token user if no access_token user found' do it 'returns feed_token user if no access_token user found' do
allow_any_instance_of(described_class).to receive(:find_user_from_feed_token).and_return(feed_token_user) allow_any_instance_of(described_class).to receive(:find_user_from_feed_token).and_return(feed_token_user)
expect(subject.find_sessionless_user).to eq feed_token_user expect(subject.find_sessionless_user([:api])).to eq feed_token_user
end end
it 'returns nil if no user found' do it 'returns nil if no user found' do
expect(subject.find_sessionless_user).to be_blank expect(subject.find_sessionless_user([:api])).to be_blank
end end
it 'rescue Gitlab::Auth::AuthenticationError exceptions' do it 'rescue Gitlab::Auth::AuthenticationError exceptions' do
allow_any_instance_of(described_class).to receive(:find_user_from_access_token).and_raise(Gitlab::Auth::UnauthorizedError) allow_any_instance_of(described_class).to receive(:find_user_from_web_access_token).and_raise(Gitlab::Auth::UnauthorizedError)
expect(subject.find_sessionless_user).to be_blank expect(subject.find_sessionless_user([:api])).to be_blank
end end
end end
end end
...@@ -9,7 +9,7 @@ describe Gitlab::Auth::UserAuthFinders do ...@@ -9,7 +9,7 @@ describe Gitlab::Auth::UserAuthFinders do
'rack.input' => '' 'rack.input' => ''
} }
end end
let(:request) { Rack::Request.new(env)} let(:request) { Rack::Request.new(env) }
def set_param(key, value) def set_param(key, value)
request.update_param(key, value) request.update_param(key, value)
...@@ -49,6 +49,7 @@ describe Gitlab::Auth::UserAuthFinders do ...@@ -49,6 +49,7 @@ describe Gitlab::Auth::UserAuthFinders do
describe '#find_user_from_feed_token' do describe '#find_user_from_feed_token' do
context 'when the request format is atom' do context 'when the request format is atom' do
before do before do
env['SCRIPT_NAME'] = 'url.atom'
env['HTTP_ACCEPT'] = 'application/atom+xml' env['HTTP_ACCEPT'] = 'application/atom+xml'
end end
...@@ -56,17 +57,17 @@ describe Gitlab::Auth::UserAuthFinders do ...@@ -56,17 +57,17 @@ describe Gitlab::Auth::UserAuthFinders do
it 'returns user if valid feed_token' do it 'returns user if valid feed_token' do
set_param(:feed_token, user.feed_token) set_param(:feed_token, user.feed_token)
expect(find_user_from_feed_token).to eq user expect(find_user_from_feed_token(:rss)).to eq user
end end
it 'returns nil if feed_token is blank' do it 'returns nil if feed_token is blank' do
expect(find_user_from_feed_token).to be_nil expect(find_user_from_feed_token(:rss)).to be_nil
end end
it 'returns exception if invalid feed_token' do it 'returns exception if invalid feed_token' do
set_param(:feed_token, 'invalid_token') set_param(:feed_token, 'invalid_token')
expect { find_user_from_feed_token }.to raise_error(Gitlab::Auth::UnauthorizedError) expect { find_user_from_feed_token(:rss) }.to raise_error(Gitlab::Auth::UnauthorizedError)
end end
end end
...@@ -74,34 +75,38 @@ describe Gitlab::Auth::UserAuthFinders do ...@@ -74,34 +75,38 @@ describe Gitlab::Auth::UserAuthFinders do
it 'returns user if valid rssd_token' do it 'returns user if valid rssd_token' do
set_param(:rss_token, user.feed_token) set_param(:rss_token, user.feed_token)
expect(find_user_from_feed_token).to eq user expect(find_user_from_feed_token(:rss)).to eq user
end end
it 'returns nil if rss_token is blank' do it 'returns nil if rss_token is blank' do
expect(find_user_from_feed_token).to be_nil expect(find_user_from_feed_token(:rss)).to be_nil
end end
it 'returns exception if invalid rss_token' do it 'returns exception if invalid rss_token' do
set_param(:rss_token, 'invalid_token') set_param(:rss_token, 'invalid_token')
expect { find_user_from_feed_token }.to raise_error(Gitlab::Auth::UnauthorizedError) expect { find_user_from_feed_token(:rss) }.to raise_error(Gitlab::Auth::UnauthorizedError)
end end
end end
end end
context 'when the request format is not atom' do context 'when the request format is not atom' do
it 'returns nil' do it 'returns nil' do
env['SCRIPT_NAME'] = 'json'
set_param(:feed_token, user.feed_token) set_param(:feed_token, user.feed_token)
expect(find_user_from_feed_token).to be_nil expect(find_user_from_feed_token(:rss)).to be_nil
end end
end end
context 'when the request format is empty' do context 'when the request format is empty' do
it 'the method call does not modify the original value' do it 'the method call does not modify the original value' do
env['SCRIPT_NAME'] = 'url.atom'
env.delete('action_dispatch.request.formats') env.delete('action_dispatch.request.formats')
find_user_from_feed_token find_user_from_feed_token(:rss)
expect(env['action_dispatch.request.formats']).to be_nil expect(env['action_dispatch.request.formats']).to be_nil
end end
...@@ -111,8 +116,12 @@ describe Gitlab::Auth::UserAuthFinders do ...@@ -111,8 +116,12 @@ describe Gitlab::Auth::UserAuthFinders do
describe '#find_user_from_access_token' do describe '#find_user_from_access_token' do
let(:personal_access_token) { create(:personal_access_token, user: user) } let(:personal_access_token) { create(:personal_access_token, user: user) }
before do
env['SCRIPT_NAME'] = 'url.atom'
end
it 'returns nil if no access_token present' do it 'returns nil if no access_token present' do
expect(find_personal_access_token).to be_nil expect(find_user_from_access_token).to be_nil
end end
context 'when validate_access_token! returns valid' do context 'when validate_access_token! returns valid' do
...@@ -131,9 +140,59 @@ describe Gitlab::Auth::UserAuthFinders do ...@@ -131,9 +140,59 @@ describe Gitlab::Auth::UserAuthFinders do
end end
end end
describe '#find_user_from_web_access_token' do
let(:personal_access_token) { create(:personal_access_token, user: user) }
before do
env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token
end
it 'returns exception if token has no user' do
allow_any_instance_of(PersonalAccessToken).to receive(:user).and_return(nil)
expect { find_user_from_access_token }.to raise_error(Gitlab::Auth::UnauthorizedError)
end
context 'no feed or API requests' do
it 'returns nil if the request is not RSS' do
expect(find_user_from_web_access_token(:rss)).to be_nil
end
it 'returns nil if the request is not ICS' do
expect(find_user_from_web_access_token(:ics)).to be_nil
end
it 'returns nil if the request is not API' do
expect(find_user_from_web_access_token(:api)).to be_nil
end
end
it 'returns the user for RSS requests' do
env['SCRIPT_NAME'] = 'url.atom'
expect(find_user_from_web_access_token(:rss)).to eq(user)
end
it 'returns the user for ICS requests' do
env['SCRIPT_NAME'] = 'url.ics'
expect(find_user_from_web_access_token(:ics)).to eq(user)
end
it 'returns the user for API requests' do
env['SCRIPT_NAME'] = '/api/endpoint'
expect(find_user_from_web_access_token(:api)).to eq(user)
end
end
describe '#find_personal_access_token' do describe '#find_personal_access_token' do
let(:personal_access_token) { create(:personal_access_token, user: user) } let(:personal_access_token) { create(:personal_access_token, user: user) }
before do
env['SCRIPT_NAME'] = 'url.atom'
end
context 'passed as header' do context 'passed as header' do
it 'returns token if valid personal_access_token' do it 'returns token if valid personal_access_token' do
env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token
......
...@@ -10,8 +10,8 @@ describe Gitlab::UrlBlocker do ...@@ -10,8 +10,8 @@ describe Gitlab::UrlBlocker do
expect(described_class.blocked_url?(import_url)).to be false expect(described_class.blocked_url?(import_url)).to be false
end end
it 'allows imports from configured SSH host and port' do it 'allows mirroring from configured SSH host and port' do
import_url = "http://#{Gitlab.config.gitlab_shell.ssh_host}:#{Gitlab.config.gitlab_shell.ssh_port}/t.git" import_url = "ssh://#{Gitlab.config.gitlab_shell.ssh_host}:#{Gitlab.config.gitlab_shell.ssh_port}/t.git"
expect(described_class.blocked_url?(import_url)).to be false expect(described_class.blocked_url?(import_url)).to be false
end end
...@@ -29,24 +29,46 @@ describe Gitlab::UrlBlocker do ...@@ -29,24 +29,46 @@ describe Gitlab::UrlBlocker do
expect(described_class.blocked_url?('https://gitlab.com/foo/foo.git', protocols: ['http'])).to be true expect(described_class.blocked_url?('https://gitlab.com/foo/foo.git', protocols: ['http'])).to be true
end end
it 'returns true for bad protocol on configured web/SSH host and ports' do
web_url = "javascript://#{Gitlab.config.gitlab.host}:#{Gitlab.config.gitlab.port}/t.git%0aalert(1)"
expect(described_class.blocked_url?(web_url)).to be true
ssh_url = "javascript://#{Gitlab.config.gitlab_shell.ssh_host}:#{Gitlab.config.gitlab_shell.ssh_port}/t.git%0aalert(1)"
expect(described_class.blocked_url?(ssh_url)).to be true
end
it 'returns true for localhost IPs' do it 'returns true for localhost IPs' do
expect(described_class.blocked_url?('https://[0:0:0:0:0:0:0:0]/foo/foo.git')).to be true
expect(described_class.blocked_url?('https://0.0.0.0/foo/foo.git')).to be true expect(described_class.blocked_url?('https://0.0.0.0/foo/foo.git')).to be true
expect(described_class.blocked_url?('https://[::1]/foo/foo.git')).to be true expect(described_class.blocked_url?('https://[::]/foo/foo.git')).to be true
expect(described_class.blocked_url?('https://127.0.0.1/foo/foo.git')).to be true
end end
it 'returns true for loopback IP' do it 'returns true for loopback IP' do
expect(described_class.blocked_url?('https://127.0.0.2/foo/foo.git')).to be true expect(described_class.blocked_url?('https://127.0.0.2/foo/foo.git')).to be true
expect(described_class.blocked_url?('https://127.0.0.1/foo/foo.git')).to be true
expect(described_class.blocked_url?('https://[::1]/foo/foo.git')).to be true
end end
it 'returns true for alternative version of 127.0.0.1 (0177.1)' do it 'returns true for alternative version of 127.0.0.1 (0177.1)' do
expect(described_class.blocked_url?('https://0177.1:65535/foo/foo.git')).to be true expect(described_class.blocked_url?('https://0177.1:65535/foo/foo.git')).to be true
end end
it 'returns true for alternative version of 127.0.0.1 (017700000001)' do
expect(described_class.blocked_url?('https://017700000001:65535/foo/foo.git')).to be true
end
it 'returns true for alternative version of 127.0.0.1 (0x7f.1)' do it 'returns true for alternative version of 127.0.0.1 (0x7f.1)' do
expect(described_class.blocked_url?('https://0x7f.1:65535/foo/foo.git')).to be true expect(described_class.blocked_url?('https://0x7f.1:65535/foo/foo.git')).to be true
end end
it 'returns true for alternative version of 127.0.0.1 (0x7f.0.0.1)' do
expect(described_class.blocked_url?('https://0x7f.0.0.1:65535/foo/foo.git')).to be true
end
it 'returns true for alternative version of 127.0.0.1 (0x7f000001)' do
expect(described_class.blocked_url?('https://0x7f000001:65535/foo/foo.git')).to be true
end
it 'returns true for alternative version of 127.0.0.1 (2130706433)' do it 'returns true for alternative version of 127.0.0.1 (2130706433)' do
expect(described_class.blocked_url?('https://2130706433:65535/foo/foo.git')).to be true expect(described_class.blocked_url?('https://2130706433:65535/foo/foo.git')).to be true
end end
...@@ -55,6 +77,27 @@ describe Gitlab::UrlBlocker do ...@@ -55,6 +77,27 @@ describe Gitlab::UrlBlocker do
expect(described_class.blocked_url?('https://127.000.000.001:65535/foo/foo.git')).to be true expect(described_class.blocked_url?('https://127.000.000.001:65535/foo/foo.git')).to be true
end end
it 'returns true for alternative version of 127.0.0.1 (127.0.1)' do
expect(described_class.blocked_url?('https://127.0.1:65535/foo/foo.git')).to be true
end
context 'with ipv6 mapped address' do
it 'returns true for localhost IPs' do
expect(described_class.blocked_url?('https://[0:0:0:0:0:ffff:0.0.0.0]/foo/foo.git')).to be true
expect(described_class.blocked_url?('https://[::ffff:0.0.0.0]/foo/foo.git')).to be true
expect(described_class.blocked_url?('https://[::ffff:0:0]/foo/foo.git')).to be true
end
it 'returns true for loopback IPs' do
expect(described_class.blocked_url?('https://[0:0:0:0:0:ffff:127.0.0.1]/foo/foo.git')).to be true
expect(described_class.blocked_url?('https://[::ffff:127.0.0.1]/foo/foo.git')).to be true
expect(described_class.blocked_url?('https://[::ffff:7f00:1]/foo/foo.git')).to be true
expect(described_class.blocked_url?('https://[0:0:0:0:0:ffff:127.0.0.2]/foo/foo.git')).to be true
expect(described_class.blocked_url?('https://[::ffff:127.0.0.2]/foo/foo.git')).to be true
expect(described_class.blocked_url?('https://[::ffff:7f00:2]/foo/foo.git')).to be true
end
end
it 'returns true for a non-alphanumeric hostname' do it 'returns true for a non-alphanumeric hostname' do
stub_resolv stub_resolv
...@@ -78,7 +121,22 @@ describe Gitlab::UrlBlocker do ...@@ -78,7 +121,22 @@ describe Gitlab::UrlBlocker do
end end
context 'when allow_local_network is' do context 'when allow_local_network is' do
let(:local_ips) { ['192.168.1.2', '10.0.0.2', '172.16.0.2'] } let(:local_ips) do
[
'192.168.1.2',
'[0:0:0:0:0:ffff:192.168.1.2]',
'[::ffff:c0a8:102]',
'10.0.0.2',
'[0:0:0:0:0:ffff:10.0.0.2]',
'[::ffff:a00:2]',
'172.16.0.2',
'[0:0:0:0:0:ffff:172.16.0.2]',
'[::ffff:ac10:20]',
'[feef::1]',
'[fee2::]',
'[fc00:bf8b:e62c:abcd:abcd:aaaa:aaaa:aaaa]'
]
end
let(:fake_domain) { 'www.fakedomain.fake' } let(:fake_domain) { 'www.fakedomain.fake' }
context 'true (default)' do context 'true (default)' do
...@@ -109,10 +167,14 @@ describe Gitlab::UrlBlocker do ...@@ -109,10 +167,14 @@ describe Gitlab::UrlBlocker do
expect(described_class).not_to be_blocked_url('http://169.254.168.100') expect(described_class).not_to be_blocked_url('http://169.254.168.100')
end end
# This is blocked due to the hostname check: https://gitlab.com/gitlab-org/gitlab-ce/issues/50227 it 'allows IPv6 link-local endpoints' do
it 'blocks IPv6 link-local endpoints' do expect(described_class).not_to be_blocked_url('http://[0:0:0:0:0:ffff:169.254.169.254]')
expect(described_class).to be_blocked_url('http://[::ffff:169.254.169.254]') expect(described_class).not_to be_blocked_url('http://[::ffff:169.254.169.254]')
expect(described_class).to be_blocked_url('http://[::ffff:169.254.168.100]') expect(described_class).not_to be_blocked_url('http://[::ffff:a9fe:a9fe]')
expect(described_class).not_to be_blocked_url('http://[0:0:0:0:0:ffff:169.254.168.100]')
expect(described_class).not_to be_blocked_url('http://[::ffff:169.254.168.100]')
expect(described_class).not_to be_blocked_url('http://[::ffff:a9fe:a864]')
expect(described_class).not_to be_blocked_url('http://[fe80::c800:eff:fe74:8]')
end end
end end
...@@ -135,14 +197,20 @@ describe Gitlab::UrlBlocker do ...@@ -135,14 +197,20 @@ describe Gitlab::UrlBlocker do
end end
it 'blocks IPv6 link-local endpoints' do it 'blocks IPv6 link-local endpoints' do
expect(described_class).to be_blocked_url('http://[0:0:0:0:0:ffff:169.254.169.254]', allow_local_network: false)
expect(described_class).to be_blocked_url('http://[::ffff:169.254.169.254]', allow_local_network: false) expect(described_class).to be_blocked_url('http://[::ffff:169.254.169.254]', allow_local_network: false)
expect(described_class).to be_blocked_url('http://[::ffff:a9fe:a9fe]', allow_local_network: false)
expect(described_class).to be_blocked_url('http://[0:0:0:0:0:ffff:169.254.168.100]', allow_local_network: false)
expect(described_class).to be_blocked_url('http://[::ffff:169.254.168.100]', allow_local_network: false) expect(described_class).to be_blocked_url('http://[::ffff:169.254.168.100]', allow_local_network: false)
expect(described_class).to be_blocked_url('http://[FE80::C800:EFF:FE74:8]', allow_local_network: false) expect(described_class).to be_blocked_url('http://[::ffff:a9fe:a864]', allow_local_network: false)
expect(described_class).to be_blocked_url('http://[fe80::c800:eff:fe74:8]', allow_local_network: false)
end end
end end
def stub_domain_resolv(domain, ip) def stub_domain_resolv(domain, ip)
allow(Addrinfo).to receive(:getaddrinfo).with(domain, any_args).and_return([double(ip_address: ip, ipv4_private?: true, ipv6_link_local?: false, ipv4_loopback?: false, ipv6_loopback?: false)]) address = double(ip_address: ip, ipv4_private?: true, ipv6_link_local?: false, ipv4_loopback?: false, ipv6_loopback?: false)
allow(Addrinfo).to receive(:getaddrinfo).with(domain, any_args).and_return([address])
allow(address).to receive(:ipv6_v4mapped?).and_return(false)
end end
def unstub_domain_resolv def unstub_domain_resolv
...@@ -183,6 +251,36 @@ describe Gitlab::UrlBlocker do ...@@ -183,6 +251,36 @@ describe Gitlab::UrlBlocker do
end end
end end
describe '#validate_hostname!' do
let(:ip_addresses) do
[
'2001:db8:1f70::999:de8:7648:6e8',
'FE80::C800:EFF:FE74:8',
'::ffff:127.0.0.1',
'::ffff:169.254.168.100',
'::ffff:7f00:1',
'0:0:0:0:0:ffff:0.0.0.0',
'localhost',
'127.0.0.1',
'127.000.000.001',
'0x7f000001',
'0x7f.0.0.1',
'0x7f.0.0.1',
'017700000001',
'0177.1',
'2130706433',
'::',
'::1'
]
end
it 'does not raise error for valid Ip addresses' do
ip_addresses.each do |ip|
expect { described_class.send(:validate_hostname!, ip) }.not_to raise_error
end
end
end
# Resolv does not support resolving UTF-8 domain names # Resolv does not support resolving UTF-8 domain names
# See https://bugs.ruby-lang.org/issues/4270 # See https://bugs.ruby-lang.org/issues/4270
def stub_resolv def stub_resolv
......
...@@ -522,7 +522,7 @@ describe Notify do ...@@ -522,7 +522,7 @@ describe Notify do
let(:project_snippet) { create(:project_snippet, project: project) } let(:project_snippet) { create(:project_snippet, project: project) }
let(:project_snippet_note) { create(:note_on_project_snippet, project: project, noteable: project_snippet) } let(:project_snippet_note) { create(:note_on_project_snippet, project: project, noteable: project_snippet) }
subject { described_class.note_snippet_email(project_snippet_note.author_id, project_snippet_note.id) } subject { described_class.note_project_snippet_email(project_snippet_note.author_id, project_snippet_note.id) }
it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do
let(:model) { project_snippet } let(:model) { project_snippet }
......
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20181108091549_cleanup_environments_external_url.rb')
describe CleanupEnvironmentsExternalUrl, :migration do
let(:environments) { table(:environments) }
let(:invalid_entries) { environments.where(environments.arel_table[:external_url].matches('javascript://%')) }
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
before do
namespace = namespaces.create(name: 'foo', path: 'foo')
project = projects.create!(namespace_id: namespace.id)
environments.create!(id: 1, project_id: project.id, name: 'poisoned', slug: 'poisoned', external_url: 'javascript://alert("1")')
end
it 'clears every environment with a javascript external_url' do
expect do
subject.up
end.to change { invalid_entries.count }.from(1).to(0)
end
it 'do not removes environments' do
expect do
subject.up
end.not_to change { environments.count }
end
end
# frozen_string_literal: true
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20181026091631_migrate_forbidden_redirect_uris.rb')
describe MigrateForbiddenRedirectUris, :migration do
let(:oauth_application) { table(:oauth_applications) }
let(:oauth_access_grant) { table(:oauth_access_grants) }
let!(:control_app) { oauth_application.create(random_params) }
let!(:control_access_grant) { oauth_application.create(random_params) }
let!(:forbidden_js_app) { oauth_application.create(random_params.merge(redirect_uri: 'javascript://alert()')) }
let!(:forbidden_vb_app) { oauth_application.create(random_params.merge(redirect_uri: 'VBSCRIPT://alert()')) }
let!(:forbidden_access_grant) { oauth_application.create(random_params.merge(redirect_uri: 'vbscript://alert()')) }
context 'oauth application' do
it 'migrates forbidden javascript URI' do
expect { migrate! }.to change { forbidden_js_app.reload.redirect_uri }.to('http://forbidden-scheme-has-been-overwritten')
end
it 'migrates forbidden VBScript URI' do
expect { migrate! }.to change { forbidden_vb_app.reload.redirect_uri }.to('http://forbidden-scheme-has-been-overwritten')
end
it 'does not migrate a valid URI' do
expect { migrate! }.not_to change { control_app.reload.redirect_uri }
end
end
context 'access grant' do
it 'migrates forbidden VBScript URI' do
expect { migrate! }.to change { forbidden_access_grant.reload.redirect_uri }.to('http://forbidden-scheme-has-been-overwritten')
end
it 'does not migrate a valid URI' do
expect { migrate! }.not_to change { control_access_grant.reload.redirect_uri }
end
end
def random_params
{
name: 'test',
secret: 'test',
uid: Doorkeeper::OAuth::Helpers::UniqueToken.generate,
redirect_uri: 'http://valid.com'
}
end
end
...@@ -517,7 +517,7 @@ describe Note do ...@@ -517,7 +517,7 @@ describe Note do
describe '#to_ability_name' do describe '#to_ability_name' do
it 'returns snippet for a project snippet note' do it 'returns snippet for a project snippet note' do
expect(build(:note_on_project_snippet).to_ability_name).to eq('snippet') expect(build(:note_on_project_snippet).to_ability_name).to eq('project_snippet')
end end
it 'returns personal_snippet for a personal snippet note' do it 'returns personal_snippet for a personal snippet note' do
......
...@@ -13,6 +13,23 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do ...@@ -13,6 +13,23 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do
it { is_expected.to belong_to :project } it { is_expected.to belong_to :project }
end end
context 'redirects' do
it 'does not follow redirects' do
redirect_to = 'https://redirected.example.com'
redirect_req_stub = stub_prometheus_request(prometheus_query_url('1'), status: 302, headers: { location: redirect_to })
redirected_req_stub = stub_prometheus_request(redirect_to, body: { 'status': 'success' })
result = service.test
# result = { success: false, result: error }
expect(result[:success]).to be_falsy
expect(result[:result]).to be_instance_of(Gitlab::PrometheusClient::Error)
expect(redirect_req_stub).to have_been_requested
expect(redirected_req_stub).not_to have_been_requested
end
end
describe 'Validations' do describe 'Validations' do
context 'when manual_configuration is enabled' do context 'when manual_configuration is enabled' do
before do before do
......
...@@ -222,6 +222,7 @@ describe Project do ...@@ -222,6 +222,7 @@ describe Project do
end end
end end
<<<<<<< HEAD
context '#mark_stuck_remote_mirrors_as_failed!' do context '#mark_stuck_remote_mirrors_as_failed!' do
it 'fails stuck remote mirrors' do it 'fails stuck remote mirrors' do
project = create(:project, :repository, :remote_mirror) project = create(:project, :repository, :remote_mirror)
...@@ -246,74 +247,95 @@ describe Project do ...@@ -246,74 +247,95 @@ describe Project do
it 'does not allow an invalid URI as import_url' do it 'does not allow an invalid URI as import_url' do
project = build(:project, import_url: 'invalid://') project = build(:project, import_url: 'invalid://')
=======
describe 'import_url' do
it 'does not allow an invalid URI as import_url' do
project = build(:project, import_url: 'invalid://')
>>>>>>> upstream/master
expect(project).not_to be_valid expect(project).not_to be_valid
end end
it 'does allow a SSH URI as import_url for persisted projects' do it 'does allow a SSH URI as import_url for persisted projects' do
project = create(:project) project = create(:project)
project.import_url = 'ssh://test@gitlab.com/project.git' project.import_url = 'ssh://test@gitlab.com/project.git'
expect(project).to be_valid expect(project).to be_valid
end end
it 'does not allow a SSH URI as import_url for new projects' do it 'does not allow a SSH URI as import_url for new projects' do
project = build(:project, import_url: 'ssh://test@gitlab.com/project.git') project = build(:project, import_url: 'ssh://test@gitlab.com/project.git')
expect(project).not_to be_valid expect(project).not_to be_valid
end end
it 'does allow a valid URI as import_url' do it 'does allow a valid URI as import_url' do
project = build(:project, import_url: 'http://gitlab.com/project.git') project = build(:project, import_url: 'http://gitlab.com/project.git')
expect(project).to be_valid expect(project).to be_valid
end end
it 'allows an empty URI' do it 'allows an empty URI' do
project = build(:project, import_url: '') project = build(:project, import_url: '')
expect(project).to be_valid expect(project).to be_valid
end end
it 'does not produce import data on an empty URI' do it 'does not produce import data on an empty URI' do
project = build(:project, import_url: '') project = build(:project, import_url: '')
expect(project.import_data).to be_nil expect(project.import_data).to be_nil
end end
it 'does not produce import data on an invalid URI' do it 'does not produce import data on an invalid URI' do
project = build(:project, import_url: 'test://') project = build(:project, import_url: 'test://')
expect(project.import_data).to be_nil expect(project.import_data).to be_nil
end end
it "does not allow import_url pointing to localhost" do it "does not allow import_url pointing to localhost" do
project = build(:project, import_url: 'http://localhost:9000/t.git') project = build(:project, import_url: 'http://localhost:9000/t.git')
expect(project).to be_invalid expect(project).to be_invalid
expect(project.errors[:import_url].first).to include('Requests to localhost are not allowed') expect(project.errors[:import_url].first).to include('Requests to localhost are not allowed')
end end
it "does not allow import_url with invalid ports for new projects" do it "does not allow import_url with invalid ports for new projects" do
project = build(:project, import_url: 'http://github.com:25/t.git') project = build(:project, import_url: 'http://github.com:25/t.git')
expect(project).to be_invalid expect(project).to be_invalid
expect(project.errors[:import_url].first).to include('Only allowed ports are 80, 443') expect(project.errors[:import_url].first).to include('Only allowed ports are 80, 443')
end end
it "does not allow import_url with invalid ports for persisted projects" do it "does not allow import_url with invalid ports for persisted projects" do
project = create(:project) project = create(:project)
project.import_url = 'http://github.com:25/t.git' project.import_url = 'http://github.com:25/t.git'
expect(project).to be_invalid expect(project).to be_invalid
expect(project.errors[:import_url].first).to include('Only allowed ports are 22, 80, 443') expect(project.errors[:import_url].first).to include('Only allowed ports are 22, 80, 443')
end end
it "does not allow import_url with invalid user" do
project = build(:project, import_url: 'http://$user:password@github.com/t.git')
expect(project).to be_invalid
expect(project.errors[:import_url].first).to include('Username needs to start with an alphanumeric character')
end
it "does not allow import_url with invalid user" do include_context 'invalid urls'
project = build(:project, import_url: 'http://$user:password@github.com/t.git')
expect(project).to be_invalid it 'does not allow urls with CR or LF characters' do
expect(project.errors[:import_url].first).to include('Username needs to start with an alphanumeric character') project = build(:project)
aggregate_failures do
urls_with_CRLF.each do |url|
project.import_url = url
expect(project).not_to be_valid
expect(project.errors.full_messages.first).to match(/is blocked: URI is invalid/)
end
end
end
end end
it 'creates import state when mirror gets enabled' do it 'creates import state when mirror gets enabled' do
......
...@@ -10,11 +10,50 @@ describe NotePolicy, mdoels: true do ...@@ -10,11 +10,50 @@ describe NotePolicy, mdoels: true do
return @policies if @policies return @policies if @policies
noteable ||= issue noteable ||= issue
note = create(:note, noteable: noteable, author: user, project: project) note = if noteable.is_a?(Commit)
create(:note_on_commit, commit_id: noteable.id, author: user, project: project)
else
create(:note, noteable: noteable, author: user, project: project)
end
@policies = described_class.new(user, note) @policies = described_class.new(user, note)
end end
shared_examples_for 'a discussion with a private noteable' do
let(:noteable) { issue }
let(:policy) { policies(noteable) }
context 'when the note author can no longer see the noteable' do
it 'can not edit nor read the note' do
expect(policy).to be_disallowed(:admin_note)
expect(policy).to be_disallowed(:resolve_note)
expect(policy).to be_disallowed(:read_note)
end
end
context 'when the note author can still see the noteable' do
before do
project.add_developer(user)
end
it 'can edit the note' do
expect(policy).to be_allowed(:admin_note)
expect(policy).to be_allowed(:resolve_note)
expect(policy).to be_allowed(:read_note)
end
end
end
context 'when the project is private' do
let(:project) { create(:project, :private, :repository) }
context 'when the noteable is a commit' do
it_behaves_like 'a discussion with a private noteable' do
let(:noteable) { project.repository.head_commit }
end
end
end
context 'when the project is public' do context 'when the project is public' do
context 'when the note author is not a project member' do context 'when the note author is not a project member' do
it 'can edit a note' do it 'can edit a note' do
...@@ -24,14 +63,48 @@ describe NotePolicy, mdoels: true do ...@@ -24,14 +63,48 @@ describe NotePolicy, mdoels: true do
end end
end end
context 'when the noteable is a snippet' do context 'when the noteable is a project snippet' do
it 'can edit note' do
policies = policies(create(:project_snippet, :public, project: project))
expect(policies).to be_allowed(:admin_note)
expect(policies).to be_allowed(:resolve_note)
expect(policies).to be_allowed(:read_note)
end
context 'when it is private' do
it_behaves_like 'a discussion with a private noteable' do
let(:noteable) { create(:project_snippet, :private, project: project) }
end
end
end
context 'when the noteable is a personal snippet' do
it 'can edit note' do it 'can edit note' do
policies = policies(create(:project_snippet, project: project)) policies = policies(create(:personal_snippet, :public))
expect(policies).to be_allowed(:admin_note) expect(policies).to be_allowed(:admin_note)
expect(policies).to be_allowed(:resolve_note) expect(policies).to be_allowed(:resolve_note)
expect(policies).to be_allowed(:read_note) expect(policies).to be_allowed(:read_note)
end end
context 'when it is private' do
it 'can not edit nor read the note' do
policies = policies(create(:personal_snippet, :private))
expect(policies).to be_disallowed(:admin_note)
expect(policies).to be_disallowed(:resolve_note)
expect(policies).to be_disallowed(:read_note)
end
end
end
context 'when a discussion is confidential' do
before do
issue.update_attribute(:confidential, true)
end
it_behaves_like 'a discussion with a private noteable'
end end
context 'when a discussion is locked' do context 'when a discussion is locked' do
......
...@@ -25,7 +25,7 @@ describe API::Applications, :api do ...@@ -25,7 +25,7 @@ describe API::Applications, :api do
it 'does not allow creating an application with the wrong redirect_uri format' do it 'does not allow creating an application with the wrong redirect_uri format' do
expect do expect do
post api('/applications', admin_user), name: 'application_name', redirect_uri: 'wrong_url_format', scopes: '' post api('/applications', admin_user), name: 'application_name', redirect_uri: 'http://', scopes: ''
end.not_to change { Doorkeeper::Application.count } end.not_to change { Doorkeeper::Application.count }
expect(response).to have_gitlab_http_status(400) expect(response).to have_gitlab_http_status(400)
...@@ -33,6 +33,16 @@ describe API::Applications, :api do ...@@ -33,6 +33,16 @@ describe API::Applications, :api do
expect(json_response['message']['redirect_uri'][0]).to eq('must be an absolute URI.') expect(json_response['message']['redirect_uri'][0]).to eq('must be an absolute URI.')
end end
it 'does not allow creating an application with a forbidden URI format' do
expect do
post api('/applications', admin_user), name: 'application_name', redirect_uri: 'javascript://alert()', scopes: ''
end.not_to change { Doorkeeper::Application.count }
expect(response).to have_gitlab_http_status(400)
expect(json_response).to be_a Hash
expect(json_response['message']['redirect_uri'][0]).to eq('is forbidden by the server.')
end
it 'does not allow creating an application without a name' do it 'does not allow creating an application without a name' do
expect do expect do
post api('/applications', admin_user), redirect_uri: 'http://application.url', scopes: '' post api('/applications', admin_user), redirect_uri: 'http://application.url', scopes: ''
......
...@@ -29,7 +29,7 @@ describe RuboCop::Cop::Migration::AddReference do ...@@ -29,7 +29,7 @@ describe RuboCop::Cop::Migration::AddReference do
expect_offense(<<~RUBY) expect_offense(<<~RUBY)
call do call do
add_reference(:projects, :users) add_reference(:projects, :users)
^^^^^^^^^^^^^ `add_reference` requires `index: true` ^^^^^^^^^^^^^ `add_reference` requires `index: true` or `index: { options... }`
end end
RUBY RUBY
end end
...@@ -38,7 +38,7 @@ describe RuboCop::Cop::Migration::AddReference do ...@@ -38,7 +38,7 @@ describe RuboCop::Cop::Migration::AddReference do
expect_offense(<<~RUBY) expect_offense(<<~RUBY)
def up def up
add_reference(:projects, :users, index: false) add_reference(:projects, :users, index: false)
^^^^^^^^^^^^^ `add_reference` requires `index: true` ^^^^^^^^^^^^^ `add_reference` requires `index: true` or `index: { options... }`
end end
RUBY RUBY
end end
...@@ -50,5 +50,13 @@ describe RuboCop::Cop::Migration::AddReference do ...@@ -50,5 +50,13 @@ describe RuboCop::Cop::Migration::AddReference do
end end
RUBY RUBY
end end
it 'does not register an offense when the index is unique' do
expect_no_offenses(<<~RUBY)
def up
add_reference(:projects, :users, index: { unique: true } )
end
RUBY
end
end end
end end
shared_examples 'authenticates sessionless user' do |path, format, params|
params ||= {}
before do
stub_authentication_activity_metrics(debug: false)
end
let(:user) { create(:user) }
let(:personal_access_token) { create(:personal_access_token, user: user) }
let(:default_params) { { format: format }.merge(params.except(:public) || {}) }
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 path, default_params.merge(private_token: personal_access_token.token)
expect(response).to have_gitlab_http_status(200)
expect(controller.current_user).to eq(user)
end
it 'does not log the user in if page is public', if: params[:public] do
get path, default_params
expect(response).to have_gitlab_http_status(200)
expect(controller.current_user).to be_nil
end
end
context 'when the personal access token has no api scope', unless: params[:public] do
it 'does not log the user in' do
expect(authentication_metrics)
.to increment(:user_unauthenticated_counter)
personal_access_token.update(scopes: [:read_user])
get path, default_params.merge(private_token: personal_access_token.token)
expect(response).not_to have_gitlab_http_status(200)
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 path, default_params
expect(response).to have_gitlab_http_status(200)
end
end
context "when the 'feed_token' param is populated with the feed token", if: format == :rss 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 path, default_params.merge(feed_token: user.feed_token)
expect(response).to have_gitlab_http_status 200
end
end
context "when the 'feed_token' param is populated with an invalid feed token", if: format == :rss, unless: params[:public] do
it "logs the user" do
expect(authentication_metrics)
.to increment(:user_unauthenticated_counter)
get path, default_params.merge(feed_token: 'token')
expect(response.status).not_to eq 200
end
end
it "doesn't log the user in otherwise", unless: params[:public] do
expect(authentication_metrics)
.to increment(:user_unauthenticated_counter)
get path, default_params.merge(private_token: 'token')
expect(response.status).not_to eq(200)
end
end
...@@ -49,11 +49,11 @@ module PrometheusHelpers ...@@ -49,11 +49,11 @@ module PrometheusHelpers
"https://prometheus.example.com/api/v1/series?#{query}" "https://prometheus.example.com/api/v1/series?#{query}"
end end
def stub_prometheus_request(url, body: {}, status: 200) def stub_prometheus_request(url, body: {}, status: 200, headers: {})
WebMock.stub_request(:get, url) WebMock.stub_request(:get, url)
.to_return({ .to_return({
status: status, status: status,
headers: { 'Content-Type' => 'application/json' }, headers: { 'Content-Type' => 'application/json' }.merge(headers),
body: body.to_json body: body.to_json
}) })
end end
......
shared_context 'invalid urls' do
let(:urls_with_CRLF) do
["http://127.0.0.1:333/pa\rth",
"http://127.0.0.1:333/pa\nth",
"http://127.0a.0.1:333/pa\r\nth",
"http://127.0.0.1:333/path?param=foo\r\nbar",
"http://127.0.0.1:333/path?param=foo\rbar",
"http://127.0.0.1:333/path?param=foo\nbar",
"http://127.0.0.1:333/pa%0dth",
"http://127.0.0.1:333/pa%0ath",
"http://127.0a.0.1:333/pa%0d%0th",
"http://127.0.0.1:333/pa%0D%0Ath",
"http://127.0.0.1:333/path?param=foo%0Abar",
"http://127.0.0.1:333/path?param=foo%0Dbar",
"http://127.0.0.1:333/path?param=foo%0D%0Abar"]
end
end
# frozen_string_literal: true
require 'spec_helper' require 'spec_helper'
describe UrlValidator do describe UrlValidator do
...@@ -6,6 +8,30 @@ describe UrlValidator do ...@@ -6,6 +8,30 @@ describe UrlValidator do
include_examples 'url validator examples', described_class::DEFAULT_PROTOCOLS include_examples 'url validator examples', described_class::DEFAULT_PROTOCOLS
describe 'validations' do
include_context 'invalid urls'
let(:validator) { described_class.new(attributes: [:link_url]) }
it 'returns error when url is nil' do
expect(validator.validate_each(badge, :link_url, nil)).to be_nil
expect(badge.errors.first[1]).to eq 'must be a valid URL'
end
it 'returns error when url is empty' do
expect(validator.validate_each(badge, :link_url, '')).to be_nil
expect(badge.errors.first[1]).to eq 'must be a valid URL'
end
it 'does not allow urls with CR or LF characters' do
aggregate_failures do
urls_with_CRLF.each do |url|
expect(validator.validate_each(badge, :link_url, url)[0]).to eq 'is blocked: URI is invalid'
end
end
end
end
context 'by default' do context 'by default' do
let(:validator) { described_class.new(attributes: [:link_url]) } let(:validator) { described_class.new(attributes: [:link_url]) }
......
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