From 7c0e50c2c270e696b7dbd6540d00b4cc38e25375 Mon Sep 17 00:00:00 2001
From: Jason Robinson <mail@jasonrobinson.me>
Date: Tue, 19 May 2015 23:01:37 +0300
Subject: [PATCH] Introduce 'authorized' configuration option for services

Since the Facebook API has changed and additional permissions are required for all users on a pod to cross-post, an additional 'authorized' flag is needed to be set for the Facebook service.
This flag allows either all users, one user or no users to use the cross-posting service.

Clarifies the situation for #5923, #5260 and #5085.

closes #5985
---
 Changelog.md                                 |   2 +-
 app/presenters/statistics_presenter.rb       |   6 +-
 app/views/services/_add_remove_services.haml |  25 ++--
 app/views/shared/_right_sections.html.haml   |   5 +-
 config/defaults.yml                          |   5 +
 config/diaspora.yml.example                  |   7 +
 lib/configuration_methods.rb                 |   7 +
 spec/lib/configuration_methods_spec.rb       |  71 +++++++---
 spec/presenters/statistics_presenter_spec.rb | 138 +++++++++++++------
 9 files changed, 190 insertions(+), 76 deletions(-)

diff --git a/Changelog.md b/Changelog.md
index 2de7a30f1f..0f8d3c4f34 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -39,7 +39,6 @@
 * Fix a freeze in new post parsing [#5965](https://github.com/diaspora/diaspora/pull/5965)
 * Add case insensitive unconfirmed email addresses as authentication key [#5967](https://github.com/diaspora/diaspora/pull/5967)
 * Fix liking on single post views when accessed via GUID [#5978](https://github.com/diaspora/diaspora/pull/5978)
-* Gracefully handle mailer failures when a like is already deleted again [#5983](https://github.com/diaspora/diaspora/pull/5983)
 
 ## Features
 * Hide post title of limited post in comment notification email [#5843](https://github.com/diaspora/diaspora/pull/5843)
@@ -54,6 +53,7 @@
 * Add a "Manage followed tags" page to mass unfollow tags in the mobile interface [#5945](https://github.com/diaspora/diaspora/pull/5945)
 * Add popover/tooltip about email visibility to registration/settings page [#5956](https://github.com/diaspora/diaspora/pull/5956)
 * Fetch person posts on sharing request [#5960](https://github.com/diaspora/diaspora/pull/5960)
+* Introduce 'authorized' configuration option for services [#5985](https://github.com/diaspora/diaspora/pull/5985)
 
 # 0.5.0.1
 
diff --git a/app/presenters/statistics_presenter.rb b/app/presenters/statistics_presenter.rb
index 51d2b367c0..af88e68711 100644
--- a/app/presenters/statistics_presenter.rb
+++ b/app/presenters/statistics_presenter.rb
@@ -98,7 +98,7 @@ class StatisticsPresenter
   def all_services_helper
     result = {}
     Configuration::KNOWN_SERVICES.each {|service, options|
-      result[service.to_s] = AppConfig["services.#{service}.enable"]
+      result[service.to_s] = AppConfig.show_service?(service, nil)
     }
     result
   end
@@ -109,13 +109,13 @@ class StatisticsPresenter
 
   def available_services
     Configuration::KNOWN_SERVICES.select {|service|
-      AppConfig["services.#{service}.enable"]
+      AppConfig.show_service?(service, nil)
     }.map(&:to_s)
   end
 
   def legacy_services
     Configuration::KNOWN_SERVICES.each_with_object({}) {|service, result|
-      result[service.to_s] = AppConfig["services.#{service}.enable"]
+      result[service.to_s] = AppConfig.show_service?(service, nil)
     }
   end
 
diff --git a/app/views/services/_add_remove_services.haml b/app/views/services/_add_remove_services.haml
index 35190c8fd9..bac69588f2 100644
--- a/app/views/services/_add_remove_services.haml
+++ b/app/views/services/_add_remove_services.haml
@@ -4,19 +4,20 @@
 
 - if AppConfig.configured_services.count > 0
   - AppConfig.configured_services.each do |provider|
-    %h3= t("services.provider.#{provider}")
-    - services_for_provider = @services.select{|x| x.provider == provider.to_s}
-    - if services_for_provider.count > 0
-      - services_for_provider.each do |service|
-        != t("services.index.logged_in_as", nickname: content_tag(:strong, service.nickname ))
-        = link_to t("services.index.disconnect"),
-                  service_path(service),
-                  data: { confirm: t("services.index.really_disconnect", service: t("services.provider.#{provider}")) },
-                  method: :delete
+    - if AppConfig.show_service?(provider, current_user)
+      %h3= t("services.provider.#{provider}")
+      - services_for_provider = @services.select{|x| x.provider == provider.to_s}
+      - if services_for_provider.count > 0
+        - services_for_provider.each do |service|
+          != t("services.index.logged_in_as", nickname: content_tag(:strong, service.nickname ))
+          = link_to t("services.index.disconnect"),
+                    service_path(service),
+                    data: { confirm: t("services.index.really_disconnect", service: t("services.provider.#{provider}")) },
+                    method: :delete
 
-    - else
-      = t("services.index.not_logged_in")
-      = link_to(t("services.index.connect"), "/auth/#{provider}")
+      - else
+        = t("services.index.not_logged_in")
+        = link_to(t("services.index.connect"), "/auth/#{provider}")
 
 - else
   .well
diff --git a/app/views/shared/_right_sections.html.haml b/app/views/shared/_right_sections.html.haml
index fb64a3e83f..feb135bebb 100644
--- a/app/views/shared/_right_sections.html.haml
+++ b/app/views/shared/_right_sections.html.haml
@@ -57,8 +57,9 @@
 
       #right_service_icons
         - AppConfig.configured_services.each do |service|
-          - unless current_user.services.any?{|x| x.provider == service}
-            = link_to(content_tag(:div, nil, :class => "social_media_logos-#{service.to_s.downcase}-24x24", :title => service.to_s.titleize), "/auth/#{service}")
+          - if AppConfig.show_service?(service, current_user)
+            - unless current_user.services.any?{|x| x.provider == service}
+              = link_to(content_tag(:div, nil, :class => "social_media_logos-#{service.to_s.downcase}-24x24", :title => service.to_s.titleize), "/auth/#{service}")
 
 .section
   .title
diff --git a/config/defaults.yml b/config/defaults.yml
index add0ee97ef..f37804600c 100644
--- a/config/defaults.yml
+++ b/config/defaults.yml
@@ -146,18 +146,22 @@ defaults:
       app_id:
       secret:
       open_graph_namespace: 'joindiaspora'
+      authorized: false
     twitter:
       enable: false
       key:
       secret:
+      authorized: true
     tumblr:
       enable: false
       key:
       secret:
+      authorized: true
     wordpress:
       enable: false
       key:
       secret:
+      authorized: true
   mail:
     enable: false
     sender_address: 'no-reply@example.org'
@@ -211,6 +215,7 @@ test:
       enable: true
       app_id: 'fake'
       secret: 'sdoigjosdfijg'
+      authorized: true
   mail:
     enable: true
 integration1:
diff --git a/config/diaspora.yml.example b/config/diaspora.yml.example
index 378e0d6d32..91fe4191b9 100644
--- a/config/diaspora.yml.example
+++ b/config/diaspora.yml.example
@@ -536,6 +536,13 @@ configuration: ## Section
       #app_id: 'abcdef'
       #secret: 'change_me'
 
+      ## This setting is required to define whether the Facebook app has permissions to post
+      ##   false == No permissions (default)
+      ##   true == Permissions for all users to post. App MUST have 'publish_actions' approved by Facebook!
+      ##   "username" == Set to local username to allow a single user to cross-post. The person who has created
+      ##                 the Facebook app will always be able to cross-post, even without 'publish_actions'.
+      #authorized: false
+
     ## OAuth credentials for Twitter
     twitter: ## Section
 
diff --git a/lib/configuration_methods.rb b/lib/configuration_methods.rb
index 8e87454ba2..107fde37c0 100644
--- a/lib/configuration_methods.rb
+++ b/lib/configuration_methods.rb
@@ -34,6 +34,13 @@ module Configuration
     end
     attr_writer :configured_services
 
+    def show_service?(service, user)
+      return false unless self["services.#{service}.enable"]
+      # Return true only if 'authorized' is true or equal to user username
+      (user && self["services.#{service}.authorized"] == user.username) ||
+        self["services.#{service}.authorized"] == true
+    end
+
     def secret_token
       if heroku?
         return ENV['SECRET_TOKEN'] if ENV['SECRET_TOKEN']
diff --git a/spec/lib/configuration_methods_spec.rb b/spec/lib/configuration_methods_spec.rb
index 5ca9e3ca21..966b4c0396 100644
--- a/spec/lib/configuration_methods_spec.rb
+++ b/spec/lib/configuration_methods_spec.rb
@@ -8,40 +8,40 @@ describe Configuration::Methods do
       extend Configuration::Methods
     end
   end
-  
+
   describe "#pod_uri" do
     before do
       @settings.environment.url = nil
       @settings.instance_variable_set(:@pod_uri, nil)
     end
-    
+
     it "properly parses the pod url" do
       @settings.environment.url = "http://example.org/"
       expect(@settings.pod_uri.scheme).to eq("http")
       expect(@settings.pod_uri.host).to eq("example.org")
     end
-    
+
      it "adds a trailing slash if there isn't one" do
       @settings.environment.url = "http://example.org"
       expect(@settings.pod_uri.to_s).to eq("http://example.org/")
     end
-    
+
     it "does not add an extra trailing slash" do
       @settings.environment.url = "http://example.org/"
       expect(@settings.pod_uri.to_s).to eq("http://example.org/")
     end
-    
+
     it "adds http:// on the front if it's missing" do
       @settings.environment.url = "example.org/"
       expect(@settings.pod_uri.to_s).to eq("http://example.org/")
     end
-    
+
     it "does not add a prefix if there already is https:// on the front" do
       @settings.environment.url = "https://example.org/"
       expect(@settings.pod_uri.to_s).to eq("https://example.org/")
     end
   end
-  
+
   describe "#bare_pod_uri" do
     it 'is #pod_uri.authority stripping www.' do
       pod_uri = double
@@ -50,7 +50,7 @@ describe Configuration::Methods do
       expect(@settings.bare_pod_uri).to eq('example.org')
     end
   end
-  
+
   describe "#configured_services" do
     it "includes the enabled services only" do
       services = double
@@ -69,7 +69,40 @@ describe Configuration::Methods do
       expect(@settings.configured_services).not_to include :wordpress
     end
   end
-  
+
+  describe "#show_service" do
+    before do
+      AppConfig.services.twitter.authorized = true
+      AppConfig.services.twitter.enable = true
+      AppConfig.services.facebook.authorized = true
+      AppConfig.services.facebook.enable = true
+      AppConfig.services.wordpress.authorized = false
+      AppConfig.services.wordpress.enable = true
+      AppConfig.services.tumblr.authorized = "alice"
+      AppConfig.services.tumblr.enable = true
+    end
+
+    it "shows service with no authorized key" do
+      expect(AppConfig.show_service?("twitter", bob)).to be_truthy
+    end
+
+    it "shows service with authorized key true" do
+      expect(AppConfig.show_service?("facebook", bob)).to be_truthy
+    end
+
+    it "doesn't show service with authorized key false" do
+      expect(AppConfig.show_service?("wordpress", bob)).to be_falsey
+    end
+
+    it "doesn't show service with authorized key not equal to username" do
+      expect(AppConfig.show_service?("tumblr", bob)).to be_falsey
+    end
+
+    it "shows service with authorized key equal to username" do
+      expect(AppConfig.show_service?("tumblr", alice)).to be_truthy
+    end
+  end
+
   describe "#version_string" do
     before do
       @version = double
@@ -83,61 +116,61 @@ describe Configuration::Methods do
     it "includes the version" do
       expect(@settings.version_string).to include @version.number
     end
-    
+
     context "with git available" do
       before do
         allow(@settings).to receive(:git_available?).and_return(true)
         allow(@settings).to receive(:git_revision).and_return("1234567890")
       end
-      
+
       it "includes the 'patchlevel'" do
         expect(@settings.version_string).to include "-p#{@settings.git_revision[0..7]}"
         expect(@settings.version_string).not_to include @settings.git_revision[0..8]
       end
     end
   end
-  
+
   describe "#get_redis_options" do
     context "with REDISTOGO_URL set" do
       before do
         ENV["REDISTOGO_URL"] = "redis://myserver"
       end
-      
+
       it "uses that" do
         expect(@settings.get_redis_options[:url]).to match "myserver"
       end
     end
-    
+
     context "with REDIS_URL set" do
       before do
         ENV["REDISTOGO_URL"] = nil
         ENV["REDIS_URL"] = "redis://yourserver"
       end
-      
+
       it "uses that" do
         expect(@settings.get_redis_options[:url]).to match "yourserver"
       end
     end
-    
+
     context "with redis set" do
       before do
         ENV["REDISTOGO_URL"] = nil
         ENV["REDIS_URL"] = nil
         @settings.environment.redis = "redis://ourserver"
       end
-      
+
       it "uses that" do
         expect(@settings.get_redis_options[:url]).to match "ourserver"
       end
     end
-    
+
     context "with a unix socket set" do
       before do
         ENV["REDISTOGO_URL"] = nil
         ENV["REDIS_URL"] = nil
         @settings.environment.redis = "unix:///tmp/redis.sock"
       end
-      
+
       it "uses that" do
         expect(@settings.get_redis_options[:url]).to match "/tmp/redis.sock"
       end
diff --git a/spec/presenters/statistics_presenter_spec.rb b/spec/presenters/statistics_presenter_spec.rb
index 40661f123e..364525ec01 100644
--- a/spec/presenters/statistics_presenter_spec.rb
+++ b/spec/presenters/statistics_presenter_spec.rb
@@ -1,76 +1,136 @@
-require 'spec_helper'
+require "spec_helper"
 
 describe StatisticsPresenter do
   before do
     @presenter = StatisticsPresenter.new
   end
 
-  describe '#as_json' do
-    it 'works' do
+  describe "#as_json" do
+    it "works" do
       expect(@presenter.as_json).to be_present
       expect(@presenter.as_json).to be_a Hash
     end
   end
 
-  describe '#statistics contents' do
+  describe "#statistics contents" do
     before do
       AppConfig.privacy.statistics.user_counts = false
       AppConfig.privacy.statistics.post_counts = false
       AppConfig.privacy.statistics.comment_counts = false
     end
 
-    it 'provides generic pod data in json' do
-      expect(@presenter.as_json).to eq({
-        "name" => AppConfig.settings.pod_name,
-        "network" => "Diaspora",
-        "version" => AppConfig.version_string,
+    it "provides generic pod data in json" do
+      expect(@presenter.as_json).to eq(
+        "name"               => AppConfig.settings.pod_name,
+        "network"            => "Diaspora",
+        "version"            => AppConfig.version_string,
         "registrations_open" => AppConfig.settings.enable_registrations?,
-        "services"=> ["facebook",],
-        "facebook" => true,
-        "tumblr" => false,
-        "twitter" => false,
-        "wordpress" => false,
-      })
+        "services"           => ["facebook"],
+        "facebook"           => true,
+        "tumblr"             => false,
+        "twitter"            => false,
+        "wordpress"          => false
+      )
     end
 
-    context 'when services are enabled' do
+    context "when services are enabled" do
       before do
-        AppConfig.privacy.statistics.user_counts = true
-        AppConfig.privacy.statistics.post_counts = true
-        AppConfig.privacy.statistics.comment_counts = true
         AppConfig.services = {
-          "facebook" => {"enable" => true},
-          "twitter" => {"enable" => true},
+          "facebook"  => {
+            "enable"     => true,
+            "authorized" => true
+          },
+          "twitter"   => {"enable" => true},
           "wordpress" => {"enable" => false},
-          "tumblr" => {"enable" => false}
+          "tumblr"    => {
+            "enable"     => true,
+            "authorized" => false
+          }
+        }
+      end
+
+      it "provides services in json" do
+        expect(@presenter.as_json).to eq(
+          "name"               => AppConfig.settings.pod_name,
+          "network"            => "Diaspora",
+          "version"            => AppConfig.version_string,
+          "registrations_open" => AppConfig.settings.enable_registrations?,
+          "services"           => %w(twitter facebook),
+          "facebook"           => true,
+          "twitter"            => true,
+          "tumblr"             => false,
+          "wordpress"          => false
+        )
+      end
+    end
+
+    context "when some services are set to username authorized" do
+      before do
+        AppConfig.services = {
+          "facebook"  => {
+            "enable"     => true,
+            "authorized" => "bob"
+          },
+          "twitter"   => {"enable" => true},
+          "wordpress" => {
+            "enable"     => true,
+            "authorized" => "alice"
+          },
+          "tumblr"    => {
+            "enable"     => true,
+            "authorized" => false
+          }
         }
       end
 
-      it 'provides generic pod data and counts in json' do
-        expect(@presenter.as_json).to eq({
-          "name" => AppConfig.settings.pod_name,
-          "network" => "Diaspora",
-          "version" => AppConfig.version_string,
+      it "provides services in json" do
+        expect(@presenter.as_json).to eq(
+          "name"               => AppConfig.settings.pod_name,
+          "network"            => "Diaspora",
+          "version"            => AppConfig.version_string,
           "registrations_open" => AppConfig.settings.enable_registrations?,
-          "total_users" => User.active.count,
+          "services"           => ["twitter"],
+          "facebook"           => false,
+          "twitter"            => true,
+          "tumblr"             => false,
+          "wordpress"          => false
+        )
+      end
+    end
+
+    context "when counts are enabled" do
+      before do
+        AppConfig.privacy.statistics.user_counts = true
+        AppConfig.privacy.statistics.post_counts = true
+        AppConfig.privacy.statistics.comment_counts = true
+      end
+
+      it "provides generic pod data and counts in json" do
+        expect(@presenter.as_json).to eq(
+          "name"                  => AppConfig.settings.pod_name,
+          "network"               => "Diaspora",
+          "version"               => AppConfig.version_string,
+          "registrations_open"    => AppConfig.settings.enable_registrations?,
+          "total_users"           => User.active.count,
           "active_users_halfyear" => User.halfyear_actives.count,
-          "active_users_monthly" => User.monthly_actives.count,
-          "local_posts" => @presenter.local_posts,
-          "local_comments" => @presenter.local_comments,
-          "services" => ["twitter","facebook"],
-          "facebook" => true,
-          "twitter" => true,
-          "tumblr" => false,
-          "wordpress" => false
-        })
+          "active_users_monthly"  => User.monthly_actives.count,
+          "local_posts"           => @presenter.local_posts,
+          "local_comments"        => @presenter.local_comments,
+          "services"              => ["facebook"],
+          "facebook"              => true,
+          "twitter"               => false,
+          "tumblr"                => false,
+          "wordpress"             => false
+        )
       end
     end
-    context 'when registrations are closed' do
+
+    context "when registrations are closed" do
       before do
         AppConfig.settings.enable_registrations = false
       end
 
-      it 'should mark open_registrations to be false' do
+      it "should mark open_registrations to be false" do
         expect(@presenter.open_registrations?).to be false
       end
     end
-- 
GitLab