diff --git a/app/controllers/api/v1/concerns/webhook_concern.rb b/app/controllers/api/v1/concerns/webhook_concern.rb deleted file mode 100644 index 9960e550..00000000 --- a/app/controllers/api/v1/concerns/webhook_concern.rb +++ /dev/null @@ -1,72 +0,0 @@ -# frozen_string_literal: true - -module Api - module V1 - # Helpers para webhooks - module WebhookConcern - extend ActiveSupport::Concern - - included do - # Responde con forbidden si falla la validación del token - rescue_from ActiveRecord::RecordNotFound, with: :platforms_answer - - private - - # Valida el token que envía la plataforma en el webhook - # - # @return [String] - def token - @token ||= - begin - header = request.headers - token = header['X-Social-Inbox'].presence - token ||= header['X-Gitlab-Token'].presence - token ||= token_from_signature(header['X-Gitea-Signature'].presence) - token ||= token_from_signature(header['X-Hub-Signature-256'].presence, 'sha256=') - token - ensure - raise ActiveRecord::RecordNotFound, 'Proveedor no soportado' if token.blank? - end - end - - # Valida token a partir de firma - # - # @param signature [String,nil] - # @param prepend [String] - # @return [String, nil] - def token_from_signature(signature, prepend = '') - return if signature.nil? - - payload = request.raw_post - - site.roles.where(temporal: false, rol: 'usuarie').pluck(:token).find do |token| - new_signature = prepend + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), token, payload) - - ActiveSupport::SecurityUtils.secure_compare(new_signature, signature.to_s) - end - end - - # Encuentra el sitio a partir de la URL - # - # @return [Site] - def site - @site ||= Site.find_by_name!(params[:site_id]) - end - - # Encuentra le usuarie - # - # @return [Site] - def usuarie - @usuarie ||= site.roles.find_by!(temporal: false, rol: 'usuarie', token: token).usuarie - end - - # Respuesta de error a plataformas - def platforms_answer(exception) - ExceptionNotifier.notify_exception(exception, data: { headers: request.headers.to_h }) - - head :forbidden - end - end - end - end -end diff --git a/app/controllers/api/v1/social_inbox_controller.rb b/app/controllers/api/v1/social_inbox_controller.rb deleted file mode 100644 index 3881b6bc..00000000 --- a/app/controllers/api/v1/social_inbox_controller.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true - -module Api - module V1 - # Recibe webhooks de la Social Inbox - class SocialInboxController < BaseController - include WebhookConcern - - def moderationqueued - head :accepted - end - - def onapproved - head :accepted - end - - def onrejected - head :accepted - end - end - end -end diff --git a/app/controllers/api/v1/webhooks/concerns/webhook_concern.rb b/app/controllers/api/v1/webhooks/concerns/webhook_concern.rb new file mode 100644 index 00000000..a546a55c --- /dev/null +++ b/app/controllers/api/v1/webhooks/concerns/webhook_concern.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +module Api + module V1 + module Webhooks + module Concerns + # Helpers para webhooks + module WebhookConcern + extend ActiveSupport::Concern + + included do + # Responde con forbidden si falla la validación del token + rescue_from ActiveRecord::RecordNotFound, with: :platforms_answer + + private + + # Valida el token que envía la plataforma en el webhook + # + # @return [String] + def token + @token ||= + begin + header = request.headers + token = header['X-Social-Inbox'].presence + token ||= header['X-Gitlab-Token'].presence + token ||= token_from_signature(header['X-Gitea-Signature'].presence) + token ||= token_from_signature(header['X-Hub-Signature-256'].presence, 'sha256=') + token + ensure + raise ActiveRecord::RecordNotFound, 'Proveedor no soportado' if token.blank? + end + end + + # Valida token a partir de firma + # + # @param signature [String,nil] + # @param prepend [String] + # @return [String, nil] + def token_from_signature(signature, prepend = '') + return if signature.nil? + + payload = request.raw_post + + site.roles.where(temporal: false, rol: 'usuarie').pluck(:token).find do |token| + new_signature = prepend + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), token, payload) + + ActiveSupport::SecurityUtils.secure_compare(new_signature, signature.to_s) + end + end + + # Encuentra el sitio a partir de la URL + # + # @return [Site] + def site + @site ||= Site.find_by_name!(params[:site_id]) + end + + # Encuentra le usuarie + # + # @return [Site] + def usuarie + @usuarie ||= site.roles.find_by!(temporal: false, rol: 'usuarie', token: token).usuarie + end + + # Respuesta de error a plataformas + def platforms_answer(exception) + ExceptionNotifier.notify_exception(exception, data: { headers: request.headers.to_h }) + + head :forbidden + end + end + end + end + end + end +end diff --git a/app/controllers/api/v1/webhooks/pull_controller.rb b/app/controllers/api/v1/webhooks/pull_controller.rb new file mode 100644 index 00000000..5f0b703b --- /dev/null +++ b/app/controllers/api/v1/webhooks/pull_controller.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Api + module V1 + module Webhooks + # Recibe webhooks y lanza un PullJob + class PullController < BaseController + include WebhookConcern + + # Trae los cambios a partir de un post de Webhooks: + # (Gitlab, Github, Gitea, etc) + # + # @return [nil] + def pull + message = I18n.with_locale(site.default_locale) do + I18n.t('webhooks.pull.message') + end + + GitPullJob.perform_later(site, usuarie, message) + head :ok + end + end + end + end +end diff --git a/app/controllers/api/v1/webhooks/social_inbox_controller.rb b/app/controllers/api/v1/webhooks/social_inbox_controller.rb new file mode 100644 index 00000000..bc604156 --- /dev/null +++ b/app/controllers/api/v1/webhooks/social_inbox_controller.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module Api + module V1 + module Webhooks + # Recibe webhooks de la Social Inbox + # + # @see {https://www.w3.org/TR/activitypub/} + class SocialInboxController < BaseController + include Api::V1::Webhooks::Concerns::WebhookConcern + + # Cuando una actividad ingresa en la cola de moderación, la + # recibimos por acá + # + # Vamos a recibir Create, Update, Delete, Follow, Undo y obtener + # el objeto dentro de cada una para guardar un estado asociado + # al sitio. + # + # El objeto del estado puede ser un objeto o une actore, + # dependiendo de la actividad. + def moderationqueued + head :accepted + end + + # Cuando aprobamos una actividad, recibimos la confirmación y + # cambiamos el estado + def onapproved + head :accepted + end + + # Cuando rechazamos una actividad, recibimos la confirmación y + # cambiamos el estado + def onrejected + head :accepted + end + end + end + end +end diff --git a/app/controllers/api/v1/webhooks_controller.rb b/app/controllers/api/v1/webhooks_controller.rb deleted file mode 100644 index f64fa93c..00000000 --- a/app/controllers/api/v1/webhooks_controller.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -module Api - module V1 - # Recibe webhooks y lanza un PullJob - class WebhooksController < BaseController - include WebhookConcern - - # Trae los cambios a partir de un post de Webhooks: - # (Gitlab, Github, Gitea, etc) - # - # @return [nil] - def pull - message = I18n.with_locale(site.default_locale) do - I18n.t('webhooks.pull.message') - end - - GitPullJob.perform_later(site, usuarie, message) - head :ok - end - end - end -end diff --git a/config/routes.rb b/config/routes.rb index f6f081f7..88376dde 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -19,9 +19,9 @@ Rails.application.routes.draw do post :'contact/:form', to: 'contact#receive', as: :contact namespace :webhooks do - post :pull, to: 'webhooks#pull' + post :pull, to: 'pull#pull' - namespace :social_inbox do + scope :social_inbox do post :moderationqueued, to: 'social_inbox#moderationqueued' post :onapproved, to: 'social_inbox#onapproved' post :onrejected, to: 'social_inbox#onrejected'