Require private asciicasts to be requested via secret token

This commit is contained in:
Marcin Kulik 2015-04-26 13:30:42 +00:00
parent 6aeb8810ad
commit 2c7d549778
13 changed files with 151 additions and 63 deletions

View File

@ -19,7 +19,7 @@ module Api
end
def show
@asciicast = Asciicast.find(params[:id])
@asciicast = Asciicast.find_by_id_or_secret_token!(params[:id])
respond_with(asciicast) do |format|
format.html do

View File

@ -15,6 +15,7 @@ class AsciicastsController < ApplicationController
end
def show
# TODO: filter out private or not (????)
respond_to do |format|
format.html do
view_counter.increment(asciicast, cookies)
@ -69,7 +70,7 @@ class AsciicastsController < ApplicationController
private
def load_resource
@asciicast = Asciicast.find(params[:id])
@asciicast = Asciicast.find_by_id_or_secret_token!(params[:id])
end
def view_counter

View File

@ -30,6 +30,14 @@ class Asciicast < ActiveRecord::Base
before_create :generate_secret_token
def self.find_by_id_or_secret_token!(thing)
if thing.size == 25
find_by_secret_token!(thing)
else
non_private.find(thing)
end
end
def self.cache_key
timestamps = scoped.select(:updated_at).map { |o| o.updated_at.to_i }
Digest::MD5.hexdigest timestamps.join('/')
@ -67,6 +75,14 @@ class Asciicast < ActiveRecord::Base
SecureRandom.hex.to_i(16).to_s(36).rjust(25, '0')
end
def to_param
if private?
secret_token
else
id.to_s
end
end
def stdout
return @stdout if @stdout
@stdout = Stdout::Buffered.new(get_stdout)

View File

@ -103,16 +103,20 @@ class User < ActiveRecord::Base
end
end
def public_asciicast_count
asciicasts.non_private.count
end
def asciicast_count
asciicasts.count
end
def asciicasts_excluding(asciicast, limit)
asciicasts.where('id <> ?', asciicast.id).order('RANDOM()').limit(limit)
def other_asciicasts(asciicast, limit)
asciicasts.non_private.where('id <> ?', asciicast.id).order('RANDOM()').limit(limit)
end
def paged_asciicasts(page, per_page)
asciicasts.
def paged_asciicasts(page, per_page, include_private)
asciicasts_scope(include_private).
includes(:user).
order("created_at DESC").
paginate(page, per_page)
@ -134,4 +138,12 @@ class User < ActiveRecord::Base
end while self.class.exists?(auth_token: self[:auth_token])
end
def asciicasts_scope(include_private)
if include_private
asciicasts
else
asciicasts.non_private
end
end
end

View File

@ -118,12 +118,8 @@ class AsciicastPagePresenter
end
end
def show_other_asciicasts_by_author?
author.asciicast_count > 1
end
def other_asciicasts_by_author
author.asciicasts_excluding(asciicast, 3).decorate
@other_asciicasts_by_author ||= author.other_asciicasts(asciicast, 3).decorate
end
def asciicast_oembed_url(format)

View File

@ -39,15 +39,17 @@ class UserPagePresenter
def asciicast_count_text(h)
if current_users_profile?
if user.asciicast_count > 0
count = h.pluralize(user.asciicast_count, 'asciicast')
count = user.asciicast_count
if count > 0
count = h.pluralize(count, 'asciicast')
"You have recorded #{count}"
else
"Record your first asciicast"
end
else
if user.asciicast_count > 0
count = h.pluralize(user.asciicast_count, 'asciicast')
count = user.public_asciicast_count
if count > 0
count = h.pluralize(count, 'asciicast')
"#{count} by #{user.display_name}"
else
"#{user.display_name} hasn't recorded anything yet"
@ -70,7 +72,8 @@ class UserPagePresenter
private
def get_asciicasts
PaginatingDecorator.new(user.paged_asciicasts(page, per_page))
asciicasts = user.paged_asciicasts(page, per_page, current_users_profile?)
PaginatingDecorator.new(asciicasts)
end
end

View File

@ -82,7 +82,7 @@
.container
.content = page.description
- if page.show_other_asciicasts_by_author?
- unless page.other_asciicasts_by_author.empty?
section.even
.container
.other-asciicasts

View File

@ -1,5 +1,24 @@
require 'rails_helper'
shared_examples_for "asciicast iframe response" do
it "responds with status 200" do
expect(response.status).to eq(200)
end
it "responds with html content type" do
expect(response.headers['Content-Type']).to match('text/html')
end
it "responds without X-Frame-Options header" do
pending "the header is added back by Rails only in tests O_o"
expect(response.headers).to_not have_key('Content-Type')
end
it "responds with player page using iframe layout" do
expect(response.body).to have_selector('body.iframe div.player')
end
end
describe "Asciicast retrieval" do
let(:asciicast) { create(:asciicast) }
@ -18,24 +37,15 @@ describe "Asciicast retrieval" do
include Capybara::RSpecMatchers
before do
get "/api/asciicasts/#{asciicast.id}", format: 'html'
get "/api/asciicasts/#{asciicast.to_param}", format: 'html'
end
it "responds with status 200" do
expect(response.status).to eq(200)
end
it_behaves_like "asciicast iframe response"
it "responds with html content type" do
expect(response.headers['Content-Type']).to match('text/html')
end
context "for private asciicast" do
let(:asciicast) { create(:asciicast, private: true) }
it "responds without X-Frame-Options header" do
pending "the header is added back by Rails only in tests O_o"
expect(response.headers).to_not have_key('Content-Type')
end
it "responds with player page using iframe layout" do
expect(response.body).to have_selector('body.iframe div.player')
it_behaves_like "asciicast iframe response"
end
end

View File

@ -41,7 +41,7 @@ describe AsciicastsController do
before do
allow(controller).to receive(:view_counter) { view_counter }
expect(Asciicast).to receive(:find).and_return(asciicast)
expect(Asciicast).to receive(:find_by_id_or_secret_token!).and_return(asciicast)
end
let(:asciicast_presenter) { double('asciicast_presenter') }
@ -74,7 +74,7 @@ describe AsciicastsController do
let(:make_request) { get :edit, :id => asciicast.id }
before do
expect(Asciicast).to receive(:find).and_return(asciicast)
expect(Asciicast).to receive(:find_by_id_or_secret_token!).and_return(asciicast)
asciicast.user = user
end
@ -114,7 +114,7 @@ describe AsciicastsController do
before do
allow(controller).to receive(:asciicast_updater) { asciicast_updater }
expect(Asciicast).to receive(:find).and_return(asciicast)
expect(Asciicast).to receive(:find_by_id_or_secret_token!).and_return(asciicast)
asciicast.user = user
end
@ -165,7 +165,7 @@ describe AsciicastsController do
let(:make_request) { delete :destroy, :id => asciicast.id }
before do
expect(Asciicast).to receive(:find).and_return(asciicast)
expect(Asciicast).to receive(:find_by_id_or_secret_token!).and_return(asciicast)
asciicast.user = user
end

View File

@ -3,6 +3,44 @@ require 'tempfile'
describe Asciicast do
describe '.find_by_id_or_secret_token!' do
subject { Asciicast.find_by_id_or_secret_token!(thing) }
context 'for public asciicast' do
let(:asciicast) { create(:asciicast, private: false) }
context 'when looked up by id' do
let(:thing) { asciicast.id }
it { should eq(asciicast) }
end
context 'when looked up by secret token' do
let(:thing) { asciicast.secret_token }
it { should eq(asciicast) }
end
end
context 'for private asciicast' do
let(:asciicast) { create(:asciicast, private: true) }
context 'when looked up by id' do
let(:thing) { asciicast.id }
it 'raises RecordNotFound' do
expect { subject }.to raise_error(ActiveRecord::RecordNotFound)
end
end
context 'when looked up by secret token' do
let(:thing) { asciicast.secret_token }
it { should eq(asciicast) }
end
end
end
describe '.for_category_ordered' do
subject { described_class.for_category_ordered(category, order) }
@ -52,7 +90,27 @@ describe Asciicast do
end
end
let(:asciicast) { described_class.new }
describe '#to_param' do
subject { asciicast.to_param }
let(:asciicast) { Asciicast.new(id: 123, secret_token: 'sekrit') }
context 'for public asciicast' do
before do
asciicast.private = false
end
it { should eq('123') }
end
context 'for private asciicast' do
before do
asciicast.private = true
end
it { should eq('sekrit') }
end
end
describe '#stdout' do
context 'for single-file, JSON asciicast' do

View File

@ -148,8 +148,8 @@ describe User do
it { should eq(2) }
end
describe '#asciicasts_excluding' do
subject { user.asciicasts_excluding(asciicast, 1) }
describe '#other_asciicasts' do
subject { user.other_asciicasts(asciicast, 1) }
let(:user) { create(:user) }
let(:asciicast) { create(:asciicast, user: user) }

View File

@ -155,26 +155,6 @@ describe AsciicastPagePresenter do
it { should eq('i am description') }
end
describe '#show_other_asciicasts_by_author?' do
subject { presenter.show_other_asciicasts_by_author? }
before do
allow(author).to receive(:asciicast_count) { count }
end
context "when user has more than 1 asciicast" do
let(:count) { 2 }
it { should be(true) }
end
context "when user doesn't have more than 1 asciicasts" do
let(:count) { 1 }
it { should be(false) }
end
end
describe '#other_asciicasts_by_author' do
subject { presenter.other_asciicasts_by_author }
@ -182,7 +162,7 @@ describe AsciicastPagePresenter do
let(:decorated_others) { double('decorated_others') }
before do
allow(author).to receive(:asciicasts_excluding).
allow(author).to receive(:other_asciicasts).
with(asciicast, 3) { others }
end

View File

@ -117,11 +117,23 @@ describe UserPagePresenter do
describe '#asciicast_count_text' do
subject { presenter.asciicast_count_text(view_context) }
context 'for non author' do
before do
allow(user).to receive(:public_asciicast_count) { 2 }
end
it { should match(/2.+cartman/) }
end
context 'for author' do
let(:current_user) { user }
before do
allow(user).to receive(:asciicast_count) { 3 }
end
it { should eq('3 asciicasts by cartman') }
it { should match(/you.+3/i) }
end
end
describe '#user_username' do
@ -143,7 +155,7 @@ describe UserPagePresenter do
it "gets user's asciicasts paged" do
subject
expect(user).to have_received(:paged_asciicasts).with(2, 5)
expect(user).to have_received(:paged_asciicasts).with(2, 5, false)
end
it "wraps the asciicasts with paginating decorator" do