Skip to content
This repository was archived by the owner on Jul 14, 2022. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## 0.2.0

- Added `report_only` option to `firewalled_belongs_to` to log violations and
not raise
2 changes: 1 addition & 1 deletion activerecord-firewall.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Gem::Specification.new do |s|

s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md"]

s.add_dependency "rails", "~> 5.1.5"
s.add_dependency "rails", "~> 5.1.0"

s.add_development_dependency "sqlite3"
end
2 changes: 1 addition & 1 deletion dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ commands:
syntax:
argument: file
optional: args...
run: bin/testunit
run: bin/test
10 changes: 7 additions & 3 deletions lib/activerecord/firewall/firewalled_belongs_to.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@
module ActiveRecord
module FirewalledBelongsTo

def firewalled_belongs_to(foreign_key_type, *args)
def firewalled_belongs_to(foreign_key_type, *args, report_only: false, **options)
key_column_name = "#{foreign_key_type.to_s}_id"

belongs_to foreign_key_type
belongs_to foreign_key_type, *args, **options

attribute key_column_name,
FirewalledIDType.new(self, foreign_key_type, ActiveRecord::Firewall.current_name.constantize)
FirewalledIDType.new(
self,
foreign_key_type,
ActiveRecord::Firewall.current_name.constantize,
report_only: report_only)

after_find do |record|
# This explicitly loads the foreign key and
Expand Down
9 changes: 7 additions & 2 deletions lib/activerecord/firewall/firewalled_id_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ module ActiveRecord
class FirewalledIDType < ActiveRecord::Type::BigInteger
class FirewalledAccess < ActiveRecord::RecordNotFound; end

def initialize(model, protected_type, source)
def initialize(model, protected_type, source, report_only: false)
super()
@model = model
@protected_type = protected_type.to_sym
@report_only = report_only
@source = source
end

Expand Down Expand Up @@ -33,7 +34,11 @@ def check_attribute!(id)
#{id} was accessed from #{humanized_protected_type} #{current_id}
END

raise FirewalledAccess, message
if @report_only
Rails.logger.info "[activerecord-firewall] #{message}"
else
raise FirewalledAccess, message
end
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/activerecord/firewall/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
module Activerecord
module Firewall
VERSION = '0.1.0'
VERSION = '0.2.0'
end
end
8 changes: 8 additions & 0 deletions test/dummy/app/controllers/blog_post_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,12 @@ def show

render html: "<h1>Blog post #{@blog.title} (#{@blog.id}, #{@blog.user.id}) is allowed for user #{Current.user.name} (#{Current.user.id})</h1>"
end

def image
Current.user = User.find_by_id(params[:user_id])

@image = Image.find_by_id(params[:image_id])

render html: "<h1>Blog post #{@image.alt} (#{@image.id}, #{@image.user.id}) is allowed for user #{Current.user.name} (#{Current.user.id})</h1>"
end
end
3 changes: 3 additions & 0 deletions test/dummy/app/models/image.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class Image < ApplicationRecord
firewalled_belongs_to :user, report_only: true
end
3 changes: 2 additions & 1 deletion test/dummy/config/routes.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
Rails.application.routes.draw do
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html

get '/:user_id/:blog_post_id', to: "blog_post#show"
get '/blogs/:user_id/:blog_post_id', to: "blog_post#show"
get '/images/:user_id/:image_id', to: "blog_post#image"
end
11 changes: 11 additions & 0 deletions test/dummy/db/migrate/20180312161755_create_images.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class CreateImages < ActiveRecord::Migration[5.1]
def change
create_table :images do |t|
t.string :alt
t.string :url
t.belongs_to :user, foreign_key: true

t.timestamps
end
end
end
11 changes: 10 additions & 1 deletion test/dummy/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: 20180307230944) do
ActiveRecord::Schema.define(version: 20180312161755) do

create_table "blog_posts", force: :cascade do |t|
t.integer "user_id"
Expand All @@ -21,6 +21,15 @@
t.index ["user_id"], name: "index_blog_posts_on_user_id"
end

create_table "images", force: :cascade do |t|
t.string "alt"
t.string "url"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["user_id"], name: "index_images_on_user_id"
end

create_table "users", force: :cascade do |t|
t.string "name"
t.string "description"
Expand Down
35 changes: 35 additions & 0 deletions test/dummy/test/models/image_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
require 'test_helper'

class ImageTest < ActiveSupport::TestCase
setup do
@goodbob = users(:goodbob)
@evilbob = users(:evilbob)

@goodbobs_image = images(:goodbobs_image)
@evilbobs_image = images(:evilbobs_image)

@log_msg = "[activerecord-firewall] Image from User #{@goodbob.id} was accessed from User #{@evilbob.id}"

Current.user = @evilbob
end

teardown do
Current.reset
end

test "image belonging to evil bob is accessible by evil bob with no log messages" do
assert_nothing_raised do
assert_not_logged(@log_msg) do
Image.where(user: @evilbob).first
end
end
end

test "image belonging to good bob is accessible by evil bob with no log messages" do
assert_nothing_raised do
assert_logged(@log_msg) do
Image.where(user: @goodbob).first
end
end
end
end
11 changes: 11 additions & 0 deletions test/fixtures/images.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html

goodbobs_image:
alt: This is alt text for good bob's image
url: https://example.com/goodbob.png
user: goodbob

evilbobs_image:
alt: This is alt text for evil bob's image
url: https://example.com/evilbob.png
user: evilbob
29 changes: 29 additions & 0 deletions test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,36 @@ class ActiveSupport::TestCase
# Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
fixtures :all

def assert_logged(message)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

old_logger = Rails.logger
log = StringIO.new
Rails.logger = Logger.new(log)

begin
yield

log.rewind
assert_match message, log.read
ensure
Rails.logger = old_logger
end
end

# Add more helper methods to be used by all tests here...
def assert_not_logged(message)
old_logger = Rails.logger
log = StringIO.new
Rails.logger = Logger.new(log)

begin
yield

log.rewind
assert_no_match message, log.read
ensure
Rails.logger = old_logger
end
end
end

# Load fixtures from the engine
Expand Down