FEATURE: Add hooks for email poller plugins (#21384)

While we are unable to support OAUTH2 with pop3 (due to upstream dependency ruby/net-pop#16), we are adding the support for mail pollers plugin. Doing so, it would be possible to write a plugin which then uses other ways (microsoft graph sdk for example) to poll emails from a mailbox.

The idea is that a plugin would define a class which inherits from Email::Poller and defines a poll_mailbox static method which returns an array of strings. Then the plugin could call register_mail_poller(<class_name>) to have it registered. All the configuration (oauth2 tokens, email, etc) could be managed by sitesettings defined in the plugin.
This commit is contained in:
Alessio Cosenza 2023-06-26 07:16:03 +02:00 committed by GitHub
parent 52894b9d7c
commit 56718504ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 86 additions and 2 deletions

View File

@ -12,6 +12,12 @@ module Jobs
def execute(args)
@args = args
poll_pop3 if should_poll?
DiscoursePluginRegistry.mail_pollers.each do |poller|
return if !poller.enabled?
poller.poll_mailbox(method(:process_popmail))
end
end
def should_poll?

View File

@ -115,7 +115,8 @@ class SiteSetting < ActiveRecord::Base
end
def self.email_polling_enabled?
SiteSetting.manual_polling_enabled? || SiteSetting.pop3_polling_enabled?
SiteSetting.manual_polling_enabled? || SiteSetting.pop3_polling_enabled? ||
DiscoursePluginRegistry.mail_pollers.any?(&:enabled?)
end
def self.blocked_attachment_content_types_regex

View File

@ -2457,7 +2457,7 @@ en:
pop3_polling_password_is_empty: "You must set a 'pop3 polling password' before enabling POP3 polling."
pop3_polling_authentication_failed: "POP3 authentication failed. Please verify your pop3 credentials."
reply_by_email_address_is_empty: "You must set a 'reply by email address' before enabling reply by email."
email_polling_disabled: "You must enable either manual or POP3 polling before enabling reply by email."
email_polling_disabled: "You must enable either manual, POP3 polling or have a custom mail poller enabled before enabling reply by email."
user_locale_not_enabled: "You must first enable 'allow user locale' before enabling this setting."
personal_message_enabled_groups_invalid: "You must specify at least one group for this setting. If you do not want anyone except staff to send PMs, choose the staff group."
invalid_regex: "Regex is invalid or not allowed."

View File

@ -72,6 +72,7 @@ class DiscoursePluginRegistry
define_register :seedfu_filter, Set
define_register :demon_processes, Set
define_register :groups_callback_for_users_search_controller_action, Hash
define_register :mail_pollers, Set
define_filtered_register :staff_user_custom_fields
define_filtered_register :public_user_custom_fields
@ -119,6 +120,10 @@ class DiscoursePluginRegistry
self.auth_providers << auth_provider
end
def self.register_mail_poller(mail_poller)
self.mail_pollers << mail_poller
end
def register_js(filename, options = {})
# If we have a server side option, add that too.
self.class.javascripts << filename

18
lib/email/poller.rb Normal file
View File

@ -0,0 +1,18 @@
# frozen_string_literal: true
module Email
class Poller
# To be implemented by concrete classes.
# This function takes as input a function that processes the incoming email.
# The function passed as argument should take as an argument the MIME string of the email.
# An example of function to pass is `process_popmail` in `app/jobs/scheduled/poll_mailbox.rb`
def poll_mailbox(process_cb)
raise NotImplementedError
end
# Child class can override this
def enabled?
true
end
end
end

View File

@ -647,6 +647,11 @@ class Plugin::Instance
end
end
def register_email_poller(poller)
plugin = self
DiscoursePluginRegistry.register_mail_poller(poller) if plugin.enabled?
end
def register_asset(file, opts = nil)
raise <<~ERROR if file.end_with?(".hbs", ".handlebars")
[#{name}] Handlebars templates can no longer be included via `register_asset`.

View File

@ -1,4 +1,5 @@
# frozen_string_literal: true
require "email/poller"
RSpec.describe Jobs::PollMailbox do
let(:poller) { Jobs::PollMailbox.new }
@ -175,4 +176,45 @@ RSpec.describe Jobs::PollMailbox do
)
end
end
describe "poller plugin" do
let(:poller_plugin) do
Class
.new(described_class) do
def set_enabled(e)
@enabled = e
end
def enabled?
@enabled
end
def poll_mailbox(process_cb)
process_cb.call(file_from_fixtures("original_message.eml", "emails"))
end
end
.new
end
let(:plugin) { Plugin::Instance.new }
before(:each) { plugin.register_email_poller(poller_plugin) }
after(:each) do
Discourse.plugins.delete plugin
DiscoursePluginRegistry.reset!
end
it "doesn't call process method when plugin is not active" do
poller_plugin.set_enabled(false)
poller.expects(:process_popmail).never
poller.execute({})
end
it "calls process method when plugin is active" do
poller_plugin.set_enabled(true)
poller.expects(:process_popmail).once
poller.execute({})
end
end
end

View File

@ -98,6 +98,13 @@ RSpec.describe DiscoursePluginRegistry do
end
end
describe "#mail_pollers" do
it "defaults to an empty Set" do
registry.reset!
expect(registry.mail_pollers).to eq(Set.new)
end
end
describe ".register_html_builder" do
it "can register and build html" do
DiscoursePluginRegistry.register_html_builder(:my_html) { "<b>my html</b>" }