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 = {}
gem_versions['activerecord_sane_schema_dumper'] = rails5? ? '1.0' : '0.2'
gem_versions['rails'] = rails5? ? '5.0.7' : '4.2.10'
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 ---
source 'https://rubygems.org'
......@@ -164,6 +169,8 @@ gem 'icalendar'
gem 'diffy', '~> 3.1.0'
# Application server
gem 'rack', gem_versions['rack']
group :unicorn do
gem 'unicorn', '~> 5.1.0'
gem 'unicorn-worker-killer', '~> 0.4.4'
......
......@@ -1125,6 +1125,7 @@ DEPENDENCIES
pry-rails (~> 0.3.4)
puma (~> 3.12)
puma_worker_killer
rack (= 2.0.6)
rack-attack (~> 4.4.1)
rack-cors (~> 1.0.0)
rack-oauth2 (~> 1.2.1)
......
......@@ -1116,6 +1116,7 @@ DEPENDENCIES
pry-rails (~> 0.3.4)
puma (~> 3.12)
puma_worker_killer
rack (= 1.6.11)
rack-attack (~> 4.4.1)
rack-cors (~> 1.0.0)
rack-oauth2 (~> 1.2.1)
......
......@@ -26,6 +26,9 @@ export default function renderMermaid($els) {
},
// mermaidAPI options
theme: 'neutral',
flowchart: {
htmlLabels: false,
},
});
$els.each((i, el) => {
......
......@@ -12,11 +12,11 @@ class ApplicationController < ActionController::Base
include WorkhorseHelper
include EnforcesTwoFactorAuthentication
include WithPerformanceBar
include SessionlessAuthentication
# this can be removed after switching to rails 5
# https://gitlab.com/gitlab-org/gitlab-ce/issues/51908
include InvalidUTF8ErrorHandler unless Gitlab.rails5?
before_action :authenticate_sessionless_user!
before_action :authenticate_user!
before_action :enforce_terms!, if: :should_enforce_terms?
before_action :validate_user_service_ticket!
......@@ -153,6 +153,7 @@ class ApplicationController < ActionController::Base
end
end
<<<<<<< HEAD
# This filter handles personal access tokens, and atom requests with rss tokens
def authenticate_sessionless_user!
user = Gitlab::Auth::RequestAuthenticator.new(request).find_sessionless_user
......@@ -164,6 +165,8 @@ class ApplicationController < ActionController::Base
render_404 unless Gitlab::CurrentSettings.should_check_namespace_plan?
end
=======
>>>>>>> upstream/master
def log_exception(exception)
Raven.capture_exception(exception) if sentry_enabled?
......@@ -434,25 +437,11 @@ class ApplicationController < ActionController::Base
Gitlab::I18n.with_user_locale(current_user, &block)
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
# 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'))
end
def sessionless_user?
current_user && !session.keys.include?('warden.user.user.key')
end
def peek_request?
request.path.start_with?('/-/peek')
end
......
......@@ -6,6 +6,7 @@ module NotesActions
extend ActiveSupport::Concern
included do
prepend_before_action :normalize_create_params, only: [:create]
before_action :set_polling_interval_header, only: [:index]
before_action :require_noteable!, only: [:index, :create]
before_action :authorize_admin_note!, only: [:update, :destroy]
......@@ -247,6 +248,15 @@ module NotesActions
DiscussionSerializer.new(project: project, noteable: noteable, current_user: current_user, note_entity: ProjectNoteEntity)
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
strong_memoize(:note_project) do
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
include ParamsBackwardCompatibility
include RendersMemberAccess
prepend_before_action(only: [:index]) { authenticate_sessionless_user!(:rss) }
before_action :set_non_archived_param
before_action :default_sorting
skip_cross_project_access_check :index, :starred
......
......@@ -4,6 +4,7 @@ class Dashboard::TodosController < Dashboard::ApplicationController
include ActionView::Helpers::NumberHelper
before_action :authorize_read_project!, only: :index
before_action :authorize_read_group!, only: :index
before_action :find_todos, only: [:index, :destroy_all]
def index
......@@ -60,6 +61,15 @@ class Dashboard::TodosController < Dashboard::ApplicationController
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
@todos ||= TodosFinder.new(current_user, todo_params).execute
end
......
......@@ -4,6 +4,9 @@ class DashboardController < Dashboard::ApplicationController
include IssuesAction
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 :projects, only: [:issues, :merge_requests]
before_action :set_show_full_reference, only: [:issues, :merge_requests]
......
......@@ -3,6 +3,7 @@
class GraphqlController < ApplicationController
# Unauthenticated users have access to the API for public data
skip_before_action :authenticate_user!
prepend_before_action(only: [:execute]) { authenticate_sessionless_user!(:api) }
before_action :check_graphql_feature_flag!
......
......@@ -8,6 +8,9 @@ class GroupsController < Groups::ApplicationController
include PreviewMarkdown
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 :group, except: [:index, :new, :create]
......
......@@ -9,7 +9,7 @@ class Oauth::ApplicationsController < Doorkeeper::ApplicationsController
before_action :verify_user_oauth_applications_enabled, except: :index
before_action :authenticate_user!
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?
......
......@@ -6,6 +6,7 @@ class Projects::CommitsController < Projects::ApplicationController
include ExtractsPath
include RendersCommits
prepend_before_action(only: [:show]) { authenticate_sessionless_user!(:rss) }
before_action :whitelist_query_limiting, except: :commits_root
before_action :require_non_empty_project
before_action :assign_ref_vars, except: :commits_root
......
......@@ -9,12 +9,15 @@ class Projects::IssuesController < Projects::ApplicationController
include IssuesCalendar
include SpammableActions
<<<<<<< HEAD
prepend ::EE::Projects::IssuesController
def self.authenticate_user_only_actions
%i[new]
end
=======
>>>>>>> upstream/master
def self.issue_except_actions
%i[index calendar new create bulk_update]
end
......@@ -23,7 +26,10 @@ class Projects::IssuesController < Projects::ApplicationController
%i[index calendar]
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 :check_issues_available!
......@@ -234,16 +240,18 @@ class Projects::IssuesController < Projects::ApplicationController
] + [{ label_ids: [], assignee_ids: [] }]
end
def authenticate_user!
def authenticate_new_issue!
return if current_user
notice = "Please sign in to create the new issue."
redirect_to new_user_session_path, notice: notice
end
def store_uri
if request.get? && !request.xhr?
store_location_for :user, request.fullpath
end
redirect_to new_user_session_path, notice: notice
end
def serializer
......
......@@ -11,7 +11,10 @@ class Projects::MilestonesController < Projects::ApplicationController
before_action :authorize_read_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
......@@ -78,7 +81,7 @@ class Projects::MilestonesController < Projects::ApplicationController
def promote
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|
format.html do
......@@ -109,6 +112,12 @@ class Projects::MilestonesController < Projects::ApplicationController
protected
def project_group
strong_memoize(:project_group) do
project.group
end
end
def milestones
strong_memoize(:milestones) do
MilestonesFinder.new(search_params).execute
......@@ -125,13 +134,17 @@ class Projects::MilestonesController < Projects::ApplicationController
return render_404 unless can?(current_user, :admin_milestone, @project)
end
def authorize_promote_milestone!
return render_404 unless can?(current_user, :admin_milestone, project_group)
end
def milestone_params
params.require(:milestone).permit(:title, :description, :start_date, :due_date, :state_event)
end
def search_params
if request.format.json? && @project.group && can?(current_user, :read_group, @project.group)
groups = @project.group.self_and_ancestors_ids
if request.format.json? && project_group && can?(current_user, :read_group, project_group)
groups = project_group.self_and_ancestors_ids
end
params.permit(:state).merge(project_ids: @project.id, group_ids: groups)
......
......@@ -3,6 +3,8 @@
class Projects::TagsController < Projects::ApplicationController
include SortingHelper
prepend_before_action(only: [:index]) { authenticate_sessionless_user!(:rss) }
# Authorize
before_action :require_non_empty_project
before_action :authorize_download_code!
......
......@@ -6,6 +6,12 @@ class ProjectsController < Projects::ApplicationController
include ExtractsPath
include PreviewMarkdown
include SendFileUpload
<<<<<<< HEAD
=======
prepend_before_action(only: [:show]) { authenticate_sessionless_user!(:rss) }
>>>>>>> upstream/master
before_action :whitelist_query_limiting, only: [:create]
before_action :authenticate_user!, except: [:index, :show, :activity, :refs]
before_action :redirect_git_extension, only: [:show]
......
......@@ -13,6 +13,7 @@ class UsersController < ApplicationController
calendar_activities: true
skip_before_action :authenticate_user!
prepend_before_action(only: [:show]) { authenticate_sessionless_user!(:rss) }
before_action :user, except: [:exists]
before_action :authorize_read_user_profile!,
only: [:calendar, :calendar_activities, :groups, :projects, :contributed_projects, :snippets]
......
......@@ -2,6 +2,7 @@
module MilestonesHelper
include EntityDateHelper
include Gitlab::Utils::StrongMemoize
def milestones_filter_path(opts = {})
if @project
......@@ -243,6 +244,18 @@ module MilestonesHelper
dashboard_milestone_path(milestone.safe_title, title: milestone.title)
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
MilestonesHelper.prepend(EE::MilestonesHelper)
......@@ -28,7 +28,7 @@ module Emails
mail_answer_note_thread(@merge_request, @note, note_thread_options(recipient_id))
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)
@snippet = @note.noteable
......
......@@ -15,7 +15,7 @@ module CacheMarkdownField
# Increment this number every time the renderer changes its output
CACHE_REDCARPET_VERSION = 3
CACHE_COMMONMARK_VERSION_START = 10
CACHE_COMMONMARK_VERSION = 11
CACHE_COMMONMARK_VERSION = 12
# changes to these attributes cause the cache to be invalidates
INVALIDATED_BY = %w[author project].freeze
......
......@@ -324,7 +324,7 @@ class Note < ActiveRecord::Base
end
def to_ability_name
for_personal_snippet? ? 'personal_snippet' : noteable_type.underscore
for_snippet? ? noteable.class.name.underscore : noteable_type.underscore
end
def can_be_discussion_note?
......
......@@ -71,7 +71,7 @@ class PrometheusService < MonitoringService
end
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
def prometheus_available?
......
......@@ -2,4 +2,6 @@
class CommitPolicy < BasePolicy
delegate { @subject.project }
rule { can?(:download_code) }.enable :read_commit
end
......@@ -9,8 +9,17 @@ class NotePolicy < BasePolicy
condition(:editable, scope: :subject) { @subject.editable? }
condition(:can_read_noteable) { can?(:"read_#{@subject.to_ability_name}") }
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
enable :read_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
Edit Label
......
- breadcrumb_title "Labels"
- page_title 'New Label'
- add_to_breadcrumbs _("Labels"), group_labels_path(@group)
- breadcrumb_title _("New")
- page_title _("New Label")
%h3.page-title
New Label
......
- page_title "Milestones"
- breadcrumb_title _("Edit")
- page_title _("Milestones")
- render "header_title"
%h3.page-title
Edit Milestone
%hr
= render "form"
- breadcrumb_title "Milestones"
- page_title "Milestones"
- @no_container = true
- add_to_breadcrumbs _("Milestones"), group_milestones_path(@group)
- breadcrumb_title _("New")
- page_title _("Milestones"), @milestone.name, _("Milestones")
%h3.page-title
%div{ class: container_class }
%h3.page-title
New Milestone
= render "form"
%hr
= render "form"
......@@ -8,6 +8,7 @@
- ref = local_assigns.fetch(:ref) { merge_request&.source_branch }
- link = commit_path(project, commit, merge_request: merge_request)
<<<<<<< HEAD
- cache_key = [project.full_path,
ref,
commit.id,
......@@ -53,6 +54,34 @@
- if show_project_name
%span.project_namespace
= project.full_name
=======
%li.commit.flex-row.js-toggle-container{ id: "commit-#{commit.short_id}" }
.avatar-cell.d-none.d-sm-block
= author_avatar(commit, size: 36, has_tooltip: false)
.commit-detail.flex-list
.commit-content.qa-commit-content
- if view_details && merge_request
= link_to commit.title, project_commit_path(project, commit.id, merge_request_iid: merge_request.iid), class: "commit-row-message item-title"
- else
= link_to_markdown_field(commit, :title, link, class: "commit-row-message item-title")
%span.commit-row-message.d-block.d-sm-none
&middot;
= commit.short_id
- if commit.status(ref)
.d-block.d-sm-none
= render_commit_status(commit, ref: ref)
- if commit.description?
%button.text-expander.js-toggle-button
= sprite_icon('ellipsis_h', size: 12)
.committer
- commit_author_link = commit_author_link(commit, avatar: false, size: 24)
- commit_timeago = time_ago_with_tooltip(commit.authored_date, placement: 'bottom')
- commit_text = _('%{commit_author_link} authored %{commit_timeago}') % { commit_author_link: commit_author_link, commit_timeago: commit_timeago }
#{ commit_text.html_safe }
>>>>>>> upstream/master
- if commit.description?
%pre.commit-row-description.js-toggle-content.append-bottom-8
......
- @no_container = true
- add_to_breadcrumbs "Labels", project_labels_path(@project)
- breadcrumb_title "Edit"
- page_title "Edit", @label.name, "Labels"
%div{ class: container_class }
......
- @no_container = true
- breadcrumb_title "Labels"
- add_to_breadcrumbs "Labels", project_labels_path(@project)
- breadcrumb_title "New"
- page_title "New Label"
%div{ class: container_class }
......
- @no_container = true
- breadcrumb_title "Edit"
- add_to_breadcrumbs "Milestones", project_milestones_path(@project)
- page_title "Edit", @milestone.title, "Milestones"
%div{ class: container_class }
%h3.page-title
......
- @no_container = true
- breadcrumb_title "Milestones"
- add_to_breadcrumbs "Milestones", project_milestones_path(@project)
- breadcrumb_title "New"
- page_title "New Milestone"
%div{ class: container_class }
......
- @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)
......
......@@ -35,8 +35,8 @@
.col-sm-2
.milestone-actions.d-flex.justify-content-sm-start.justify-content-md-end
- if @project
- if can?(current_user, :admin_milestone, milestone.project) and milestone.active?
- if @project.group
- if can_admin_project_milestones? and milestone.active?
- 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'),
disabled: true,
type: 'button',
......
......@@ -3,31 +3,31 @@
.d-none.d-sm-block
- if can?(current_user, :update_personal_snippet, @snippet)
= link_to edit_snippet_path(@snippet), class: "btn btn-grouped" do
Edit
= _("Edit")
- 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
Delete
= link_to new_snippet_path, class: "btn btn-grouped btn-inverted btn-success", title: "New snippet" do
New 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
= _("Delete")
= link_to new_snippet_path, class: "btn btn-grouped btn-inverted btn-create", title: _("New snippet") do
= _("New snippet")
- 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
%button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
Options
= _("Options")
= icon('caret-down')
.dropdown-menu.dropdown-menu-full-width
%ul
%li
= link_to new_snippet_path, title: "New snippet" do
New snippet
= link_to new_snippet_path, title: _("New snippet") do
= _("New snippet")
- if can?(current_user, :admin_personal_snippet, @snippet)
%li
= link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do
Delete
= link_to snippet_path(@snippet), method: :delete, data: { confirm: _("Are you sure?") }, title: _('Delete Snippet') do
= _("Delete")
- if can?(current_user, :update_personal_snippet, @snippet)
%li
= link_to edit_snippet_path(@snippet) do
Edit
= _("Edit")
- if @snippet.submittable_as_spam_by?(current_user)
%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 @@
= render partial: 'shared/snippets/snippet', collection: @snippets, locals: { link_project: link_project }
- if @snippets.empty?
%li
.nothing-here-block Nothing here.
.nothing-here-block= _("Nothing here.")
= paginate @snippets, theme: 'gitlab'
......@@ -4,7 +4,7 @@
.nav-links.snippet-scope-menu.mobile-separator.nav.nav-tabs
%li{ class: active_when(params[:scope].nil?) }
= link_to subject_snippets_path(subject) do
All
= _("All")
%span.badge.badge-pill
- if include_private
= subject.snippets.count
......@@ -14,18 +14,18 @@
- if include_private
%li{ class: active_when(params[:scope] == "are_private") }
= link_to subject_snippets_path(subject, scope: 'are_private') do
Private
= _("Private")
%span.badge.badge-pill
= subject.snippets.are_private.count
%li{ class: active_when(params[:scope] == "are_internal") }
= link_to subject_snippets_path(subject, scope: 'are_internal') do
Internal
= _("Internal")
%span.badge.badge-pill
= subject.snippets.are_internal.count
%li{ class: active_when(params[:scope] == "are_public") }
= link_to subject_snippets_path(subject, scope: 'are_public') do
Public
= _("Public")
%span.badge.badge-pill
= 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
Edit Snippet
= _("Edit Snippet")
%hr
= 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
%li.breadcrumb-item
= link_to snippets_path do
Snippets
= _("Snippets")
%li.breadcrumb-item
= @user.name
.float-right.d-none.d-sm-block
= link_to user_path(@user) do
#{@user.name} profile page
= _("%{user_name} profile page") % { user_name: @user.name }
= render 'snippets'
- @hide_top_links = true
- @hide_breadcrumbs = true
- page_title "New Snippet"
- page_title _("New Snippet")
.page-title-holder
%h1.page-title= _('New Snippet')
......
- if current_user
- if note.emoji_awardable?
.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')
%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')
......@@ -9,7 +9,7 @@
- if note_editable
.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
= custom_icon('icon_pencil')
......
- @hide_top_links = true
- @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
- page_title "#{@snippet.title} (#{@snippet.to_reference})", "Snippets"
- page_title "#{@snippet.title} (#{@snippet.to_reference})", _("Snippets")
= 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)
# Max memory size (RSS) per worker
use Unicorn::WorkerKiller::Oom, min, max
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
require ::File.expand_path('../config/environment', __FILE__)
......
......@@ -117,6 +117,9 @@ module Gitlab
# - Webhook URLs (:hook)
# - Sentry DSN (:sentry_dsn)
# - 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 += %i(
certificate
......
......@@ -103,6 +103,9 @@ Devise.setup do |config|
# Send a notification email when the user's password is changed
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
# Range for password length. Default is 6..128.
config.password_length = 8..128
......
......@@ -48,6 +48,13 @@ Doorkeeper.configure do
#
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)
# Optional parameter confirmation: true (default false) if you want to enforce ownership of
# a registered application
......
......@@ -33,24 +33,24 @@ class Rack::Attack
throttle('throttle_authenticated_api', Gitlab::Throttle.authenticated_api_options) do |req|
Gitlab::Throttle.settings.throttle_authenticated_api_enabled &&
req.api_request? &&
req.authenticated_user_id
req.authenticated_user_id([:api])
end
throttle('throttle_authenticated_web', Gitlab::Throttle.authenticated_web_options) do |req|
Gitlab::Throttle.settings.throttle_authenticated_web_enabled &&
req.web_request? &&
req.authenticated_user_id
req.authenticated_user_id([:api, :rss, :ics])
end
class Request
prepend ::EE::Gitlab::Rack::Attack::Request
def unauthenticated?
!authenticated_user_id
!authenticated_user_id([:api, :rss, :ics])
end
def authenticated_user_id
Gitlab::Auth::RequestAuthenticator.new(self).user&.id
def authenticated_user_id(request_formats)
Gitlab::Auth::RequestAuthenticator.new(self).user(request_formats)&.id
end
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:
| Plan Topics | Description |
|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------|
<<<<<<< HEAD
| [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. |
| [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. |
=======
| [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. |
| [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. |
| [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. |
| [Related Issues](user/project/issues/related_issues.md) **[STARTER]** | Create a relationship between issues. |
| [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. |
=======
>>>>>>> upstream/master
| [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. |
......@@ -129,6 +138,7 @@ The following documentation relates to the DevOps **Create** stage:
#### Projects and Groups
<<<<<<< HEAD
| 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. |
......@@ -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. |
| [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. |
=======
| 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">
<a type="button" class="btn btn-default" href="#overview">
......@@ -163,9 +185,13 @@ The following documentation relates to the DevOps **Create** stage:
| [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. |
| [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. |
| [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 |
=======
| [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. |
<div align="right">
......@@ -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. |
| [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. |
<<<<<<< HEAD
| [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. |
| [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. |
=======
| [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. |
| [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
those conflicts in the GitLab UI. (See
[conflicts available for resolution](#conflicts-available-for-resolution) for
more information on when this is available.) If this is an option, you will see
a **resolve these conflicts** link in the merge request widget:
Git is able to automatically merge changes between branches in most cases, but
there are situations where Git will require your assistance to resolve the
conflicts manually. Typically, this is necessary when people change the same
parts of the same files.
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)
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
highlighted:
......@@ -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`,
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,
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
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
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:
|------------------------------|-------------------------------------------------------------------|------------------------------|
| New SSH key 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)|
| 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 |
......
......@@ -17,6 +17,9 @@ module Banzai
# This is a small extension to the CommonMark spec. If they start allowing
# 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
include ActionView::Helpers::TagHelper
......
......@@ -14,13 +14,16 @@ module Banzai
def self.filters
@filters ||= FilterArray[
Filter::PlantumlFilter,
# Must always be before the SanitizationFilter to prevent XSS attacks
Filter::SpacedLinkFilter,
Filter::SanitizationFilter,
Filter::SyntaxHighlightFilter,
Filter::MathFilter,
Filter::ColorFilter,
Filter::MermaidFilter,
Filter::SpacedLinkFilter,
Filter::VideoLinkFilter,
Filter::ImageLazyLoadFilter,
Filter::ImageLinkFilter,
......
......@@ -13,12 +13,18 @@ module Gitlab
@request = request
end
def user
find_sessionless_user || find_user_from_warden
def user(request_formats)
request_formats.each do |format|
user = find_sessionless_user(format)
return user if user
end
find_user_from_warden
end
def find_sessionless_user
find_user_from_access_token || find_user_from_feed_token
def find_sessionless_user(request_format)
find_user_from_web_access_token(request_format) || find_user_from_feed_token(request_format)
rescue Gitlab::Auth::AuthenticationError
nil
end
......
......@@ -29,8 +29,8 @@ module Gitlab
current_request.env['warden']&.authenticate if verified_request?
end
def find_user_from_feed_token
return unless rss_request? || ics_request?
def find_user_from_feed_token(request_format)
return unless valid_rss_format?(request_format)
# 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
......@@ -40,6 +40,17 @@ module Gitlab
User.find_by_feed_token(token) || raise(UnauthorizedError)
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
return unless access_token
......@@ -111,6 +122,26 @@ module Gitlab
@current_request ||= ensure_action_dispatch_request(request)
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?
current_request.path.ends_with?('.atom') || current_request.format.atom?
end
......@@ -118,6 +149,10 @@ module Gitlab
def ics_request?
current_request.path.ends_with?('.ics') || current_request.format.ics?
end
def api_request?
current_request.path.starts_with?("/api/")
end
end
end
end
# frozen_string_literal: true
require 'resolv'
require 'ipaddress'
module Gitlab
class UrlBlocker
......@@ -10,11 +11,8 @@ module Gitlab
def validate!(url, allow_localhost: false, allow_local_network: true, enforce_user: false, ports: [], protocols: [])
return true if url.nil?
begin
uri = Addressable::URI.parse(url)
rescue Addressable::URI::InvalidURIError
raise BlockedUrlError, "URI is invalid"
end
# Param url can be a string, URI or Addressable::URI
uri = parse_url(url)
# Allow imports from the GitLab instance itself but only from the configured ports
return true if internal?(uri)
......@@ -26,7 +24,9 @@ module Gitlab
validate_hostname!(uri.hostname)
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
return true
end
......@@ -49,6 +49,18 @@ module Gitlab
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)
return if port.blank?
# Only ports under 1024 are restricted
......@@ -73,13 +85,14 @@ module Gitlab
def validate_hostname!(value)
return if value.blank?
return if IPAddress.valid?(value)
return if value =~ /\A\p{Alnum}/
raise BlockedUrlError, "Hostname needs to start with an alphanumeric character"
raise BlockedUrlError, "Hostname or IP address invalid"
end
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))
return if (local_ips & addrs_info.map(&:ip_address)).empty?
......@@ -94,7 +107,7 @@ module Gitlab
end
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"
end
......@@ -111,11 +124,13 @@ module Gitlab
end
def internal_web?(uri)
uri.scheme == config.gitlab.protocol &&
uri.hostname == config.gitlab.host &&
(uri.port.blank? || uri.port == config.gitlab.port)
end
def internal_shell?(uri)
uri.scheme == 'ssh' &&
uri.hostname == config.gitlab_shell.ssh_host &&
(uri.port.blank? || uri.port == config.gitlab_shell.ssh_port)
end
......
......@@ -188,6 +188,9 @@ msgstr ""
msgid "%{usage_ping_link_start}Learn more%{usage_ping_link_end} about what information is shared with GitLab Inc."
msgstr ""
msgid "%{user_name} profile page"
msgstr ""
msgid "+ %{count} more"
msgstr ""
......@@ -1363,6 +1366,9 @@ msgstr ""
msgid "Business metrics (Custom)"
msgstr ""
msgid "By %{user_name}"
msgstr ""
msgid "ByAuthor|by"
msgstr ""
......@@ -3016,6 +3022,9 @@ msgstr ""
msgid "Edit application"
msgstr ""
msgid "Edit comment"
msgstr ""
msgid "Edit environment"
msgstr ""
......@@ -4592,6 +4601,9 @@ msgstr ""
msgid "Interested parties can even contribute by pushing commits if they want to."
msgstr ""
msgid "Internal"
msgstr ""
msgid "Internal - The group and any internal projects can be viewed by any logged in user."
msgstr ""
......@@ -5738,6 +5750,9 @@ msgstr ""
msgid "Notes|Show history only"
msgstr ""
msgid "Nothing here."
msgstr ""
msgid "Notification events"
msgstr ""
......@@ -6256,6 +6271,9 @@ msgstr ""
msgid "Prioritized label"
msgstr ""
msgid "Private"
msgstr ""
msgid "Private - Project access must be granted explicitly to each user."
msgstr ""
......@@ -6820,7 +6838,11 @@ msgstr ""
msgid "Provider"
msgstr ""
<<<<<<< HEAD
msgid "Pseudonymizer data collection"
=======
msgid "Public"
>>>>>>> upstream/master
msgstr ""
msgid "Public - The group and any public projects can be viewed without any authentication."
......
......@@ -8,7 +8,7 @@ module RuboCop
class AddReference < RuboCop::Cop::Cop
include MigrationHelpers
MSG = '`add_reference` requires `index: true`'
MSG = '`add_reference` requires `index: true` or `index: { options... }`'
def on_send(node)
return unless in_migration?(node)
......@@ -33,7 +33,12 @@ module RuboCop
private
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
def hash_key_type(pair)
......
......@@ -107,59 +107,6 @@ describe ApplicationController do
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
controller(described_class) do
# The anonymous controller will report 401 and fail to run any actions.
......@@ -224,74 +171,6 @@ describe ApplicationController do
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
it 'renders 404 if authenticated' do
allow(controller).to receive(:current_user).and_return(user)
......@@ -557,36 +436,6 @@ describe ApplicationController do
expect(response).to have_gitlab_http_status(200)
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
......
require 'spec_helper'
describe Dashboard::ProjectsController do
it_behaves_like 'authenticates sessionless user', :index, :atom
end
......@@ -42,6 +42,16 @@ describe Dashboard::TodosController do
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
let(:last_page) { user.todos.page.total_pages }
let!(:issues) { create_list(:issue, 3, project: project, assignees: [user]) }
......
require 'spec_helper'
describe DashboardController do
context 'signed in' do
let(:user) { create(:user) }
let(:project) { create(:project) }
......@@ -18,4 +19,8 @@ describe DashboardController do
it_behaves_like 'issuables list meta-data', :merge_request, :merge_requests
it_behaves_like 'issuables requiring filter', :merge_requests
end
end
it_behaves_like 'authenticates sessionless user', :issues, :atom, author_id: User.first
it_behaves_like 'authenticates sessionless user', :issues_calendar, :ics
end
......@@ -52,15 +52,58 @@ describe GraphqlController do
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
def run_test_query!(variables: { 'text' => 'test success' })
def run_test_query!(variables: { 'text' => 'test success' }, private_token: nil)
query = <<~QUERY
query Echo($text: String) {
echo(text: $text)
}
QUERY
post :execute, query: query, operationName: 'Echo', variables: variables
post :execute, query: query, operationName: 'Echo', variables: variables, private_token: private_token
end
def query_response
......
......@@ -645,4 +645,24 @@ describe GroupsController do
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
......@@ -40,6 +40,23 @@ describe Oauth::ApplicationsController do
expect(response).to have_gitlab_http_status(302)
expect(response).to redirect_to(profile_path)
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
......
......@@ -5,10 +5,14 @@ describe Projects::CommitsController do
let(:user) { create(:user) }
before do
sign_in(user)
project.add_maintainer(user)
end
context 'signed in' do
before do
sign_in(user)
end
describe "GET commits_root" do
context "no ref is provided" do
it 'should redirect to the default branch of the project' do
......@@ -90,4 +94,28 @@ describe Projects::CommitsController do
end
end
end
end
context 'token authentication' do
context 'public project' do
it_behaves_like 'authenticates sessionless user', :show, :atom, public: true do
before do
public_project = create(:project, :repository, :public)
default_params.merge!(namespace_id: public_project.namespace, project_id: public_project, id: "master.atom")
end
end
end
context 'private project' do
it_behaves_like 'authenticates sessionless user', :show, :atom, public: false do
before do
private_project = create(:project, :repository, :private)
private_project.add_maintainer(user)
default_params.merge!(namespace_id: private_project.namespace, project_id: private_project, id: "master.atom")
end
end
end
end
end
......@@ -1068,4 +1068,40 @@ describe Projects::IssuesController do
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
......@@ -143,11 +143,27 @@ describe Projects::MilestonesController do
end
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
before do
group = create(:group)
group.add_developer(user)
milestone.project.update(namespace: group)
end
it 'shows group milestone' do
......@@ -166,12 +182,17 @@ describe Projects::MilestonesController do
end
end
context 'promotion fails' do
it 'shows project milestone' do
context 'when user cannot admin group milestones' 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
expect(response).to redirect_to(project_milestone_path(project, milestone))
expect(flash[:alert]).to eq('Promotion failed - Project does not belong to a group.')
expect(response).to have_gitlab_http_status(404)
end
end
end
......
......@@ -283,7 +283,7 @@ describe Projects::NotesController do
def post_create(extra_params = {})
post :create, {
note: { note: 'some other note' },
note: { note: 'some other note', noteable_id: merge_request.id },
namespace_id: project.namespace,
project_id: project,
target_type: 'merge_request',
......@@ -324,6 +324,30 @@ describe Projects::NotesController do
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
before do
project.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PUBLIC)
......@@ -376,6 +400,7 @@ describe Projects::NotesController do
end
describe 'PUT update' do
context "should update the note with a valid issue" do
let(:request_params) do
{
namespace_id: project.namespace,
......@@ -397,6 +422,30 @@ describe Projects::NotesController do
expect { put :update, request_params }.to change { note.reload.note }
end
end
context "doesnt update the note" do
let(:issue) { create(:issue, :confidential, project: project) }
let(:note) { create(:note, noteable: issue, project: project) }
before do
sign_in(user)
project.add_guest(user)
end
it "disallows edits when the issue is confidential and the user has guest permissions" do
request_params = {
namespace_id: project.namespace,
project_id: project,
id: note,
format: :json,
note: {
note: "New comment"
}
}
expect { put :update, request_params }.not_to change { note.reload.note }
expect(response).to have_gitlab_http_status(404)
end
end
end
describe 'DELETE destroy' do
let(:request_params) do
......
......@@ -35,4 +35,26 @@ describe Projects::TagsController do
it { is_expected.to respond_with(:not_found) }
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
......@@ -927,6 +927,28 @@ describe ProjectsController do
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)
"Project '#{redirect_route.path}' was moved to '#{project.full_path}'. Please update any links and bookmarks that may still have the old path."
end
......
......@@ -395,6 +395,14 @@ describe UsersController do
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)
"User '#{redirect_route.path}' was moved to '#{user.full_path}'. Please update any links and bookmarks that may still have the old path."
end
......
......@@ -308,7 +308,7 @@ FactoryBot.define do
trait :with_runner_session do
after(:build) do |build|
build.build_runner_session(url: 'ws://localhost')
build.build_runner_session(url: 'https://localhost')
end
end
end
......
......@@ -40,6 +40,18 @@ describe "User comments on issue", :js do
expect(page.find('pre code').text).to eq code_block_content
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
context "when editing comments" do
......
require 'rails_helper'
describe 'New issue breadcrumbs' do
describe 'New issue breadcrumb' do
let(:project) { create(:project) }
let(:user) { project.creator }
before do
sign_in(user)
visit new_project_issue_path(project)
visit(new_project_issue_path(project))
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
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))
......
......@@ -18,7 +18,7 @@ describe 'Mermaid rendering', :js do
visit project_issue_path(project, issue)
%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
require 'rails_helper'
describe 'New merge request breadcrumbs' do
describe 'New merge request breadcrumb' do
let(:project) { create(:project, :repository) }
let(:user) { project.creator }
before do
sign_in(user)
visit project_new_merge_request_path(project)
visit(project_new_merge_request_path(project))
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
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))
......
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
include RepoHelpers
let(:user) { create(:user) }
let(:project) { create(:project, :repository, namespace: user.namespace) }
let(:project) { create(:project, :public, :repository, namespace: user.namespace) }
before do
project.add_maintainer(user)
sign_in(user)
end
......@@ -127,6 +126,26 @@ describe 'User browses commits' do
.and have_selector('entry summary', text: commit.description[0..10].delete("\r\n"))
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
before do
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
expect(output).to include("src=\"test%20image.png\"")
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
......@@ -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_user_from_warden).and_return(session_user)
expect(subject.user).to eq sessionless_user
expect(subject.user([:api])).to eq sessionless_user
end
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)
expect(subject.user).to eq session_user
expect(subject.user([:api])).to eq session_user
end
it 'returns nil if no user found' do
expect(subject.user).to be_blank
expect(subject.user([:api])).to be_blank
end
it 'bubbles up exceptions' do
......@@ -42,26 +42,26 @@ describe Gitlab::Auth::RequestAuthenticator do
let!(:feed_token_user) { build(:user) }
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)
expect(subject.find_sessionless_user).to eq access_token_user
expect(subject.find_sessionless_user([:api])).to eq access_token_user
end
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)
expect(subject.find_sessionless_user).to eq feed_token_user
expect(subject.find_sessionless_user([:api])).to eq feed_token_user
end
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
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
......@@ -9,7 +9,7 @@ describe Gitlab::Auth::UserAuthFinders do
'rack.input' => ''
}
end
let(:request) { Rack::Request.new(env)}
let(:request) { Rack::Request.new(env) }
def set_param(key, value)
request.update_param(key, value)
......@@ -49,6 +49,7 @@ describe Gitlab::Auth::UserAuthFinders do
describe '#find_user_from_feed_token' do
context 'when the request format is atom' do
before do
env['SCRIPT_NAME'] = 'url.atom'
env['HTTP_ACCEPT'] = 'application/atom+xml'
end
......@@ -56,17 +57,17 @@ describe Gitlab::Auth::UserAuthFinders do
it 'returns user if valid feed_token' do
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
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
it 'returns exception if invalid feed_token' do
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
......@@ -74,34 +75,38 @@ describe Gitlab::Auth::UserAuthFinders do
it 'returns user if valid rssd_token' do
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
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
it 'returns exception if invalid rss_token' do
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
context 'when the request format is not atom' do
it 'returns nil' do
env['SCRIPT_NAME'] = 'json'
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
context 'when the request format is empty' do
it 'the method call does not modify the original value' do
env['SCRIPT_NAME'] = 'url.atom'
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
end
......@@ -111,8 +116,12 @@ describe Gitlab::Auth::UserAuthFinders do
describe '#find_user_from_access_token' do
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
expect(find_personal_access_token).to be_nil
expect(find_user_from_access_token).to be_nil
end
context 'when validate_access_token! returns valid' do
......@@ -131,9 +140,59 @@ describe Gitlab::Auth::UserAuthFinders do
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
let(:personal_access_token) { create(:personal_access_token, user: user) }
before do
env['SCRIPT_NAME'] = 'url.atom'
end
context 'passed as header' do
it 'returns token if valid personal_access_token' do
env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = personal_access_token.token
......
......@@ -10,8 +10,8 @@ describe Gitlab::UrlBlocker do
expect(described_class.blocked_url?(import_url)).to be false
end
it 'allows imports from configured SSH host and port' do
import_url = "http://#{Gitlab.config.gitlab_shell.ssh_host}:#{Gitlab.config.gitlab_shell.ssh_port}/t.git"
it 'allows mirroring from configured SSH host and port' do
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
end
......@@ -29,24 +29,46 @@ describe Gitlab::UrlBlocker do
expect(described_class.blocked_url?('https://gitlab.com/foo/foo.git', protocols: ['http'])).to be true
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
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://[::1]/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://[::]/foo/foo.git')).to be true
end
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.1/foo/foo.git')).to be true
expect(described_class.blocked_url?('https://[::1]/foo/foo.git')).to be true
end
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
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
expect(described_class.blocked_url?('https://0x7f.1:65535/foo/foo.git')).to be true
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
expect(described_class.blocked_url?('https://2130706433:65535/foo/foo.git')).to be true
end
......@@ -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
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
stub_resolv
......@@ -78,7 +121,22 @@ describe Gitlab::UrlBlocker do
end
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' }
context 'true (default)' do
......@@ -109,10 +167,14 @@ describe Gitlab::UrlBlocker do
expect(described_class).not_to be_blocked_url('http://169.254.168.100')
end
# This is blocked due to the hostname check: https://gitlab.com/gitlab-org/gitlab-ce/issues/50227
it 'blocks IPv6 link-local endpoints' do
expect(described_class).to be_blocked_url('http://[::ffff:169.254.169.254]')
expect(described_class).to be_blocked_url('http://[::ffff:169.254.168.100]')
it 'allows 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).not_to be_blocked_url('http://[::ffff:169.254.169.254]')
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
......@@ -135,14 +197,20 @@ describe Gitlab::UrlBlocker do
end
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: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://[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
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
def unstub_domain_resolv
......@@ -183,6 +251,36 @@ describe Gitlab::UrlBlocker do
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
# See https://bugs.ruby-lang.org/issues/4270
def stub_resolv
......
......@@ -522,7 +522,7 @@ describe Notify do
let(:project_snippet) { create(:project_snippet, project: project) }
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
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
describe '#to_ability_name' 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
it 'returns personal_snippet for a personal snippet note' do
......
......@@ -13,6 +13,23 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do
it { is_expected.to belong_to :project }
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
context 'when manual_configuration is enabled' do
before do
......
......@@ -222,6 +222,7 @@ describe Project do
end
end
<<<<<<< HEAD
context '#mark_stuck_remote_mirrors_as_failed!' do
it 'fails stuck remote mirrors' do
project = create(:project, :repository, :remote_mirror)
......@@ -246,6 +247,11 @@ describe Project do
it 'does not allow an invalid URI as import_url' do
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
end
......@@ -316,6 +322,22 @@ describe Project do
expect(project.errors[:import_url].first).to include('Username needs to start with an alphanumeric character')
end
include_context 'invalid urls'
it 'does not allow urls with CR or LF characters' do
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
it 'creates import state when mirror gets enabled' do
project2 = create(:project)
......
......@@ -10,11 +10,50 @@ describe NotePolicy, mdoels: true do
return @policies if @policies
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)
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 note author is not a project member' do
it 'can edit a note' do
......@@ -24,14 +63,48 @@ describe NotePolicy, mdoels: true do
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
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(:resolve_note)
expect(policies).to be_allowed(:read_note)
end
context 'when it is private' do
it 'can not edit nor read the note' do
policies = policies(create(:personal_snippet, :private))
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
context 'when a discussion is locked' do
......
......@@ -25,7 +25,7 @@ describe API::Applications, :api do
it 'does not allow creating an application with the wrong redirect_uri format' 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 }
expect(response).to have_gitlab_http_status(400)
......@@ -33,6 +33,16 @@ describe API::Applications, :api do
expect(json_response['message']['redirect_uri'][0]).to eq('must be an absolute URI.')
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
expect do
post api('/applications', admin_user), redirect_uri: 'http://application.url', scopes: ''
......
......@@ -29,7 +29,7 @@ describe RuboCop::Cop::Migration::AddReference do
expect_offense(<<~RUBY)
call do
add_reference(:projects, :users)
^^^^^^^^^^^^^ `add_reference` requires `index: true`
^^^^^^^^^^^^^ `add_reference` requires `index: true` or `index: { options... }`
end
RUBY
end
......@@ -38,7 +38,7 @@ describe RuboCop::Cop::Migration::AddReference do
expect_offense(<<~RUBY)
def up
add_reference(:projects, :users, index: false)
^^^^^^^^^^^^^ `add_reference` requires `index: true`
^^^^^^^^^^^^^ `add_reference` requires `index: true` or `index: { options... }`
end
RUBY
end
......@@ -50,5 +50,13 @@ describe RuboCop::Cop::Migration::AddReference do
end
RUBY
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
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
"https://prometheus.example.com/api/v1/series?#{query}"
end
def stub_prometheus_request(url, body: {}, status: 200)
def stub_prometheus_request(url, body: {}, status: 200, headers: {})
WebMock.stub_request(:get, url)
.to_return({
status: status,
headers: { 'Content-Type' => 'application/json' },
headers: { 'Content-Type' => 'application/json' }.merge(headers),
body: body.to_json
})
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'
describe UrlValidator do
......@@ -6,6 +8,30 @@ describe UrlValidator do
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
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