From 4fe52c8cbe633b4eebd6071861b2e19d234793ca Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 26 Aug 2016 12:47:10 +1000 Subject: [PATCH] FEATURE: backend support for pushing notifications to clients --- app/jobs/regular/push_notification.rb | 28 ++++++++++++ app/services/post_alerter.rb | 14 ++++++ config/initializers/005-site_settings.rb | 5 ++ config/site_settings.yml | 3 ++ spec/services/post_alerter_spec.rb | 58 ++++++++++++++++++++++++ 5 files changed, 108 insertions(+) create mode 100644 app/jobs/regular/push_notification.rb diff --git a/app/jobs/regular/push_notification.rb b/app/jobs/regular/push_notification.rb new file mode 100644 index 00000000000..d050ca7201a --- /dev/null +++ b/app/jobs/regular/push_notification.rb @@ -0,0 +1,28 @@ +module Jobs + class PushNotification < Jobs::Base + def execute(args) + notification = args["payload"] + notification["url"] = UrlHelper.absolute(notification["post_url"]) + notification.delete("post_url") + + payload = { + secret_key: SiteSetting.push_api_secret_key, + url: Discourse.base_url, + title: SiteSetting.title, + description: SiteSetting.site_description, + } + + clients = args["clients"] + clients.group_by{|r| r[1]}.each do |push_url, group| + notifications = group.map do |client_id, _| + notification.merge({ + client_id: client_id + }) + end + + RestClient.send :post, push_url, payload.merge({notifications: notifications}) + end + + end + end +end diff --git a/app/services/post_alerter.rb b/app/services/post_alerter.rb index 86ecfbc2f1a..5fc0c8aaf91 100644 --- a/app/services/post_alerter.rb +++ b/app/services/post_alerter.rb @@ -388,12 +388,26 @@ class PostAlerter } MessageBus.publish("/notification-alert/#{user.id}", payload, user_ids: [user.id]) + push_notification(user, payload) DiscourseEvent.trigger(:post_notification_alert, user, payload) end end end + def push_notification(user, payload) + if SiteSetting.allow_push_user_api_keys && SiteSetting.allowed_user_api_push_urls.present? + clients = user.user_api_keys + .where('push AND push_url IS NOT NULL AND position(push_url in ?) > 0 AND revoked_at IS NULL', + SiteSetting.allowed_user_api_push_urls) + .pluck(:client_id, :push_url) + + if clients.length > 0 + Jobs.enqueue(:push_notification, clients: clients, payload: payload, user_id: user.id) + end + end + end + def expand_group_mentions(groups, post) return unless post.user && groups diff --git a/config/initializers/005-site_settings.rb b/config/initializers/005-site_settings.rb index bb5ca5e5a73..f53f1684648 100644 --- a/config/initializers/005-site_settings.rb +++ b/config/initializers/005-site_settings.rb @@ -7,6 +7,11 @@ reload_settings = lambda { RailsMultisite::ConnectionManagement.each_connection do begin SiteSetting.refresh! + + unless String === SiteSetting.push_api_secret_key && SiteSetting.push_api_secret_key.length == 32 + SiteSetting.push_api_secret_key = SecureRandom.hex + end + rescue ActiveRecord::StatementInvalid # This will happen when migrating a new database rescue => e diff --git a/config/site_settings.yml b/config/site_settings.yml index 42646109d21..25226e2ebd7 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -1270,11 +1270,14 @@ user_api: default: true max_api_keys_per_user: default: 10 + push_api_secret_key: + hidden: true min_trust_level_for_user_api_key: default: 1 allowed_user_api_push_urls: default: '' type: list + shadowed_by_global: true allowed_user_api_auth_redirects: default: 'https://api.discourse.org/api/auth_redirect|discourse://auth_redirect' type: list diff --git a/spec/services/post_alerter_spec.rb b/spec/services/post_alerter_spec.rb index e0288c4a6ee..184f965b364 100644 --- a/spec/services/post_alerter_spec.rb +++ b/spec/services/post_alerter_spec.rb @@ -326,6 +326,64 @@ describe PostAlerter do end end + describe "push_notification" do + let(:mention_post) { create_post_with_alerts(user: user, raw: 'Hello @eviltrout')} + let(:topic) { mention_post.topic } + + it "correctly pushes notifications if configured correctly" do + SiteSetting.allowed_user_api_push_urls = "https://site.com/push|https://site2.com/push" + + 2.times do |i| + UserApiKey.create!(user_id: evil_trout.id, + client_id: "xxx#{i}", + key: "yyy#{i}", + application_name: "iPhone#{i}", + read: true, + write: true, + push: true, + push_url: "https://site2.com/push") + end + + # I want to test payload ... but we have chicked egg problem + # if I test it then it makes the req and the the expects is not correct ... + # need to track all reqs in rest client and check after the fact + + # payload = { + # secret_key: SiteSetting.push_api_secret_key, + # url: Discourse.base_url, + # title: SiteSetting.title, + # description: SiteSetting.site_description, + # notifications: [ + # { + # 'notification_type' => 1, + # 'post_number' => 1, + # 'topic_title' => topic.title, + # 'topic_id' => topic.id, + # 'excerpt' => 'Hello @eviltrout', + # 'username' => user.username, + # 'url' => UrlHelper.absolute(mention_post.url), + # 'client_id' => 'xxx0' + # }, + # { + # 'notification_type' => 1, + # 'post_number' => 1, + # 'topic_title' => topic.title, + # 'topic_id' => topic.id, + # 'excerpt' => 'Hello @eviltrout', + # 'username' => user.username, + # 'url' => UrlHelper.absolute(mention_post.url), + # 'client_id' => 'xxx1' + # } + # ] + # } + + # should only happen once even though we are using 2 keys + RestClient.expects(:post).returns("OK") + + mention_post + end + end + describe "watching_first_post" do let(:group) { Fabricate(:group) } let(:user) { Fabricate(:user) }