pgcrypto 0.0.4 → 0.1.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.
- data/CHANGES +14 -5
- data/Gemfile +1 -0
- data/README.markdown +6 -8
- data/VERSION +1 -1
- data/lib/generators/pgcrypto/install/USAGE +9 -0
- data/lib/generators/pgcrypto/install/install_generator.rb +21 -0
- data/lib/generators/pgcrypto/install/templates/initializer.rb +9 -0
- data/lib/generators/pgcrypto/install/templates/migration.rb +14 -0
- data/lib/pgcrypto.rb +59 -21
- data/lib/pgcrypto/active_record.rb +25 -76
- data/lib/pgcrypto/arel.rb +6 -4
- data/lib/pgcrypto/column.rb +6 -0
- data/pgcrypto.gemspec +11 -2
- metadata +20 -3
data/CHANGES
CHANGED
@@ -1,12 +1,21 @@
|
|
1
|
+
0.1.0
|
2
|
+
* Overhauled the underpinnings to rely on a separate column. Adds
|
3
|
+
on-demand loading of encrypted attributes from a central table
|
4
|
+
which provides dramatic speed improvements when a record's
|
5
|
+
encrypted attributes aren't needed most of the time.
|
6
|
+
|
7
|
+
0.0.4
|
8
|
+
* Compatibility fix between ActiveRecord 3.2.1 and 3.2.2
|
9
|
+
|
1
10
|
0.0.3
|
2
11
|
* Fixed a join bug on SELECT statements
|
3
12
|
|
4
13
|
0.0.2
|
5
|
-
|
6
|
-
|
7
|
-
|
14
|
+
* Fixed a number of key-related bugs discovered in testing with our
|
15
|
+
second production app with encrypted columns. Also duck-typed AREL
|
16
|
+
statement types in a few places.
|
8
17
|
|
9
18
|
0.0.1
|
10
|
-
|
11
|
-
|
19
|
+
* INSERT, SELECT, and UPDATE statements are working. But I wrote this
|
20
|
+
while testing with a production app, and thus haven't written
|
12
21
|
specific tests, so don't get your panties in a twist.
|
data/Gemfile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
gem 'activerecord', '>= 3.2'
|
data/README.markdown
CHANGED
@@ -17,18 +17,16 @@ You need to have PGCrypto installed before this guy will work. [LMGTFY](http://l
|
|
17
17
|
|
18
18
|
bundle
|
19
19
|
|
20
|
-
3.
|
20
|
+
3. Generate the migration and the initializer:
|
21
|
+
|
22
|
+
rails g pgcrypto:install
|
23
|
+
rake db:migrate
|
24
|
+
|
25
|
+
4. Edit the initializer in `config/initializers/pgcrypto.rb` to point out your public and private GPG keys:
|
21
26
|
|
22
27
|
PGCrypto.keys[:private] = {:path => "~/.keys/private.key"}
|
23
28
|
PGCrypto.keys[:public] = {:path => "~/.keys/public.key"}
|
24
29
|
|
25
|
-
4. PGCrypto columns are named `attribute_encrypted` in the binary format, so do something like this:
|
26
|
-
|
27
|
-
add_column :users, :social_security_number_encrypted, :binary
|
28
|
-
|
29
|
-
This will allow you to access `User#social_security_number` and store the user's social in an encrypted
|
30
|
-
column called `social_security_number_encryped`.
|
31
|
-
|
32
30
|
5. Tell the User class to encrypt and decrypt the `social_security_number` attribute on the fly:
|
33
31
|
|
34
32
|
class User < ActiveRecord::Base
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0
|
1
|
+
0.1.0
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'rails/generators/migration'
|
2
|
+
require 'rails/generators/active_record/migration'
|
3
|
+
|
4
|
+
module Pgcrypto
|
5
|
+
module Generators
|
6
|
+
class InstallGenerator < Rails::Generators::Base
|
7
|
+
include Rails::Generators::Migration
|
8
|
+
extend ActiveRecord::Generators::Migration
|
9
|
+
|
10
|
+
source_root File.expand_path('../templates', __FILE__)
|
11
|
+
|
12
|
+
def copy_migration
|
13
|
+
migration_template("migration.rb", "db/migrate/install_pgcrypto")
|
14
|
+
end
|
15
|
+
|
16
|
+
def create_initializer
|
17
|
+
copy_file("initializer.rb", "config/initializers/pgcrypto.rb")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# Uncomment the line below and point it to your private key
|
2
|
+
# PGCrypto.keys[:private] = {:path => 'path/to/private/keyfile'}
|
3
|
+
|
4
|
+
# You can also specify the file contents directly:
|
5
|
+
# PGCrypto.keys[:private] = ENV['PRIVATE_KEY']
|
6
|
+
|
7
|
+
# You can also specify custom keys:
|
8
|
+
# PGCrypto.keys[:message_columns] = {:path => 'path/to/message_columns/keyfile'}
|
9
|
+
# PGCrypto.keys[:user_columns] = {:path => 'path/to/user_columns/keyfile'}
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class InstallPgcrypto < ActiveRecord::Migration
|
2
|
+
def up
|
3
|
+
create_table :pgcrypto_columns do |t|
|
4
|
+
t.belongs_to :owner, :polymorphic => true
|
5
|
+
t.string :name, :limit => 64
|
6
|
+
t.binary :value
|
7
|
+
end
|
8
|
+
add_index :pgcrypto_columns, [:owner_id, :owner_type, :name], :name => :pgcrypto_column_finder
|
9
|
+
end
|
10
|
+
|
11
|
+
def down
|
12
|
+
drop_table :pgcrypto_columns
|
13
|
+
end
|
14
|
+
end
|
data/lib/pgcrypto.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'pgcrypto/active_record'
|
2
2
|
require 'pgcrypto/arel'
|
3
|
+
require 'pgcrypto/column'
|
3
4
|
require 'pgcrypto/key'
|
4
5
|
require 'pgcrypto/table_manager'
|
5
6
|
|
@@ -17,40 +18,77 @@ module PGCrypto
|
|
17
18
|
class Error < StandardError; end
|
18
19
|
|
19
20
|
module ClassMethods
|
20
|
-
def pgcrypto(*
|
21
|
+
def pgcrypto(*pgcrypto_column_names)
|
22
|
+
options = pgcrypto_column_names.last.is_a?(Hash) ? pgcrypto_column_names.pop : {}
|
23
|
+
options = {:include => false, :type => :pgp}.merge(options)
|
21
24
|
|
22
|
-
|
23
|
-
options = {:type => :pgp}.merge(options)
|
24
|
-
|
25
|
-
column_names.map(&:to_s).each do |column_name|
|
26
|
-
encrypted_column_name = "#{column_name}_encrypted"
|
27
|
-
unless columns_hash[encrypted_column_name]
|
28
|
-
puts "WARNING: You defined #{column_name} as an encrypted column, but you don't have a corresponding #{encrypted_column_name} column in your database!"
|
29
|
-
end
|
25
|
+
has_many :pgcrypto_columns, :as => :owner, :autosave => true, :class_name => 'PGCrypto::Column', :dependent => :delete_all
|
30
26
|
|
27
|
+
pgcrypto_column_names.map(&:to_s).each do |column_name|
|
31
28
|
# Stash the encryption type in our module so various monkeypatches can access it later!
|
32
|
-
PGCrypto[table_name][
|
29
|
+
PGCrypto[table_name][column_name] = options.symbolize_keys
|
33
30
|
|
34
31
|
# Add attribute readers/writers to keep this baby as fluid and clean as possible.
|
35
|
-
|
32
|
+
start_line = __LINE__; pgcrypto_methods = <<-PGCRYPTO_METHODS
|
36
33
|
def #{column_name}
|
37
|
-
@
|
34
|
+
return @_pgcrypto_#{column_name}.try(:value) if defined?(@_pgcrypto_#{column_name})
|
35
|
+
@_pgcrypto_#{column_name} ||= select_pgcrypto_column(:#{column_name})
|
36
|
+
@_pgcrypto_#{column_name}.try(:value)
|
38
37
|
end
|
39
38
|
|
40
|
-
# We write the attribute
|
41
|
-
# so the actual attribute value is dirty and will be queued up for assignment
|
39
|
+
# We write the attribute directly to its child value. Neato!
|
42
40
|
def #{column_name}=(value)
|
43
|
-
|
44
|
-
|
41
|
+
if value.nil?
|
42
|
+
pgcrypto_columns.where(:name => "#{column_name}").mark_for_destruction
|
43
|
+
remove_instance_variable("@_pgcrypto_#{column_name}") if defined?(@_pgcrypto_#{column_name})
|
44
|
+
else
|
45
|
+
@_pgcrypto_#{column_name} ||= pgcrypto_columns.select{|column| column.name == "#{column_name}"}.first || pgcrypto_columns.new(:name => "#{column_name}")
|
46
|
+
@_pgcrypto_#{column_name}.value = value
|
47
|
+
end
|
45
48
|
end
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
+
PGCRYPTO_METHODS
|
50
|
+
|
51
|
+
class_eval pgcrypto_methods, __FILE__, start_line
|
52
|
+
end
|
53
|
+
|
54
|
+
# If any columns are set to be included in the parent record's finder,
|
55
|
+
# we'll go ahead and add 'em!
|
56
|
+
if PGCrypto[table_name].any?{|column, options| options[:include] }
|
57
|
+
default_scope includes(:pgcrypto_columns)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
module InstanceMethods
|
63
|
+
def select_pgcrypto_column(column_name)
|
64
|
+
# Now here's the fun part. We want the selector on PGCrypto columns to do the decryption
|
65
|
+
# for us, so we have override the SELECT and add a JOIN to build out the decrypted value
|
66
|
+
# whenever it's requested.
|
67
|
+
options = PGCrypto[self.class.table_name][column_name]
|
68
|
+
pgcrypto_column_finder = pgcrypto_columns
|
69
|
+
if key = PGCrypto.keys[options[:private_key] || :private]
|
70
|
+
pgcrypto_column_finder = pgcrypto_column_finder.select([
|
71
|
+
'"pgcrypto_columns"."id"',
|
72
|
+
%[pgp_pub_decrypt("pgcrypto_columns"."value", pgcrypto_keys.#{key.name}_key#{", '#{key.password}'" if key.password}) AS "value"]
|
73
|
+
]).joins(%[CROSS JOIN (SELECT #{key.dearmored} AS "#{key.name}_key") AS pgcrypto_keys])
|
74
|
+
end
|
75
|
+
pgcrypto_column_finder.where(:name => column_name).first
|
76
|
+
rescue ActiveRecord::StatementInvalid => e
|
77
|
+
case e.message
|
78
|
+
when /^PGError: ERROR: Wrong key or corrupt data/
|
79
|
+
# If a column has been corrupted, we'll return nil and let the DBA
|
80
|
+
# figure out WTF the is going on
|
81
|
+
logger.error(e.message.split("\n").first)
|
82
|
+
nil
|
83
|
+
else
|
84
|
+
raise e
|
49
85
|
end
|
50
86
|
end
|
51
87
|
end
|
52
88
|
end
|
53
89
|
|
54
90
|
PGCrypto.keys[:public] = {:path => '.pgcrypto'} if File.file?('.pgcrypto')
|
55
|
-
|
56
|
-
ActiveRecord::Base.extend PGCrypto::ClassMethods
|
91
|
+
if defined? ActiveRecord::Base
|
92
|
+
ActiveRecord::Base.extend PGCrypto::ClassMethods
|
93
|
+
ActiveRecord::Base.send :include, PGCrypto::InstanceMethods
|
94
|
+
end
|
@@ -1,26 +1,30 @@
|
|
1
1
|
require 'active_record/connection_adapters/postgresql_adapter'
|
2
2
|
|
3
3
|
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do
|
4
|
-
|
4
|
+
unless instance_methods.include?(:to_sql_without_pgcrypto) || instance_methods.include?('to_sql_without_pgcrypto')
|
5
|
+
alias :to_sql_without_pgcrypto :to_sql
|
6
|
+
end
|
7
|
+
|
5
8
|
def to_sql(arel, *args)
|
6
9
|
case arel
|
7
10
|
when Arel::InsertManager
|
8
|
-
pgcrypto_tweak_insert(arel)
|
9
|
-
when Arel::SelectManager
|
10
|
-
pgcrypto_tweak_select(arel)
|
11
|
+
pgcrypto_tweak_insert(arel, *args)
|
11
12
|
when Arel::UpdateManager
|
12
13
|
pgcrypto_tweak_update(arel)
|
13
14
|
end
|
14
|
-
|
15
|
+
to_sql_without_pgcrypto(arel, *args)
|
15
16
|
end
|
16
17
|
|
17
18
|
private
|
18
|
-
def pgcrypto_tweak_insert(arel)
|
19
|
-
|
20
|
-
unless PGCrypto[table].empty?
|
19
|
+
def pgcrypto_tweak_insert(arel, *args)
|
20
|
+
if arel.ast.relation.name == PGCrypto::Column.table_name && (binds = args.last).is_a?(Array)
|
21
21
|
arel.ast.columns.each_with_index do |column, i|
|
22
|
-
if
|
23
|
-
|
22
|
+
if column.name == 'value'
|
23
|
+
model_column, model_class_name = binds.select {|column, value| column.name == 'owner_type' }.first
|
24
|
+
model_class = Object.const_get(model_class_name)
|
25
|
+
column_column, model_column_name = binds.select {|column, value| column.name == 'name' }.first
|
26
|
+
options = PGCrypto[model_class.table_name][model_column_name]
|
27
|
+
if options && key = PGCrypto.keys[options[:public_key] || :public]
|
24
28
|
value = arel.ast.values.expressions[i]
|
25
29
|
quoted_value = quote_string(value)
|
26
30
|
encryption_instruction = %[pgp_pub_encrypt(#{quoted_value}, #{key.dearmored})]
|
@@ -31,73 +35,18 @@ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do
|
|
31
35
|
end
|
32
36
|
end
|
33
37
|
|
34
|
-
def pgcrypto_tweak_select(arel)
|
35
|
-
# We start by looping through each "core," which is just
|
36
|
-
# a SelectStatement, and tweaking both *what* it's selecting
|
37
|
-
# and correcting plain-text queries against an encrypted
|
38
|
-
# column...
|
39
|
-
joins = {}
|
40
|
-
arel.ast.cores.each do |core|
|
41
|
-
# Now we loop through each SelectStatement's actual selction -
|
42
|
-
# typically it's just '*'; and, in fact, that one of the only
|
43
|
-
# things we care about!
|
44
|
-
new_projections = []
|
45
|
-
core.projections.each do |projection|
|
46
|
-
next unless projection.respond_to?(:relation)
|
47
|
-
# The one other situation we might care about is if the projection is
|
48
|
-
# selecting a specifically encrypted column, in which case, we want to
|
49
|
-
# _wrap_ it. See how that's different?
|
50
|
-
if !PGCrypto[projection.relation.name].empty?
|
51
|
-
# Okay, so first, check if it's a broad select
|
52
|
-
if projection.name == '*'
|
53
|
-
# In this case, we want to just grab all the keys from columns in this table
|
54
|
-
# and select them fancy-like
|
55
|
-
PGCrypto[projection.relation.name].each do |column, options|
|
56
|
-
new_projections.push(pgcrypto_tweak_select_column(column, options, joins))
|
57
|
-
end
|
58
|
-
elsif options = PGCrypto[projection.relation.name][projection.name]
|
59
|
-
# And in this case, we're just selecting a single column!
|
60
|
-
new_projections.push(pgcrypto_tweak_select_column(projection.name, options, joins))
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
core.projections.push(*new_projections.compact)
|
65
|
-
|
66
|
-
# Dios mio! What an operation! Now we'll do something similar for the WHERE statements
|
67
|
-
core.wheres.each do |where|
|
68
|
-
# Now loop through the children to encrypt them for the SELECT
|
69
|
-
where.children.each do |child|
|
70
|
-
if options = PGCrypto[child.left.relation.name]["#{child.left.name}_encrypted"]
|
71
|
-
if key = PGCrypto.keys[options[:private_key] || :private]
|
72
|
-
joins[key.name] ||= "#{key.dearmored} AS #{key.name}_key"
|
73
|
-
child.left = Arel::Nodes::SqlLiteral.new("pgp_pub_decrypt(#{child.left.name}_encrypted, keys.#{key.name}_key)")
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end if where.respond_to?(:children)
|
77
|
-
end
|
78
|
-
end
|
79
|
-
unless joins.empty?
|
80
|
-
arel.join(Arel::Nodes::SqlLiteral.new("CROSS JOIN (SELECT #{joins.values.join(', ')}) AS keys"))
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
def pgcrypto_tweak_select_column(column, options, joins)
|
85
|
-
return nil unless options[:type] == :pgp
|
86
|
-
if key = PGCrypto.keys[options[:private_key] || :private]
|
87
|
-
select = %[pgp_pub_decrypt(#{column}, keys.#{key.name}_key#{", '#{key.password}'" if key.password}) AS "#{column.to_s.gsub(/_encrypted$/, '')}"]
|
88
|
-
joins[key.name] ||= "#{key.dearmored} AS #{key.name}_key"
|
89
|
-
Arel::Nodes::SqlLiteral.new(select)
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
38
|
def pgcrypto_tweak_update(arel)
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
39
|
+
if arel.ast.relation.name == PGCrypto::Column.table_name
|
40
|
+
# Loop through the assignments and make sure we take care of that whole
|
41
|
+
# NULL value thing!
|
42
|
+
value = arel.ast.values.select{|value| value.respond_to?(:left) && value.left.name == 'value' }.first
|
43
|
+
id = arel.ast.wheres.map { |where| where.children.select { |child| child.left.name == 'id' }.first }.first.right
|
44
|
+
if value.right.nil?
|
45
|
+
value.right = Arel::Nodes::SqlLiteral.new('NULL')
|
46
|
+
else column = PGCrypto::Column.select([:id, :owner_id, :owner_type, :name]).find(id)
|
47
|
+
model_class = Object.const_get(column.owner_type)
|
48
|
+
options = PGCrypto[model_class.table_name][column.name]
|
49
|
+
if key = PGCrypto.keys[options[:public_key] || :public]
|
101
50
|
quoted_right = quote_string(value.right)
|
102
51
|
encryption_instruction = %[pgp_pub_encrypt('#{quoted_right}', #{key.dearmored})]
|
103
52
|
value.right = Arel::Nodes::SqlLiteral.new(encryption_instruction)
|
data/lib/pgcrypto/arel.rb
CHANGED
@@ -8,15 +8,17 @@ require 'arel/visitors/postgresql'
|
|
8
8
|
# more specific bits here!
|
9
9
|
|
10
10
|
Arel::Visitors::PostgreSQL.class_eval do
|
11
|
-
|
11
|
+
unless instance_methods.include?(:visit_Arel_Nodes_Assignment_without_pgcrypto) || instance_methods.include?('visit_Arel_Nodes_Assignment_without_pgcrypto')
|
12
|
+
alias :visit_Arel_Nodes_Assignment_without_pgcrypto :visit_Arel_Nodes_Assignment
|
13
|
+
end
|
14
|
+
|
12
15
|
def visit_Arel_Nodes_Assignment(assignment)
|
13
16
|
# Hijack the normally inoccuous assignment that happens, seeing as how
|
14
17
|
# Arel normally forwards this shit to someone else and I hate it.
|
15
|
-
if
|
16
|
-
# raise "#{visit(assignment.left)} = #{visit(assignment.right)}"
|
18
|
+
if assignment.left.relation.name == PGCrypto::Column.table_name && assignment.left.name == 'value'
|
17
19
|
"#{visit(assignment.left)} = #{visit(assignment.right)}"
|
18
20
|
else
|
19
|
-
|
21
|
+
visit_Arel_Nodes_Assignment_without_pgcrypto(assignment)
|
20
22
|
end
|
21
23
|
end
|
22
24
|
end
|
data/pgcrypto.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "pgcrypto"
|
8
|
-
s.version = "0.0
|
8
|
+
s.version = "0.1.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Flip Sasser"]
|
12
|
-
s.date = "2012-03-
|
12
|
+
s.date = "2012-03-28"
|
13
13
|
s.description = "\n PGCrypto is an ActiveRecord::Base extension that allows you to asymmetrically\n encrypt PostgreSQL columns with as little trouble as possible. It's totally\n freaking rad.\n "
|
14
14
|
s.email = "flip@x451.com"
|
15
15
|
s.extra_rdoc_files = [
|
@@ -19,13 +19,19 @@ Gem::Specification.new do |s|
|
|
19
19
|
s.files = [
|
20
20
|
".rspec",
|
21
21
|
"CHANGES",
|
22
|
+
"Gemfile",
|
22
23
|
"LICENSE",
|
23
24
|
"README.markdown",
|
24
25
|
"Rakefile",
|
25
26
|
"VERSION",
|
27
|
+
"lib/generators/pgcrypto/install/USAGE",
|
28
|
+
"lib/generators/pgcrypto/install/install_generator.rb",
|
29
|
+
"lib/generators/pgcrypto/install/templates/initializer.rb",
|
30
|
+
"lib/generators/pgcrypto/install/templates/migration.rb",
|
26
31
|
"lib/pgcrypto.rb",
|
27
32
|
"lib/pgcrypto/active_record.rb",
|
28
33
|
"lib/pgcrypto/arel.rb",
|
34
|
+
"lib/pgcrypto/column.rb",
|
29
35
|
"lib/pgcrypto/key.rb",
|
30
36
|
"lib/pgcrypto/table_manager.rb",
|
31
37
|
"pgcrypto.gemspec"
|
@@ -39,9 +45,12 @@ Gem::Specification.new do |s|
|
|
39
45
|
s.specification_version = 3
|
40
46
|
|
41
47
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
48
|
+
s.add_runtime_dependency(%q<activerecord>, [">= 3.2"])
|
42
49
|
else
|
50
|
+
s.add_dependency(%q<activerecord>, [">= 3.2"])
|
43
51
|
end
|
44
52
|
else
|
53
|
+
s.add_dependency(%q<activerecord>, [">= 3.2"])
|
45
54
|
end
|
46
55
|
end
|
47
56
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pgcrypto
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,8 +9,19 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-03-
|
13
|
-
dependencies:
|
12
|
+
date: 2012-03-28 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activerecord
|
16
|
+
requirement: &70130059262260 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '3.2'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70130059262260
|
14
25
|
description: ! "\n PGCrypto is an ActiveRecord::Base extension that allows you
|
15
26
|
to asymmetrically\n encrypt PostgreSQL columns with as little trouble as possible.
|
16
27
|
It's totally\n freaking rad.\n "
|
@@ -23,13 +34,19 @@ extra_rdoc_files:
|
|
23
34
|
files:
|
24
35
|
- .rspec
|
25
36
|
- CHANGES
|
37
|
+
- Gemfile
|
26
38
|
- LICENSE
|
27
39
|
- README.markdown
|
28
40
|
- Rakefile
|
29
41
|
- VERSION
|
42
|
+
- lib/generators/pgcrypto/install/USAGE
|
43
|
+
- lib/generators/pgcrypto/install/install_generator.rb
|
44
|
+
- lib/generators/pgcrypto/install/templates/initializer.rb
|
45
|
+
- lib/generators/pgcrypto/install/templates/migration.rb
|
30
46
|
- lib/pgcrypto.rb
|
31
47
|
- lib/pgcrypto/active_record.rb
|
32
48
|
- lib/pgcrypto/arel.rb
|
49
|
+
- lib/pgcrypto/column.rb
|
33
50
|
- lib/pgcrypto/key.rb
|
34
51
|
- lib/pgcrypto/table_manager.rb
|
35
52
|
- pgcrypto.gemspec
|