Skip to content

Commit

Permalink
Private visibility on statuses prevents non-followers from seeing those
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Gargron committed Dec 21, 2016
1 parent 6d71044 commit 80e02b9
Show file tree
Hide file tree
Showing 17 changed files with 106 additions and 149 deletions.
2 changes: 1 addition & 1 deletion app/assets/javascripts/components/actions/compose.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 }));

Expand Down
4 changes: 2 additions & 2 deletions app/controllers/accounts_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 2 additions & 3 deletions app/controllers/api/v1/accounts_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -47,7 +46,7 @@ def followers
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)
Expand Down
3 changes: 2 additions & 1 deletion app/controllers/api/v1/statuses_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def favourited_by
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

Expand Down Expand Up @@ -95,5 +95,6 @@ def unfavourite

def set_status
@status = Status.find(params[:id])
raise ActiveRecord::RecordNotFound unless @status.permitted?(current_account)
end
end
6 changes: 3 additions & 3 deletions app/controllers/stream_entries_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ def show
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

Expand Down Expand Up @@ -43,7 +43,7 @@ def set_link_headers
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

Expand Down
22 changes: 22 additions & 0 deletions app/models/block.rb
Original file line number Diff line number Diff line change
@@ -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
6 changes: 5 additions & 1 deletion app/models/concerns/streamable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,12 @@ def thread
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
29 changes: 22 additions & 7 deletions app/models/status.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -66,19 +66,19 @@ def title
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
Expand All @@ -87,7 +87,7 @@ def descendants(account = nil)
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
Expand Down Expand Up @@ -128,6 +128,14 @@ def reblogs_map(status_ids, account_id)
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 = []

Expand Down Expand Up @@ -161,5 +169,12 @@ def filter_timeline_default(query)
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
5 changes: 3 additions & 2 deletions app/models/stream_entry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -29,7 +30,7 @@ def verb
end

def targeted?
[:follow, :share, :favorite].include? verb
[:follow, :unfollow, :block, :unblock, :share, :favorite].include? verb
end

def target
Expand Down Expand Up @@ -57,7 +58,7 @@ def mentions
end

def activity
send(activity_type.downcase.to_sym)
!new_record? ? send(activity_type.downcase) : super
end

private
Expand Down
2 changes: 1 addition & 1 deletion app/services/post_status_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions app/services/reblog_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 3 additions & 3 deletions app/views/api/v1/statuses/_show.rabl
Original file line number Diff line number Diff line change
@@ -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'
Expand Down
5 changes: 5 additions & 0 deletions db/migrate/20161221152630_add_hidden_to_stream_entries.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddHiddenToStreamEntries < ActiveRecord::Migration[5.0]
def change
add_column :stream_entries, :hidden, :boolean, null: false, default: false
end
end
7 changes: 4 additions & 3 deletions db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 20161205214545) do
ActiveRecord::Schema.define(version: 20161221152630) do

# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
Expand Down Expand Up @@ -196,8 +196,9 @@
t.integer "account_id"
t.integer "activity_id"
t.string "activity_type"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.boolean "hidden", default: false, null: false
t.index ["account_id"], name: "index_stream_entries_on_account_id", using: :btree
t.index ["activity_id", "activity_type"], name: "index_stream_entries_on_activity_id_and_activity_type", using: :btree
end
Expand Down
75 changes: 25 additions & 50 deletions public/404.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,67 +2,42 @@
<html lang="en">
<head>
<meta charset="utf-8">
<title>The page you were looking for doesn't exist (404)</title>
<title>The page you were looking for doesn't exist</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="https://fonts.googleapis.com/css?family=Roboto:400" rel="stylesheet">
<style>
body {
background-color: #EFEFEF;
color: #2E2F30;
text-align: center;
font-family: arial, sans-serif;
margin: 0;
}
body {
font-family: 'Roboto', sans-serif;
background: #282c37;
color: #9baec8;
text-align: center;
margin: 0;
padding: 20px;
}

div.dialog {
width: 95%;
max-width: 33em;
margin: 4em auto 0;
}
.dialog img {
display: block;
margin: 20px auto;
margin-top: 50px;
max-width: 600px;
width: 100%;
height: auto;
}

div.dialog > div {
border: 1px solid #CCC;
border-right-color: #999;
border-left-color: #999;
border-bottom-color: #BBB;
border-top: #B00100 solid 4px;
border-top-left-radius: 9px;
border-top-right-radius: 9px;
background-color: white;
padding: 7px 12% 0;
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
}

h1 {
font-size: 100%;
color: #730E15;
line-height: 1.5em;
}

div.dialog > p {
margin: 0 0 1em;
padding: 1em;
background-color: #F7F7F7;
border: 1px solid #CCC;
border-right-color: #999;
border-left-color: #999;
border-bottom-color: #999;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
border-top-color: #DADADA;
color: #666;
box-shadow: 0 3px 8px rgba(50, 50, 50, 0.17);
}
.dialog h1 {
font: 20px/28px 'Roboto', sans-serif;
font-weight: 400;
}
</style>
</head>

<body>
<!-- This file lives in public/404.html -->
<div class="dialog">
<img src="/oops.png" alt="Mastodon" />

<div>
<h1>The page you were looking for doesn't exist.</h1>
<p>You may have mistyped the address or the page may have moved.</p>
<h1>The page you were looking for doesn't exist</h1>
</div>
<p>If you are the application owner check the logs for more information.</p>
</div>
</body>
</html>
Loading

0 comments on commit 80e02b9

Please sign in to comment.