diff --git a/app/controllers/api/v1/concerns/webhook_concern.rb b/app/controllers/api/v1/concerns/webhook_concern.rb new file mode 100644 index 00000000..59d48b28 --- /dev/null +++ b/app/controllers/api/v1/concerns/webhook_concern.rb @@ -0,0 +1,71 @@ +# 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 + _headers = request.headers + _token ||= _headers['X-Gitlab-Token'].presence + _token ||= token_from_signature(_headers['X-Gitea-Signature'].presence) + _token ||= token_from_signature(_headers['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/webhooks_controller.rb b/app/controllers/api/v1/webhooks_controller.rb index 6e7b7022..f64fa93c 100644 --- a/app/controllers/api/v1/webhooks_controller.rb +++ b/app/controllers/api/v1/webhooks_controller.rb @@ -4,8 +4,7 @@ module Api module V1 # Recibe webhooks y lanza un PullJob class WebhooksController < BaseController - # responde con forbidden si falla la validación del token - rescue_from ActiveRecord::RecordNotFound, with: :platforms_answer + include WebhookConcern # Trae los cambios a partir de un post de Webhooks: # (Gitlab, Github, Gitea, etc) @@ -19,59 +18,6 @@ module Api GitPullJob.perform_later(site, usuarie, message) head :ok end - - private - - # encuentra el sitio a partir de la url - def site - @site ||= Site.find_by_name!(params[:site_id]) - end - - # valida el token que envía la plataforma del webhook - # - # @return [String] - def token - @token ||= - begin - # Gitlab - if request.headers['X-Gitlab-Token'].present? - request.headers['X-Gitlab-Token'] - # Github - elsif request.headers['X-Hub-Signature-256'].present? - token_from_signature(request.headers['X-Hub-Signature-256'], 'sha256=') - # Gitea - elsif request.headers['X-Gitea-Signature'].present? - token_from_signature(request.headers['X-Gitea-Signature']) - else - raise ActiveRecord::RecordNotFound, 'proveedor no soportado' - end - end - end - - # valida token a partir de firma de webhook - # - # @return [String, Boolean] - def token_from_signature(signature, prepend = '') - payload = request.body.read - 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.tap do |t| - raise ActiveRecord::RecordNotFound, 'token no encontrado' if t.nil? - end - end - - # encuentra le usuarie - 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 diff --git a/config/routes.rb b/config/routes.rb index 635be07a..8186b64e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -18,7 +18,9 @@ Rails.application.routes.draw do get :'contact/cookie', to: 'invitades#contact_cookie' post :'contact/:form', to: 'contact#receive', as: :contact - post :'webhooks/pull', to: 'webhooks#pull' + namespace :webhooks do + post :pull, to: 'webhooks#pull' + end end end end