colaboraciones anonimas #75
This commit is contained in:
parent
6713821dd5
commit
583405f9ff
9 changed files with 237 additions and 16 deletions
1
Gemfile
1
Gemfile
|
@ -96,4 +96,5 @@ end
|
||||||
group :test do
|
group :test do
|
||||||
gem 'database_cleaner'
|
gem 'database_cleaner'
|
||||||
gem 'factory_bot_rails'
|
gem 'factory_bot_rails'
|
||||||
|
gem 'timecop'
|
||||||
end
|
end
|
||||||
|
|
|
@ -246,7 +246,7 @@ GEM
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
pundit (2.1.0)
|
pundit (2.1.0)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
rack (2.0.8)
|
rack (2.2.2)
|
||||||
rack-proxy (0.6.5)
|
rack-proxy (0.6.5)
|
||||||
rack
|
rack
|
||||||
rack-test (1.1.0)
|
rack-test (1.1.0)
|
||||||
|
@ -367,6 +367,7 @@ GEM
|
||||||
thor (1.0.1)
|
thor (1.0.1)
|
||||||
thread_safe (0.3.6)
|
thread_safe (0.3.6)
|
||||||
tilt (2.0.10)
|
tilt (2.0.10)
|
||||||
|
timecop (0.9.1)
|
||||||
turbolinks (5.2.1)
|
turbolinks (5.2.1)
|
||||||
turbolinks-source (~> 5.2)
|
turbolinks-source (~> 5.2)
|
||||||
turbolinks-source (5.2.0)
|
turbolinks-source (5.2.0)
|
||||||
|
@ -452,6 +453,7 @@ DEPENDENCIES
|
||||||
sqlite3
|
sqlite3
|
||||||
sucker_punch
|
sucker_punch
|
||||||
terminal-table
|
terminal-table
|
||||||
|
timecop
|
||||||
turbolinks (~> 5)
|
turbolinks (~> 5)
|
||||||
uglifier (>= 1.3.0)
|
uglifier (>= 1.3.0)
|
||||||
validates_hostname
|
validates_hostname
|
||||||
|
|
|
@ -28,7 +28,7 @@ module Api
|
||||||
expires = 30.minutes
|
expires = 30.minutes
|
||||||
cookies.encrypted[site] = {
|
cookies.encrypted[site] = {
|
||||||
httponly: true,
|
httponly: true,
|
||||||
secure: true,
|
secure: !Rails.env.test?,
|
||||||
expires: expires,
|
expires: expires,
|
||||||
same_site: :none,
|
same_site: :none,
|
||||||
value: {
|
value: {
|
||||||
|
|
|
@ -26,8 +26,15 @@ module Api
|
||||||
# No procesar nada más si ya se aplicaron todos los filtros
|
# No procesar nada más si ya se aplicaron todos los filtros
|
||||||
return if performed?
|
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
|
# Redirigir a la URL de agradecimiento
|
||||||
redirect_to params[:redirect_to]
|
redirect_to params[:redirect_to] || site.url
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -39,7 +46,7 @@ module Api
|
||||||
def cookie_is_valid?
|
def cookie_is_valid?
|
||||||
unless cookies.encrypted[site_id] &&
|
unless cookies.encrypted[site_id] &&
|
||||||
cookies.encrypted[site_id]['expires'] > Time.now.to_i
|
cookies.encrypted[site_id]['expires'] > Time.now.to_i
|
||||||
render html: nil, status: :no_content
|
render html: 'cookie_invalid', status: :no_content
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -50,9 +57,11 @@ module Api
|
||||||
# TODO: Pensar una forma de redirigir al origen sin vaciar el
|
# TODO: Pensar una forma de redirigir al origen sin vaciar el
|
||||||
# formulario para que le usuarie recargue la cookie.
|
# formulario para que le usuarie recargue la cookie.
|
||||||
def valid_authenticity_token_in_cookie?
|
def valid_authenticity_token_in_cookie?
|
||||||
unless valid_authenticity_token? session, cookies.encrypted[site_id]
|
if valid_authenticity_token? session, cookies.encrypted[site_id]['csrf']
|
||||||
render html: nil, status: :no_content
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
render html: 'token_invalid', status: :no_content
|
||||||
end
|
end
|
||||||
|
|
||||||
# El sitio existe y soporta colaboracion anónima
|
# El sitio existe y soporta colaboracion anónima
|
||||||
|
@ -62,7 +71,7 @@ module Api
|
||||||
def site_exists_and_is_anonymous?
|
def site_exists_and_is_anonymous?
|
||||||
_, anon = site_anon_pair
|
_, anon = site_anon_pair
|
||||||
|
|
||||||
render html: nil, status: :no_content
|
render html: 'site_not_anon', status: :no_content unless anon
|
||||||
end
|
end
|
||||||
|
|
||||||
# El navegador envía la URL del sitio en el encabezado Origin,
|
# El navegador envía la URL del sitio en el encabezado Origin,
|
||||||
|
@ -70,9 +79,9 @@ module Api
|
||||||
def site_is_origin?
|
def site_is_origin?
|
||||||
site, = site_anon_pair
|
site, = site_anon_pair
|
||||||
|
|
||||||
unless "https://#{site}" === request.headers['Origin']
|
return if request.headers['Origin'] == "https://#{site}"
|
||||||
render html: nil, status: :no_content
|
|
||||||
end
|
render html: 'site_not_origin', status: :no_content
|
||||||
end
|
end
|
||||||
|
|
||||||
# Solo soy un atajo
|
# Solo soy un atajo
|
||||||
|
|
5
app/models/site/author.rb
Normal file
5
app/models/site/author.rb
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Site
|
||||||
|
Author = Struct.new :email, :name, keyword_init: true
|
||||||
|
end
|
|
@ -8,7 +8,7 @@ PostService = Struct.new(:site, :usuarie, :post, :params, keyword_init: true) do
|
||||||
#
|
#
|
||||||
# @return Post
|
# @return Post
|
||||||
def create
|
def create
|
||||||
self.post = site.posts(lang: params[:post][:lang] || I18n.locale)
|
self.post = site.posts(lang: lang)
|
||||||
.build(layout: params[:post][:layout])
|
.build(layout: params[:post][:layout])
|
||||||
post.usuaries << usuarie
|
post.usuaries << usuarie
|
||||||
params[:post][:draft] = true if site.invitade? 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
|
post
|
||||||
end
|
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
|
def update
|
||||||
post.usuaries << usuarie
|
post.usuaries << usuarie
|
||||||
params[:post][:draft] = true if site.invitade? 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)
|
params.require(:post).permit(post.params)
|
||||||
end
|
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
|
def lang
|
||||||
params[:post][:lang] || I18n.locale
|
params[:post][:lang] || I18n.locale
|
||||||
end
|
end
|
||||||
|
|
12
db/seeds.rb
12
db/seeds.rb
|
@ -16,10 +16,12 @@ licencias.each do |l|
|
||||||
licencia.update l
|
licencia.update l
|
||||||
end
|
end
|
||||||
|
|
||||||
YAML.safe_load(File.read('db/seeds/sites.yml')).each do |site|
|
unless Rails.env.test?
|
||||||
site = Site.find_or_create_by name: site['name']
|
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,
|
site.update licencia: Licencia.first, design: Design.first,
|
||||||
title: site.name, description: 'x' * 50,
|
title: site.name, description: 'x' * 50,
|
||||||
deploys: [DeployLocal.new]
|
deploys: [DeployLocal.new]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
46
test/controllers/api/v1/invitades_controller_test.rb
Normal file
46
test/controllers/api/v1/invitades_controller_test.rb
Normal 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
|
136
test/controllers/api/v1/posts_controller_test.rb
Normal file
136
test/controllers/api/v1/posts_controller_test.rb
Normal 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
|
Loading…
Reference in a new issue