about summary refs log tree commit diff
path: root/app
diff options
context:
space:
mode:
authorEugen Rochko <eugen@zeonfederated.com>2016-12-21 20:00:18 +0100
committerEugen Rochko <eugen@zeonfederated.com>2016-12-21 20:04:13 +0100
commit80e02b90e4210b0f4982be6a8e817900143374a5 (patch)
tree6014b3fe6fa965f6bd2d3f531c5cb97cc6a492bc /app
parent6d71044c854c453f499f97af420fe0ed762238d3 (diff)
Private visibility on statuses prevents non-followers from seeing those
Filters out hidden stream entries from Atom feed
Blocks now generate hidden stream entries, can be used to federate blocks
Private statuses cannot be reblogged (generates generic 422 error for now)
POST /api/v1/statuses now takes visibility=(public|unlisted|private) param instead of unlisted boolean
Statuses JSON now contains visibility=(public|unlisted|private) field
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/components/actions/compose.jsx2
-rw-r--r--app/controllers/accounts_controller.rb4
-rw-r--r--app/controllers/api/v1/accounts_controller.rb5
-rw-r--r--app/controllers/api/v1/statuses_controller.rb3
-rw-r--r--app/controllers/stream_entries_controller.rb6
-rw-r--r--app/models/block.rb22
-rw-r--r--app/models/concerns/streamable.rb6
-rw-r--r--app/models/status.rb29
-rw-r--r--app/models/stream_entry.rb5
-rw-r--r--app/services/post_status_service.rb2
-rw-r--r--app/services/reblog_service.rb2
-rw-r--r--app/views/api/v1/statuses/_show.rabl6
12 files changed, 68 insertions, 24 deletions
diff --git a/app/assets/javascripts/components/actions/compose.jsx b/app/assets/javascripts/components/actions/compose.jsx
index a9fbe6b91..fdb0abdcd 100644
--- a/app/assets/javascripts/components/actions/compose.jsx
+++ b/app/assets/javascripts/components/actions/compose.jsx
@@ -67,7 +67,7 @@ export function submitCompose() {
       in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null),
       media_ids: getState().getIn(['compose', 'media_attachments']).map(item => item.get('id')),
       sensitive: getState().getIn(['compose', 'sensitive']),
-      unlisted: getState().getIn(['compose', 'unlisted'])
+      visibility: getState().getIn(['compose', 'unlisted']) ? 'unlisted' : 'public'
     }).then(function (response) {
       dispatch(submitComposeSuccess({ ...response.data }));
 
diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb
index 57f25a273..411a41ccc 100644
--- a/app/controllers/accounts_controller.rb
+++ b/app/controllers/accounts_controller.rb
@@ -11,12 +11,12 @@ class AccountsController < ApplicationController
   def show
     respond_to do |format|
       format.html do
-        @statuses = @account.statuses.order('id desc').paginate_by_max_id(20, params[:max_id], params[:since_id])
+        @statuses = @account.statuses.permitted_for(@account, current_account).order('id desc').paginate_by_max_id(20, params[:max_id], params[:since_id])
         @statuses = cache_collection(@statuses, Status)
       end
 
       format.atom do
-        @entries = @account.stream_entries.order('id desc').with_includes.paginate_by_max_id(20, params[:max_id], params[:since_id])
+        @entries = @account.stream_entries.order('id desc').where(hidden: false).with_includes.paginate_by_max_id(20, params[:max_id], params[:since_id])
       end
     end
   end
diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb
index 0abdfd9fa..de53a9602 100644
--- a/app/controllers/api/v1/accounts_controller.rb
+++ b/app/controllers/api/v1/accounts_controller.rb
@@ -8,8 +8,7 @@ class Api::V1::AccountsController < ApiController
 
   respond_to :json
 
-  def show
-  end
+  def show; end
 
   def verify_credentials
     @account = current_user.account
@@ -47,7 +46,7 @@ class Api::V1::AccountsController < ApiController
   end
 
   def statuses
-    @statuses = @account.statuses.paginate_by_max_id(DEFAULT_STATUSES_LIMIT, params[:max_id], params[:since_id])
+    @statuses = @account.statuses.permitted_for(@account, current_account).paginate_by_max_id(DEFAULT_STATUSES_LIMIT, params[:max_id], params[:since_id])
     @statuses = cache_collection(@statuses, Status)
 
     set_maps(@statuses)
diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb
index 453d003da..f7b4ed610 100644
--- a/app/controllers/api/v1/statuses_controller.rb
+++ b/app/controllers/api/v1/statuses_controller.rb
@@ -52,7 +52,7 @@ class Api::V1::StatusesController < ApiController
   end
 
   def create
-    @status = PostStatusService.new.call(current_user.account, params[:status], params[:in_reply_to_id].blank? ? nil : Status.find(params[:in_reply_to_id]), media_ids: params[:media_ids], sensitive: params[:sensitive], unlisted: params[:unlisted])
+    @status = PostStatusService.new.call(current_user.account, params[:status], params[:in_reply_to_id].blank? ? nil : Status.find(params[:in_reply_to_id]), media_ids: params[:media_ids], sensitive: params[:sensitive], visibility: params[:visibility])
     render action: :show
   end
 
@@ -95,5 +95,6 @@ class Api::V1::StatusesController < ApiController
 
   def set_status
     @status = Status.find(params[:id])
+    raise ActiveRecord::RecordNotFound unless @status.permitted?(current_account)
   end
 end
diff --git a/app/controllers/stream_entries_controller.rb b/app/controllers/stream_entries_controller.rb
index 58dd423f7..438d51a84 100644
--- a/app/controllers/stream_entries_controller.rb
+++ b/app/controllers/stream_entries_controller.rb
@@ -14,8 +14,8 @@ class StreamEntriesController < ApplicationController
         return gone if @stream_entry.activity.nil?
 
         if @stream_entry.activity_type == 'Status'
-          @ancestors   = @stream_entry.activity.ancestors
-          @descendants = @stream_entry.activity.descendants
+          @ancestors   = @stream_entry.activity.ancestors(current_account)
+          @descendants = @stream_entry.activity.descendants(current_account)
         end
       end
 
@@ -43,7 +43,7 @@ class StreamEntriesController < ApplicationController
   end
 
   def set_stream_entry
-    @stream_entry = @account.stream_entries.find(params[:id])
+    @stream_entry = @account.stream_entries.where(hidden: false).find(params[:id])
     @type         = @stream_entry.activity_type.downcase
   end
 
diff --git a/app/models/block.rb b/app/models/block.rb
index dc05bce87..ad225d180 100644
--- a/app/models/block.rb
+++ b/app/models/block.rb
@@ -1,9 +1,31 @@
 # frozen_string_literal: true
 
 class Block < ApplicationRecord
+  include Streamable
+
   belongs_to :account
   belongs_to :target_account, class_name: 'Account'
 
   validates :account, :target_account, presence: true
   validates :account_id, uniqueness: { scope: :target_account_id }
+
+  def verb
+    destroyed? ? :unblock : :block
+  end
+
+  def target
+    target_account
+  end
+
+  def object_type
+    :person
+  end
+
+  def hidden?
+    true
+  end
+
+  def title
+    destroyed? ? "#{account.acct} is no longer blocking #{target_account.acct}" : "#{account.acct} blocked #{target_account.acct}"
+  end
 end
diff --git a/app/models/concerns/streamable.rb b/app/models/concerns/streamable.rb
index d9f5dc4d8..58c15cfbc 100644
--- a/app/models/concerns/streamable.rb
+++ b/app/models/concerns/streamable.rb
@@ -26,8 +26,12 @@ module Streamable
       super
     end
 
+    def hidden?
+      false
+    end
+
     after_create do
-      account.stream_entries.create!(activity: self) if account.local?
+      account.stream_entries.create!(activity: self, hidden: hidden?) if account.local?
     end
   end
 end
diff --git a/app/models/status.rb b/app/models/status.rb
index e87828e32..603f3b7a2 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -5,7 +5,7 @@ class Status < ApplicationRecord
   include Streamable
   include Cacheable
 
-  enum visibility: [:public, :unlisted], _suffix: :visibility
+  enum visibility: [:public, :unlisted, :private], _suffix: :visibility
 
   belongs_to :account, inverse_of: :statuses
 
@@ -66,19 +66,19 @@ class Status < ApplicationRecord
     content
   end
 
-  def reblogs_count
-    attributes['reblogs_count'] || reblogs.count
+  def hidden?
+    private_visibility?
   end
 
-  def favourites_count
-    attributes['favourites_count'] || favourites.count
+  def permitted?(other_account = nil)
+    private_visibility? ? (account.id == other_account&.id || other_account&.following?(account)) : true
   end
 
   def ancestors(account = nil)
     ids      = (Status.find_by_sql(['WITH RECURSIVE search_tree(id, in_reply_to_id, path) AS (SELECT id, in_reply_to_id, ARRAY[id] FROM statuses WHERE id = ? UNION ALL SELECT statuses.id, statuses.in_reply_to_id, path || statuses.id FROM search_tree JOIN statuses ON statuses.id = search_tree.in_reply_to_id WHERE NOT statuses.id = ANY(path)) SELECT id FROM search_tree ORDER BY path DESC', id]) - [self]).pluck(:id)
     statuses = Status.where(id: ids).with_includes.group_by(&:id)
     results  = ids.map { |id| statuses[id].first }
-    results  = results.reject { |status| account.blocking?(status.account) } unless account.nil?
+    results  = results.reject { |status| filter_from_context?(status, account) }
 
     results
   end
@@ -87,7 +87,7 @@ class Status < ApplicationRecord
     ids      = (Status.find_by_sql(['WITH RECURSIVE search_tree(id, path) AS (SELECT id, ARRAY[id] FROM statuses WHERE id = ? UNION ALL SELECT statuses.id, path || statuses.id FROM search_tree JOIN statuses ON statuses.in_reply_to_id = search_tree.id WHERE NOT statuses.id = ANY(path)) SELECT id FROM search_tree ORDER BY path', id]) - [self]).pluck(:id)
     statuses = Status.where(id: ids).with_includes.group_by(&:id)
     results  = ids.map { |id| statuses[id].first }
-    results  = results.reject { |status| account.blocking?(status.account) } unless account.nil?
+    results  = results.reject { |status| filter_from_context?(status, account) }
 
     results
   end
@@ -128,6 +128,14 @@ class Status < ApplicationRecord
       select('reblog_of_id').where(reblog_of_id: status_ids).where(account_id: account_id).map { |s| [s.reblog_of_id, true] }.to_h
     end
 
+    def permitted_for(target_account, account)
+      if account&.id == target_account.id || account&.following?(target_account)
+        self
+      else
+        where.not(visibility: :private)
+      end
+    end
+
     def reload_stale_associations!(cached_items)
       account_ids = []
 
@@ -161,5 +169,12 @@ class Status < ApplicationRecord
   before_validation do
     text.strip!
     self.in_reply_to_account_id = thread.account_id if reply?
+    self.visibility             = :public if visibility.nil?
+  end
+
+  private
+
+  def filter_from_context?(status, account)
+    account&.blocking?(status.account) || !status.permitted?(account)
   end
 end
diff --git a/app/models/stream_entry.rb b/app/models/stream_entry.rb
index f6c8f461b..fcc691bef 100644
--- a/app/models/stream_entry.rb
+++ b/app/models/stream_entry.rb
@@ -9,6 +9,7 @@ class StreamEntry < ApplicationRecord
   belongs_to :status,    foreign_type: 'Status',    foreign_key: 'activity_id'
   belongs_to :follow,    foreign_type: 'Follow',    foreign_key: 'activity_id'
   belongs_to :favourite, foreign_type: 'Favourite', foreign_key: 'activity_id'
+  belongs_to :block,     foreign_type: 'Block',     foreign_key: 'activity_id'
 
   validates :account, :activity, presence: true
 
@@ -29,7 +30,7 @@ class StreamEntry < ApplicationRecord
   end
 
   def targeted?
-    [:follow, :share, :favorite].include? verb
+    [:follow, :unfollow, :block, :unblock, :share, :favorite].include? verb
   end
 
   def target
@@ -57,7 +58,7 @@ class StreamEntry < ApplicationRecord
   end
 
   def activity
-    send(activity_type.downcase.to_sym)
+    !new_record? ? send(activity_type.downcase) : super
   end
 
   private
diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb
index d5204151b..55405c0db 100644
--- a/app/services/post_status_service.rb
+++ b/app/services/post_status_service.rb
@@ -10,7 +10,7 @@ class PostStatusService < BaseService
   # @option [Enumerable] :media_ids Optional array of media IDs to attach
   # @return [Status]
   def call(account, text, in_reply_to = nil, options = {})
-    status = account.statuses.create!(text: text, thread: in_reply_to, sensitive: options[:sensitive], visibility: options[:unlisted] ? :unlisted : :public)
+    status = account.statuses.create!(text: text, thread: in_reply_to, sensitive: options[:sensitive], visibility: options[:visibility])
     attach_media(status, options[:media_ids])
     process_mentions_service.call(status)
     process_hashtags_service.call(status)
diff --git a/app/services/reblog_service.rb b/app/services/reblog_service.rb
index 7d0c90d2f..1a78b8f69 100644
--- a/app/services/reblog_service.rb
+++ b/app/services/reblog_service.rb
@@ -6,6 +6,8 @@ class ReblogService < BaseService
   # @param [Status] reblogged_status Status to be reblogged
   # @return [Status]
   def call(account, reblogged_status)
+    raise ActiveRecord::RecordInvalid if reblogged_status.private_visibility?
+
     reblog = account.statuses.create!(reblog: reblogged_status, text: '')
 
     DistributionWorker.perform_async(reblog.id)
diff --git a/app/views/api/v1/statuses/_show.rabl b/app/views/api/v1/statuses/_show.rabl
index 579c47b26..a3391a67e 100644
--- a/app/views/api/v1/statuses/_show.rabl
+++ b/app/views/api/v1/statuses/_show.rabl
@@ -1,10 +1,10 @@
-attributes :id, :created_at, :in_reply_to_id, :sensitive
+attributes :id, :created_at, :in_reply_to_id, :sensitive, :visibility
 
 node(:uri)              { |status| TagManager.instance.uri_for(status) }
 node(:content)          { |status| Formatter.instance.format(status) }
 node(:url)              { |status| TagManager.instance.url_for(status) }
-node(:reblogs_count)    { |status| defined?(@reblogs_counts_map)    ? (@reblogs_counts_map[status.id]    || 0) : status.reblogs_count }
-node(:favourites_count) { |status| defined?(@favourites_counts_map) ? (@favourites_counts_map[status.id] || 0) : status.favourites_count }
+node(:reblogs_count)    { |status| defined?(@reblogs_counts_map)    ? (@reblogs_counts_map[status.id]    || 0) : status.reblogs.count }
+node(:favourites_count) { |status| defined?(@favourites_counts_map) ? (@favourites_counts_map[status.id] || 0) : status.favourites.count }
 
 child :account do
   extends 'api/v1/accounts/show'