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

Use rss token for atom access

See merge request !11647
This commit is contained in:
Douwe Maan 2017-05-24 23:47:12 +00:00
commit ec2130bea8
38 changed files with 280 additions and 99 deletions

View file

@ -11,6 +11,7 @@ class ApplicationController < ActionController::Base
include EnforcesTwoFactorAuthentication
before_action :authenticate_user_from_private_token!
before_action :authenticate_user_from_rss_token!
before_action :authenticate_user!
before_action :validate_user_service_ticket!
before_action :check_password_expiration
@ -72,13 +73,20 @@ class ApplicationController < ActionController::Base
user = User.find_by_authentication_token(token) || User.find_by_personal_access_token(token)
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
sessionless_sign_in(user)
end
# This filter handles authentication for atom request with an rss_token
def authenticate_user_from_rss_token!
return unless request.format.atom?
token = params[:rss_token].presence
return unless token.present?
user = User.find_by_rss_token(token)
sessionless_sign_in(user)
end
def log_exception(exception)
@ -282,4 +290,14 @@ class ApplicationController < ActionController::Base
ensure
Gitlab::I18n.reset_locale
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

View file

@ -40,6 +40,14 @@ class ProfilesController < Profiles::ApplicationController
redirect_to profile_account_path
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
@events = AuditEvent.where(entity_type: "User", entity_id: current_user.id).
order("created_at DESC").

View file

@ -1,5 +1,5 @@
module RssHelper
def rss_url_options
{ format: :atom, private_token: current_user.try(:private_token) }
{ format: :atom, rss_token: current_user.try(:rss_token) }
end
end

View file

@ -15,6 +15,7 @@ class User < ActiveRecord::Base
add_authentication_token_field :authentication_token
add_authentication_token_field :incoming_email_token
add_authentication_token_field :rss_token
default_value_for :admin, false
default_value_for(:external) { current_application_settings.user_default_external }
@ -1004,6 +1005,13 @@ class User < ActiveRecord::Base
save
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
# override, from Devise::Validatable

View file

@ -0,0 +1,11 @@
- 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'

View file

@ -8,35 +8,17 @@
.row.prepend-top-default
.col-lg-3.profile-settings-sidebar
%h4.prepend-top-0
= incoming_email_token_enabled? ? "Private Tokens" : "Private Token"
Private Tokens
%p
Keep
= incoming_email_token_enabled? ? "these tokens" : "this token"
secret, anyone with access to them can interact with GitLab as if they were you.
Keep these tokens secret, anyone with access to them can interact with
GitLab as if they were you.
.col-lg-9.private-tokens-reset
.reset-action
%p.cgray
- if current_user.private_token
= 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"
= 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.' }
= 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.' }
- if incoming_email_token_enabled?
.reset-action
%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"
= 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.' }
%hr
.row.prepend-top-default

View file

@ -0,0 +1,4 @@
---
title: Expose atom links with an RSS token instead of using the private token
merge_request: 11647
author: Alexis Reigel

View file

@ -65,6 +65,7 @@ module Gitlab
hook
import_url
incoming_email_token
rss_token
key
otp_attempt
password

View file

@ -5,6 +5,7 @@ resource :profile, only: [:show, :update] do
put :reset_private_token
put :reset_incoming_email_token
put :reset_rss_token
put :update_username
end

View file

@ -0,0 +1,19 @@
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

View file

@ -11,7 +11,7 @@
#
# 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
enable_extension "plpgsql"
@ -1362,6 +1362,7 @@ ActiveRecord::Schema.define(version: 20170521184006) do
t.date "last_activity_on"
t.boolean "notified_of_own_activity"
t.string "preferred_language"
t.string "rss_token"
end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
@ -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_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"}

View file

@ -11,6 +11,11 @@ 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!)
end
def reset_all_users_token(reset_token_method)
TmpUser.find_in_batches do |batch|
puts "Processing batch starting with user ID: #{batch.first.id}"
@ -35,4 +40,9 @@ class TmpUser < ActiveRecord::Base
write_new_token(:incoming_email_token)
save!(validate: false)
end
def reset_rss_token!
write_new_token(:rss_token)
save!(validate: false)
end
end

View file

@ -99,6 +99,42 @@ describe ApplicationController do
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
it 'renders 404 if authenticated' do
allow(controller).to receive(:current_user).and_return(user)

View file

@ -8,6 +8,10 @@ FactoryGirl.define do
confirmation_token { nil }
can_create_group true
before(:create) do |user|
user.ensure_rss_token
end
trait :admin do
admin true
end

View file

@ -20,13 +20,20 @@ describe "Dashboard Issues Feed", feature: true 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)
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, 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"]')
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('assignee_id' => [user.id.to_s])
end
@ -35,7 +42,7 @@ describe "Dashboard Issues Feed", feature: true do
let!(:issue2) { create(:issue, author: user, assignees: [assignee], project: project2, description: 'test desc') }
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}')]")
@ -58,7 +65,7 @@ describe "Dashboard Issues Feed", feature: true do
end
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}')]")

View file

@ -11,6 +11,13 @@ describe "Dashboard Feed", feature: true do
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
let(:project) { create(:project) }
let(:issue) { create(:issue, project: project, author: user, description: '') }
@ -20,7 +27,7 @@ describe "Dashboard Feed", feature: true do
project.team << [user, :master]
issue_event(issue, 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
it "has issue opened event" do

View file

@ -43,25 +43,40 @@ describe 'Issues Feed', feature: true do
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
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"]')
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('assignee_id' => [user.id.to_s])
end
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"]')
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('assignee_id' => [user.id.to_s])
end

View file

@ -11,6 +11,13 @@ describe "User Feed", feature: true do
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
let(:project) { create(:project) }
let(:issue) do
@ -40,7 +47,7 @@ describe "User Feed", feature: true do
issue_event(issue, user)
note_event(note, 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
it 'has issue opened event' do

View file

@ -5,7 +5,7 @@ RSpec.describe 'Dashboard Activity', feature: true do
login_as(create :user)
visit activity_dashboard_path
end
it_behaves_like "it has an RSS button with current_user's private token"
it_behaves_like "an autodiscoverable RSS feed 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 RSS token"
end

View file

@ -62,6 +62,6 @@ RSpec.describe 'Dashboard Issues', feature: true do
expect(page).to have_content(other_issue.title)
end
it_behaves_like "it has an RSS button with current_user's private token"
it_behaves_like "an autodiscoverable RSS feed 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 RSS token"
end

View file

@ -31,5 +31,5 @@ RSpec.describe 'Dashboard Projects', feature: true do
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

View file

@ -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_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('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('assignee_id' => [user.id.to_s])
end

View file

@ -11,8 +11,8 @@ feature 'Group activity page', feature: true do
visit path
end
it_behaves_like "it has an RSS button with current_user's private token"
it_behaves_like "an autodiscoverable RSS feed 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 RSS token"
end
context 'when signed out' do
@ -20,7 +20,7 @@ feature 'Group activity page', feature: true do
visit path
end
it_behaves_like "it has an RSS button without a private token"
it_behaves_like "an autodiscoverable RSS feed without a private token"
it_behaves_like "it has an RSS button without an RSS token"
it_behaves_like "an autodiscoverable RSS feed without an RSS token"
end
end

View file

@ -12,15 +12,15 @@ feature 'Group issues page', feature: true do
context 'when signed in' do
let(:user) { user_in_group }
it_behaves_like "it has an RSS button with current_user's private token"
it_behaves_like "an autodiscoverable RSS feed 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 RSS token"
end
context 'when signed out' do
let(:user) { nil }
it_behaves_like "it has an RSS button without a private token"
it_behaves_like "an autodiscoverable RSS feed without a private token"
it_behaves_like "it has an RSS button without an RSS token"
it_behaves_like "an autodiscoverable RSS feed without an RSS token"
end
end

View file

@ -11,7 +11,7 @@ feature 'Group show page', feature: true do
visit path
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
context 'when signed out' do
@ -19,6 +19,6 @@ feature 'Group show page', feature: true do
visit path
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

View file

@ -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_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('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('assignee_id' => [user.id.to_s])
end
@ -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_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('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('assignee_id' => [user.id.to_s])
end

View file

@ -47,6 +47,21 @@ describe 'Profile account page', feature: true do
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
before do
allow(Gitlab.config.incoming_email).to receive(:enabled).and_return(true)

View file

@ -16,7 +16,7 @@ feature 'Project Activity RSS' do
visit path
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
context 'when signed out' do
@ -24,6 +24,6 @@ feature 'Project Activity RSS' do
visit path
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

View file

@ -12,8 +12,8 @@ feature 'Project Commits RSS' do
visit path
end
it_behaves_like "it has an RSS button with current_user's private token"
it_behaves_like "an autodiscoverable RSS feed 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 RSS 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 a private token"
it_behaves_like "an autodiscoverable RSS feed without a private token"
it_behaves_like "it has an RSS button without an RSS token"
it_behaves_like "an autodiscoverable RSS feed without an RSS token"
end
end

View file

@ -16,8 +16,8 @@ feature 'Project Issues RSS' do
visit path
end
it_behaves_like "it has an RSS button with current_user's private token"
it_behaves_like "an autodiscoverable RSS feed 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 RSS token"
end
context 'when signed out' do
@ -25,7 +25,7 @@ feature 'Project Issues RSS' do
visit path
end
it_behaves_like "it has an RSS button without a private token"
it_behaves_like "an autodiscoverable RSS feed without a private token"
it_behaves_like "it has an RSS button without an RSS token"
it_behaves_like "an autodiscoverable RSS feed without an RSS token"
end
end

View file

@ -12,7 +12,7 @@ feature 'Project RSS' do
visit path
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
context 'when signed out' do
@ -20,6 +20,6 @@ feature 'Project RSS' do
visit path
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

View file

@ -12,7 +12,7 @@ feature 'Project Tree RSS' do
visit path
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
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 a private token"
it_behaves_like "an autodiscoverable RSS feed without an RSS token"
end
end

View file

@ -9,7 +9,7 @@ feature 'User RSS' do
visit path
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
context 'when signed out' do
@ -17,6 +17,6 @@ feature 'User RSS' do
visit path
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

View file

@ -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 private_token" do
it "includes the current_user's rss_token" do
current_user = create(: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
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)
expect(helper.rss_url_options[:private_token]).to be_nil
expect(helper.rss_url_options[:rss_token]).to be_nil
end
end
end

View file

@ -440,6 +440,22 @@ describe User, models: true do
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
it 'is false when reset_password_sent_at is nil' do
user = build_stubbed(:user, reset_password_sent_at: nil)

View file

@ -151,6 +151,10 @@ describe ProfilesController, "routing" do
expect(put("/profile/reset_private_token")).to route_to('profiles#reset_private_token')
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
expect(get("/profile")).to route_to('profiles#show')
end

View file

@ -1,23 +1,23 @@
shared_examples "an autodiscoverable RSS feed with current_user's private token" do
it "has an RSS autodiscovery link tag with current_user's private token" do
expect(page).to have_css("link[type*='atom+xml'][href*='private_token=#{Thread.current[:current_user].private_token}']", visible: false)
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=#{Thread.current[:current_user].rss_token}']", visible: false)
end
end
shared_examples "it has an RSS button with current_user's private token" do
it "shows the RSS button with current_user's private token" do
expect(page).to have_css("a:has(.fa-rss)[href*='private_token=#{Thread.current[:current_user].private_token}']")
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=#{Thread.current[:current_user].rss_token}']")
end
end
shared_examples "an autodiscoverable RSS feed without a private token" do
it "has an RSS autodiscovery link tag without a private token" do
expect(page).to have_css("link[type*='atom+xml']:not([href*='private_token'])", visible: false)
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)
end
end
shared_examples "it has an RSS button without a private token" do
it "shows the RSS button without a private token" do
expect(page).to have_css("a:has(.fa-rss):not([href*='private_token'])")
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'])")
end
end

View file

@ -18,4 +18,10 @@ describe 'tokens rake tasks' do
expect { run_rake_task('tokens:reset_all_email') }.to change { user.reload.incoming_email_token }
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