pivotal-sentry 0.4.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/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ .idea/*
2
+ *.log
3
+
data/CHANGELOG ADDED
@@ -0,0 +1,58 @@
1
+ *0.3.1* (7 Jan 2006)
2
+
3
+ * removed useless breakpoint [Solomon White]
4
+
5
+ *0.3* (29 Oct 2005)
6
+
7
+ * added rake task for generating asymmetric keys
8
+ * Switch to migrations and schema for testing setup
9
+
10
+ *0.2.9* (18 Sep 2005)
11
+
12
+ * First RubyForge release.
13
+
14
+ *0.2.8* (17 Sep 2005)
15
+
16
+ * Added Active Record unit tests
17
+
18
+ *0.2.7* (17 Sep 2005)
19
+
20
+ * Added rdocs and stubs for AR unit tests
21
+
22
+ *0.2.6* (2 Aug 2005)
23
+
24
+ * Fixed generates_crypted so it adds attribute accessors
25
+
26
+ *0.2.5* (27 Jul 2005)
27
+
28
+ * Set ActiveRecord callback objects to only encrypt fields when they are not empty.
29
+
30
+ *0.2.4* (11 Jul 2005)
31
+
32
+ * Split ActiveRecord callback methods into their own classes.
33
+ * Set AR virtual columns to fail silently on errors.
34
+
35
+ *0.2.3* (11 Jul 2005)
36
+
37
+ * Added ActiveRecord callback objects for SymmetricSentry and AsymmetricSentry. +one_way_encrypt+ is depreciated.
38
+ * Readme doc added too
39
+
40
+ *0.2.1* (9 Jul 2005)
41
+
42
+ * vastly simplified one_way_encrypt at danp's suggestion. Use this in your model to try it out:
43
+
44
+ +one_way_encrypt :password*
45
+
46
+ That generates an SHA hash of model.password to model.crypted_password which is saved in the DB.
47
+ model.password is a virtual field. Continue using validates_confirmation_of for confirmation.
48
+
49
+
50
+ *0.2* (9 Jul 2005)
51
+
52
+ * added ActiveRecord::Base#one_way_encrypt class method to hash passwords with SHA
53
+ * Renamed core classes to SymmetricSentry and AsymmetricSentry
54
+ * Test Suite added
55
+
56
+ *0.1*
57
+
58
+ * Initial Import
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2005 Rick Olson
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,94 @@
1
+ = Sentry lib - painless encryption library
2
+
3
+ Sentry is a simple wrapper around the mostly undocumented OpenSSL encryption classes.
4
+ For now, look at the pseudo test cases in sentry.rb until I can get more examples written out.
5
+
6
+ == Resources
7
+
8
+ Install
9
+
10
+ * gem install sentry
11
+
12
+ Rubyforge project
13
+
14
+ * http://rubyforge.org/projects/sentry
15
+
16
+ RDocs
17
+
18
+ * http://sentry.rubyforge.org
19
+
20
+ Subversion
21
+
22
+ * http://techno-weenie.net/svn/projects/sentry
23
+
24
+ Collaboa
25
+
26
+ * http://collaboa.techno-weenie.net/repository/browse/sentry
27
+
28
+ == Using with ActiveRecord
29
+
30
+ I wrote this for the purpose of encrypting ActiveRecord attributes. Just <tt>require 'sentry'</tt>, and some new
31
+ class methods will be available to you:
32
+
33
+ === generates_crypted
34
+
35
+ generates_crypted :password, :mode => :sha | :symmetric | :asymmetric
36
+
37
+ This is the generic class method to use. Default mode is :sha.
38
+
39
+ === generates_crypted_hash_of
40
+
41
+ generates_crypted_hash_of :password
42
+
43
+ This is a shortcut for using SHA encryption. No different than specifying <tt>generates_crypted :password</tt>. In the above
44
+ example, model.password is a virtual field, and the SHA hash is saved to model.crypted_password
45
+
46
+ === asymmetrically_encrypts
47
+
48
+ asymmetrically_encrypts :password
49
+
50
+ This is a shortcut for using an asymmetrical algorithm with a private/public key file. To use this, generate a public and
51
+ private key with Sentry::AsymmetricalSentry.save_random_rsa_key(private_key_file, public_key_file). If you want to encrypt the
52
+ private key file with a symmetrical algorithm, pass a secret key (neither the key nor the decrypted value will be stored).
53
+
54
+ Sentry::AsymmetricSentry.save_random_rsa_key(private_key_file, public_key_file, :key => 'secret_password')
55
+
56
+ What that does, is requires you to pass in that same secret password when accesing the method.
57
+
58
+ class Model < ActiveRecord::Base
59
+ generates_crypted :password, :mode => :asymmetric
60
+ end
61
+
62
+ model.password = '5234523453425'
63
+ model.save # password is encrypted and saved to crypted_password in the database,
64
+ # model.password is cleared and becomes a virtual field.
65
+ model.password('secret_password')
66
+ => '5234523453425'
67
+
68
+ The public and private key file names can be set in config/environment.rb
69
+
70
+ Sentry::AsymmetricSentry.default_public_key_file = "#{RAILS_ROOT}/config/public.key"
71
+ Sentry::AsymmetricSentry.default_private_key_file = "#{RAILS_ROOT}/config/private.key"
72
+
73
+ If the private key was encrypted with the Sentry::AsymmetricalSentry#save_random_rsa_key, you must provide that same key
74
+ when accessing the AR model.
75
+
76
+ === symmetrically_encrypts
77
+
78
+ symmetrically_encrypts :password
79
+
80
+ This is a shortcut for using a symmetrical algorithm with a secret password to encrypt the field.
81
+
82
+ class Model < ActiveRecord::Base
83
+ generates_crypted :password, :mode => :symmetric
84
+ end
85
+
86
+ model.password = '5234523453425'
87
+ model.save # password is encrypted and saved to crypted_password in the database,
88
+ # model.password is cleared and becomes a virtual field.
89
+ model.password
90
+ => '5234523453425'
91
+
92
+ The secret password can be set in config/environment.rb
93
+
94
+ Sentry::SymmetricSentry.default_key = "secret_password"
@@ -0,0 +1,42 @@
1
+ == Creating the test database
2
+
3
+ The default name for the test databases is "sentry_plugin_test". If you
4
+ want to use another database name then be sure to update the connection
5
+ adapter setups you want to test with in test/database.yml.
6
+
7
+ Make sure that you create database objects with the same user that you specified in i
8
+ database.yml otherwise (on Postgres, at least) tests for default values will fail.
9
+
10
+ == Running with Rake
11
+
12
+ The easiest way to run the unit tests is through Rake. The default task runs
13
+ the entire test suite for the sqlite adapter. You can also run the suite on just
14
+ one adapter by passing the DB environment variable.
15
+
16
+ rake test DB=mysql
17
+
18
+ For more information, checkout the full array of rake tasks with "rake -T"
19
+
20
+ Rake can be found at http://rake.rubyforge.org
21
+
22
+ == Running by hand
23
+
24
+ Unit tests are located in test directory. If you only want to run a single test suite,
25
+ or don't want to bother with Rake, you can do so with something like:
26
+
27
+ cd test; DB=mysql ruby base_test.rb
28
+
29
+ That'll run the base suite using the MySQL adapter. Change the adapter
30
+ and test suite name as needed.
31
+
32
+ == Faster tests
33
+
34
+ If you are using a database that supports transactions, you can set the
35
+ "AR_TX_FIXTURES" environment variable to "yes" to use transactional fixtures.
36
+ This gives a very large speed boost. With rake:
37
+
38
+ rake AR_TX_FIXTURES=yes
39
+
40
+ Or, by hand:
41
+
42
+ AR_TX_FIXTURES=yes ruby -I connections/native_sqlite3 base_test.rb
data/Rakefile ADDED
@@ -0,0 +1,192 @@
1
+ require 'rubygems'
2
+
3
+ Gem::manage_gems
4
+
5
+ require 'rake/rdoctask'
6
+ require 'rake/packagetask'
7
+ require 'rake/gempackagetask'
8
+ require 'rake/testtask'
9
+ require 'rake/contrib/rubyforgepublisher'
10
+
11
+ PKG_NAME = 'sentry'
12
+ PKG_VERSION = '0.3.1'
13
+ PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
14
+ PROD_HOST = "technoweenie@bidwell.textdrive.com"
15
+ RUBY_FORGE_PROJECT = 'sentry'
16
+ RUBY_FORGE_USER = 'technoweenie'
17
+
18
+ task :default => [:test]
19
+ Rake::TestTask.new("test") do |t|
20
+ t.libs << "test"
21
+ t.pattern = "test/*_test.rb"
22
+ t.verbose = true
23
+ end
24
+
25
+ load 'tasks/sentry.rake'
26
+
27
+ Rake::RDocTask.new do |rdoc|
28
+ rdoc.rdoc_dir = 'doc'
29
+ rdoc.title = "#{PKG_NAME} -- painless encryption for Active Record"
30
+ rdoc.options << '--line-numbers --inline-source --accessor cattr_accessor=object'
31
+ rdoc.template = "#{ENV['template']}.rb" if ENV['template']
32
+ rdoc.rdoc_files.include('README', 'CHANGELOG', 'RUNNING_UNIT_TESTS')
33
+ rdoc.rdoc_files.include('lib/**/*.rb')
34
+ end
35
+
36
+ spec = Gem::Specification.new do |s|
37
+ s.name = PKG_NAME
38
+ s.version = PKG_VERSION
39
+ s.platform = Gem::Platform::RUBY
40
+ s.summary = "Sentry provides painless encryption services with a wrapper around some OpenSSL classes"
41
+ s.files = FileList["{lib,test}/**/*"].to_a + %w(README MIT-LICENSE CHANGELOG RUNNING_UNIT_TESTS)
42
+ s.files.delete "test/sentry_plugin.sqlite.db"
43
+ s.files.delete "test/sentry_plugin.sqlite3.db"
44
+ s.require_path = 'lib'
45
+ s.autorequire = 'sentry'
46
+ s.has_rdoc = true
47
+ s.test_file = 'test/tests.rb'
48
+ s.author = "Rick Olson"
49
+ s.email = "technoweenie@gmail.com"
50
+ s.homepage = "http://techno-weenie.net"
51
+ end
52
+
53
+ Rake::GemPackageTask.new(spec) do |pkg|
54
+ pkg.need_tar = true
55
+ end
56
+
57
+ desc "Publish the API documentation"
58
+ task :pdoc => [:rdoc] do
59
+ Rake::RubyForgePublisher.new(RUBY_FORGE_PROJECT, RUBY_FORGE_USER).upload
60
+ end
61
+
62
+ desc 'Publish the gem and API docs'
63
+ task :publish => [:pdoc, :rubyforge_upload]
64
+
65
+ desc "Publish the release files to RubyForge."
66
+ task :rubyforge_upload => :package do
67
+ files = %w(gem tgz).map { |ext| "pkg/#{PKG_FILE_NAME}.#{ext}" }
68
+
69
+ if RUBY_FORGE_PROJECT then
70
+ require 'net/http'
71
+ require 'open-uri'
72
+
73
+ project_uri = "http://rubyforge.org/projects/#{RUBY_FORGE_PROJECT}/"
74
+ project_data = open(project_uri) { |data| data.read }
75
+ group_id = project_data[/[?&]group_id=(\d+)/, 1]
76
+ raise "Couldn't get group id" unless group_id
77
+
78
+ # This echos password to shell which is a bit sucky
79
+ if ENV["RUBY_FORGE_PASSWORD"]
80
+ password = ENV["RUBY_FORGE_PASSWORD"]
81
+ else
82
+ print "#{RUBY_FORGE_USER}@rubyforge.org's password: "
83
+ password = STDIN.gets.chomp
84
+ end
85
+
86
+ login_response = Net::HTTP.start("rubyforge.org", 80) do |http|
87
+ data = [
88
+ "login=1",
89
+ "form_loginname=#{RUBY_FORGE_USER}",
90
+ "form_pw=#{password}"
91
+ ].join("&")
92
+ http.post("/account/login.php", data)
93
+ end
94
+
95
+ cookie = login_response["set-cookie"]
96
+ raise "Login failed" unless cookie
97
+ headers = { "Cookie" => cookie }
98
+
99
+ release_uri = "http://rubyforge.org/frs/admin/?group_id=#{group_id}"
100
+ release_data = open(release_uri, headers) { |data| data.read }
101
+ package_id = release_data[/[?&]package_id=(\d+)/, 1]
102
+ raise "Couldn't get package id" unless package_id
103
+
104
+ first_file = true
105
+ release_id = ""
106
+
107
+ files.each do |filename|
108
+ basename = File.basename(filename)
109
+ file_ext = File.extname(filename)
110
+ file_data = File.open(filename, "rb") { |file| file.read }
111
+
112
+ puts "Releasing #{basename}..."
113
+
114
+ release_response = Net::HTTP.start("rubyforge.org", 80) do |http|
115
+ release_date = Time.now.strftime("%Y-%m-%d %H:%M")
116
+ type_map = {
117
+ ".zip" => "3000",
118
+ ".tgz" => "3110",
119
+ ".gz" => "3110",
120
+ ".gem" => "1400"
121
+ }; type_map.default = "9999"
122
+ type = type_map[file_ext]
123
+ boundary = "rubyqMY6QN9bp6e4kS21H4y0zxcvoor"
124
+
125
+ query_hash = if first_file then
126
+ {
127
+ "group_id" => group_id,
128
+ "package_id" => package_id,
129
+ "release_name" => PKG_FILE_NAME,
130
+ "release_date" => release_date,
131
+ "type_id" => type,
132
+ "processor_id" => "8000", # Any
133
+ "release_notes" => "",
134
+ "release_changes" => "",
135
+ "preformatted" => "1",
136
+ "submit" => "1"
137
+ }
138
+ else
139
+ {
140
+ "group_id" => group_id,
141
+ "release_id" => release_id,
142
+ "package_id" => package_id,
143
+ "step2" => "1",
144
+ "type_id" => type,
145
+ "processor_id" => "8000", # Any
146
+ "submit" => "Add This File"
147
+ }
148
+ end
149
+
150
+ query = "?" + query_hash.map do |(name, value)|
151
+ [name, URI.encode(value)].join("=")
152
+ end.join("&")
153
+
154
+ data = [
155
+ "--" + boundary,
156
+ "Content-Disposition: form-data; name=\"userfile\"; filename=\"#{basename}\"",
157
+ "Content-Type: application/octet-stream",
158
+ "Content-Transfer-Encoding: binary",
159
+ "", file_data, ""
160
+ ].join("\x0D\x0A")
161
+
162
+ release_headers = headers.merge(
163
+ "Content-Type" => "multipart/form-data; boundary=#{boundary}"
164
+ )
165
+
166
+ target = first_file ? "/frs/admin/qrs.php" : "/frs/admin/editrelease.php"
167
+ http.post(target + query, data, release_headers)
168
+ end
169
+
170
+ if first_file then
171
+ release_id = release_response.body[/release_id=(\d+)/, 1]
172
+ raise("Couldn't get release id") unless release_id
173
+ end
174
+
175
+ first_file = false
176
+ end
177
+ end
178
+ end
179
+
180
+ begin
181
+ require 'jeweler'
182
+ Jeweler::Tasks.new do |gemspec|
183
+ gemspec.name = "sentry"
184
+ gemspec.summary = "Asymmetric encryption of active record fields"
185
+ gemspec.description = "Asymmetric encryption of active record fields"
186
+ gemspec.email = "commoncode@pivotallabs.com"
187
+ gemspec.homepage = "http://github.com/pivotal/sentry"
188
+ gemspec.authors = ["John Pelly", "David Stevenson"]
189
+ end
190
+ rescue LoadError
191
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
192
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.4.0
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'sentry'
@@ -0,0 +1,94 @@
1
+ module ActiveRecord # :nodoc:
2
+ module Sentry
3
+ def self.included(base) # :nodoc:
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+ def generates_crypted(attr_name, options = {})
9
+ mode = options[:mode] || :asymmetric
10
+ case mode
11
+ #when :sha
12
+ # generates_crypted_hash_of(attr_name)
13
+ when :asymmetric, :asymmetrical
14
+ asymmetrically_encrypts(attr_name)
15
+ #when :symmetric, :symmetrical
16
+ # symmetrically_encrypts(attr_name)
17
+ end
18
+ end
19
+
20
+ #def generates_crypted_hash_of(attribute)
21
+ # before_validation ::Sentry::ShaSentry.new(attribute)
22
+ # attr_accessor attribute
23
+ #end
24
+
25
+ def asymmetrically_encrypts(attr_name, options = {})
26
+ #temp_sentry = ::Sentry::AsymmetricSentryCallback.new(attr_name)
27
+ #before_validation temp_sentry
28
+ #after_save temp_sentry
29
+ unless instance_methods.include?("#{attr_name}_with_decryption")
30
+ define_read_methods
31
+
32
+ define_method("#{attr_name}_with_decryption") do |*optional|
33
+ begin
34
+ return decrypted_values[attr_name] unless decrypted_values[attr_name].nil?
35
+ crypted_value = self.send("#{attr_name}_without_decryption")
36
+ return nil if crypted_value.nil?
37
+ key = optional.shift || (options[:key].is_a?(Proc) ? options[:key].call : options[:key])
38
+ decrypted_values[attr_name] = ::Sentry::AsymmetricSentry.decrypt_from_base64(crypted_value, key)
39
+ return decrypted_values[attr_name]
40
+ rescue
41
+ nil
42
+ end
43
+ end
44
+
45
+ alias_method_chain attr_name, :decryption
46
+ alias_method "crypted_#{attr_name}", "#{attr_name}_without_decryption"
47
+ alias_method "#{attr_name}_before_type_cast", "#{attr_name}_with_decryption"
48
+
49
+ define_method("#{attr_name}_with_encryption=") do |value|
50
+ decrypted_values[attr_name] = value
51
+ self.send("#{attr_name}_without_encryption=", ::Sentry::SymmetricSentry.encrypt_to_base64(decrypted_values[attr_name]))
52
+ nil
53
+ end
54
+
55
+ alias_method_chain "#{attr_name}=", :encryption
56
+ #define_method("crypted_#{attr_name}") do
57
+ # self.attributes[attr_name.to_s]
58
+ #end
59
+ #
60
+ private
61
+ define_method(:decrypted_values) do
62
+ @decrypted_values ||= {}
63
+ end
64
+ end
65
+ end
66
+
67
+ #def symmetrically_encrypts(attr_name)
68
+ # temp_sentry = ::Sentry::SymmetricSentryCallback.new(attr_name)
69
+ # before_validation temp_sentry
70
+ # after_save temp_sentry
71
+ #
72
+ # define_method(attr_name) do
73
+ # send("#{attr_name}!") rescue nil
74
+ # end
75
+ #
76
+ # define_method("#{attr_name}!") do
77
+ # return decrypted_values[attr_name] unless decrypted_values[attr_name].nil?
78
+ # return nil if send("crypted_#{attr_name}").nil?
79
+ # ::Sentry::SymmetricSentry.decrypt_from_base64(send("crypted_#{attr_name}"))
80
+ # end
81
+ #
82
+ # define_method("#{attr_name}=") do |value|
83
+ # decrypted_values[attr_name] = value
84
+ # nil
85
+ # end
86
+ #
87
+ # private
88
+ # define_method(:decrypted_values) do
89
+ # @decrypted_values ||= {}
90
+ # end
91
+ #end
92
+ end
93
+ end
94
+ end