Commit 20dfe25c authored by Imre Farkas's avatar Imre Farkas Committed by Douwe Maan

Export assigned issues in iCalendar feed

parent 2fdd8982
......@@ -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