Skip to content

Commit 307db89

Browse files
committed
Add support for security barrier and invoker
1 parent 1666aed commit 307db89

File tree

4 files changed

+77
-18
lines changed

4 files changed

+77
-18
lines changed

lib/scenic/adapters/postgres.rb

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,13 @@ def views
5555
#
5656
# @param name The name of the view to create
5757
# @param sql_definition The SQL schema for the view.
58+
# @param security_barrier If we should enable security_barrier
59+
# @param security_invoker If we should enable security_invoker
5860
#
5961
# @return [void]
60-
def create_view(name, sql_definition)
61-
execute "CREATE VIEW #{quote_table_name(name)} AS #{sql_definition};"
62+
def create_view(name, sql_definition, security_barrier, security_invoker)
63+
with_statement = build_with_statement(security_barrier, security_invoker)
64+
execute "CREATE VIEW #{quote_table_name(name)} #{with_statement} AS #{sql_definition};"
6265
end
6366

6467
# Updates a view in the database.
@@ -75,11 +78,13 @@ def create_view(name, sql_definition)
7578
#
7679
# @param name The name of the view to update
7780
# @param sql_definition The SQL schema for the updated view.
81+
# @param security_barrier If we should enable security_barrier
82+
# @param security_invoker If we should enable security_invoker
7883
#
7984
# @return [void]
80-
def update_view(name, sql_definition)
85+
def update_view(name, sql_definition, security_barrier, security_invoker)
8186
drop_view(name)
82-
create_view(name, sql_definition)
87+
create_view(name, sql_definition, security_barrier, security_invoker)
8388
end
8489

8590
# Replaces a view in the database using `CREATE OR REPLACE VIEW`.
@@ -101,10 +106,13 @@ def update_view(name, sql_definition)
101106
#
102107
# @param name The name of the view to update
103108
# @param sql_definition The SQL schema for the updated view.
109+
# @param security_barrier If we should enable security_barrier
110+
# @param security_invoker If we should enable security_invoker
104111
#
105112
# @return [void]
106-
def replace_view(name, sql_definition)
107-
execute "CREATE OR REPLACE VIEW #{quote_table_name(name)} AS #{sql_definition};"
113+
def replace_view(name, sql_definition, security_barrier, security_invoker)
114+
with_statement = build_with_statement(security_barrier, security_invoker)
115+
execute "CREATE OR REPLACE VIEW #{quote_table_name(name)} #{with_statement} AS #{sql_definition};"
108116
end
109117

110118
# Drops the named view from the database
@@ -276,6 +284,18 @@ def refresh_dependencies_for(name, concurrently: false)
276284
concurrently: concurrently
277285
)
278286
end
287+
288+
def build_with_statement(security_barrier, security_invoker)
289+
if security_invoker && security_barrier
290+
return "WITH (security_barrier, security_invoker = true)"
291+
elsif security_invoker
292+
return "WITH (security_invoker = true)"
293+
elsif security_barrier
294+
return "WITH (security_barrier)"
295+
end
296+
297+
return ""
298+
end
279299
end
280300
end
281301
end

lib/scenic/adapters/postgres/views.rb

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ def views_from_postgres
2828
c.relname as viewname,
2929
pg_get_viewdef(c.oid) AS definition,
3030
c.relkind AS kind,
31+
c.reloptions AS options,
3132
n.nspname AS namespace
3233
FROM pg_class c
3334
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
@@ -41,7 +42,17 @@ def views_from_postgres
4142
end
4243

4344
def to_scenic_view(result)
44-
namespace, viewname = result.values_at "namespace", "viewname"
45+
namespace, viewname, options = result.values_at "namespace", "viewname", "options"
46+
47+
if options.present?
48+
security_invoker = options.include?("security_invoker=true")
49+
security_barrier = options.include?("security_barrier=true")
50+
end
51+
52+
options = {
53+
security_invoker:,
54+
security_barrier:
55+
}
4556

4657
namespaced_viewname = if namespace != "public"
4758
"#{pg_identifier(namespace)}.#{pg_identifier(viewname)}"
@@ -52,7 +63,8 @@ def to_scenic_view(result)
5263
Scenic::View.new(
5364
name: namespaced_viewname,
5465
definition: result["definition"].strip,
55-
materialized: result["kind"] == "m"
66+
materialized: result["kind"] == "m",
67+
options:
5668
)
5769
end
5870

lib/scenic/statements.rb

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ module Statements
1212
# @param materialized [Boolean, Hash] Set to true to create a materialized
1313
# view. Set to { no_data: true } to create materialized view without
1414
# loading data. Defaults to false.
15+
# @param security_barrier [Boolean] Set to true to enable the security barrier
16+
# option on the view. Defaults to false.
17+
# @param security_invoker [Boolean] Set to true to enable the security invoker
18+
# option on the view. Defaults to false.
1519
# @return The database response from executing the create statement.
1620
#
1721
# @example Create from `db/views/searches_v02.sql`
@@ -22,7 +26,8 @@ module Statements
2226
# SELECT * FROM users WHERE users.active = 't'
2327
# SQL
2428
#
25-
def create_view(name, version: nil, sql_definition: nil, materialized: false)
29+
def create_view(name, version: nil, sql_definition: nil, materialized: false,
30+
security_barrier: false, security_invoker: false)
2631
if version.present? && sql_definition.present?
2732
raise(
2833
ArgumentError,
@@ -43,7 +48,7 @@ def create_view(name, version: nil, sql_definition: nil, materialized: false)
4348
no_data: no_data(materialized)
4449
)
4550
else
46-
Scenic.database.create_view(name, sql_definition)
51+
Scenic.database.create_view(name, sql_definition, security_barrier, security_invoker)
4752
end
4853
end
4954

@@ -55,12 +60,16 @@ def create_view(name, version: nil, sql_definition: nil, materialized: false)
5560
# `version` argument to {#create_view}.
5661
# @param materialized [Boolean] Set to true if dropping a meterialized view.
5762
# defaults to false.
63+
# @param security_barrier [Boolean] Set to true to enable the security barrier
64+
# option on the view. Defaults to false.
65+
# @param security_invoker [Boolean] Set to true to enable the security invoker
66+
# option on the view. Defaults to false.
5867
# @return The database response from executing the drop statement.
5968
#
6069
# @example Drop a view, rolling back to version 3 on rollback
6170
# drop_view(:users_who_recently_logged_in, revert_to_version: 3)
6271
#
63-
def drop_view(name, revert_to_version: nil, materialized: false)
72+
def drop_view(name, revert_to_version: nil, materialized: false, security_barrier: false, security_invoker: false)
6473
if materialized
6574
Scenic.database.drop_materialized_view(name)
6675
else
@@ -83,12 +92,17 @@ def drop_view(name, revert_to_version: nil, materialized: false)
8392
# @param materialized [Boolean, Hash] True if updating a materialized view.
8493
# Set to { no_data: true } to update materialized view without loading
8594
# data. Defaults to false.
95+
# @param security_barrier [Boolean] Set to true to enable the security barrier
96+
# option on the view. Defaults to false.
97+
# @param security_invoker [Boolean] Set to true to enable the security invoker
98+
# option on the view. Defaults to false.
8699
# @return The database response from executing the create statement.
87100
#
88101
# @example
89102
# update_view :engagement_reports, version: 3, revert_to_version: 2
90103
#
91-
def update_view(name, version: nil, sql_definition: nil, revert_to_version: nil, materialized: false)
104+
def update_view(name, version: nil, sql_definition: nil, revert_to_version: nil, materialized: false,
105+
security_barrier: false, security_invoker: false)
92106
if version.blank? && sql_definition.blank?
93107
raise(
94108
ArgumentError,
@@ -112,7 +126,7 @@ def update_view(name, version: nil, sql_definition: nil, revert_to_version: nil,
112126
no_data: no_data(materialized)
113127
)
114128
else
115-
Scenic.database.update_view(name, sql_definition)
129+
Scenic.database.update_view(name, sql_definition, security_barrier, security_invoker)
116130
end
117131
end
118132

@@ -127,12 +141,17 @@ def update_view(name, version: nil, sql_definition: nil, revert_to_version: nil,
127141
# @param version [Fixnum] The version number of the view.
128142
# @param revert_to_version [Fixnum] The version number to rollback to on
129143
# `rake db rollback`
144+
# @param security_barrier [Boolean] Set to true to enable the security barrier
145+
# option on the view. Defaults to false.
146+
# @param security_invoker [Boolean] Set to true to enable the security invoker
147+
# option on the view. Defaults to false.
130148
# @return The database response from executing the create statement.
131149
#
132150
# @example
133151
# replace_view :engagement_reports, version: 3, revert_to_version: 2
134152
#
135-
def replace_view(name, version: nil, revert_to_version: nil, materialized: false)
153+
def replace_view(name, version: nil, revert_to_version: nil, materialized: false,
154+
security_barrier: false, security_invoker: false)
136155
if version.blank?
137156
raise ArgumentError, "version is required"
138157
end
@@ -143,7 +162,7 @@ def replace_view(name, version: nil, revert_to_version: nil, materialized: false
143162

144163
sql_definition = definition(name, version)
145164

146-
Scenic.database.replace_view(name, sql_definition)
165+
Scenic.database.replace_view(name, sql_definition, security_barrier, security_invoker)
147166
end
148167

149168
private

lib/scenic/view.rb

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,30 +22,38 @@ class View
2222
# @return [Boolean]
2323
attr_reader :materialized
2424

25+
# Options definition for security_invoker and security_barrier
26+
# @return Hash[Symbol, Boolean]
27+
attr_reader :options
28+
2529
# Returns a new instance of View.
2630
#
2731
# @param name [String] The name of the view.
2832
# @param definition [String] The SQL for the query that defines the view.
2933
# @param materialized [Boolean] `true` if the view is materialized.
30-
def initialize(name:, definition:, materialized:)
34+
def initialize(name:, definition:, materialized:, options:)
3135
@name = name
3236
@definition = definition
3337
@materialized = materialized
38+
@options = options
3439
end
3540

3641
# @api private
3742
def ==(other)
3843
name == other.name &&
3944
definition == other.definition &&
40-
materialized == other.materialized
45+
materialized == other.materialized &&
46+
options == other.options
4147
end
4248

4349
# @api private
4450
def to_schema
4551
materialized_option = materialized ? "materialized: true, " : ""
52+
security_barrier_option = options[:security_barrier] ? "security_barrier: true, " : ""
53+
security_invoker_option = options[:security_invoker] ? "security_invoker: true, " : ""
4654

4755
<<-DEFINITION
48-
create_view #{UnaffixedName.for(name).inspect}, #{materialized_option}sql_definition: <<-\SQL
56+
create_view #{UnaffixedName.for(name).inspect}, #{security_barrier_option}#{security_invoker_option}#{materialized_option}sql_definition: <<-\SQL
4957
#{escaped_definition.indent(2)}
5058
SQL
5159
DEFINITION

0 commit comments

Comments
 (0)