Skip to content

Commit ef3e8a5

Browse files
pglombardoPeter Giacomo Lombardo
andauthored
Harden push ownership checks against nil comparisons (#4704)
* Harden push ownership checks against nil comparisons Centralize ownership in Push#owned_by? so audit, edit, update, and related paths require a present authenticated owner instead of comparing optional user associations directly. * Require present user_id in Push#owned_by? Guard against nil == nil when a non-persisted User.new is passed for an anonymous push, and cover that case in tests. --------- Co-authored-by: Peter Giacomo Lombardo <pglombardo@apnotic.com>
1 parent 3c1ae9c commit ef3e8a5

6 files changed

Lines changed: 35 additions & 12 deletions

File tree

app/controllers/api/v1/pushes_controller.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ def preview
262262
https://docs.pwpush.com/docs/json-api/
263263
EOS
264264
def audit
265-
if @push.user != current_user
265+
unless @push.owned_by?(current_user)
266266
render json: {error: I18n._("That push doesn't belong to you.")}, status: :forbidden
267267
return
268268
end

app/controllers/api/v2/pushes_controller.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def create
5454
def audit
5555
authenticate_user!
5656

57-
if @push.user != current_user
57+
unless @push.owned_by?(current_user)
5858
render json: {error: I18n._("That push doesn't belong to you.")}, status: :forbidden
5959
return
6060
end
@@ -74,7 +74,7 @@ def audit
7474
def notify_emails
7575
authenticate_user!
7676

77-
if @push.user != current_user
77+
unless @push.owned_by?(current_user)
7878
render json: {error: I18n._("That push doesn't belong to you.")}, status: :forbidden
7979
return
8080
end

app/controllers/concerns/log_events.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ def log_view(push)
1111
elsif user_signed_in? && current_user.admin?
1212
# Admin views take precedence over owner views
1313
log_event(push, :admin_view)
14-
elsif user_signed_in? && push.user_id == current_user.id
14+
elsif push.owned_by?(current_user)
1515
log_event(push, :owner_view)
1616
else
1717
log_event(push, :view)

app/controllers/pushes_controller.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ def new
130130
# GET /p/:url_token/edit
131131
def edit
132132
# Verify the push belongs to the current user
133-
if @push.user_id != current_user.id
133+
unless @push.owned_by?(current_user)
134134
redirect_to :root, notice: I18n._("That push doesn't belong to you.")
135135
return
136136
end
@@ -172,7 +172,7 @@ def create
172172
# PATCH/PUT /p/:url_token
173173
def update
174174
# Verify the push belongs to the current user
175-
if @push.user_id != current_user.id
175+
unless @push.owned_by?(current_user)
176176
redirect_to :root, notice: I18n._("That push doesn't belong to you.")
177177
return
178178
end
@@ -266,7 +266,7 @@ def preview
266266
def notify_emails
267267
authenticate_user!
268268

269-
if @push.user_id != current_user.id
269+
unless @push.owned_by?(current_user)
270270
redirect_to :root, notice: I18n._("That push doesn't belong to you.")
271271
return
272272
end
@@ -322,7 +322,7 @@ def audit
322322
end
323323
return
324324
end
325-
if @push.user_id != current_user.id
325+
unless @push.owned_by?(current_user)
326326
redirect_to :root, notice: I18n._("That push doesn't belong to you.")
327327
return
328328
end
@@ -352,7 +352,7 @@ def expire
352352

353353
def delete_file
354354
# Verify the push belongs to the current user
355-
if @push.user_id != current_user.id
355+
unless @push.owned_by?(current_user)
356356
redirect_to :root, notice: I18n._("That push doesn't belong to you.")
357357
return
358358
end

app/models/push.rb

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -193,11 +193,17 @@ def expire!
193193
save!
194194
end
195195

196+
# True when +user+ is the authenticated owner of this push.
197+
# Require a real owner id on both sides so nil == nil (anonymous push /
198+
# non-persisted User.new) never counts as ownership.
199+
def owned_by?(user)
200+
user_id.present? && user.present? && user_id == user.id
201+
end
202+
196203
# True when +user+ is the authenticated owner, or when viewer deletion is
197-
# explicitly enabled. Anonymous pushes have a nil owner; comparing two nils
198-
# must not count as ownership.
204+
# explicitly enabled.
199205
def deletable_by?(user)
200-
(user.present? && user_id == user.id) || deletable_by_viewer == true
206+
owned_by?(user) || deletable_by_viewer == true
201207
end
202208

203209
def settings_for_kind

test/models/push_edit_test.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,23 @@ class PushEditTest < ActiveSupport::TestCase
393393
assert_includes push.errors[:files], "You can only attach up to #{max_files} files per push."
394394
end
395395

396+
test "owned_by? requires an authenticated matching owner" do
397+
owner = users(:luca)
398+
other = users(:one)
399+
400+
owned = Push.create!(kind: "text", payload: "owned", user: owner)
401+
assert owned.owned_by?(owner)
402+
assert_not owned.owned_by?(other)
403+
assert_not owned.owned_by?(nil)
404+
assert_not owned.owned_by?(User.new)
405+
406+
anonymous = Push.create!(kind: "text", payload: "anon")
407+
assert_nil anonymous.user_id
408+
assert_not anonymous.owned_by?(nil)
409+
assert_not anonymous.owned_by?(other)
410+
assert_not anonymous.owned_by?(User.new)
411+
end
412+
396413
test "deletable_by? requires authenticated owner or explicit viewer deletion" do
397414
owner = users(:luca)
398415
other = users(:one)

0 commit comments

Comments
 (0)