From f77a907753980c30fcb7200420e22ebd32afdc2e Mon Sep 17 00:00:00 2001 From: f Date: Sat, 22 Aug 2020 21:04:46 -0300 Subject: [PATCH] version privadas del sitio #180 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit se puede compilar una versión privada del sitio solo accesible a través del panel, es decir con los mismos niveles de acceso. en combinación con el complemento de jekyll correspondiente, sería posible ver un sitio con otros datos que no se publican. --- .gitignore | 1 + Dockerfile | 1 + app/controllers/private_controller.rb | 76 ++++++++++++++++++++++++++ app/models/deploy_private.rb | 23 ++++++++ app/models/site.rb | 2 +- app/policies/site_policy.rb | 5 ++ app/views/deploys/_deploy_private.haml | 20 +++++++ app/views/posts/index.haml | 6 +- config/locales/en.yml | 12 ++++ config/locales/es.yml | 12 ++++ config/routes.rb | 7 ++- 11 files changed, 160 insertions(+), 5 deletions(-) create mode 100644 app/controllers/private_controller.rb create mode 100644 app/models/deploy_private.rb create mode 100644 app/views/deploys/_deploy_private.haml diff --git a/.gitignore b/.gitignore index 380b9840..0487e3ec 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ /_sites/* /_deploy/* +/_private/* /data/* /_storage/* diff --git a/Dockerfile b/Dockerfile index b056debc..2ca5e301 100644 --- a/Dockerfile +++ b/Dockerfile @@ -103,6 +103,7 @@ RUN ln -s data/_storage /srv/http/_storage RUN ln -s data/_sites /srv/http/_sites RUN ln -s data/_deploy /srv/http/_deploy RUN ln -s data/_public /srv/http/_public +RUN ln -s data/_private /srv/http/_private # Volver a root para cerrar la compilación USER root diff --git a/app/controllers/private_controller.rb b/app/controllers/private_controller.rb new file mode 100644 index 00000000..a9c5bb96 --- /dev/null +++ b/app/controllers/private_controller.rb @@ -0,0 +1,76 @@ +# Gestiona las versiones privadas de los sitios. Solo se puede acceder +# con una cuenta +class PrivateController < ApplicationController + # XXX: Permite ejecutar JS + skip_forgery_protection + + include Pundit + rescue_from Pundit::NilPolicyError, with: :page_not_found + + # Enviar el archivo si existe, agregar una / al final siempre para no + # romper las direcciones relativas. + def show + authorize site + + # Detectar si necesitamos una / al final + if needs_trailing_slash? + redirect_to request.url + '/' + return + end + + if deploy_private + send_file path, disposition: 'inline' + else + head :not_found + end + end + + private + + # Detects if the URL should have a trailing slash + def needs_trailing_slash? + !trailing_slash? && params[:format].blank? + end + + def trailing_slash? + request.env['REQUEST_URI'].ends_with?('/') + end + + def site + @site ||= find_site + end + + def deploy_private + @deploy_private ||= site.deploys.find_by(type: 'DeployPrivate') + end + + # Devuelve la ruta completa del archivo + def path + return @path if @path + + @path = Pathname.new(File.join(deploy_private.destination, file)).realpath.to_s + + raise Errno::ENOENT unless @path.starts_with? deploy_private.destination + + @path + rescue Errno::ENOENT + File.join(deploy_private.destination, '404.html') + end + + # Devuelve la ruta del archivo, limpieza copiada desde Jekyll + # + # @see Jekyll::URL#sanitize_url + def file + return @file if @file + + @file = params[:file] || '/' + @file += '/' if trailing_slash? + @file += if @file.ends_with? '/' + 'index.html' + else + '.' + params[:format].to_s + end + + @file = @file.gsub('..', '/').gsub('./', '').squeeze('/') + end +end diff --git a/app/models/deploy_private.rb b/app/models/deploy_private.rb new file mode 100644 index 00000000..8535bc82 --- /dev/null +++ b/app/models/deploy_private.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +# Permite generar el sitio en una versión privada, mostrando información +# que no se vería públicamente (borradores, campos privados, etc.) +# +# XXX: La plantilla tiene que soportar esto con el plugin +# jekyll-private-data +class DeployPrivate < DeployLocal + # No es necesario volver a instalar dependencias + def deploy + jekyll_build + end + + # Hacer el deploy a un directorio privado + def destination + File.join(Rails.root, '_private', site.name) + end + + # No usar recursos en compresión y habilitar los datos privados + def env + @env ||= super.merge({ 'JEKYLL_ENV' => 'development', 'JEKYLL_PRIVATE' => 'true' }) + end +end diff --git a/app/models/site.rb b/app/models/site.rb index 64312a9f..8f74b3c8 100644 --- a/app/models/site.rb +++ b/app/models/site.rb @@ -15,7 +15,7 @@ class Site < ApplicationRecord # TODO: Hacer que los diferentes tipos de deploy se auto registren # @see app/services/site_service.rb - DEPLOYS = %i[local www zip hidden_service].freeze + DEPLOYS = %i[local private www zip hidden_service].freeze validates :name, uniqueness: true, hostname: { allow_root_label: true diff --git a/app/policies/site_policy.rb b/app/policies/site_policy.rb index 1177596c..33bdb2af 100644 --- a/app/policies/site_policy.rb +++ b/app/policies/site_policy.rb @@ -14,6 +14,11 @@ class SitePolicy true end + # Puede ver la versión privada del sitio? + def private? + edit? && site.deploys.find_by_type('DeployPrivate') + end + # Todes les usuaries pueden ver el sitio si aceptaron la invitación def show? !current_role.temporal diff --git a/app/views/deploys/_deploy_private.haml b/app/views/deploys/_deploy_private.haml new file mode 100644 index 00000000..e1a46052 --- /dev/null +++ b/app/views/deploys/_deploy_private.haml @@ -0,0 +1,20 @@ +-# Formulario para alojar una copia privada + +.row + .col + = deploy.hidden_field :id + = deploy.hidden_field :type + + .custom-control.custom-switch + -# + El checkbox invierte la lógica de destrucción porque queremos + crear el deploy si está activado y destruirlo si está + desactivado. + = deploy.check_box :_destroy, + { checked: deploy.object.persisted?, class: 'custom-control-input' }, + '0', '1' + = deploy.label :_destroy, class: 'custom-control-label' do + %h3= t('.title') + = sanitize_markdown t('.help'), + tags: %w[p strong em a] +%hr/ diff --git a/app/views/posts/index.haml b/app/views/posts/index.haml index 4ed451ec..a77705ac 100644 --- a/app/views/posts/index.haml +++ b/app/views/posts/index.haml @@ -17,8 +17,10 @@ new_site_post_path(@site, layout: layout) - if policy(@site).edit? - = link_to t('sites.edit.btn', site: @site.title), - edit_site_path(@site), class: 'btn' + = link_to t('sites.edit.btn', site: @site.title), edit_site_path(@site), class: 'btn' + + - if policy(@site).private? + = link_to t('sites.private'), '../private/' + @site.name, class: 'btn', target: '_blank' = render 'sites/build', site: @site diff --git a/config/locales/en.yml b/config/locales/en.yml index 6f2562c6..09fe29a7 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -71,6 +71,10 @@ en: title: Link to www success: Success! error: Error + deploy_private: + title: Private version + success: Success! + error: Error deploy_zip: title: Build ZIP file success: Available for download @@ -255,6 +259,13 @@ en: We're working out the details for allowing your own site domains, you can help us! ejemplo: 'example' + deploy_private: + title: 'Generate private version' + help: | + Some templates support gathering private information. By + enabling this option, when changes are published, you and your + collaborators will be able to access this information in a + private copy of the site. deploy_www: title: 'Add www to the address' help: | @@ -337,6 +348,7 @@ en: title: 'Sites' enqueued: 'Waiting for build' enqueue: 'Publish all changes' + private: 'Private version' failed: 'Failed!' build_log: 'Read log' invitations: diff --git a/config/locales/es.yml b/config/locales/es.yml index ee25cc1b..097ee2fc 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -79,6 +79,10 @@ es: title: Alojar como servicio oculto de Tor success: ¡Éxito! error: Hubo un error + deploy_private: + title: Versión privada del sitio + success: ¡Éxito! + error: Hubo un error help: Por cualquier duda, responde este correo para contactarte con nosotres. maintenance_mailer: notice: @@ -257,6 +261,13 @@ es: Estamos desarrollando la posibilidad de agregar tus propios dominios, ¡ayudanos! ejemplo: 'ejemplo' + deploy_private: + title: 'Generar versión privada' + help: | + Algunas plantillas contienen información privada, activando esta + opción, al publicar los cambios podrás acceder a una versión + privada del sitio, que solo estará accesible para todes les + colaboradores del sitio. deploy_www: title: 'Agregar www a la dirección' help: | @@ -339,6 +350,7 @@ es: title: 'Sitios' enqueued: 'Esperando publicación' enqueue: 'Publicar todos los cambios' + private: 'Versión privada' failed: '¡Falló!' build_log: 'Ver registro' invitations: diff --git a/config/routes.rb b/config/routes.rb index 20ddf5f6..22ff6a7f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -26,9 +26,12 @@ Rails.application.routes.draw do end end - resources :sites, constraints: { site_id: %r{[^/]+}, id: %r{[^/]+} } do - get 'public/:type/:basename', to: 'sites#send_public_file' + # Las rutas privadas empiezan con una ruta única para poder hacer un + # alias en nginx sin tener que usar expresiones regulares para + # detectar el nombre del sitio. + get '/sites/private/:site_id(*file)', to: 'private#show', constraints: { site_id: %r{[^/]+} } + resources :sites, constraints: { site_id: %r{[^/]+}, id: %r{[^/]+} } do # Gestionar actualizaciones del sitio get 'pull', to: 'sites#fetch' post 'pull', to: 'sites#merge'