Commit feab9afd authored by Douwe Maan's avatar Douwe Maan

Merge branch '44184-issues_ical_feed' into 'master'

Export assigned issues in iCalendar feed

Closes #44184

See merge request gitlab-org/gitlab-ce!17783
parents 2fdd8982 20dfe25c
......@@ -144,6 +144,9 @@ gem 'truncato', '~> 0.7.9'
gem 'bootstrap_form', '~> 2.7.0'
gem 'nokogiri', '~> 1.8.2'
# Calendar rendering
gem 'icalendar'
# Diffs
gem 'diffy', '~> 3.1.0'
......
......@@ -410,6 +410,7 @@ GEM
httpclient (2.8.3)
i18n (0.9.5)
concurrent-ruby (~> 1.0)
icalendar (2.4.1)
ice_nine (0.11.2)
influxdb (0.2.3)
cause
......@@ -1060,6 +1061,7 @@ DEPENDENCIES
html-pipeline (~> 2.7.1)
html2text
httparty (~> 0.13.3)
icalendar
influxdb (~> 0.2)
jira-ruby (~> 1.4)
jquery-atwho-rails (~> 1.3.2)
......
......@@ -17,10 +17,23 @@ module IssuesAction
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def issues_calendar
@issues = issuables_collection
.non_archived
.with_due_date
.limit(100)
respond_to do |format|
format.ics { response.headers['Content-Disposition'] = 'inline' }
end
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
private
def finder_type
(super if defined?(super)) ||
(IssuesFinder if action_name == 'issues')
(IssuesFinder if %w(issues issues_calendar).include?(action_name))
end
end
......@@ -34,12 +34,12 @@ class ProfilesController < Profiles::ApplicationController
redirect_to profile_personal_access_tokens_path
end
def reset_rss_token
def reset_feed_token
Users::UpdateService.new(current_user, user: @user).execute! do |user|
user.reset_rss_token!
user.reset_feed_token!
end
flash[:notice] = "RSS token was successfully reset"
flash[:notice] = 'Feed token was successfully reset'
redirect_to profile_personal_access_tokens_path
end
......
......@@ -10,8 +10,8 @@ class Projects::IssuesController < Projects::ApplicationController
before_action :whitelist_query_limiting, only: [:create, :create_merge_request, :move, :bulk_update]
before_action :check_issues_available!
before_action :issue, except: [:index, :new, :create, :bulk_update]
before_action :set_issuables_index, only: [:index]
before_action :issue, except: [:index, :calendar, :new, :create, :bulk_update]
before_action :set_issuables_index, only: [:index, :calendar]
# Allow write(create) issue
before_action :authorize_create_issue!, only: [:new, :create]
......@@ -39,6 +39,17 @@ class Projects::IssuesController < Projects::ApplicationController
end
end
def calendar
@issues = @issuables
.non_archived
.with_due_date
.limit(100)
respond_to do |format|
format.ics { response.headers['Content-Disposition'] = 'inline' }
end
end
def new
params[:issue] ||= ActionController::Parameters.new(
assignee_ids: ""
......
......@@ -75,6 +75,8 @@ class IssuesFinder < IssuableFinder
items = items.due_between(Date.today.beginning_of_week, Date.today.end_of_week)
elsif filter_by_due_this_month?
items = items.due_between(Date.today.beginning_of_month, Date.today.end_of_month)
elsif filter_by_due_next_month_and_previous_two_weeks?
items = items.due_between(Date.today - 2.weeks, (Date.today + 1.month).end_of_month)
end
end
......@@ -97,6 +99,10 @@ class IssuesFinder < IssuableFinder
due_date? && params[:due_date] == Issue::DueThisMonth.name
end
def filter_by_due_next_month_and_previous_two_weeks?
due_date? && params[:due_date] == Issue::DueNextMonthAndPreviousTwoWeeks.name
end
def due_date?
params[:due_date].present?
end
......
module CalendarHelper
def calendar_url_options
{ format: :ics,
feed_token: current_user.try(:feed_token),
due_date: Issue::DueNextMonthAndPreviousTwoWeeks.name,
sort: 'closest_future_date' }
end
end
module RssHelper
def rss_url_options
{ format: :atom, rss_token: current_user.try(:rss_token) }
{ format: :atom, feed_token: current_user.try(:feed_token) }
end
end
......@@ -14,12 +14,13 @@ class Issue < ActiveRecord::Base
ignore_column :assignee_id, :branch_name, :deleted_at
DueDateStruct = Struct.new(:title, :name).freeze
NoDueDate = DueDateStruct.new('No Due Date', '0').freeze
AnyDueDate = DueDateStruct.new('Any Due Date', '').freeze
Overdue = DueDateStruct.new('Overdue', 'overdue').freeze
DueThisWeek = DueDateStruct.new('Due This Week', 'week').freeze
DueThisMonth = DueDateStruct.new('Due This Month', 'month').freeze
DueDateStruct = Struct.new(:title, :name).freeze
NoDueDate = DueDateStruct.new('No Due Date', '0').freeze
AnyDueDate = DueDateStruct.new('Any Due Date', '').freeze
Overdue = DueDateStruct.new('Overdue', 'overdue').freeze
DueThisWeek = DueDateStruct.new('Due This Week', 'week').freeze
DueThisMonth = DueDateStruct.new('Due This Month', 'month').freeze
DueNextMonthAndPreviousTwoWeeks = DueDateStruct.new('Due Next Month And Previous Two Weeks', 'next_month_and_previous_two_weeks').freeze
belongs_to :project
belongs_to :moved_to, class_name: 'Issue'
......@@ -46,6 +47,7 @@ class Issue < ActiveRecord::Base
scope :unassigned, -> { where('NOT EXISTS (SELECT TRUE FROM issue_assignees WHERE issue_id = issues.id)') }
scope :assigned_to, ->(u) { where('EXISTS (SELECT TRUE FROM issue_assignees WHERE user_id = ? AND issue_id = issues.id)', u.id)}
scope :with_due_date, -> { where('due_date IS NOT NULL') }
scope :without_due_date, -> { where(due_date: nil) }
scope :due_before, ->(date) { where('issues.due_date < ?', date) }
scope :due_between, ->(from_date, to_date) { where('issues.due_date >= ?', from_date).where('issues.due_date <= ?', to_date) }
......@@ -53,6 +55,7 @@ class Issue < ActiveRecord::Base
scope :order_due_date_asc, -> { reorder('issues.due_date IS NULL, issues.due_date ASC') }
scope :order_due_date_desc, -> { reorder('issues.due_date IS NULL, issues.due_date DESC') }
scope :order_closest_future_date, -> { reorder('CASE WHEN due_date >= CURRENT_DATE THEN 0 ELSE 1 END ASC, ABS(CURRENT_DATE - due_date) ASC') }
scope :preload_associations, -> { preload(:labels, project: :namespace) }
......@@ -119,6 +122,7 @@ class Issue < ActiveRecord::Base
def self.sort_by_attribute(method, excluded_labels: [])
case method.to_s
when 'closest_future_date' then order_closest_future_date
when 'due_date' then order_due_date_asc
when 'due_date_asc' then order_due_date_asc
when 'due_date_desc' then order_due_date_desc
......
......@@ -26,7 +26,7 @@ class User < ActiveRecord::Base
ignore_column :authentication_token
add_authentication_token_field :incoming_email_token
add_authentication_token_field :rss_token
add_authentication_token_field :feed_token
default_value_for :admin, false
default_value_for(:external) { Gitlab::CurrentSettings.user_default_external }
......@@ -1167,11 +1167,11 @@ class User < ActiveRecord::Base
save
end
# each existing user needs to have an `rss_token`.
# each existing user needs to have an `feed_token`.
# we do this on read since migrating all existing users is not a feasible
# solution.
def rss_token
ensure_rss_token!
def feed_token
ensure_feed_token!
end
def sync_attribute?(attribute)
......
......@@ -7,8 +7,7 @@
.top-area
= render 'shared/issuable/nav', type: :issues, display_count: !@no_filters_set
.nav-controls
= link_to safe_params.merge(rss_url_options), class: 'btn has-tooltip', data: { container: 'body' }, title: 'Subscribe' do
= icon('rss')
= render 'shared/issuable/feed_buttons'
= render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues
= render 'shared/issuable/filter', type: :issues
......
= render 'issues/issues_calendar', issues: @issues
......@@ -8,10 +8,7 @@
.top-area
= render 'shared/issuable/nav', type: :issues
.nav-controls
= link_to safe_params.merge(rss_url_options), class: 'btn' do
= icon('rss')
%span.icon-label
Subscribe
= render 'shared/issuable/feed_buttons'
= render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", type: :issues
= render 'shared/issuable/search_bar', type: :issues
......
= render 'issues/issues_calendar', issues: @issues
cal = Icalendar::Calendar.new
cal.prodid = '-//GitLab//NONSGML GitLab//EN'
cal.x_wr_calname = 'GitLab Issues'
@issues.includes(project: :namespace).each do |issue|
cal.event do |event|
event.dtstart = Icalendar::Values::Date.new(issue.due_date)
event.summary = "#{issue.title} (in #{issue.project.full_path})"
event.description = "Find out more at #{issue_url(issue)}"
event.url = issue_url(issue)
event.transp = 'TRANSPARENT'
end
end
cal.to_ical
......@@ -34,18 +34,18 @@
.row.prepend-top-default
.col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
RSS token
Feed token
%p
Your RSS token is used to authenticate you when your RSS reader loads a personalized RSS feed, and is included in your personal RSS feed URLs.
Your feed token is used to authenticate you when your RSS reader loads a personalized RSS feed or when when your calendar application loads a personalized calendar, and is included in those feed URLs.
%p
It cannot be used to access any other data.
.col-lg-8.rss-token-reset
= label_tag :rss_token, 'RSS token', class: "label-light"
= text_field_tag :rss_token, current_user.rss_token, class: 'form-control', readonly: true, onclick: 'this.select()'
.col-lg-8.feed-token-reset
= label_tag :feed_token, 'Feed token', class: "label-light"
= text_field_tag :feed_token, current_user.feed_token, class: 'form-control', readonly: true, onclick: 'this.select()'
%p.form-text.text-muted
Keep this token secret. Anyone who gets ahold of it can read activity and issue RSS feeds as if they were you.
Keep this token secret. Anyone who gets ahold of it can read activity and issue RSS feeds or your calendar feed as if they were you.
You should
= link_to 'reset it', [:reset, :rss_token, :profile], method: :put, data: { confirm: 'Are you sure? Any RSS URLs currently in use will stop working.' }
= link_to 'reset it', [:reset, :feed_token, :profile], method: :put, data: { confirm: 'Are you sure? Any RSS or calendar URLs currently in use will stop working.' }
if that ever happens.
- if incoming_email_token_enabled?
......
= link_to safe_params.merge(rss_url_options), class: 'btn btn-default append-right-10 has-tooltip', title: 'Subscribe' do
= icon('rss')
= render 'shared/issuable/feed_buttons'
- if @can_bulk_update
= button_tag "Edit issues", class: "btn btn-default append-right-10 js-bulk-update-toggle"
- if show_new_issue_link?(@project)
......
= render 'issues/issues_calendar', issues: @issues
<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M15 5v7a3 3 0 0 1-3 3H4a3 3 0 0 1-3-3V5a2 2 0 0 1 2-2h1V2a1 1 0 1 1 2 0v1h4V2a1 1 0 1 1 2 0v1h1a2 2 0 0 1 2 2zM3 6v6a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V6H3zm2 2h2a1 1 0 1 1 0 2H5a1 1 0 1 1 0-2z" fill="#000" fill-rule="evenodd"/></svg>
\ No newline at end of file
= link_to safe_params.merge(rss_url_options), class: 'btn has-tooltip', data: { container: 'body' }, title: 'Subscribe to RSS feed' do
= icon('rss')
= link_to safe_params.merge(calendar_url_options), class: 'btn has-tooltip', data: { container: 'body' }, title: 'Subscribe to calendar' do
= custom_icon('icon_calendar')
---
title: Export assigned issues in iCalendar feed
merge_request: 17783
author: Imre Farkas
type: added
resource :dashboard, controller: 'dashboard', only: [] do
get :issues, action: :issues_calendar, constraints: lambda { |req| req.format == :ics }
get :issues
get :merge_requests
get :activity
......
......@@ -5,9 +5,10 @@ end
constraints(::Constraints::GroupUrlConstrainer.new) do
scope(path: 'groups/*id',
controller: :groups,
constraints: { id: Gitlab::PathRegex.full_namespace_route_regex, format: /(html|json|atom)/ }) do
constraints: { id: Gitlab::PathRegex.full_namespace_route_regex, format: /(html|json|atom|ics)/ }) do
scope(path: '-') do
get :edit, as: :edit_group
get :issues, as: :issues_group_calendar, action: :issues_calendar, constraints: lambda { |req| req.format == :ics }
get :issues, as: :issues_group
get :merge_requests, as: :merge_requests_group
get :projects, as: :projects_group
......
......@@ -7,7 +7,7 @@ resource :profile, only: [:show, :update] do
get :applications, to: 'oauth/applications#index'
put :reset_incoming_email_token
put :reset_rss_token
put :reset_feed_token
put :update_username
end
......
......@@ -353,6 +353,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
get :issues, to: 'issues#calendar', constraints: lambda { |req| req.format == :ics }
resources :issues, concerns: :awardable, constraints: { id: /\d+/ } do
member do
post :toggle_subscription
......
class RenameUsersRssTokenToFeedToken < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
rename_column_concurrently :users, :rss_token, :feed_token
end
def down
cleanup_concurrent_column_rename :users, :feed_token, :rss_token
end
end
class CleanupUsersRssTokenRename < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
def up
cleanup_concurrent_column_rename :users, :rss_token, :feed_token
end
def down
rename_column_concurrently :users, :feed_token, :rss_token
end
end
......@@ -2082,9 +2082,9 @@ ActiveRecord::Schema.define(version: 20180529093006) do
t.date "last_activity_on"
t.boolean "notified_of_own_activity"
t.string "preferred_language"
t.string "rss_token"
t.integer "theme_id", limit: 2
t.integer "accepted_term_id"
t.string "feed_token"
end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
......@@ -2092,12 +2092,12 @@ ActiveRecord::Schema.define(version: 20180529093006) do
add_index "users", ["created_at"], name: "index_users_on_created_at", using: :btree
add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
add_index "users", ["email"], name: "index_users_on_email_trigram", using: :gin, opclasses: {"email"=>"gin_trgm_ops"}
add_index "users", ["feed_token"], name: "index_users_on_feed_token", using: :btree
add_index "users", ["ghost"], name: "index_users_on_ghost", using: :btree
add_index "users", ["incoming_email_token"], name: "index_users_on_incoming_email_token", using: :btree
add_index "users", ["name"], name: "index_users_on_name", using: :btree
add_index "users", ["name"], name: "index_users_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"}
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
add_index "users", ["rss_token"], name: "index_users_on_rss_token", using: :btree
add_index "users", ["state"], name: "index_users_on_state", using: :btree
add_index "users", ["username"], name: "index_users_on_username", using: :btree
add_index "users", ["username"], name: "index_users_on_username_trigram", using: :gin, opclasses: {"username"=>"gin_trgm_ops"}
......
......@@ -39,5 +39,13 @@ The day before an open issue is due, an email will be sent to all participants
of the issue. Both the due date and the day before are calculated using the
server's timezone.
Issues with due dates can also be exported as an iCalendar feed. The URL of the
feed can be added to calendar applications. The feed is accessible by clicking
on the _Subscribe to calendar_ button on the following pages:
- on the **Assigned Issues** page that is linked on the right-hand side of the
GitLab header
- on the **Project Issues** page
- on the **Group Issues** page
[ce-3614]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3614
[permissions]: ../../permissions.md#project
......@@ -16,7 +16,7 @@ module Gitlab
end
def find_sessionless_user
find_user_from_access_token || find_user_from_rss_token
find_user_from_access_token || find_user_from_feed_token
rescue Gitlab::Auth::AuthenticationError
nil
end
......
......@@ -25,13 +25,15 @@ module Gitlab
current_request.env['warden']&.authenticate if verified_request?
end
def find_user_from_rss_token
return unless current_request.path.ends_with?('.atom') || current_request.format.atom?
def find_user_from_feed_token
return unless rss_request? || ics_request?
token = current_request.params[:rss_token].presence
# 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
token = current_request.params[:feed_token].presence || current_request.params[:rss_token].presence
return unless token
User.find_by_rss_token(token) || raise(UnauthorizedError)
User.find_by_feed_token(token) || raise(UnauthorizedError)
end
def find_user_from_access_token
......@@ -104,6 +106,14 @@ module Gitlab
def current_request
@current_request ||= ensure_action_dispatch_request(request)
end
def rss_request?
current_request.path.ends_with?('.atom') || current_request.format.atom?
end
def ics_request?
current_request.path.ends_with?('.ics') || current_request.format.ics?
end
end
end
end
......@@ -31,27 +31,27 @@ map $http_upgrade $connection_upgrade_gitlab {
log_format gitlab_access $remote_addr - $remote_user [$time_local] "$request_method $gitlab_filtered_request_uri $server_protocol" $status $body_bytes_sent "$gitlab_filtered_http_referer" "$http_user_agent";
## Remove private_token from the request URI
# In: /foo?private_token=unfiltered&authenticity_token=unfiltered&rss_token=unfiltered&...
# Out: /foo?private_token=[FILTERED]&authenticity_token=unfiltered&rss_token=unfiltered&...
# In: /foo?private_token=unfiltered&authenticity_token=unfiltered&feed_token=unfiltered&...
# Out: /foo?private_token=[FILTERED]&authenticity_token=unfiltered&feed_token=unfiltered&...
map $request_uri $gitlab_temp_request_uri_1 {
default $request_uri;
~(?i)^(?<start>.*)(?<temp>[\?&]private[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
}
## Remove authenticity_token from the request URI
# In: /foo?private_token=[FILTERED]&authenticity_token=unfiltered&rss_token=unfiltered&...
# Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=unfiltered&...
# In: /foo?private_token=[FILTERED]&authenticity_token=unfiltered&feed_token=unfiltered&...
# Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&feed_token=unfiltered&...
map $gitlab_temp_request_uri_1 $gitlab_temp_request_uri_2 {
default $gitlab_temp_request_uri_1;
~(?i)^(?<start>.*)(?<temp>[\?&]authenticity[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
}
## Remove rss_token from the request URI
# In: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=unfiltered&...
# Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=[FILTERED]&...
## Remove feed_token from the request URI
# In: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&feed_token=unfiltered&...
# Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&feed_token=[FILTERED]&...
map $gitlab_temp_request_uri_2 $gitlab_filtered_request_uri {
default $gitlab_temp_request_uri_2;
~(?i)^(?<start>.*)(?<temp>[\?&]rss[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
~(?i)^(?<start>.*)(?<temp>[\?&]feed[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
}
## A version of the referer without the query string
......
......@@ -36,27 +36,27 @@ map $http_upgrade $connection_upgrade_gitlab_ssl {
log_format gitlab_ssl_access $remote_addr - $remote_user [$time_local] "$request_method $gitlab_ssl_filtered_request_uri $server_protocol" $status $body_bytes_sent "$gitlab_ssl_filtered_http_referer" "$http_user_agent";
## Remove private_token from the request URI
# In: /foo?private_token=unfiltered&authenticity_token=unfiltered&rss_token=unfiltered&...
# Out: /foo?private_token=[FILTERED]&authenticity_token=unfiltered&rss_token=unfiltered&...
# In: /foo?private_token=unfiltered&authenticity_token=unfiltered&feed_token=unfiltered&...
# Out: /foo?private_token=[FILTERED]&authenticity_token=unfiltered&feed_token=unfiltered&...
map $request_uri $gitlab_ssl_temp_request_uri_1 {
default $request_uri;
~(?i)^(?<start>.*)(?<temp>[\?&]private[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
}
## Remove authenticity_token from the request URI
# In: /foo?private_token=[FILTERED]&authenticity_token=unfiltered&rss_token=unfiltered&...
# Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=unfiltered&...
# In: /foo?private_token=[FILTERED]&authenticity_token=unfiltered&feed_token=unfiltered&...
# Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&feed_token=unfiltered&...
map $gitlab_ssl_temp_request_uri_1 $gitlab_ssl_temp_request_uri_2 {
default $gitlab_ssl_temp_request_uri_1;
~(?i)^(?<start>.*)(?<temp>[\?&]authenticity[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
}
## Remove rss_token from the request URI
# In: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=unfiltered&...
# Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=[FILTERED]&...
## Remove feed_token from the request URI
# In: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&feed_token=unfiltered&...
# Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&feed_token=[FILTERED]&...
map $gitlab_ssl_temp_request_uri_2 $gitlab_ssl_filtered_request_uri {
default $gitlab_ssl_temp_request_uri_2;
~(?i)^(?<start>.*)(?<temp>[\?&]rss[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
~(?i)^(?<start>.*)(?<temp>[\?&]feed[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
}
## A version of the referer without the query string
......
......@@ -6,9 +6,9 @@ namespace :tokens do
reset_all_users_token(:reset_incoming_email_token!)
end
desc "Reset all GitLab RSS tokens"
task reset_all_rss: :environment do
reset_all_users_token(:reset_rss_token!)
desc "Reset all GitLab feed tokens"
task reset_all_feed: :environment do
reset_all_users_token(:reset_feed_token!)
end
def reset_all_users_token(reset_token_method)
......@@ -31,8 +31,8 @@ class TmpUser < ActiveRecord::Base
save!(validate: false)
end
def reset_rss_token!
write_new_token(:rss_token)
def reset_feed_token!
write_new_token(:feed_token)
save!(validate: false)
end
end
......@@ -146,35 +146,43 @@ describe ApplicationController do
end
end
describe '#authenticate_user_from_rss_token' do
describe "authenticating a user from an RSS token" do
describe '#authenticate_sessionless_user!' do
describe 'authenticating a user from a feed token' do
controller(described_class) do
def index
render text: 'authenticated'
end
end
context "when the 'rss_token' param is populated with the RSS token" do
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
get :index, rss_token: user.rss_token, format: :atom
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 not atom' do
context 'when the request format is ics' do
it "logs the user in" do
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
get :index, rss_token: user.rss_token
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 'rss_token' param is populated with an invalid RSS token" do
context "when the 'feed_token' param is populated with an invalid feed token" do
it "doesn't log the user" do
get :index, rss_token: "token"
get :index, feed_token: 'token', format: :atom
expect(response.status).not_to eq 200
expect(response.body).not_to eq 'authenticated'
end
......@@ -454,7 +462,7 @@ describe ApplicationController do
end
it 'renders a 403 when the sessionless user did not accept the terms' do
get :index, rss_token: user.rss_token, format: :atom
get :index, feed_token: user.feed_token, format: :atom
expect(response).to have_gitlab_http_status(403)
end
......@@ -462,7 +470,7 @@ describe ApplicationController do
it 'renders a 200 when the sessionless user accepted the terms' do
accept_terms(user)
get :index, rss_token: user.rss_token, format: :atom
get :index, feed_token: user.feed_token, format: :atom
expect(response).to have_gitlab_http_status(200)
end
......
......@@ -12,10 +12,6 @@ FactoryBot.define do
user.notification_email = user.email
end
before(:create) do |user|
user.ensure_rss_token
end
trait :admin do
admin true
end
......
......@@ -31,20 +31,20 @@ describe "Dashboard Issues Feed" do
expect(body).to have_selector('title', text: "#{user.name} issues")
end
it "renders atom feed via RSS token" do
visit issues_dashboard_path(:atom, rss_token: user.rss_token, assignee_id: user.id)
it "renders atom feed via feed token" do
visit issues_dashboard_path(:atom, feed_token: user.feed_token, assignee_id: user.id)
expect(response_headers['Content-Type']).to have_content('application/atom+xml')
expect(body).to have_selector('title', text: "#{user.name} issues")
end
it "renders atom feed with url parameters" do
visit issues_dashboard_path(:atom, rss_token: user.rss_token, state: 'opened', assignee_id: user.id)
visit issues_dashboard_path(:atom, feed_token: user.feed_token, state: 'opened', assignee_id: user.id)
link = find('link[type="application/atom+xml"]')
params = CGI.parse(URI.parse(link[:href]).query)
expect(params).to include('rss_token' => [user.rss_token])
expect(params).to include('feed_token' => [user.feed_token])
expect(params).to include('state' => ['opened'])
expect(params).to include('assignee_id' => [user.id.to_s])
end
......@@ -53,7 +53,7 @@ describe "Dashboard Issues Feed" do
let!(:issue2) { create(:issue, author: user, assignees: [assignee], project: project2, description: 'test desc') }
it "renders issue fields" do
visit issues_dashboard_path(:atom, rss_token: user.rss_token, assignee_id: assignee.id)
visit issues_dashboard_path(:atom, feed_token: user.feed_token, assignee_id: assignee.id)
entry = find(:xpath, "//feed/entry[contains(summary/text(),'#{issue2.title}')]")
......@@ -76,7 +76,7 @@ describe "Dashboard Issues Feed" do
end
it "renders issue label and milestone info" do
visit issues_dashboard_path(:atom, rss_token: user.rss_token, assignee_id: assignee.id)
visit issues_dashboard_path(:atom, feed_token: user.feed_token, assignee_id: assignee.id)
entry = find(:xpath, "//feed/entry[contains(summary/text(),'#{issue1.title}')]")
......
......@@ -13,9 +13,9 @@ describe "Dashboard Feed" do
end
end
context "projects atom feed via RSS token" do
context "projects atom feed via feed token" do
it "renders projects atom feed" do
visit dashboard_projects_path(:atom, rss_token: user.rss_token)
visit dashboard_projects_path(:atom, feed_token: user.feed_token)
expect(body).to have_selector('feed title')
end
end
......@@ -29,7 +29,7 @@ describe "Dashboard Feed" do
project.add_master(user)
issue_event(issue, user)
note_event(note, user)
visit dashboard_projects_path(:atom, rss_token: user.rss_token)
visit dashboard_projects_path(:atom, feed_token: user.feed_token)
end
it "has issue opened event" do
......
......@@ -45,10 +45,10 @@ describe 'Issues Feed' do
end
end
context 'when authenticated via RSS token' do
context 'when authenticated via feed token' do
it 'renders atom feed' do
visit project_issues_path(project, :atom,
rss_token: user.rss_token)
feed_token: user.feed_token)
expect(response_headers['Content-Type'])
.to have_content('application/atom+xml')
......@@ -61,24 +61,23 @@ describe 'Issues Feed' do
end
it "renders atom feed with url parameters for project issues" do
visit project_issues_path(project,
:atom, rss_token: user.rss_token, state: 'opened', assignee_id: user.id)
visit project_issues_path(project, :atom, feed_token: user.feed_token, state: 'opened', assignee_id: user.id)
link = find('link[type="application/atom+xml"]')
params = CGI.parse(URI.parse(link[:href]).query)
expect(params).to include('rss_token' => [user.rss_token])
expect(params).to include('feed_token' => [user.feed_token])
expect(params).to include('state' => ['opened'])
expect(params).to include('assignee_id' => [user.id.to_s])
end
it "renders atom feed with url parameters for group issues" do
visit issues_group_path(group, :atom, rss_token: user.rss_token, state: 'opened', assignee_id: user.id)
visit issues_group_path(group, :atom, feed_token: user.feed_token, state: 'opened', assignee_id: user.id)
link = find('link[type="application/atom+xml"]')
params = CGI.parse(URI.parse(link[:href]).query)
expect(params).to include('rss_token' => [user.rss_token])
expect(params).to include('feed_token' => [user.feed_token])
expect(params).to include('state' => ['opened'])
expect(params).to include('assignee_id' => [user.id.to_s])
end
......
......@@ -13,9 +13,9 @@ describe "User Feed" do
end
end
context 'user atom feed via RSS token' do
context 'user atom feed via feed token' do
it "renders user atom feed" do
visit user_path(user, :atom, rss_token: user.rss_token)
visit user_path(user, :atom, feed_token: user.feed_token)
expect(body).to have_selector('feed title')
end
end
......@@ -51,7 +51,7 @@ describe "User Feed" do
issue_event(issue, user)
note_event(note, user)
merge_request_event(merge_request, user)
visit user_path(user, :atom, rss_token: user.rss_token)
visit user_path(user, :atom, feed_token: user.feed_token)
end
it 'has issue opened event' do
......
......@@ -12,8 +12,8 @@ feature 'Dashboard > Activity' do
visit activity_dashboard_path
end
it_behaves_like "it has an RSS button with current_user's RSS token"
it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
it_behaves_like "it has an RSS button with current_user's feed token"
it_behaves_like "an autodiscoverable RSS feed with current_user's feed token"
end
context 'event filters', :js do
......
......@@ -47,15 +47,15 @@ feature 'Dashboard Issues filtering', :js do
it 'updates atom feed link' do
visit_issues(milestone_title: '', assignee_id: user.id)
link = find('.nav-controls a[title="Subscribe"]')
link = find('.nav-controls a[title="Subscribe to RSS feed"]')
params = CGI.parse(URI.parse(link[:href]).query)
auto_discovery_link = find('link[type="application/atom+xml"]', visible: false)
auto_discovery_params = CGI.parse(URI.parse(auto_discovery_link[:href]).query)
expect(params).to include('rss_token' => [user.rss_token])
expect(params).to include('feed_token' => [user.feed_token])
expect(params).to include('milestone_title' => [''])
expect(params).to include('assignee_id' => [user.id.to_s])
expect(auto_discovery_params).to include('rss_token' => [user.rss_token])
expect(auto_discovery_params).to include('feed_token' => [user.feed_token])
expect(auto_discovery_params).to include('milestone_title' => [''])
expect(auto_discovery_params).to include('assignee_id' => [user.id.to_s])
end
......
......@@ -56,8 +56,8 @@ RSpec.describe 'Dashboard Issues' do
expect(page).to have_current_path(issues_dashboard_url(assignee_id: current_user.id, state: 'closed'), url: true)
end
it_behaves_like "it has an RSS button with current_user's RSS token"
it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
it_behaves_like "it has an RSS button with current_user's feed token"
it_behaves_like "an autodiscoverable RSS feed with current_user's feed token"
end
describe 'new issue dropdown' do
......
......@@ -10,7 +10,7 @@ feature 'Dashboard Projects' do
sign_in(user)
end
it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token" do
it_behaves_like "an autodiscoverable RSS feed with current_user's feed token" do
before do
visit dashboard_projects_path
end
......
......@@ -15,8 +15,8 @@ feature 'Group activity page' do
visit path
end
it_behaves_like "it has an RSS button with current_user's RSS token"
it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
it_behaves_like "it has an RSS button with current_user's feed token"
it_behaves_like "an autodiscoverable RSS feed with current_user's feed token"
end
context 'when project is in the group', :js do
......@@ -39,7 +39,7 @@ feature 'Group activity page' do
visit path
end
it_behaves_like "it has an RSS button without an RSS token"
it_behaves_like "an autodiscoverable RSS feed without an RSS token"
it_behaves_like "it has an RSS button without a feed token"
it_behaves_like "an autodiscoverable RSS feed without a feed token"
end
end
......@@ -16,17 +16,21 @@ feature 'Group issues page' do
let(:access_level) { ProjectFeature::ENABLED }
context 'when signed in' do
let(:user) { user_in_group }
it_behaves_like "it has an RSS button with current_user's RSS token"
it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
let(:user) do
user_in_group.ensure_feed_token
user_in_group.save!
user_in_group
end
it_behaves_like "it has an RSS button with current_user's feed token"
it_behaves_like "an autodiscoverable RSS feed with current_user's feed token"
end
context 'when signed out' do
let(:user) { nil }
it_behaves_like "it has an RSS button without an RSS token"
it_behaves_like "an autodiscoverable RSS feed without an RSS token"
it_behaves_like "it has an RSS button without a feed token"
it_behaves_like "an autodiscoverable RSS feed without a feed token"
end
end
......
......@@ -14,7 +14,7 @@ feature 'Group show page' do
visit path
end
it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
it_behaves_like "an autodiscoverable RSS feed with current_user's feed token"
context 'when group does not exist' do
let(:path) { group_path('not-exist') }
......@@ -29,7 +29,7 @@ feature 'Group show page' do
visit path
end
it_behaves_like "an autodiscoverable RSS feed without an RSS token"
it_behaves_like "an autodiscoverable RSS feed without a feed token"
end
context 'when group has a public project', :js do
......
require 'spec_helper'
describe 'Dashboard Issues Calendar Feed' do
describe 'GET /issues' do
let!(:user) { create(:user, email: 'private1@example.com', public_email: 'public1@example.com') }
let!(:assignee) { create(:user, email: 'private2@example.com', public_email: 'public2@example.com') }
let!(:project) { create(:project) }
before do
project.add_master(user)
end
context 'when authenticated' do
it 'renders calendar feed' do
sign_in user
visit issues_dashboard_path(:ics)
expect(response_headers['Content-Type']).to have_content('text/calendar')
expect(response_headers['Content-Disposition']).to have_content('inline')
expect(body).to have_text('BEGIN:VCALENDAR')
end
end
context 'when authenticated via personal access token' do
it 'renders calendar feed' do
personal_access_token = create(:personal_access_token, user: user)
visit issues_dashboard_path(:ics, private_token: personal_access_token.token)
expect(response_headers['Content-Type']).to have_content('text/calendar')
expect(response_headers['Content-Disposition']).to have_content('inline')
expect(body).to have_text('BEGIN:VCALENDAR')
end
end
context 'when authenticated via feed token' do
it 'renders calendar feed' do
visit issues_dashboard_path(:ics, feed_token: user.feed_token)
expect(response_headers['Content-Type']).to have_content('text/calendar')
expect(response_headers['Content-Disposition']).to have_content('inline')
expect(body).to have_text('BEGIN:VCALENDAR')
end
end
context 'issue with due date' do
let!(:issue) do
create(:issue, author: user, assignees: [assignee], project: project, title: 'test title',
description: 'test desc', due_date: Date.tomorrow)
end
it 'renders issue fields' do
visit issues_dashboard_path(:ics, feed_token: user.feed_token)
expect(body).to have_text("SUMMARY:test title (in #{project.full_path})")
# line length for ics is 75 chars
expected_description = "DESCRIPTION:Find out more at #{issue_url(issue)}".insert(75, "\r\n")
expect(body).to have_text(expected_description)
expect(body).to have_text("DTSTART;VALUE=DATE:#{Date.tomorrow.strftime('%Y%m%d')}")
expect(body).to have_text("URL:#{issue_url(issue)}")
expect(body).to have_text('TRANSP:TRANSPARENT')
end
end
end
end
require 'spec_helper'
describe 'Group Issues Calendar Feed' do
describe 'GET /issues' do
let!(:user) { create(:user, email: 'private1@example.com', public_email: 'public1@example.com') }
let!(:assignee) { create(:user, email: 'private2@example.com', public_email: 'public2@example.com') }
let!(:group) { create(:group) }
let!(:project) { create(:project, group: group) }
before do
project.add_developer(user)
group.add_developer(user)
end
context 'when authenticated' do
it 'renders calendar feed' do
sign_in user
visit issues_group_path(group, :ics)
expect(response_headers['Content-Type']).to have_content('text/calendar')
expect(response_headers['Content-Disposition']).to have_content('inline')
expect(body).to have_text('BEGIN:VCALENDAR')
end
end
context 'when authenticated via personal access token' do
it 'renders calendar feed' do
personal_access_token = create(:personal_access_token, user: user)
visit issues_group_path(group, :ics, private_token: personal_access_token.token)
expect(response_headers['Content-Type']).to have_content('text/calendar')
expect(response_headers['Content-Disposition']).to have_content('inline')
expect(body).to have_text('BEGIN:VCALENDAR')
end
end
context 'when authenticated via feed token' do
it 'renders calendar feed' do
visit issues_group_path(group, :ics, feed_token: user.feed_token)
expect(response_headers['Content-Type']).to have_content('text/calendar')
expect(response_headers['Content-Disposition']).to have_content('inline')
expect(body).to have_text('BEGIN:VCALENDAR')
end
end
context 'issue with due date' do
let!(:issue) do
create(:issue, author: user, assignees: [assignee], project: project, title: 'test title',
description: 'test desc', due_date: Date.tomorrow)
end
it 'renders issue fields' do
visit issues_group_path(group, :ics, feed_token: user.feed_token)
expect(body).to have_text("SUMMARY:test title (in #{project.full_path})")
# line length for ics is 75 chars
expected_description = "DESCRIPTION:Find out more at #{issue_url(issue)}".insert(75, "\r\n")
expect(body).to have_text(expected_description)
expect(body).to have_text("DTSTART;VALUE=DATE:#{Date.tomorrow.strftime('%Y%m%d')}")
expect(body).to have_text("URL:#{issue_url(issue)}")
expect(body).to have_text('TRANSP:TRANSPARENT')
end
end
end
end
require 'spec_helper'
describe 'Project Issues Calendar Feed' do
describe 'GET /issues' do
let!(:user) { create(:user, email: 'private1@example.com', public_email: 'public1@example.com') }
let!(:assignee) { create(:user, email: 'private2@example.com', public_email: 'public2@example.com') }
let!(:project) { create(:project) }
let!(:issue) { create(:issue, author: user, assignees: [assignee], project: project) }
before do
project.add_developer(user)
end
context 'when authenticated' do
it 'renders calendar feed' do
sign_in user
visit project_issues_path(project, :ics)
expect(response_headers['Content-Type']).to have_content('text/calendar')
expect(response_headers['Content-Disposition']).to have_content('inline')
expect(body).to have_text('BEGIN:VCALENDAR')
end
end
context 'when authenticated via personal access token' do
it 'renders calendar feed' do
personal_access_token = create(:personal_access_token, user: user)
visit project_issues_path(project, :ics, private_token: personal_access_token.token)
expect(response_headers['Content-Type']).to have_content('text/calendar')
expect(response_headers['Content-Disposition']).to have_content('inline')
expect(body).to have_text('BEGIN:VCALENDAR')
end
end
context 'when authenticated via feed token' do
it 'renders calendar feed' do
visit project_issues_path(project, :ics, feed_token: user.feed_token)
expect(response_headers['Content-Type']).to have_content('text/calendar')
expect(response_headers['Content-Disposition']).to have_content('inline')
expect(body).to have_text('BEGIN:VCALENDAR')
end
end
context 'issue with due date' do
let!(:issue) do
create(:issue, author: user, assignees: [assignee], project: project, title: 'test title',
description: 'test desc', due_date: Date.tomorrow)
end
it 'renders issue fields' do
visit project_issues_path(project, :ics, feed_token: user.feed_token)
expect(body).to have_text("SUMMARY:test title (in #{project.full_path})")
# line length for ics is 75 chars
expected_description = "DESCRIPTION:Find out more at #{issue_url(issue)}".insert(75, "\r\n")
expect(body).to have_text(expected_description)
expect(body).to have_text("DTSTART;VALUE=DATE:#{Date.tomorrow.strftime('%Y%m%d')}")
expect(body).to have_text("URL:#{issue_url(issue)}")
expect(body).to have_text('TRANSP:TRANSPARENT')
end
end
end
end
......@@ -468,13 +468,13 @@ describe 'Filter issues', :js do
it "for #{type}" do
visit path
link = find_link('Subscribe')
link = find_link('Subscribe to RSS feed')
params = CGI.parse(URI.parse(link[:href]).query)
auto_discovery_link = find('link[type="application/atom+xml"]', visible: false)
auto_discovery_params = CGI.parse(URI.parse(auto_discovery_link[:href]).query)
expected = {
'rss_token' => [user.rss_token],
'feed_token' => [user.feed_token],
'milestone_title' => [milestone.title],
'assignee_id' => [user.id.to_s]
}
......
......@@ -340,6 +340,20 @@ describe 'Issues' do
expect(page).to have_content('baz')
end
end
it 'filters by due next month and previous two weeks' do
foo.update(due_date: Date.today - 4.weeks)
bar.update(due_date: (Date.today + 2.months).beginning_of_month)
baz.update(due_date: Date.yesterday)
visit project_issues_path(project, due_date: Issue::DueNextMonthAndPreviousTwoWeeks.name)
page.within '.issues-holder' do
expect(page).not_to have_content('foo')
expect(page).not_to have_content('bar')
expect(page).to have_content('baz')
end
end
end
describe 'sorting by milestone' do
......
......@@ -56,21 +56,21 @@ describe 'Profile account page', :js do
end
end
describe 'when I reset RSS token' do
describe 'when I reset feed token' do
before do
visit profile_personal_access_tokens_path
end
it 'resets RSS token' do
within('.rss-token-reset') do
previous_token = find("#rss_token").value
it 'resets feed token' do
within('.feed-token-reset') do
previous_token = find("#feed_token").value
accept_confirm { click_link('reset it') }
expect(find('#rss_token').value).not_to eq(previous_token)
expect(find('#feed_token').value).not_to eq(previous_token)
end
expect(page).to have_content 'RSS token was successfully reset'
expect(page).to have_content 'Feed token was successfully reset'
end
end
......
......@@ -15,7 +15,7 @@ feature 'Project Activity RSS' do
visit path
end
it_behaves_like "it has an RSS button with current_user's RSS token"
it_behaves_like "it has an RSS button with current_user's feed token"
end
context 'when signed out' do
......@@ -23,6 +23,6 @@ feature 'Project Activity RSS' do
visit path
end
it_behaves_like "it has an RSS button without an RSS token"
it_behaves_like "it has an RSS button without a feed token"
end
end
......@@ -12,8 +12,8 @@ feature 'Project Commits RSS' do
visit path
end
it_behaves_like "it has an RSS button with current_user's RSS token"
it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
it_behaves_like "it has an RSS button with current_user's feed token"
it_behaves_like "an autodiscoverable RSS feed with current_user's feed token"
end
context 'when signed out' do
......@@ -21,7 +21,7 @@ feature 'Project Commits RSS' do
visit path
end
it_behaves_like "it has an RSS button without an RSS token"
it_behaves_like "an autodiscoverable RSS feed without an RSS token"
it_behaves_like "it has an RSS button without a feed token"
it_behaves_like "an autodiscoverable RSS feed without a feed token"
end
end
......@@ -17,8 +17,8 @@ feature 'Project Issues RSS' do
visit path
end
it_behaves_like "it has an RSS button with current_user's RSS token"
it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
it_behaves_like "it has an RSS button with current_user's feed token"
it_behaves_like "an autodiscoverable RSS feed with current_user's feed token"
end
context 'when signed out' do
......@@ -26,7 +26,7 @@ feature 'Project Issues RSS' do
visit path
end
it_behaves_like "it has an RSS button without an RSS token"
it_behaves_like "an autodiscoverable RSS feed without an RSS token"
it_behaves_like "it has an RSS button without a feed token"
it_behaves_like "an autodiscoverable RSS feed without a feed token"
end
end
......@@ -12,7 +12,7 @@ feature 'Projects > Show > RSS' do
visit path
end
it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
it_behaves_like "an autodiscoverable RSS feed with current_user's feed token"
end
context 'when signed out' do
......@@ -20,6 +20,6 @@ feature 'Projects > Show > RSS' do
visit path
end
it_behaves_like "an autodiscoverable RSS feed without an RSS token"
it_behaves_like "an autodiscoverable RSS feed without a feed token"
end
end
......@@ -12,7 +12,7 @@ feature 'Project Tree RSS' do
visit path
end
it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
it_behaves_like "an autodiscoverable RSS feed with current_user's feed token"
end
context 'when signed out' do
......@@ -20,6 +20,6 @@ feature 'Project Tree RSS' do
visit path
end
it_behaves_like "an autodiscoverable RSS feed without an RSS token"
it_behaves_like "an autodiscoverable RSS feed without a feed token"
end
end
......@@ -10,7 +10,7 @@ feature 'User RSS' do
visit path
end
it_behaves_like "it has an RSS button with current_user's RSS token"
it_behaves_like "it has an RSS button with current_user's feed token"
end
context 'when signed out' do
......@@ -18,6 +18,6 @@ feature 'User RSS' do
visit path
end
it_behaves_like "it has an RSS button without an RSS token"
it_behaves_like "it has an RSS button without a feed token"
end
end
require 'spec_helper'
describe CalendarHelper do
describe '#calendar_url_options' do
context 'when signed in' do
it "includes the current_user's feed_token" do
current_user = create(:user)
allow(helper).to receive(:current_user).and_return(current_user)
expect(helper.calendar_url_options).to include feed_token: current_user.feed_token
end
end
context 'when signed out' do
it "does not have a feed_token" do
allow(helper).to receive(:current_user).and_return(nil)
expect(helper.calendar_url_options[:feed_token]).to be_nil
end
end
end
end
......@@ -3,17 +3,17 @@ require 'spec_helper'
describe RssHelper do
describe '#rss_url_options' do
context 'when signed in' do
it "includes the current_user's rss_token" do
it "includes the current_user's feed_token" do
current_user = create(:user)
allow(helper).to receive(:current_user).and_return(current_user)
expect(helper.rss_url_options).to include rss_token: current_user.rss_token
expect(helper.rss_url_options).to include feed_token: current_user.feed_token
end
end
context 'when signed out' do
it "does not have an rss_token" do
it "does not have a feed_token" do
allow(helper).to receive(:current_user).and_return(nil)
expect(helper.rss_url_options[:rss_token]).to be_nil
expect(helper.rss_url_options[:feed_token]).to be_nil
end
end
end
......
......@@ -39,19 +39,19 @@ describe Gitlab::Auth::RequestAuthenticator do
describe '#find_sessionless_user' do
let!(:access_token_user) { build(:user) }
let!(:rss_token_user) { build(:user) }
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_rss_token).and_return(rss_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
end
it 'returns rss_token user if no access_token user found' do
allow_any_instance_of(described_class).to receive(:find_user_from_rss_token).and_return(rss_token_user)
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 rss_token_user
expect(subject.find_sessionless_user).to eq feed_token_user
end
it 'returns nil if no user found' do
......
......@@ -46,34 +46,54 @@ describe Gitlab::Auth::UserAuthFinders do
end
end
describe '#find_user_from_rss_token' do
describe '#find_user_from_feed_token' do
context 'when the request format is atom' do
before do
env['HTTP_ACCEPT'] = 'application/atom+xml'
end
it 'returns user if valid rss_token' do
set_param(:rss_token, user.rss_token)
context 'when feed_token param is provided' do
it 'returns user if valid feed_token' do
set_param(:feed_token, user.feed_token)
expect(find_user_from_rss_token).to eq user
end
expect(find_user_from_feed_token).to eq user
end
it 'returns nil if feed_token is blank' do
expect(find_user_from_feed_token).to be_nil
end
it 'returns exception if invalid feed_token' do
set_param(:feed_token, 'invalid_token')
it 'returns nil if rss_token is blank' do
expect(find_user_from_rss_token).to be_nil
expect { find_user_from_feed_token }.to raise_error(Gitlab::Auth::UnauthorizedError)
end
end
it 'returns exception if invalid rss_token' do
set_param(:rss_token, 'invalid_token')
context 'when rss_token param is provided' do
it 'returns user if valid rssd_token' do
set_param(:rss_token, user.feed_token)
expect { find_user_from_rss_token }.to raise_error(Gitlab::Auth::UnauthorizedError)
expect(find_user_from_feed_token).to eq user
end
it 'returns nil if rss_token is blank' do
expect(find_user_from_feed_token).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)
end
end
end
context 'when the request format is not atom' do
it 'returns nil' do
set_param(:rss_token, user.rss_token)
set_param(:feed_token, user.feed_token)
expect(find_user_from_rss_token).to be_nil
expect(find_user_from_feed_token).to be_nil
end
end
......@@ -81,7 +101,7 @@ describe Gitlab::Auth::UserAuthFinders do
it 'the method call does not modify the original value' do
env['action_dispatch.request.formats'] = nil
find_user_from_rss_token
find_user_from_feed_token
expect(env['action_dispatch.request.formats']).to be_nil
end
......
......@@ -12,7 +12,7 @@ shared_examples 'TokenAuthenticatable' do
end
describe User, 'TokenAuthenticatable' do
let(:token_field) { :rss_token }
let(:token_field) { :feed_token }
it_behaves_like 'TokenAuthenticatable'
describe 'ensures authentication token' do
......
......@@ -644,13 +644,13 @@ describe User do
end
end
describe 'rss token' do
it 'ensures an rss token on read' do
user = create(:user, rss_token: nil)
rss_token = user.rss_token
describe 'feed token' do
it 'ensures a feed token on read' do
user = create(:user, feed_token: nil)
feed_token = user.feed_token
expect(rss_token).not_to be_blank
expect(user.reload.rss_token).to eq rss_token
expect(feed_token).not_to be_blank
expect(user.reload.feed_token).to eq feed_token
end
end
......
......@@ -349,7 +349,7 @@ describe 'Rack Attack global throttles' do
end
def rss_url(user)
"/dashboard/projects.atom?rss_token=#{user.rss_token}"
"/dashboard/projects.atom?feed_token=#{user.feed_token}"
end
def private_token_headers(user)
......
......@@ -162,8 +162,8 @@ describe ProfilesController, "routing" do
expect(get("/profile/audit_log")).to route_to('profiles#audit_log')
end
it "to #reset_rss_token" do
expect(put("/profile/reset_rss_token")).to route_to('profiles#reset_rss_token')
it "to #reset_feed_token" do
expect(put("/profile/reset_feed_token")).to route_to('profiles#reset_feed_token')
end
it "to #show" do
......@@ -249,7 +249,11 @@ describe DashboardController, "routing" do
end
it "to #issues" do
expect(get("/dashboard/issues")).to route_to('dashboard#issues')
expect(get("/dashboard/issues.html")).to route_to('dashboard#issues', format: 'html')
end
it "to #calendar_issues" do
expect(get("/dashboard/issues.ics")).to route_to('dashboard#issues_calendar', format: 'ics')
end
it "to #merge_requests" do
......
shared_examples "an autodiscoverable RSS feed with current_user's RSS token" do
it "has an RSS autodiscovery link tag with current_user's RSS token" do
expect(page).to have_css("link[type*='atom+xml'][href*='rss_token=#{user.rss_token}']", visible: false)
shared_examples "an autodiscoverable RSS feed with current_user's feed token" do
it "has an RSS autodiscovery link tag with current_user's feed token" do
expect(page).to have_css("link[type*='atom+xml'][href*='feed_token=#{user.feed_token}']", visible: false)
end
end
shared_examples "it has an RSS button with current_user's RSS token" do
it "shows the RSS button with current_user's RSS token" do
expect(page).to have_css("a:has(.fa-rss)[href*='rss_token=#{user.rss_token}']")
shared_examples "it has an RSS button with current_user's feed token" do
it "shows the RSS button with current_user's feed token" do
expect(page).to have_css("a:has(.fa-rss)[href*='feed_token=#{user.feed_token}']")
end
end
shared_examples "an autodiscoverable RSS feed without an RSS token" do
it "has an RSS autodiscovery link tag without an RSS token" do
expect(page).to have_css("link[type*='atom+xml']:not([href*='rss_token'])", visible: false)
shared_examples "an autodiscoverable RSS feed without a feed token" do
it "has an RSS autodiscovery link tag without a feed token" do
expect(page).to have_css("link[type*='atom+xml']:not([href*='feed_token'])", visible: false)
end
end
shared_examples "it has an RSS button without an RSS token" do
it "shows the RSS button without an RSS token" do
expect(page).to have_css("a:has(.fa-rss):not([href*='rss_token'])")
shared_examples "it has an RSS button without a feed token" do
it "shows the RSS button without a feed token" do
expect(page).to have_css("a:has(.fa-rss):not([href*='feed_token'])")
end
end
......@@ -13,9 +13,9 @@ describe 'tokens rake tasks' do
end
end
describe 'reset_all_rss task' do
describe 'reset_all_feed task' do
it 'invokes create_hooks task' do
expect { run_rake_task('tokens:reset_all_rss') }.to change { user.reload.rss_token }
expect { run_rake_task('tokens:reset_all_feed') }.to change { user.reload.feed_token }
end
end
end
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