# frozen_string_literal: true 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 # 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 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) 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, env: request.env, data: { headers: request.headers.to_h }) head :forbidden end end end end