colaboraciones anonimas #75

This commit is contained in:
f 2020-02-18 13:45:08 -03:00
parent 6713821dd5
commit 583405f9ff
No known key found for this signature in database
GPG key ID: 2AE5A13E321F953D
9 changed files with 237 additions and 16 deletions

View file

@ -96,4 +96,5 @@ end
group :test do
gem 'database_cleaner'
gem 'factory_bot_rails'
gem 'timecop'
end

View file

@ -246,7 +246,7 @@ GEM
nio4r (~> 2.0)
pundit (2.1.0)
activesupport (>= 3.0.0)
rack (2.0.8)
rack (2.2.2)
rack-proxy (0.6.5)
rack
rack-test (1.1.0)
@ -367,6 +367,7 @@ GEM
thor (1.0.1)
thread_safe (0.3.6)
tilt (2.0.10)
timecop (0.9.1)
turbolinks (5.2.1)
turbolinks-source (~> 5.2)
turbolinks-source (5.2.0)
@ -452,6 +453,7 @@ DEPENDENCIES
sqlite3
sucker_punch
terminal-table
timecop
turbolinks (~> 5)
uglifier (>= 1.3.0)
validates_hostname

View file

@ -28,7 +28,7 @@ module Api
expires = 30.minutes
cookies.encrypted[site] = {
httponly: true,
secure: true,
secure: !Rails.env.test?,
expires: expires,
same_site: :none,
value: {

View file

@ -26,8 +26,15 @@ module Api
# No procesar nada más si ya se aplicaron todos los filtros
return if performed?
usuarie = Site::Author.new name: 'Anon', email: "anon@#{site.hostname}"
service = PostService.new(params: params,
site: site,
usuarie: usuarie)
service.create_anonymous
# Redirigir a la URL de agradecimiento
redirect_to params[:redirect_to]
redirect_to params[:redirect_to] || site.url
end
private
@ -39,7 +46,7 @@ module Api
def cookie_is_valid?
unless cookies.encrypted[site_id] &&
cookies.encrypted[site_id]['expires'] > Time.now.to_i
render html: nil, status: :no_content
render html: 'cookie_invalid', status: :no_content
end
end
@ -50,9 +57,11 @@ module Api
# TODO: Pensar una forma de redirigir al origen sin vaciar el
# formulario para que le usuarie recargue la cookie.
def valid_authenticity_token_in_cookie?
unless valid_authenticity_token? session, cookies.encrypted[site_id]
render html: nil, status: :no_content
if valid_authenticity_token? session, cookies.encrypted[site_id]['csrf']
return
end
render html: 'token_invalid', status: :no_content
end
# El sitio existe y soporta colaboracion anónima
@ -62,7 +71,7 @@ module Api
def site_exists_and_is_anonymous?
_, anon = site_anon_pair
render html: nil, status: :no_content
render html: 'site_not_anon', status: :no_content unless anon
end
# El navegador envía la URL del sitio en el encabezado Origin,
@ -70,9 +79,9 @@ module Api
def site_is_origin?
site, = site_anon_pair
unless "https://#{site}" === request.headers['Origin']
render html: nil, status: :no_content
end
return if request.headers['Origin'] == "https://#{site}"
render html: 'site_not_origin', status: :no_content
end
# Solo soy un atajo

View file

@ -0,0 +1,5 @@
# frozen_string_literal: true
class Site
Author = Struct.new :email, :name, keyword_init: true
end

View file

@ -8,7 +8,7 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do
#
# @return Post
def create
self.post = site.posts(lang: params[:post][:lang] || I18n.locale)
self.post = site.posts(lang: lang)
.build(layout: params[:post][:layout])
post.usuaries << usuarie
params[:post][:draft] = true if site.invitade? usuarie
@ -20,6 +20,19 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do
post
end
# Crear un post anónimo, con opciones más limitadas
def create_anonymous
# XXX: Confiamos en el parámetro de idioma porque estamos
# verificándolos en Site#posts
self.post = site.posts(lang: lang)
.build(layout: params[:post][:layout])
# Los artículos anónimos siempre son borradores
params[:post][:draft] = true
commit(action: :created) if post.update(anon_post_params)
post
end
def update
post.usuaries << usuarie
params[:post][:draft] = true if site.invitade? usuarie
@ -82,6 +95,13 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do
params.require(:post).permit(post.params)
end
# Eliminar metadatos internos
def anon_post_params
post_params.delete_if do |k, _|
%w[date slug order uuid].include? k
end
end
def lang
params[:post][:lang] || I18n.locale
end

View file

@ -16,10 +16,12 @@ licencias.each do |l|
licencia.update l
end
YAML.safe_load(File.read('db/seeds/sites.yml')).each do |site|
site = Site.find_or_create_by name: site['name']
unless Rails.env.test?
YAML.safe_load(File.read('db/seeds/sites.yml')).each do |site|
site = Site.find_or_create_by name: site['name']
site.update licencia: Licencia.first, design: Design.first,
title: site.name, description: 'x' * 50,
deploys: [DeployLocal.new]
site.update licencia: Licencia.first, design: Design.first,
title: site.name, description: 'x' * 50,
deploys: [DeployLocal.new]
end
end

View file

@ -0,0 +1,46 @@
# frozen_string_literal: true
require 'test_helper'
module Api
module V1
class PostsControllerTest < ActionDispatch::IntegrationTest
setup do
@rol = create :rol
@site = @rol.site
@usuarie = @rol.usuarie
@site.update_attribute :colaboracion_anonima, true
end
teardown do
@site.destroy
end
test 'primero hay que pedir una cookie' do
get v1_site_invitades_cookie_url(@site)
assert cookies[@site.name]
assert cookies['_sutty_session']
end
test 'solo si el sitio existe' do
site = SecureRandom.hex
get v1_site_invitades_cookie_url(site_id: site)
assert_not cookies[site]
assert_not cookies['_sutty_session']
end
test 'solo si el sitio tiene colaboracion anonima' do
@site.update_attribute :colaboracion_anonima, false
get v1_site_invitades_cookie_url(@site)
assert_not cookies[@site.name]
assert_not cookies['_sutty_session']
end
end
end
end

View file

@ -0,0 +1,136 @@
# frozen_string_literal: true
require 'test_helper'
module Api
module V1
class PostsControllerTest < ActionDispatch::IntegrationTest
setup do
@rol = create :rol
@site = @rol.site
@usuarie = @rol.usuarie
@site.update_attribute :colaboracion_anonima, true
end
teardown do
@site.destroy
end
test 'no se pueden enviar sin cookie' do
post v1_site_posts_url(@site), params: {
post: {
title: SecureRandom.hex,
description: SecureRandom.hex
}
}
posts = @site.posts.size
@site = Site.find(@site.id)
assert_equal posts, @site.posts.size
assert_response :no_content
end
test 'no se pueden enviar a sitios que no existen' do
site = SecureRandom.hex
get v1_site_invitades_cookie_url(site_id: site)
post v1_site_posts_url(site_id: site),
headers: { cookies: cookies },
params: {
post: {
title: SecureRandom.hex,
description: SecureRandom.hex
}
}
assert_response :no_content
end
test 'antes hay que pedir una cookie' do
assert_equal 2, @site.posts.size
get v1_site_invitades_cookie_url(@site)
post v1_site_posts_url(@site),
headers: {
cookies: cookies,
origin: "https://#{@site.name}"
},
params: {
post: {
title: SecureRandom.hex,
description: SecureRandom.hex
}
}
# XXX: No tenemos reload
@site = Site.find @site.id
assert_equal 3, @site.posts.size
assert_response :redirect
end
test 'no se pueden enviar algunos valores' do
uuid = SecureRandom.uuid
date = Date.today + 2.days
slug = SecureRandom.hex
title = SecureRandom.hex
order = (rand * 100).to_i
get v1_site_invitades_cookie_url(@site)
post v1_site_posts_url(@site),
headers: {
cookies: cookies,
origin: "https://#{@site.name}"
},
params: {
post: {
title: title,
description: SecureRandom.hex,
uuid: uuid,
date: date,
slug: slug,
order: order
}
}
# XXX: No tenemos reload
@site = Site.find @site.id
p = @site.posts.find_by title: title
assert_not_equal uuid, p.uuid.value
assert_not_equal slug, p.slug.value
assert_not_equal order, p.order.value
assert_not_equal date, p.date.value
end
test 'las cookies tienen un vencimiento interno' do
assert_equal 2, @site.posts.size
get v1_site_invitades_cookie_url(@site)
Timecop.freeze(Time.now + 31.minutes) do
post v1_site_posts_url(@site),
headers: {
cookies: cookies,
origin: "https://#{@site.name}"
},
params: {
post: {
title: SecureRandom.hex,
description: SecureRandom.hex
}
}
end
@site = Site.find @site.id
assert_response :no_content
assert_equal 2, @site.posts.size
end
end
end
end