Commit ec2130be authored by Douwe Maan's avatar Douwe Maan

Merge branch 'feature/rss-scoped-token' into 'master'

Use rss token for atom access

See merge request !11647
parents d893b897 f0d0f601
...@@ -11,6 +11,7 @@ class ApplicationController < ActionController::Base ...@@ -11,6 +11,7 @@ class ApplicationController < ActionController::Base
include EnforcesTwoFactorAuthentication include EnforcesTwoFactorAuthentication
before_action :authenticate_user_from_private_token! before_action :authenticate_user_from_private_token!
before_action :authenticate_user_from_rss_token!
before_action :authenticate_user! before_action :authenticate_user!
before_action :validate_user_service_ticket! before_action :validate_user_service_ticket!
before_action :check_password_expiration before_action :check_password_expiration
...@@ -72,13 +73,20 @@ class ApplicationController < ActionController::Base ...@@ -72,13 +73,20 @@ class ApplicationController < ActionController::Base
user = User.find_by_authentication_token(token) || User.find_by_personal_access_token(token) user = User.find_by_authentication_token(token) || User.find_by_personal_access_token(token)
if user && can?(user, :log_in) sessionless_sign_in(user)
# Notice we are passing store false, so the user is not end
# actually stored in the session and a token is needed
# for every request. If you want the token to work as a # This filter handles authentication for atom request with an rss_token
# sign in token, you can simply remove store: false. def authenticate_user_from_rss_token!
sign_in user, store: false return unless request.format.atom?
end
token = params[:rss_token].presence
return unless token.present?
user = User.find_by_rss_token(token)
sessionless_sign_in(user)
end end
def log_exception(exception) def log_exception(exception)
...@@ -282,4 +290,14 @@ class ApplicationController < ActionController::Base ...@@ -282,4 +290,14 @@ class ApplicationController < ActionController::Base
ensure ensure
Gitlab::I18n.reset_locale Gitlab::I18n.reset_locale
end end
def sessionless_sign_in(user)
if user && can?(user, :log_in)
# Notice we are passing store false, so the user is not
# actually stored in the session and a token is needed
# for every request. If you want the token to work as a
# sign in token, you can simply remove store: false.
sign_in user, store: false
end
end
end end
...@@ -40,6 +40,14 @@ class ProfilesController < Profiles::ApplicationController ...@@ -40,6 +40,14 @@ class ProfilesController < Profiles::ApplicationController
redirect_to profile_account_path redirect_to profile_account_path
end end
def reset_rss_token
if current_user.reset_rss_token!
flash[:notice] = "RSS token was successfully reset"
end
redirect_to profile_account_path
end
def audit_log def audit_log
@events = AuditEvent.where(entity_type: "User", entity_id: current_user.id). @events = AuditEvent.where(entity_type: "User", entity_id: current_user.id).
order("created_at DESC"). order("created_at DESC").
......
module RssHelper module RssHelper
def rss_url_options def rss_url_options
{ format: :atom, private_token: current_user.try(:private_token) } { format: :atom, rss_token: current_user.try(:rss_token) }
end end
end end
...@@ -15,6 +15,7 @@ class User < ActiveRecord::Base ...@@ -15,6 +15,7 @@ class User < ActiveRecord::Base
add_authentication_token_field :authentication_token add_authentication_token_field :authentication_token
add_authentication_token_field :incoming_email_token add_authentication_token_field :incoming_email_token
add_authentication_token_field :rss_token
default_value_for :admin, false default_value_for :admin, false
default_value_for(:external) { current_application_settings.user_default_external } default_value_for(:external) { current_application_settings.user_default_external }
...@@ -1004,6 +1005,13 @@ class User < ActiveRecord::Base ...@@ -1004,6 +1005,13 @@ class User < ActiveRecord::Base
save save
end end
# each existing user needs to have an `rss_token`.
# we do this on read since migrating all existing users is not a feasible
# solution.
def rss_token
ensure_rss_token!
end
protected protected
# override, from Devise::Validatable # override, from Devise::Validatable
......
- name = label.parameterize
- attribute = name.underscore
.reset-action
%p.cgray
= label_tag name, label, class: "label-light"
= text_field_tag name, current_user.send(attribute), class: 'form-control', readonly: true, onclick: 'this.select()'
%p.help-block
= help_text
.prepend-top-default
= link_to button_label, [:reset, attribute, :profile], method: :put, data: { confirm: 'Are you sure?' }, class: 'btn btn-default private-token'
...@@ -8,35 +8,17 @@ ...@@ -8,35 +8,17 @@
.row.prepend-top-default .row.prepend-top-default
.col-lg-3.profile-settings-sidebar .col-lg-3.profile-settings-sidebar
%h4.prepend-top-0 %h4.prepend-top-0
= incoming_email_token_enabled? ? "Private Tokens" : "Private Token" Private Tokens
%p %p
Keep Keep these tokens secret, anyone with access to them can interact with
= incoming_email_token_enabled? ? "these tokens" : "this token" GitLab as if they were you.
secret, anyone with access to them can interact with GitLab as if they were you.
.col-lg-9.private-tokens-reset .col-lg-9.private-tokens-reset
.reset-action = render partial: 'reset_token', locals: { label: 'Private token', button_label: 'Reset private token', help_text: 'Your private token is used to access the API and Atom feeds without username/password authentication.' }
%p.cgray
- if current_user.private_token = render partial: 'reset_token', locals: { label: 'RSS token', button_label: 'Reset RSS token', help_text: 'Your RSS token is used to create urls for personalized RSS feeds.' }
= label_tag "private-token", "Private token", class: "label-light"
= text_field_tag "private-token", current_user.private_token, class: "form-control", readonly: true, onclick: "this.select()"
- else
%span You don't have one yet. Click generate to fix it.
%p.help-block
Your private token is used to access the API and Atom feeds without username/password authentication.
.prepend-top-default
- if current_user.private_token
= link_to 'Reset private token', reset_private_token_profile_path, method: :put, data: { confirm: "Are you sure?" }, class: "btn btn-default private-token"
- else
= f.submit 'Generate', class: "btn btn-default"
- if incoming_email_token_enabled? - if incoming_email_token_enabled?
.reset-action = render partial: 'reset_token', locals: { label: 'Incoming email token', button_label: 'Reset incoming email token', help_text: 'Your incoming email token is used to create new issues by email, and is included in your project-specific email addresses.' }
%p.cgray
= label_tag "incoming-email-token", "Incoming Email Token", class: 'label-light'
= text_field_tag "incoming-email-token", current_user.incoming_email_token, class: "form-control", readonly: true, onclick: "this.select()"
%p.help-block
Your incoming email token is used to create new issues by email, and is included in your project-specific email addresses.
.prepend-top-default
= link_to 'Reset incoming email token', reset_incoming_email_token_profile_path, method: :put, data: { confirm: "Are you sure?" }, class: "btn btn-default incoming-email-token"
%hr %hr
.row.prepend-top-default .row.prepend-top-default
......
---
title: Expose atom links with an RSS token instead of using the private token
merge_request: 11647
author: Alexis Reigel
...@@ -65,6 +65,7 @@ module Gitlab ...@@ -65,6 +65,7 @@ module Gitlab
hook hook
import_url import_url
incoming_email_token incoming_email_token
rss_token
key key
otp_attempt otp_attempt
password password
......
...@@ -5,6 +5,7 @@ resource :profile, only: [:show, :update] do ...@@ -5,6 +5,7 @@ resource :profile, only: [:show, :update] do
put :reset_private_token put :reset_private_token
put :reset_incoming_email_token put :reset_incoming_email_token
put :reset_rss_token
put :update_username put :update_username
end end
......
class AddRssTokenToUsers < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_column :users, :rss_token, :string
add_concurrent_index :users, :rss_token
end
def down
remove_concurrent_index :users, :rss_token if index_exists? :users, :rss_token
remove_column :users, :rss_token
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20170521184006) do ActiveRecord::Schema.define(version: 20170523091700) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -1362,6 +1362,7 @@ ActiveRecord::Schema.define(version: 20170521184006) do ...@@ -1362,6 +1362,7 @@ ActiveRecord::Schema.define(version: 20170521184006) do
t.date "last_activity_on" t.date "last_activity_on"
t.boolean "notified_of_own_activity" t.boolean "notified_of_own_activity"
t.string "preferred_language" t.string "preferred_language"
t.string "rss_token"
end end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
...@@ -1375,6 +1376,7 @@ ActiveRecord::Schema.define(version: 20170521184006) do ...@@ -1375,6 +1376,7 @@ ActiveRecord::Schema.define(version: 20170521184006) do
add_index "users", ["name"], name: "index_users_on_name", 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", ["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", ["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", ["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", using: :btree
add_index "users", ["username"], name: "index_users_on_username_trigram", using: :gin, opclasses: {"username"=>"gin_trgm_ops"} add_index "users", ["username"], name: "index_users_on_username_trigram", using: :gin, opclasses: {"username"=>"gin_trgm_ops"}
......
...@@ -11,6 +11,11 @@ namespace :tokens do ...@@ -11,6 +11,11 @@ namespace :tokens do
reset_all_users_token(:reset_incoming_email_token!) reset_all_users_token(:reset_incoming_email_token!)
end end
desc "Reset all GitLab RSS tokens"
task reset_all_rss: :environment do
reset_all_users_token(:reset_rss_token!)
end
def reset_all_users_token(reset_token_method) def reset_all_users_token(reset_token_method)
TmpUser.find_in_batches do |batch| TmpUser.find_in_batches do |batch|
puts "Processing batch starting with user ID: #{batch.first.id}" puts "Processing batch starting with user ID: #{batch.first.id}"
...@@ -35,4 +40,9 @@ class TmpUser < ActiveRecord::Base ...@@ -35,4 +40,9 @@ class TmpUser < ActiveRecord::Base
write_new_token(:incoming_email_token) write_new_token(:incoming_email_token)
save!(validate: false) save!(validate: false)
end end
def reset_rss_token!
write_new_token(:rss_token)
save!(validate: false)
end
end end
...@@ -99,6 +99,42 @@ describe ApplicationController do ...@@ -99,6 +99,42 @@ describe ApplicationController do
end end
end end
describe '#authenticate_user_from_rss_token' do
describe "authenticating a user from an RSS 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 request format is atom' do
it "logs the user in" do
get :index, rss_token: user.rss_token, format: :atom
expect(response).to have_http_status 200
expect(response.body).to eq 'authenticated'
end
end
context 'when the request format is not atom' do
it "doesn't log the user in" do
get :index, rss_token: user.rss_token
expect(response.status).not_to have_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
it "doesn't log the user" do
get :index, rss_token: "token"
expect(response.status).not_to eq 200
expect(response.body).not_to eq 'authenticated'
end
end
end
end
describe '#route_not_found' do describe '#route_not_found' do
it 'renders 404 if authenticated' do it 'renders 404 if authenticated' do
allow(controller).to receive(:current_user).and_return(user) allow(controller).to receive(:current_user).and_return(user)
......
...@@ -8,6 +8,10 @@ FactoryGirl.define do ...@@ -8,6 +8,10 @@ FactoryGirl.define do
confirmation_token { nil } confirmation_token { nil }
can_create_group true can_create_group true
before(:create) do |user|
user.ensure_rss_token
end
trait :admin do trait :admin do
admin true admin true
end end
......
...@@ -20,13 +20,20 @@ describe "Dashboard Issues Feed", feature: true do ...@@ -20,13 +20,20 @@ describe "Dashboard Issues Feed", feature: true do
expect(body).to have_selector('title', text: "#{user.name} issues") expect(body).to have_selector('title', text: "#{user.name} issues")
end end
it "renders atom feed via RSS token" do
visit issues_dashboard_path(:atom, rss_token: user.rss_token)
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 it "renders atom feed with url parameters" do
visit issues_dashboard_path(:atom, private_token: user.private_token, state: 'opened', assignee_id: user.id) visit issues_dashboard_path(:atom, rss_token: user.rss_token, state: 'opened', assignee_id: user.id)
link = find('link[type="application/atom+xml"]') link = find('link[type="application/atom+xml"]')
params = CGI.parse(URI.parse(link[:href]).query) params = CGI.parse(URI.parse(link[:href]).query)
expect(params).to include('private_token' => [user.private_token]) expect(params).to include('rss_token' => [user.rss_token])
expect(params).to include('state' => ['opened']) expect(params).to include('state' => ['opened'])
expect(params).to include('assignee_id' => [user.id.to_s]) expect(params).to include('assignee_id' => [user.id.to_s])
end end
...@@ -35,7 +42,7 @@ describe "Dashboard Issues Feed", feature: true do ...@@ -35,7 +42,7 @@ describe "Dashboard Issues Feed", feature: true do
let!(:issue2) { create(:issue, author: user, assignees: [assignee], project: project2, description: 'test desc') } let!(:issue2) { create(:issue, author: user, assignees: [assignee], project: project2, description: 'test desc') }
it "renders issue fields" do it "renders issue fields" do
visit issues_dashboard_path(:atom, private_token: user.private_token) visit issues_dashboard_path(:atom, rss_token: user.rss_token)
entry = find(:xpath, "//feed/entry[contains(summary/text(),'#{issue2.title}')]") entry = find(:xpath, "//feed/entry[contains(summary/text(),'#{issue2.title}')]")
...@@ -58,7 +65,7 @@ describe "Dashboard Issues Feed", feature: true do ...@@ -58,7 +65,7 @@ describe "Dashboard Issues Feed", feature: true do
end end
it "renders issue label and milestone info" do it "renders issue label and milestone info" do
visit issues_dashboard_path(:atom, private_token: user.private_token) visit issues_dashboard_path(:atom, rss_token: user.rss_token)
entry = find(:xpath, "//feed/entry[contains(summary/text(),'#{issue1.title}')]") entry = find(:xpath, "//feed/entry[contains(summary/text(),'#{issue1.title}')]")
......
...@@ -11,6 +11,13 @@ describe "Dashboard Feed", feature: true do ...@@ -11,6 +11,13 @@ describe "Dashboard Feed", feature: true do
end end
end end
context "projects atom feed via RSS token" do
it "renders projects atom feed" do
visit dashboard_projects_path(:atom, rss_token: user.rss_token)
expect(body).to have_selector('feed title')
end
end
context 'feed content' do context 'feed content' do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:issue) { create(:issue, project: project, author: user, description: '') } let(:issue) { create(:issue, project: project, author: user, description: '') }
...@@ -20,7 +27,7 @@ describe "Dashboard Feed", feature: true do ...@@ -20,7 +27,7 @@ describe "Dashboard Feed", feature: true do
project.team << [user, :master] project.team << [user, :master]
issue_event(issue, user) issue_event(issue, user)
note_event(note, user) note_event(note, user)
visit dashboard_projects_path(:atom, private_token: user.private_token) visit dashboard_projects_path(:atom, rss_token: user.rss_token)
end end
it "has issue opened event" do it "has issue opened event" do
......
...@@ -43,25 +43,40 @@ describe 'Issues Feed', feature: true do ...@@ -43,25 +43,40 @@ describe 'Issues Feed', feature: true do
end end
end end
context 'when authenticated via RSS token' do
it 'renders atom feed' do
visit namespace_project_issues_path(project.namespace, project, :atom,
rss_token: user.rss_token)
expect(response_headers['Content-Type']).
to have_content('application/atom+xml')
expect(body).to have_selector('title', text: "#{project.name} issues")
expect(body).to have_selector('author email', text: issue.author_public_email)
expect(body).to have_selector('assignees assignee email', text: issue.assignees.first.public_email)
expect(body).to have_selector('assignee email', text: issue.assignees.first.public_email)
expect(body).to have_selector('entry summary', text: issue.title)
end
end
it "renders atom feed with url parameters for project issues" do it "renders atom feed with url parameters for project issues" do
visit namespace_project_issues_path(project.namespace, project, visit namespace_project_issues_path(project.namespace, project,
:atom, private_token: user.private_token, state: 'opened', assignee_id: user.id) :atom, rss_token: user.rss_token, state: 'opened', assignee_id: user.id)
link = find('link[type="application/atom+xml"]') link = find('link[type="application/atom+xml"]')
params = CGI.parse(URI.parse(link[:href]).query) params = CGI.parse(URI.parse(link[:href]).query)
expect(params).to include('private_token' => [user.private_token]) expect(params).to include('rss_token' => [user.rss_token])
expect(params).to include('state' => ['opened']) expect(params).to include('state' => ['opened'])
expect(params).to include('assignee_id' => [user.id.to_s]) expect(params).to include('assignee_id' => [user.id.to_s])
end end
it "renders atom feed with url parameters for group issues" do it "renders atom feed with url parameters for group issues" do
visit issues_group_path(group, :atom, private_token: user.private_token, state: 'opened', assignee_id: user.id) visit issues_group_path(group, :atom, rss_token: user.rss_token, state: 'opened', assignee_id: user.id)
link = find('link[type="application/atom+xml"]') link = find('link[type="application/atom+xml"]')
params = CGI.parse(URI.parse(link[:href]).query) params = CGI.parse(URI.parse(link[:href]).query)
expect(params).to include('private_token' => [user.private_token]) expect(params).to include('rss_token' => [user.rss_token])
expect(params).to include('state' => ['opened']) expect(params).to include('state' => ['opened'])
expect(params).to include('assignee_id' => [user.id.to_s]) expect(params).to include('assignee_id' => [user.id.to_s])
end end
......
...@@ -11,6 +11,13 @@ describe "User Feed", feature: true do ...@@ -11,6 +11,13 @@ describe "User Feed", feature: true do
end end
end end
context 'user atom feed via RSS token' do
it "renders user atom feed" do
visit user_path(user, :atom, rss_token: user.rss_token)
expect(body).to have_selector('feed title')
end
end
context 'feed content' do context 'feed content' do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:issue) do let(:issue) do
...@@ -40,7 +47,7 @@ describe "User Feed", feature: true do ...@@ -40,7 +47,7 @@ describe "User Feed", feature: true do
issue_event(issue, user) issue_event(issue, user)
note_event(note, user) note_event(note, user)
merge_request_event(merge_request, user) merge_request_event(merge_request, user)
visit user_path(user, :atom, private_token: user.private_token) visit user_path(user, :atom, rss_token: user.rss_token)
end end
it 'has issue opened event' do it 'has issue opened event' do
......
...@@ -5,7 +5,7 @@ RSpec.describe 'Dashboard Activity', feature: true do ...@@ -5,7 +5,7 @@ RSpec.describe 'Dashboard Activity', feature: true do
login_as(create :user) login_as(create :user)
visit activity_dashboard_path visit activity_dashboard_path
end end
it_behaves_like "it has an RSS button with current_user's private token" 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 private token" it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
end end
...@@ -62,6 +62,6 @@ RSpec.describe 'Dashboard Issues', feature: true do ...@@ -62,6 +62,6 @@ RSpec.describe 'Dashboard Issues', feature: true do
expect(page).to have_content(other_issue.title) expect(page).to have_content(other_issue.title)
end end
it_behaves_like "it has an RSS button with current_user's private token" 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 private token" it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
end end
...@@ -31,5 +31,5 @@ RSpec.describe 'Dashboard Projects', feature: true do ...@@ -31,5 +31,5 @@ RSpec.describe 'Dashboard Projects', feature: true do
end end
end end
it_behaves_like "an autodiscoverable RSS feed with current_user's private token" it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
end end
...@@ -53,10 +53,10 @@ describe "Dashboard Issues filtering", feature: true, js: true do ...@@ -53,10 +53,10 @@ describe "Dashboard Issues filtering", feature: true, js: true do
auto_discovery_link = find('link[type="application/atom+xml"]', visible: false) auto_discovery_link = find('link[type="application/atom+xml"]', visible: false)
auto_discovery_params = CGI.parse(URI.parse(auto_discovery_link[:href]).query) auto_discovery_params = CGI.parse(URI.parse(auto_discovery_link[:href]).query)
expect(params).to include('private_token' => [user.private_token]) expect(params).to include('rss_token' => [user.rss_token])
expect(params).to include('milestone_title' => ['']) expect(params).to include('milestone_title' => [''])
expect(params).to include('assignee_id' => [user.id.to_s]) expect(params).to include('assignee_id' => [user.id.to_s])
expect(auto_discovery_params).to include('private_token' => [user.private_token]) expect(auto_discovery_params).to include('rss_token' => [user.rss_token])
expect(auto_discovery_params).to include('milestone_title' => ['']) expect(auto_discovery_params).to include('milestone_title' => [''])
expect(auto_discovery_params).to include('assignee_id' => [user.id.to_s]) expect(auto_discovery_params).to include('assignee_id' => [user.id.to_s])
end end
......
...@@ -11,8 +11,8 @@ feature 'Group activity page', feature: true do ...@@ -11,8 +11,8 @@ feature 'Group activity page', feature: true do
visit path visit path
end end
it_behaves_like "it has an RSS button with current_user's private token" 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 private token" it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
end end
context 'when signed out' do context 'when signed out' do
...@@ -20,7 +20,7 @@ feature 'Group activity page', feature: true do ...@@ -20,7 +20,7 @@ feature 'Group activity page', feature: true do
visit path visit path
end end
it_behaves_like "it has an RSS button without a private token" it_behaves_like "it has an RSS button without an RSS token"
it_behaves_like "an autodiscoverable RSS feed without a private token" it_behaves_like "an autodiscoverable RSS feed without an RSS token"
end end
end end
...@@ -12,15 +12,15 @@ feature 'Group issues page', feature: true do ...@@ -12,15 +12,15 @@ feature 'Group issues page', feature: true do
context 'when signed in' do context 'when signed in' do
let(:user) { user_in_group } let(:user) { user_in_group }
it_behaves_like "it has an RSS button with current_user's private token" 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 private token" it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
end end
context 'when signed out' do context 'when signed out' do
let(:user) { nil } let(:user) { nil }
it_behaves_like "it has an RSS button without a private token" it_behaves_like "it has an RSS button without an RSS token"
it_behaves_like "an autodiscoverable RSS feed without a private token" it_behaves_like "an autodiscoverable RSS feed without an RSS token"
end end
end end
......
...@@ -11,7 +11,7 @@ feature 'Group show page', feature: true do ...@@ -11,7 +11,7 @@ feature 'Group show page', feature: true do
visit path visit path
end end
it_behaves_like "an autodiscoverable RSS feed with current_user's private token" it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
end end
context 'when signed out' do context 'when signed out' do
...@@ -19,6 +19,6 @@ feature 'Group show page', feature: true do ...@@ -19,6 +19,6 @@ feature 'Group show page', feature: true do
visit path visit path
end end
it_behaves_like "an autodiscoverable RSS feed without a private token" it_behaves_like "an autodiscoverable RSS feed without an RSS token"
end end
end end
...@@ -810,10 +810,10 @@ describe 'Filter issues', js: true, feature: true do ...@@ -810,10 +810,10 @@ describe 'Filter issues', js: true, feature: true do
auto_discovery_link = find('link[type="application/atom+xml"]', visible: false) auto_discovery_link = find('link[type="application/atom+xml"]', visible: false)
auto_discovery_params = CGI.parse(URI.parse(auto_discovery_link[:href]).query) auto_discovery_params = CGI.parse(URI.parse(auto_discovery_link[:href]).query)
expect(params).to include('private_token' => [user.private_token]) expect(params).to include('rss_token' => [user.rss_token])
expect(params).to include('milestone_title' => [milestone.title]) expect(params).to include('milestone_title' => [milestone.title])
expect(params).to include('assignee_id' => [user.id.to_s]) expect(params).to include('assignee_id' => [user.id.to_s])
expect(auto_discovery_params).to include('private_token' => [user.private_token]) expect(auto_discovery_params).to include('rss_token' => [user.rss_token])
expect(auto_discovery_params).to include('milestone_title' => [milestone.title]) expect(auto_discovery_params).to include('milestone_title' => [milestone.title])
expect(auto_discovery_params).to include('assignee_id' => [user.id.to_s]) expect(auto_discovery_params).to include('assignee_id' => [user.id.to_s])
end end
...@@ -825,10 +825,10 @@ describe 'Filter issues', js: true, feature: true do ...@@ -825,10 +825,10 @@ describe 'Filter issues', js: true, feature: true do
auto_discovery_link = find('link[type="application/atom+xml"]', visible: false) auto_discovery_link = find('link[type="application/atom+xml"]', visible: false)
auto_discovery_params = CGI.parse(URI.parse(auto_discovery_link[:href]).query) auto_discovery_params = CGI.parse(URI.parse(auto_discovery_link[:href]).query)
expect(params).to include('private_token' => [user.private_token]) expect(params).to include('rss_token' => [user.rss_token])
expect(params).to include('milestone_title' => [milestone.title]) expect(params).to include('milestone_title' => [milestone.title])
expect(params).to include('assignee_id' => [user.id.to_s]) expect(params).to include('assignee_id' => [user.id.to_s])
expect(auto_discovery_params).to include('private_token' => [user.private_token]) expect(auto_discovery_params).to include('rss_token' => [user.rss_token])
expect(auto_discovery_params).to include('milestone_title' => [milestone.title]) expect(auto_discovery_params).to include('milestone_title' => [milestone.title])
expect(auto_discovery_params).to include('assignee_id' => [user.id.to_s]) expect(auto_discovery_params).to include('assignee_id' => [user.id.to_s])
end end
......
...@@ -47,6 +47,21 @@ describe 'Profile account page', feature: true do ...@@ -47,6 +47,21 @@ describe 'Profile account page', feature: true do
end end
end end
describe 'when I reset RSS token' do
before do
visit profile_account_path
end
it 'resets RSS token' do
previous_token = find("#rss-token").value
click_link('Reset RSS token')
expect(page).to have_content 'RSS token was successfully reset'
expect(find('#rss-token').value).not_to eq(previous_token)
end
end
describe 'when I reset incoming email token' do describe 'when I reset incoming email token' do
before do before do
allow(Gitlab.config.incoming_email).to receive(:enabled).and_return(true) allow(Gitlab.config.incoming_email).to receive(:enabled).and_return(true)
......
...@@ -16,7 +16,7 @@ feature 'Project Activity RSS' do ...@@ -16,7 +16,7 @@ feature 'Project Activity RSS' do
visit path visit path
end end
it_behaves_like "it has an RSS button with current_user's private token" it_behaves_like "it has an RSS button with current_user's RSS token"
end end
context 'when signed out' do context 'when signed out' do
...@@ -24,6 +24,6 @@ feature 'Project Activity RSS' do ...@@ -24,6 +24,6 @@ feature 'Project Activity RSS' do
visit path visit path
end end
it_behaves_like "it has an RSS button without a private token" it_behaves_like "it has an RSS button without an RSS token"
end end
end end
...@@ -12,8 +12,8 @@ feature 'Project Commits RSS' do ...@@ -12,8 +12,8 @@ feature 'Project Commits RSS' do
visit path visit path
end end
it_behaves_like "it has an RSS button with current_user's private token" 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 private token" it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
end end
context 'when signed out' do context 'when signed out' do
...@@ -21,7 +21,7 @@ feature 'Project Commits RSS' do ...@@ -21,7 +21,7 @@ feature 'Project Commits RSS' do
visit path visit path
end end
it_behaves_like "it has an RSS button without a private token" it_behaves_like "it has an RSS button without an RSS token"
it_behaves_like "an autodiscoverable RSS feed without a private token" it_behaves_like "an autodiscoverable RSS feed without an RSS token"
end end
end end
...@@ -16,8 +16,8 @@ feature 'Project Issues RSS' do ...@@ -16,8 +16,8 @@ feature 'Project Issues RSS' do
visit path visit path
end end
it_behaves_like "it has an RSS button with current_user's private token" 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 private token" it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
end end
context 'when signed out' do context 'when signed out' do
...@@ -25,7 +25,7 @@ feature 'Project Issues RSS' do ...@@ -25,7 +25,7 @@ feature 'Project Issues RSS' do
visit path visit path
end end
it_behaves_like "it has an RSS button without a private token" it_behaves_like "it has an RSS button without an RSS token"
it_behaves_like "an autodiscoverable RSS feed without a private token" it_behaves_like "an autodiscoverable RSS feed without an RSS token"
end end
end end
...@@ -12,7 +12,7 @@ feature 'Project RSS' do ...@@ -12,7 +12,7 @@ feature 'Project RSS' do
visit path visit path
end end
it_behaves_like "an autodiscoverable RSS feed with current_user's private token" it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
end end
context 'when signed out' do context 'when signed out' do
...@@ -20,6 +20,6 @@ feature 'Project RSS' do ...@@ -20,6 +20,6 @@ feature 'Project RSS' do
visit path visit path
end end
it_behaves_like "an autodiscoverable RSS feed without a private token" it_behaves_like "an autodiscoverable RSS feed without an RSS token"
end end
end end
...@@ -12,7 +12,7 @@ feature 'Project Tree RSS' do ...@@ -12,7 +12,7 @@ feature 'Project Tree RSS' do
visit path visit path
end end
it_behaves_like "an autodiscoverable RSS feed with current_user's private token" it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token"
end end
context 'when signed out' do context 'when signed out' do
...@@ -20,6 +20,6 @@ feature 'Project Tree RSS' do ...@@ -20,6 +20,6 @@ feature 'Project Tree RSS' do
visit path visit path
end end
it_behaves_like "an autodiscoverable RSS feed without a private token" it_behaves_like "an autodiscoverable RSS feed without an RSS token"
end end
end end
...@@ -9,7 +9,7 @@ feature 'User RSS' do ...@@ -9,7 +9,7 @@ feature 'User RSS' do
visit path visit path
end end
it_behaves_like "it has an RSS button with current_user's private token" it_behaves_like "it has an RSS button with current_user's RSS token"
end end
context 'when signed out' do context 'when signed out' do
...@@ -17,6 +17,6 @@ feature 'User RSS' do ...@@ -17,6 +17,6 @@ feature 'User RSS' do
visit path visit path
end end
it_behaves_like "it has an RSS button without a private token" it_behaves_like "it has an RSS button without an RSS token"
end end
end end
...@@ -3,17 +3,17 @@ require 'spec_helper' ...@@ -3,17 +3,17 @@ require 'spec_helper'
describe RssHelper do describe RssHelper do
describe '#rss_url_options' do describe '#rss_url_options' do
context 'when signed in' do context 'when signed in' do
it "includes the current_user's private_token" do it "includes the current_user's rss_token" do
current_user = create(:user) current_user = create(:user)
allow(helper).to receive(:current_user).and_return(current_user) allow(helper).to receive(:current_user).and_return(current_user)
expect(helper.rss_url_options).to include private_token: current_user.private_token expect(helper.rss_url_options).to include rss_token: current_user.rss_token
end end
end end
context 'when signed out' do context 'when signed out' do
it "does not have a private_token" do it "does not have an rss_token" do
allow(helper).to receive(:current_user).and_return(nil) allow(helper).to receive(:current_user).and_return(nil)
expect(helper.rss_url_options[:private_token]).to be_nil expect(helper.rss_url_options[:rss_token]).to be_nil
end end
end end
end end
......
...@@ -440,6 +440,22 @@ describe User, models: true do ...@@ -440,6 +440,22 @@ describe User, models: true do
end end
end end
describe 'ensure incoming email token' do
it 'has incoming email token' do
user = create(:user)
expect(user.incoming_email_token).not_to be_blank
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
expect(rss_token).not_to be_blank
expect(user.reload.rss_token).to eq rss_token
end
end
describe '#recently_sent_password_reset?' do describe '#recently_sent_password_reset?' do
it 'is false when reset_password_sent_at is nil' do it 'is false when reset_password_sent_at is nil' do
user = build_stubbed(:user, reset_password_sent_at: nil) user = build_stubbed(:user, reset_password_sent_at: nil)
......
...@@ -151,6 +151,10 @@ describe ProfilesController, "routing" do ...@@ -151,6 +151,10 @@ describe ProfilesController, "routing" do
expect(put("/profile/reset_private_token")).to route_to('profiles#reset_private_token') expect(put("/profile/reset_private_token")).to route_to('profiles#reset_private_token')
end end
it "to #reset_rss_token" do
expect(put("/profile/reset_rss_token")).to route_to('profiles#reset_rss_token')
end
it "to #show" do it "to #show" do
expect(get("/profile")).to route_to('profiles#show') expect(get("/profile")).to route_to('profiles#show')
end end
......
shared_examples "an autodiscoverable RSS feed with current_user's private token" 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 private 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*='private_token=#{Thread.current[:current_user].private_token}']", visible: false) expect(page).to have_css("link[type*='atom+xml'][href*='rss_token=#{Thread.current[:current_user].rss_token}']", visible: false)
end end
end end
shared_examples "it has an RSS button with current_user's private token" do shared_examples "it has an RSS button with current_user's RSS token" do
it "shows the RSS button with current_user's private token" do it "shows the RSS button with current_user's RSS token" do
expect(page).to have_css("a:has(.fa-rss)[href*='private_token=#{Thread.current[:current_user].private_token}']") expect(page).to have_css("a:has(.fa-rss)[href*='rss_token=#{Thread.current[:current_user].rss_token}']")
end end
end end
shared_examples "an autodiscoverable RSS feed without a private token" do shared_examples "an autodiscoverable RSS feed without an RSS token" do
it "has an RSS autodiscovery link tag without a private 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*='private_token'])", visible: false) expect(page).to have_css("link[type*='atom+xml']:not([href*='rss_token'])", visible: false)
end end
end end
shared_examples "it has an RSS button without a private token" do shared_examples "it has an RSS button without an RSS token" do
it "shows the RSS button without a private token" do it "shows the RSS button without an RSS token" do
expect(page).to have_css("a:has(.fa-rss):not([href*='private_token'])") expect(page).to have_css("a:has(.fa-rss):not([href*='rss_token'])")
end end
end end
...@@ -18,4 +18,10 @@ describe 'tokens rake tasks' do ...@@ -18,4 +18,10 @@ describe 'tokens rake tasks' do
expect { run_rake_task('tokens:reset_all_email') }.to change { user.reload.incoming_email_token } expect { run_rake_task('tokens:reset_all_email') }.to change { user.reload.incoming_email_token }
end end
end end
describe 'reset_all_rss task' do
it 'invokes create_hooks task' do
expect { run_rake_task('tokens:reset_all_rss') }.to change { user.reload.rss_token }
end
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