omen 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +35 -0
- data/INSTRUCTIONS.md +227 -0
- data/README.md +86 -128
- data/app/models/omen/conversation.rb +5 -3
- data/app/models/omen/executed.rb +8 -3
- data/app/models/omen/instructions.md +2 -2
- data/app/models/omen/instructions.rb +15 -24
- data/app/models/omen/narrowed.rb +35 -0
- data/app/models/omen/query.rb +6 -2
- data/app/models/omen/reading.rb +4 -1
- data/app/models/omen/refusal.rb +43 -0
- data/app/models/omen/reply.rb +20 -0
- data/app/models/omen/role.rb +14 -2
- data/app/models/omen/schema.rb +25 -4
- data/db/migrate/20260912000000_add_type_to_omen_readings.rb +6 -0
- data/lib/generators/omen/install/install_generator.rb +1 -1
- data/lib/generators/omen/install/templates/omen.rb +4 -0
- data/lib/generators/omen/pages/USAGE +25 -0
- data/lib/generators/omen/pages/pages_generator.rb +70 -0
- data/lib/generators/omen/pages/templates/_form.html.erb.tt +8 -0
- data/lib/generators/omen/pages/templates/controller.rb.tt +54 -0
- data/lib/generators/omen/pages/templates/index.html.erb.tt +12 -0
- data/lib/generators/omen/pages/templates/model.rb.tt +7 -0
- data/lib/generators/omen/pages/templates/show.html.erb.tt +45 -0
- data/lib/omen/bound.rb +26 -0
- data/lib/omen/config.rb +4 -0
- data/lib/omen/grants.rb +56 -5
- data/lib/omen/inquirer.rb +36 -20
- data/lib/omen/narrowing.rb +87 -0
- data/lib/omen/version.rb +1 -1
- data/lib/omen.rb +17 -0
- data/lib/tasks/omen.rake +16 -2
- metadata +15 -1
data/lib/omen/grants.rb
CHANGED
|
@@ -3,6 +3,12 @@ module Omen
|
|
|
3
3
|
# may read, and what it may not. Kept apart from the task that runs them, which is about a
|
|
4
4
|
# database it has to find and a refusal it has to survive rather than about privileges.
|
|
5
5
|
module Grants
|
|
6
|
+
# Membership without inheritance, so a role that enters a narrowed one with SET LOCAL ROLE
|
|
7
|
+
# is not itself held back by its policies. Postgres 16 and later; below that the grant is
|
|
8
|
+
# refused and the task says so, which leaves a reading misconfigured rather than narrowing
|
|
9
|
+
# somebody nobody meant to narrow.
|
|
10
|
+
APART = 'GRANT %{role} TO %{member} WITH INHERIT FALSE'
|
|
11
|
+
|
|
6
12
|
# DDL, which Active Record has no expression for, and not a query.
|
|
7
13
|
# @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] a writing one.
|
|
8
14
|
# @param members [Array<String>] the roles that may SET LOCAL ROLE to this one. The owner
|
|
@@ -12,14 +18,11 @@ module Omen
|
|
|
12
18
|
def self.statements(connection, members)
|
|
13
19
|
role = connection.quote_table_name Omen.config.narrow_role
|
|
14
20
|
[
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
'END IF; END $$',
|
|
18
|
-
"ALTER ROLE #{role} WITH #{Attributes::SETTABLE}",
|
|
21
|
+
*made(connection, Omen.config.narrow_role),
|
|
22
|
+
*members.map { |member| "GRANT #{role} TO #{connection.quote_table_name member}" },
|
|
19
23
|
"GRANT USAGE ON SCHEMA public TO #{role}",
|
|
20
24
|
"GRANT SELECT ON ALL TABLES IN SCHEMA public TO #{role}",
|
|
21
25
|
"ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO #{role}",
|
|
22
|
-
*members.map { |member| "GRANT #{role} TO #{connection.quote_table_name member}" },
|
|
23
26
|
*revoked(connection, role),
|
|
24
27
|
*Omen::Renamed.statements,
|
|
25
28
|
*Omen::TimeZone.statements(connection),
|
|
@@ -27,6 +30,54 @@ module Omen
|
|
|
27
30
|
]
|
|
28
31
|
end
|
|
29
32
|
|
|
33
|
+
# The same role, made for one audience rather than for every table: it is granted nothing
|
|
34
|
+
# here, since a narrowing names the tables and the columns of each itself.
|
|
35
|
+
# @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] a writing one.
|
|
36
|
+
# @param name [String] the role to make.
|
|
37
|
+
# @param members [Array<String>] the roles that may enter it, none of them inheriting it.
|
|
38
|
+
# @return [Array<String>] the statements to run, in order.
|
|
39
|
+
def self.narrowed(connection, name, members)
|
|
40
|
+
role = connection.quote_table_name name
|
|
41
|
+
[
|
|
42
|
+
*made(connection, name),
|
|
43
|
+
*members.map { |member| apart role, connection.quote_table_name(member) },
|
|
44
|
+
"GRANT USAGE ON SCHEMA public TO #{role}",
|
|
45
|
+
]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @param role [String] the role, quoted.
|
|
49
|
+
# @param member [String] the role that may enter it, quoted.
|
|
50
|
+
# @return [String] the statement that lets it in without handing it what the role holds.
|
|
51
|
+
def self.apart(role, member) = APART % { role: role, member: member }
|
|
52
|
+
|
|
53
|
+
# Named one by one rather than granted whole: a column added to hold a secret, or to count
|
|
54
|
+
# what belongs to everybody, is one a role has to be given before it can read it. Taken away
|
|
55
|
+
# first, since a grant only ever adds: without the revoke, a column dropped from the list
|
|
56
|
+
# stays readable by whoever was granted it the last time this ran.
|
|
57
|
+
# @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] a writing one.
|
|
58
|
+
# @param name [String] the role to grant them to.
|
|
59
|
+
# @param table [String] the table whose columns are being granted.
|
|
60
|
+
# @param refused [Regexp] the columns of it that role may not read.
|
|
61
|
+
# @return [Array<String>] what it may read of that table, said over again.
|
|
62
|
+
def self.granted(connection, name, table, refused)
|
|
63
|
+
role = connection.quote_table_name name
|
|
64
|
+
quoted = connection.quote_table_name table
|
|
65
|
+
columns = connection.columns(table).map(&:name).grep_v(refused)
|
|
66
|
+
.map { |column| connection.quote_column_name column }
|
|
67
|
+
[ "REVOKE ALL ON #{quoted} FROM #{role}",
|
|
68
|
+
"GRANT SELECT (#{columns.join ', '}) ON #{quoted} TO #{role}", ]
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] a writing one.
|
|
72
|
+
# @param name [String] the role to make, where the database has not got it.
|
|
73
|
+
# @return [Array<String>] the statements that make it and say what it may be.
|
|
74
|
+
def self.made(connection, name)
|
|
75
|
+
role = connection.quote_table_name name
|
|
76
|
+
[ 'DO $$ BEGIN IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = ' \
|
|
77
|
+
"#{connection.quote name}) THEN CREATE ROLE #{role} NOLOGIN; END IF; END $$",
|
|
78
|
+
"ALTER ROLE #{role} WITH #{Attributes::SETTABLE}", ]
|
|
79
|
+
end
|
|
80
|
+
|
|
30
81
|
# Intersected, so a bare db:create with no table yet to revoke on is not a failure.
|
|
31
82
|
# @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] a writing one.
|
|
32
83
|
# @param role [String] the role to hide this feature's own tables from.
|
data/lib/omen/inquirer.rb
CHANGED
|
@@ -14,30 +14,30 @@ module Omen
|
|
|
14
14
|
UNMADE = 'Could not make %{role}, so every reading will say this app is misconfigured. Ask ' \
|
|
15
15
|
'for that role, NOLOGIN, granted SELECT on every table but %{tables}.'
|
|
16
16
|
|
|
17
|
-
# Said per
|
|
18
|
-
#
|
|
17
|
+
# Said per group, since the grants, the revocations and the functions need nothing from one
|
|
18
|
+
# another and one refusal should not discard the rest.
|
|
19
19
|
REFUSED = 'Skipped, refused by the database: %{statement} (%{error})'
|
|
20
20
|
|
|
21
21
|
# Said where a role that already existed is one a reading should not be able to reach through.
|
|
22
22
|
DANGEROUS = '%{role} holds %{held}. This gem cannot take that away without being a superuser ' \
|
|
23
23
|
'itself, so ask for it to be taken away.'
|
|
24
24
|
|
|
25
|
-
# Creates the role and the
|
|
25
|
+
# Creates the role and the functions, and writes every narrowing a host declared, on every
|
|
26
|
+
# database this environment prepares.
|
|
26
27
|
# @return [void]
|
|
27
|
-
def self.grant
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
def self.grant = Omen.each_database { |connection| grant_on connection }
|
|
29
|
+
|
|
30
|
+
# Takes every narrowing back off, leaving each table read the way it was read before one.
|
|
31
|
+
# @return [void]
|
|
32
|
+
def self.widen
|
|
33
|
+
Omen.each_database do |connection|
|
|
34
|
+
Omen::Reading.narrowings.each do |narrowing|
|
|
35
|
+
narrowing.widening(connection).each { |group| attempted connection, *group }
|
|
36
|
+
puts "Widened #{connection.current_database} back out of #{narrowing.role}"
|
|
34
37
|
end
|
|
35
38
|
end
|
|
36
39
|
end
|
|
37
40
|
|
|
38
|
-
# @return [Array<String>] the environments whose databases this run should cover.
|
|
39
|
-
def self.environments = Rails.env.development? ? %w[ development test ] : [Rails.env.to_s]
|
|
40
|
-
|
|
41
41
|
# Warns rather than raises: a managed database never grants CREATEROLE, and a deploy that
|
|
42
42
|
# cannot make the role must still finish, having said what has to be made by hand.
|
|
43
43
|
# @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] a writing one.
|
|
@@ -54,18 +54,34 @@ module Omen
|
|
|
54
54
|
held = Attributes.dangerous connection
|
|
55
55
|
warn DANGEROUS % { role: role, held: held.to_sentence } if held.any?
|
|
56
56
|
puts "Granted SELECT on #{connection.current_database} to #{role}"
|
|
57
|
+
narrow connection, members
|
|
57
58
|
end
|
|
58
59
|
|
|
59
|
-
#
|
|
60
|
-
# inside a savepoint of its own, since a refusal inside a transaction refuses everything
|
|
61
|
-
# after it too, and this task is as likely to be run from a console as from a deploy.
|
|
60
|
+
# Every audience a host declared, written into the database its readings are answered from.
|
|
62
61
|
# @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] a writing one.
|
|
63
|
-
# @param
|
|
62
|
+
# @param members [Array<String>] the roles that may enter a narrowed one.
|
|
64
63
|
# @return [void]
|
|
65
|
-
def self.
|
|
66
|
-
|
|
64
|
+
def self.narrow(connection, members)
|
|
65
|
+
Omen::Reading.narrowings.each do |narrowing|
|
|
66
|
+
narrowing.statements(connection, members).each { |group| attempted connection, *group }
|
|
67
|
+
puts "Narrowed #{narrowing.role} to the rows #{narrowing.setting} names"
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# A group at a time, so a database that refuses one still runs the others -- and each inside
|
|
72
|
+
# a savepoint of its own, since a refusal inside a transaction refuses everything after it
|
|
73
|
+
# too, and this task is as likely to be run from a console as from a deploy. What arrives
|
|
74
|
+
# together is applied together: a table is never left with row level security on and no
|
|
75
|
+
# policy under it.
|
|
76
|
+
# @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] a writing one.
|
|
77
|
+
# @param statements [Array<String>] what Omen::Grants or a narrowing built.
|
|
78
|
+
# @return [void]
|
|
79
|
+
def self.attempted(connection, *statements)
|
|
80
|
+
connection.transaction(requires_new: true) do
|
|
81
|
+
statements.each { |statement| connection.execute statement }
|
|
82
|
+
end
|
|
67
83
|
rescue ActiveRecord::StatementInvalid => error
|
|
68
|
-
warn REFUSED % { statement:
|
|
84
|
+
warn REFUSED % { statement: statements.first.squish, error: error.message.lines.first.strip }
|
|
69
85
|
end
|
|
70
86
|
|
|
71
87
|
# Discovered rather than named: SET LOCAL ROLE needs the connecting role to be a member of
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
module Omen
|
|
2
|
+
# What one audience of a reading may read: the role its statement runs as, the rows each
|
|
3
|
+
# table admits it, and the tables it reads whole because nothing in them is anybody's.
|
|
4
|
+
class Narrowing
|
|
5
|
+
# The policy every role holds, so that turning row level security on takes nothing away
|
|
6
|
+
# from whoever could already read -- a role made after this ran included. The narrowed role
|
|
7
|
+
# is held back by the restrictive policy beside it, which is ANDed with this one and
|
|
8
|
+
# applies to nobody else.
|
|
9
|
+
EVERYBODY = 'omen_everybody'
|
|
10
|
+
|
|
11
|
+
# @return [String] the Postgres role a reading of this audience runs as.
|
|
12
|
+
attr_reader :role
|
|
13
|
+
|
|
14
|
+
# @return [Symbol] the column of the reading that says whose rows these are.
|
|
15
|
+
attr_reader :by
|
|
16
|
+
|
|
17
|
+
# @param role [String] the Postgres role a reading of this audience runs as.
|
|
18
|
+
# @param by [Symbol] the column of the reading that says whose rows these are.
|
|
19
|
+
# @param own [Hash] every table it reads rows of, to what makes a row its own. A table is
|
|
20
|
+
# named either way, since a host writing one of these writes symbols or strings by habit.
|
|
21
|
+
# @param whole [Array<String>] the tables it reads whole, nobody's in particular.
|
|
22
|
+
# @param except [Regexp, nil] the columns of those it may not read, a counter over everybody
|
|
23
|
+
# being the case this exists for: it counts every owner rather than this one.
|
|
24
|
+
def initialize(role:, by:, own:, whole: [], except: nil)
|
|
25
|
+
@role = role
|
|
26
|
+
@by = by
|
|
27
|
+
@own = own.transform_keys(&:to_s)
|
|
28
|
+
@whole = whole.map(&:to_s)
|
|
29
|
+
@except = except
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @return [String] the setting a policy reads to know whose rows it is looking at.
|
|
33
|
+
def setting = "omen.#{@by}"
|
|
34
|
+
|
|
35
|
+
# @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] a writing one.
|
|
36
|
+
# @param members [Array<String>] the roles that may enter this one.
|
|
37
|
+
# @return [Array<Array<String>>] the statements, grouped: what arrives together is applied
|
|
38
|
+
# together, so no table is left with row level security on and no policy under it.
|
|
39
|
+
def statements(connection, members)
|
|
40
|
+
[ Grants.narrowed(connection, @role, members),
|
|
41
|
+
*@whole.map { |table| granted connection, table },
|
|
42
|
+
*@own.map { |table, own| narrowed connection, table, own }, ]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] a writing one.
|
|
46
|
+
# @return [Array<Array<String>>] the statements that take it back off, table by table.
|
|
47
|
+
def widening(connection)
|
|
48
|
+
@own.keys.map { |table| widened connection, table }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
# The permissive policy first and the table turned on last, so a group that stops short
|
|
54
|
+
# leaves a table nobody has been narrowed on rather than one nobody can read.
|
|
55
|
+
def narrowed(connection, table, own)
|
|
56
|
+
quoted = connection.quote_table_name table
|
|
57
|
+
[ *granted(connection, table),
|
|
58
|
+
*policy(connection, quoted, EVERYBODY, 'FOR ALL TO PUBLIC USING (true) WITH CHECK (true)'),
|
|
59
|
+
*policy(connection, quoted, "#{table}_#{@role}",
|
|
60
|
+
"AS RESTRICTIVE FOR SELECT TO #{connection.quote_table_name @role} " \
|
|
61
|
+
"USING (#{format own, owner: owner, setting: setting})"),
|
|
62
|
+
"ALTER TABLE #{quoted} ENABLE ROW LEVEL SECURITY", ]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Dropped first, so that running this again says what it said the first time.
|
|
66
|
+
def policy(connection, quoted, name, rule)
|
|
67
|
+
named = connection.quote_table_name name
|
|
68
|
+
[ "DROP POLICY IF EXISTS #{named} ON #{quoted}",
|
|
69
|
+
"CREATE POLICY #{named} ON #{quoted} #{rule}", ]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def widened(connection, table)
|
|
73
|
+
quoted = connection.quote_table_name table
|
|
74
|
+
[ "DROP POLICY IF EXISTS #{connection.quote_table_name "#{table}_#{@role}"} ON #{quoted}",
|
|
75
|
+
"DROP POLICY IF EXISTS #{connection.quote_table_name EVERYBODY} ON #{quoted}",
|
|
76
|
+
"ALTER TABLE #{quoted} DISABLE ROW LEVEL SECURITY", ]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Empty where the reading names nobody, and no row is nobody's, so nobody reads nothing.
|
|
80
|
+
def owner = "NULLIF(current_setting('#{setting}', true), '')::bigint"
|
|
81
|
+
|
|
82
|
+
def granted(connection, table)
|
|
83
|
+
refused = [ Omen::Column::CREDENTIALS, (@except if @whole.include? table) ].compact
|
|
84
|
+
Grants.granted connection, @role, table, Regexp.union(refused)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
data/lib/omen/version.rb
CHANGED
data/lib/omen.rb
CHANGED
|
@@ -7,6 +7,8 @@ require 'omen/distance'
|
|
|
7
7
|
require 'omen/grants'
|
|
8
8
|
require 'omen/renamed'
|
|
9
9
|
require 'omen/time_zone'
|
|
10
|
+
require 'omen/bound'
|
|
11
|
+
require 'omen/narrowing'
|
|
10
12
|
require 'omen/inquirer'
|
|
11
13
|
require 'omen/requirements'
|
|
12
14
|
require 'omen/version'
|
|
@@ -24,4 +26,19 @@ module Omen
|
|
|
24
26
|
# Yields the configuration, so a host states its own facts in one initializer.
|
|
25
27
|
# @return [void]
|
|
26
28
|
def self.configure = yield config
|
|
29
|
+
|
|
30
|
+
# @return [Array<String>] the environments whose databases a grant should cover.
|
|
31
|
+
def self.environments = Rails.env.development? ? %w[ development test ] : [Rails.env.to_s]
|
|
32
|
+
|
|
33
|
+
# @yield [ActiveRecord::ConnectionAdapters::AbstractAdapter] a writing connection to each
|
|
34
|
+
# database this environment prepares.
|
|
35
|
+
# @return [void]
|
|
36
|
+
def self.each_database
|
|
37
|
+
environments.each do |environment|
|
|
38
|
+
config = ActiveRecord::Base.configurations.configs_for env_name: environment,
|
|
39
|
+
name: 'primary'
|
|
40
|
+
next unless config
|
|
41
|
+
ActiveRecord::Tasks::DatabaseTasks.with_temporary_connection(config) { |it| yield it }
|
|
42
|
+
end
|
|
43
|
+
end
|
|
27
44
|
end
|
data/lib/tasks/omen.rake
CHANGED
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
namespace :db do
|
|
2
2
|
namespace :omen do
|
|
3
|
-
desc 'Create the role a reading runs its statement as,
|
|
4
|
-
'timestamp through'
|
|
3
|
+
desc 'Create the role a reading runs its statement as, the function it reads a ' \
|
|
4
|
+
'timestamp through, and the policies every narrowing a host declared asks for'
|
|
5
5
|
task grant: :environment do
|
|
6
6
|
Omen::Inquirer.grant
|
|
7
7
|
end
|
|
8
|
+
|
|
9
|
+
desc 'Take every narrowing back off, leaving each table read the way it was read before'
|
|
10
|
+
task widen: :environment do
|
|
11
|
+
Omen::Inquirer.widen
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
desc 'Say which roles the policies of a narrowing hold back, membership included'
|
|
15
|
+
task narrowed: :environment do
|
|
16
|
+
Omen.config.record.with_connection do |connection|
|
|
17
|
+
Omen::Bound.roles(connection).each { |row| puts row.join ' ' }
|
|
18
|
+
surprises = Omen::Bound.surprises connection
|
|
19
|
+
abort "Narrowed without being asked for: #{surprises.inspect}" if surprises.any?
|
|
20
|
+
end
|
|
21
|
+
end
|
|
8
22
|
end
|
|
9
23
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: omen
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Claudio Baccigalupo
|
|
@@ -73,6 +73,7 @@ extensions: []
|
|
|
73
73
|
extra_rdoc_files: []
|
|
74
74
|
files:
|
|
75
75
|
- CHANGELOG.md
|
|
76
|
+
- INSTRUCTIONS.md
|
|
76
77
|
- LICENSE.txt
|
|
77
78
|
- README.md
|
|
78
79
|
- app/models/omen/answer.rb
|
|
@@ -83,9 +84,12 @@ files:
|
|
|
83
84
|
- app/models/omen/executed.rb
|
|
84
85
|
- app/models/omen/instructions.md
|
|
85
86
|
- app/models/omen/instructions.rb
|
|
87
|
+
- app/models/omen/narrowed.rb
|
|
86
88
|
- app/models/omen/query.rb
|
|
87
89
|
- app/models/omen/question.rb
|
|
88
90
|
- app/models/omen/reading.rb
|
|
91
|
+
- app/models/omen/refusal.rb
|
|
92
|
+
- app/models/omen/reply.rb
|
|
89
93
|
- app/models/omen/revealed.rb
|
|
90
94
|
- app/models/omen/role.rb
|
|
91
95
|
- app/models/omen/schema.rb
|
|
@@ -94,15 +98,25 @@ files:
|
|
|
94
98
|
- db/migrate/20260821120000_create_omen_readings.rb
|
|
95
99
|
- db/migrate/20260821120001_create_omen_questions.rb
|
|
96
100
|
- db/migrate/20260821120002_create_omen_answers.rb
|
|
101
|
+
- db/migrate/20260912000000_add_type_to_omen_readings.rb
|
|
97
102
|
- lib/generators/omen/install/install_generator.rb
|
|
98
103
|
- lib/generators/omen/install/templates/omen.rb
|
|
104
|
+
- lib/generators/omen/pages/USAGE
|
|
105
|
+
- lib/generators/omen/pages/pages_generator.rb
|
|
106
|
+
- lib/generators/omen/pages/templates/_form.html.erb.tt
|
|
107
|
+
- lib/generators/omen/pages/templates/controller.rb.tt
|
|
108
|
+
- lib/generators/omen/pages/templates/index.html.erb.tt
|
|
109
|
+
- lib/generators/omen/pages/templates/model.rb.tt
|
|
110
|
+
- lib/generators/omen/pages/templates/show.html.erb.tt
|
|
99
111
|
- lib/omen.rb
|
|
100
112
|
- lib/omen/attributes.rb
|
|
113
|
+
- lib/omen/bound.rb
|
|
101
114
|
- lib/omen/config.rb
|
|
102
115
|
- lib/omen/distance.rb
|
|
103
116
|
- lib/omen/engine.rb
|
|
104
117
|
- lib/omen/grants.rb
|
|
105
118
|
- lib/omen/inquirer.rb
|
|
119
|
+
- lib/omen/narrowing.rb
|
|
106
120
|
- lib/omen/renamed.rb
|
|
107
121
|
- lib/omen/requirements.rb
|
|
108
122
|
- lib/omen/stubs.rb
|