rs_yettings 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/README.md +186 -0
  2. data/Rakefile +38 -0
  3. data/lib/tasks/yettings.rake +17 -0
  4. data/lib/yettings.rb +79 -0
  5. data/lib/yettings/base.rb +41 -0
  6. data/lib/yettings/encryption.rb +124 -0
  7. data/lib/yettings/railtie.rb +15 -0
  8. data/lib/yettings/version.rb +3 -0
  9. data/spec/dummy/Guardfile +228 -0
  10. data/spec/dummy/README.rdoc +261 -0
  11. data/spec/dummy/Rakefile +7 -0
  12. data/spec/dummy/app/assets/javascripts/application.js +15 -0
  13. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  14. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  15. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  16. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  17. data/spec/dummy/config.ru +4 -0
  18. data/spec/dummy/config/application.rb +59 -0
  19. data/spec/dummy/config/boot.rb +10 -0
  20. data/spec/dummy/config/database.yml +25 -0
  21. data/spec/dummy/config/environment.rb +5 -0
  22. data/spec/dummy/config/environments/development.rb +37 -0
  23. data/spec/dummy/config/environments/production.rb +67 -0
  24. data/spec/dummy/config/environments/test.rb +37 -0
  25. data/spec/dummy/config/ignored.yml +17 -0
  26. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  27. data/spec/dummy/config/initializers/inflections.rb +15 -0
  28. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  29. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  30. data/spec/dummy/config/initializers/session_store.rb +8 -0
  31. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  32. data/spec/dummy/config/locales/en.yml +5 -0
  33. data/spec/dummy/config/routes.rb +58 -0
  34. data/spec/dummy/config/yetting.yml +17 -0
  35. data/spec/dummy/config/yetting.yml.pub +0 -0
  36. data/spec/dummy/config/yettings/blank.yml +0 -0
  37. data/spec/dummy/config/yettings/defaults.yml +6 -0
  38. data/spec/dummy/config/yettings/hendrix.yml +11 -0
  39. data/spec/dummy/config/yettings/jimi.yml +11 -0
  40. data/spec/dummy/config/yettings/secret.yml.pub +1 -0
  41. data/spec/dummy/db/test.sqlite3 +0 -0
  42. data/spec/dummy/log/development.log +1 -0
  43. data/spec/dummy/log/test.log +141 -0
  44. data/spec/dummy/public/404.html +26 -0
  45. data/spec/dummy/public/422.html +26 -0
  46. data/spec/dummy/public/500.html +25 -0
  47. data/spec/dummy/public/favicon.ico +0 -0
  48. data/spec/dummy/script/rails +6 -0
  49. data/spec/spec_helper.rb +28 -0
  50. data/spec/yettings/base_spec.rb +66 -0
  51. data/spec/yettings/encryption_spec.rb +104 -0
  52. data/spec/yettings_spec.rb +90 -0
  53. metadata +320 -0
data/README.md ADDED
@@ -0,0 +1,186 @@
1
+ # yettings
2
+
3
+ YAML settings for your Rails 3 app.
4
+
5
+ ## What does it do?
6
+
7
+ Yettings allows you to add a yml file to your "config" directory and you can access the values defined in the YAML in your Rails app. You can
8
+ use this to store API keys, constants, and other key/value pairs. This plugin was heavily inspired by settingslogic, with a few differences... You don't
9
+ have to add a class and point to the YML file. The Yetting class will be created dynamically and will be available to your Rails app. This plugin is also
10
+ more basic than settingslogic. It does not have support for dynamic setting creation... only the values in the yetting.yml will be available.
11
+
12
+
13
+ ## This project only supports Rails 3 and Ruby >= 1.9.2
14
+
15
+ There is a branch for 1.8.7, but it has not been merged into master. If you want to use it, you can reference the github location and branch in your Gemfile. See the issue tracker for more details
16
+
17
+ ## Known bug in YAML psych parser (Ruby < 1.9.2-p271)
18
+ This bug can cause issues loading the YAML keys when using Yettings. The workaround is to set your YAML parser to sych if your environment is currently using psych:
19
+
20
+ YAML::ENGINE.yamler = "syck"
21
+
22
+ More info here: http://pivotallabs.com/users/mkocher/blog/articles/1692-yaml-psych-and-ruby-1-9-2-p180-here-there-be-dragons
23
+
24
+ This issue is fixed in ruby-1.9.2-p271.
25
+
26
+ ## Usage
27
+
28
+ ###Install the gem
29
+
30
+ Add this to your Gemfile
31
+ gem "yettings"
32
+
33
+ Install with Bundler
34
+ bundle install
35
+
36
+ ###Adding the YAML file with your key/value pairs
37
+
38
+ 1. Create a YAML file inside /your_rails_app/config called yetting.yml
39
+ 2. If you want to namespace your Yettings, create a YAML file inside /your_rails_app/config/yettings/ and call it whatever you want.
40
+
41
+ ###YAML file content
42
+ You can define key/value pairs in the YAML file and these will be available in your app. You can set the defaults and any environment specific values.
43
+ The file must contain each environment that you will use in your Rails app. Here is a sample:
44
+
45
+ defaults: &defaults
46
+ api_key: asdf12345lkj
47
+ some_number: 999
48
+ an_erb_yetting: <%= "erb stuff works" %>
49
+ some_array:
50
+ - element1
51
+ - element2
52
+
53
+ development:
54
+ <<: *defaults
55
+ api_key: api key for dev
56
+
57
+ test:
58
+ <<: *defaults
59
+
60
+ production:
61
+ <<: *defaults
62
+
63
+ In the above example, you can define the key/value pair using strings, numbers, erb code, or arrays. Notice that the "api_key" in the development
64
+ environment will override the "api_key" from defaults.
65
+
66
+
67
+ ###Accessing the values in your Rails app
68
+
69
+ You simply call the Yetting class or the namespaced class and the key as a class method. For namespaced yml files, Yettings will convert the filename in
70
+ /your_rails_app/config/yettings/ to a class name and append Yetting. So if you have main.yml, then it will use MainYetting as the class name.
71
+ Then you can call the key that you put in the YAML as a class method. Here are 2 examples:
72
+
73
+ #/your_rails_app/config/yetting.yml in production
74
+ Yetting.some_number #=> 999
75
+ Yetting.api_key #=> "asdf12345lkj"
76
+
77
+ #/your_rails_app/config/yettings/main.yml
78
+ MainYetting.some_number #=> 999
79
+ MainYetting.some_array #=> ["element1","element2"]
80
+
81
+
82
+ ###Default settings
83
+ The above YAML content explicitly specifies settings for each environment using
84
+ YAML splats. In case you'd rather not write all those out, settings in the
85
+ 'defaults' section will be used to populate each environment. So, the above file
86
+ could be written as
87
+
88
+ defaults:
89
+ api_key: asdf12345lkj
90
+ some_number: 999
91
+ an_erb_yetting: <%= "erb stuff works" %>
92
+ some_array:
93
+ - element1
94
+ - element2
95
+
96
+ development:
97
+ api_key: api key for dev
98
+
99
+
100
+ ## Encryption
101
+
102
+ You may be tempted to store sensitive information in your settings
103
+ files. For example, you may have places in your app where you use secret API
104
+ keys, passwords or other credentials that are a liability to keep in revision
105
+ control.
106
+
107
+ With version 0.2.0, Yettings supports encrypted values. To use this feature,
108
+ you'll need to generate a public/private keypair:
109
+
110
+ rake yettings:gen_keys
111
+
112
+ Now you'll find the following files and directories in your project:
113
+
114
+ config/yettings/.private_key
115
+ config/yettings/.public_key
116
+ config/yettings/.private/
117
+
118
+ You'll also automatically have the private key and private directory added to
119
+ the .gitignore file to avoid checking these into git. Now create a file in the
120
+ .private directory, let's call it secret.yml, and put something in there you
121
+ don't ever want anyone else to know about:
122
+
123
+ # config/yettings/.private/secret.yml
124
+ defaults:
125
+ guilty_pleasure: singing into hairbrush
126
+
127
+ Whenever you run rails, you'll see a warning like this:
128
+
129
+ $ ./bin/rails c
130
+ WARNING: overwriting config/yettings/secret.yml.pub with contents of config/yettings/.private/secret.yml
131
+
132
+ Take a look at the secret.yml.pub file in that location and you'll see the contents are now encrypted:
133
+
134
+ # config/yettings/secret.yml.pub
135
+ ---
136
+ defaults:
137
+ guilty_pleasure: !binary |-
138
+ YSgVHF+rhhBSRRaHIyOZwkd99ovrTvnfvsEdXjUXmbm2RdZTkBLzP+Ha275r
139
+ gAwfY2P7AtkluGQmEpmr6f1C9XLI6hs3AHpkIE4OSJmOQAD2AU8lmw6oOg1j
140
+ SJBX7F+v1i8WS+rhxF3y5uNtAh+Fv4w+N/d9w6iDed0wywLq1e3jXjbQv8KL
141
+ rCf9FRpW2WTUYa+tntalaAQkNcp2Es3bWODfkZYOnsMm2POi5mtaCQR0/O8E
142
+ 1k1sToOqvt/vL1g24NeSTGXLndqo1pRkdhREkj7TiY6fFj3CXQtk+4JTJGGs
143
+ bex7be+v9eEk5rJc7gu6uq1F9ymuWx+LUNHczppw4g==
144
+
145
+ Check this into git, then edit your private file again:
146
+
147
+ # config/yettings/.private/secret.yml
148
+ defaults:
149
+ guilty_pleasure: singing into hairbrush
150
+ specifically: '"Only in My Dreams" by Debbie Gibson'
151
+
152
+ Next time you run rails, your public file will be appended to, and you'll see
153
+ which key was updated when you commit to git again. This will help you if you
154
+ ever need to roll back your credentials.
155
+
156
+ diff --git a/test_app/config/yettings/secret.yml.pub b/test_app/config/yettings/
157
+ index 3c0e462..8d9555d 100644
158
+ --- a/test_app/config/yettings/secret.yml.pub
159
+ +++ b/test_app/config/yettings/secret.yml.pub
160
+ @@ -7,3 +7,10 @@ defaults:
161
+ rCf9FRpW2WTUYa+tntalaAQkNcp2Es3bWODfkZYOnsMm2POi5mtaCQR0/O8E
162
+ 1k1sToOqvt/vL1g24NeSTGXLndqo1pRkdhREkj7TiY6fFj3CXQtk+4JTJGGs
163
+ bex7be+v9eEk5rJc7gu6uq1F9ymuWx+LUNHczppw4g==
164
+ + specifically: !binary |-
165
+ + A5M0/A3AbzkJaIXP3Ehtx0jPQtq2p8Y4SOqWN6OobkStjwSB6t9oHPDxA/jB
166
+ + mnzkyH6Hwsq0MMQjvJrRoDDNU7lTPnVSxGkwkHBD37I09X9JEim0qTC4M1Q3
167
+ + /VQEHtdEF8NfG9ZJs1b3iQNzu1CA2KNzUywmVpMJuwDNx4mSGdqpE37EeWMZ
168
+ + iuY/F+nfi6pJYktlmuis2uy8IDrAdEQ7k0x2i3dGs9KotiNAmCkRvq5jwH9a
169
+ + FXf30fXLX2cjWHQd3Ru3XurSOiN4LoYvAQBdgLyfX2ipapY8W+vcP8RmDBQR
170
+ + vu9miVub5T1xndclETuL97JTO6Yg8PU98Pv42289GQ==
171
+
172
+
173
+ ## Contributing to yettings
174
+
175
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
176
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
177
+ * Fork the project
178
+ * Start a feature/bugfix branch
179
+ * Commit and push until you are happy with your contribution
180
+ * Make sure to add tests for it. I will not even look at patches without a test included.
181
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
182
+
183
+ ## Copyright
184
+
185
+ Copyright (c) 2011 mc-2
186
+
data/Rakefile ADDED
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'Yettings'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+
24
+
25
+
26
+ Bundler::GemHelper.install_tasks
27
+
28
+ require 'rake/testtask'
29
+
30
+ Rake::TestTask.new(:test) do |t|
31
+ t.libs << 'lib'
32
+ t.libs << 'test'
33
+ t.pattern = 'test/**/*_test.rb'
34
+ t.verbose = false
35
+ end
36
+
37
+
38
+ task :default => :test
@@ -0,0 +1,17 @@
1
+ namespace :yettings do
2
+ desc "generate public/private key pair for encrypting sensitive yetting files"
3
+ task :gen_keys => :environment do
4
+ Yettings.gen_keys
5
+ end
6
+
7
+ desc "encrypt sensitive yetting files for checking into revision control"
8
+ task :encrypt => :environment do
9
+ Yettings.encrypt_files!
10
+ end
11
+
12
+ desc "decrypt sensitive yetting files for editing"
13
+ task :decrypt => :environment do
14
+ Yettings.decrypt_files!
15
+ end
16
+ end
17
+
data/lib/yettings.rb ADDED
@@ -0,0 +1,79 @@
1
+ require 'yaml'
2
+ require 'erb'
3
+ require 'openssl'
4
+ require "yettings/railtie.rb"
5
+ require "yettings/base.rb"
6
+ require "yettings/encryption.rb"
7
+
8
+ module Yettings
9
+ include Yettings::Encryption
10
+
11
+ class UndefinedYettingError < StandardError; end
12
+ class NameConflictError < StandardError; end
13
+
14
+ class << self
15
+ def setup!
16
+ EncryptedStrings::SymmetricCipher.default_algorithm = 'DES-EDE3-CBC'
17
+ find_yml_files.each do |yml_file|
18
+ create_yetting_class yml_file
19
+ end
20
+ end
21
+
22
+ def create_yetting_class(yml_file)
23
+ name = klass_name(yml_file)
24
+ klass = Object.const_set(name, Class.new(Yettings::Base))
25
+ klass.load_yml_erb yaml_erb(yml_file)
26
+ end
27
+
28
+ def yaml_erb(yml_file)
29
+ yaml_erb = File.read(yml_file)
30
+ yaml_erb = decrypt_string(yaml_erb) if pub?(yml_file)
31
+ yaml_erb
32
+ end
33
+
34
+ def pub?(yml_file)
35
+ yml_file.end_with? ".pub"
36
+ end
37
+
38
+ def klass_name(yml_file)
39
+ basename = File.basename(yml_file)
40
+ if basename == "yetting.yml"
41
+ name = "Yetting"
42
+ else
43
+ name = basename.gsub(/\.pub$/,"").gsub(/\.yml$/,"").camelize + "Yetting"
44
+ end
45
+ return name unless Object.const_defined?(name)
46
+ if name.constantize.ancestors.include? Yettings::Base
47
+ Object.module_eval { remove_const name }
48
+ return name
49
+ else
50
+ raise NameConflictError, "#{name} is already defined"
51
+ end
52
+ end
53
+
54
+ def rails_config
55
+ "#{Rails.root}/config"
56
+ end
57
+
58
+ def root
59
+ "#{rails_config}/yettings"
60
+ end
61
+
62
+ def private_root
63
+ "#{root}/\.private"
64
+ end
65
+
66
+ def find_yml_files
67
+ Dir.glob("#{rails_config}/yetting.yml") + Dir.glob("#{root}/**/*.yml") + Dir.glob("#{root}/**/*.yml.pub")
68
+ end
69
+
70
+ def find_public_yml_files
71
+ Dir.glob("#{root}/**/*.yml.pub")
72
+ end
73
+
74
+ def find_private_yml_files
75
+ Dir.glob("#{private_root}/**/*.yml")
76
+ end
77
+ end # class << self
78
+ end
79
+
@@ -0,0 +1,41 @@
1
+ require 'yaml'
2
+ require 'erb'
3
+ require 'openssl'
4
+
5
+ class Yettings::Base
6
+ class UndefinedYettingError < StandardError; end
7
+
8
+ class << self
9
+ def method_missing(method_id, *args)
10
+ raise UndefinedYettingError, "#{method_id} is not defined in #{self}"
11
+ end
12
+
13
+ def [](key)
14
+ send key
15
+ end
16
+
17
+ def load_yml_erb(yml_erb)
18
+ yml = ERB.new(yml_erb).result
19
+ full_hash = build_hash yml
20
+ defaults = full_hash.delete('defaults') || {}
21
+ env_hash = full_hash[Rails.env] || {}
22
+ define_methods defaults.merge(env_hash)
23
+ end
24
+
25
+ def define_methods(hash)
26
+ hash.each do |key, value|
27
+ class_attribute key
28
+ if value.is_a? Hash
29
+ send "#{key}=", Class.new(Yettings::Base)
30
+ send(key).define_methods(value)
31
+ else
32
+ define_singleton_method(key) { value }
33
+ end
34
+ end
35
+ end
36
+
37
+ def build_hash(yml)
38
+ yml.present? ? YAML.load(yml).to_hash : {}
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,124 @@
1
+ require 'base64'
2
+
3
+ module Yettings
4
+ module Encryption
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ def decrypt_string(encrypted)
9
+ if key_and_iv_exists?
10
+ decipher = OpenSSL::Cipher::AES.new(256, :CBC)
11
+ decipher.decrypt
12
+ decipher.key = key
13
+ decipher.iv = iv
14
+ encrypted = Base64.strict_decode64(encrypted)
15
+ plain = decipher.update(encrypted) + decipher.final
16
+ else
17
+ "access denied (no key file and/or IV file found)"
18
+ end
19
+ end
20
+
21
+ def encrypt_string(data)
22
+ cipher = OpenSSL::Cipher::AES.new(256, :CBC)
23
+ cipher.encrypt
24
+ cipher.key = key
25
+ cipher.iv = iv
26
+ encrypted = cipher.update(data) + cipher.final
27
+ encrypted = Base64.strict_encode64(encrypted)
28
+ end
29
+
30
+ def encrypt_file(private_file)
31
+ return unless key_and_iv_exists? # Don't overwrite encrypted file without key
32
+ public_file = public_path(private_file)
33
+ public_yml = encrypt_string File.read(private_file)
34
+ return unless check_overwrite(public_file, private_file, public_yml)
35
+ File.open(public_file, 'w') { |f| f.write public_yml }
36
+ end
37
+
38
+ def encrypt_files!
39
+ find_private_yml_files.each do |yml_file|
40
+ encrypt_file yml_file
41
+ end
42
+ end
43
+
44
+ def decrypt_file(public_file)
45
+ private_file = private_path(public_file)
46
+ private_yml = decrypt_string File.read(public_file)
47
+ return unless check_overwrite(private_file, public_file, private_yml)
48
+ File.open(private_file, 'w') { |f| f.write private_yml }
49
+ end
50
+
51
+ def decrypt_files!
52
+ find_public_yml_files.each do |yml_file|
53
+ decrypt_file yml_file
54
+ end
55
+ end
56
+
57
+ def private_path(path)
58
+ path.gsub(/^#{root}/, "#{private_root}").gsub(/.pub$/, "")
59
+ end
60
+
61
+ def public_path(path)
62
+ path.gsub(/^#{private_root}/, root) + '.pub'
63
+ end
64
+
65
+ def check_overwrite(dest, source, content)
66
+ unless File.exists?(dest)
67
+ STDERR.puts "WARNING: creating #{dest} with contents of #{source}"
68
+ FileUtils.mkpath File.dirname(dest)
69
+ return true
70
+ end
71
+ return false if File.read(dest) == content
72
+ if File.mtime(source) > File.mtime(dest)
73
+ STDERR.puts "WARNING: overwriting #{dest} with contents of #{source}"
74
+ true
75
+ else
76
+ false
77
+ end
78
+ end
79
+
80
+ def key_and_iv_exists?
81
+ File.exists?(key_path) && File.exists?(iv_path)
82
+ end
83
+
84
+ def key_path
85
+ ENV["YETTINGS_KEY"] || "#{root}/.key"
86
+ end
87
+
88
+ def iv_path
89
+ ENV["YETTINGS_IV"] || "#{root}/.iv"
90
+ end
91
+
92
+ def key
93
+ file = File.open(key_path, "rb")
94
+ b64_key = file.read
95
+ file.close
96
+ key = Base64.strict_decode64(b64_key)
97
+ end
98
+
99
+ def iv
100
+ file = File.open(iv_path, "rb")
101
+ b64_iv = file.read
102
+ file.close
103
+ iv = Base64.strict_decode64(b64_iv)
104
+ end
105
+
106
+ def gen_keys
107
+ cipher = OpenSSL::Cipher::AES.new(256, :CBC)
108
+ key = cipher.random_key
109
+ b64_key = Base64.strict_encode64(key)
110
+ iv = cipher.random_iv
111
+ b64_iv = Base64.strict_encode64(iv)
112
+
113
+ private_path = "#{root}/.private"
114
+ FileUtils.mkpath private_path
115
+
116
+ key_file = "#{root}/.key"
117
+ File.open(key_file, 'w') { |f| f.write b64_key }
118
+
119
+ iv_file = "#{root}/.iv"
120
+ File.open(iv_file, 'w') { |f| f.write b64_iv }
121
+ end
122
+ end
123
+ end
124
+ end