configliere 0.0.1

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.
@@ -0,0 +1,72 @@
1
+ require 'openssl'
2
+ require 'digest/sha2'
3
+ module Configliere
4
+ #
5
+ # Encrypt and decrypt values in configliere stores
6
+ #
7
+ module Crypter
8
+ CIPHER_TYPE = "aes-256-cbc" unless defined?(CIPHER_TYPE)
9
+
10
+ #
11
+ # Encrypt the given string
12
+ #
13
+ # @param plaintext the text to encrypt
14
+ # @param [String] encrypt_pass secret passphrase to encrypt with
15
+ # @return [String] encrypted text, suitable for deciphering with Crypter#decrypt
16
+ #
17
+ def self.encrypt plaintext, encrypt_pass, options={}
18
+ # The cipher's IV (Initialization Vector) is prepended (unencrypted) to
19
+ # the ciphertext, which as far as I can tell is safe for our purposes:
20
+ # http://www.ciphersbyritter.com/NEWS6/CBCIV.HTM
21
+ cipher = new_cipher :encrypt, encrypt_pass, options
22
+ cipher.iv = iv = cipher.random_iv
23
+ ciphertext = cipher.update(plaintext)
24
+ ciphertext << cipher.final
25
+ combine_iv_and_ciphertext(iv, ciphertext)
26
+ end
27
+ #
28
+ # Decrypt the given string, using the key and iv supplied
29
+ #
30
+ # @param ciphertext the text to decrypt, probably produced with Crypter#decrypt
31
+ # @param [String] encrypt_pass secret passphrase to decrypt with
32
+ # @return [String] the decrypted plaintext
33
+ #
34
+ def self.decrypt iv_and_ciphertext, encrypt_pass, options={}
35
+ cipher = new_cipher :decrypt, encrypt_pass, options
36
+ cipher.iv, ciphertext = separate_iv_and_ciphertext(cipher, iv_and_ciphertext)
37
+ plaintext = cipher.update(ciphertext)
38
+ plaintext << cipher.final
39
+ plaintext
40
+ end
41
+ protected
42
+ #
43
+ # Create a new cipher machine, with its dials set in the given direction
44
+ #
45
+ # @param [:encrypt, :decrypt] direction whether to encrypt or decrypt
46
+ # @param [String] encrypt_pass secret passphrase to decrypt with
47
+ #
48
+ def self.new_cipher direction, encrypt_pass, options={}
49
+ cipher = OpenSSL::Cipher::Cipher.new(CIPHER_TYPE)
50
+ case direction when :encrypt then cipher.encrypt when :decrypt then cipher.decrypt else raise "Bad cipher direction #{direction}" end
51
+ cipher.key = encrypt_key(encrypt_pass, options)
52
+ cipher
53
+ end
54
+
55
+ # prepend the initialization vector to the encoded message
56
+ def self.combine_iv_and_ciphertext iv, message
57
+ iv + message
58
+ end
59
+ # pull the initialization vector from the front of the encoded message
60
+ def self.separate_iv_and_ciphertext cipher, iv_and_ciphertext
61
+ idx = cipher.iv_len
62
+ [ iv_and_ciphertext[0..(idx-1)], iv_and_ciphertext[idx..-1] ]
63
+ end
64
+
65
+ # Convert the encrypt_pass passphrase into the key used for encryption
66
+ def self.encrypt_key encrypt_pass, options={}
67
+ raise 'Blank encryption password!' if encrypt_pass.to_s == ''
68
+ # this provides the required 256 bits of key for the aes-256-cbc cipher
69
+ Digest::SHA256.digest(encrypt_pass)
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,151 @@
1
+ module Configliere
2
+ module Define
3
+ # Definitions for params: :description, :type, :encrypted, etc.
4
+ attr_accessor :param_definitions
5
+
6
+ # @param param the setting to describe. Either a simple symbol or a dotted param string.
7
+ # @param definitions the defineables to set (:description, :type, :encrypted, etc.)
8
+ #
9
+ # @example
10
+ # Settings.define :dest_time, :type => Date, :description => 'Arrival time. If only a date is given, the current time of day on that date is assumed.'
11
+ # Settings.define 'delorean.power_source', :description => 'Delorean subsytem supplying power to the Flux Capacitor.'
12
+ # Settings.define :password, :required => true, :obscure => true
13
+ #
14
+ def define param, definitions={}
15
+ self.param_definitions[param].merge! definitions
16
+ end
17
+
18
+ def param_definitions
19
+ # initialize the param_definitions as an auto-vivifying hash if it's never been set
20
+ @param_definitions ||= Hash.new{|hsh, key| hsh[key] = {} }
21
+ end
22
+
23
+ protected
24
+ # all params with a value for the definable aspect
25
+ #
26
+ # @param definable the aspect to list (:description, :type, :encrypted, etc.)
27
+ def params_with defineable
28
+ param_definitions.keys.find_all{|param| param_definitions[param][defineable] } || []
29
+ end
30
+
31
+ def definitions_for defineable
32
+ hsh = {}
33
+ param_definitions.each do |param, defs|
34
+ hsh[param] = defs[defineable] if defs[defineable]
35
+ end
36
+ hsh
37
+ end
38
+ public
39
+
40
+ # performs type coercion
41
+ def resolve!
42
+ resolve_types!
43
+ begin ; super() ; rescue NoMethodError ; nil ; end
44
+ self
45
+ end
46
+
47
+ def validate!
48
+ validate_requireds!
49
+ begin ; super() ; rescue NoMethodError ; nil ; end
50
+ true
51
+ end
52
+
53
+ # ===========================================================================
54
+ #
55
+ # Describe params with
56
+ #
57
+ # Settings.define :param, :description => '...'
58
+ #
59
+
60
+ # gets the description (if any) for the param
61
+ # @param param the setting to describe. Either a simple symbol or a dotted param string.
62
+ def description_for param
63
+ param_definitions[param][:description]
64
+ end
65
+
66
+ # All described params with their descriptions
67
+ def descriptions
68
+ definitions_for(:description)
69
+ end
70
+
71
+ # List of params that have descriptions
72
+ def described_params
73
+ params_with(:description)
74
+ end
75
+
76
+ # ===========================================================================
77
+ #
78
+ # Type coercion
79
+ #
80
+ # Define types with
81
+ #
82
+ # Settings.define :param, :type => Date
83
+ #
84
+
85
+ def type_for param
86
+ param_definitions[param][:type]
87
+ end
88
+
89
+ # All described params with their descriptions
90
+ def types
91
+ definitions_for(:type)
92
+ end
93
+
94
+ # List of params that have descriptions
95
+ def typed_params
96
+ params_with(:type)
97
+ end
98
+
99
+ # Coerce all params with types defined to their proper form
100
+ def resolve_types!
101
+ types.each do |param, type|
102
+ val = self[param]
103
+ case
104
+ when val.nil? then val = nil
105
+ when (type == :boolean) then
106
+ if ['false', '0', ''].include?(val.to_s) then val = false else val = true end
107
+ when (val.to_s == '') then val = nil
108
+ when (type == Float) then val = val.to_f
109
+ when (type == Integer) then val = val.to_i
110
+ when (type == Date) then val = Date.parse(val) rescue nil
111
+ when (type == DateTime) then val = DateTime.parse(val) rescue nil
112
+ when (type == Time) then
113
+ require 'time'
114
+ val = Time.parse(val) rescue nil
115
+ when (type == Symbol) then val = val.to_s.to_sym rescue nil
116
+ end
117
+ self[param] = val
118
+ end
119
+ end
120
+
121
+ # ===========================================================================
122
+ #
123
+ # Required params
124
+ #
125
+ # Define requireds with
126
+ #
127
+ # Settings.define :param, :required => true
128
+ #
129
+
130
+ # List of params that are required
131
+ # @return [Array] list of required params
132
+ def required_params
133
+ params_with(:required)
134
+ end
135
+
136
+ # Check that all required params are present.
137
+ def validate_requireds!
138
+ missing = []
139
+ required_params.each do |param|
140
+ missing << param if self[param].nil?
141
+ end
142
+ raise "Missing values for #{missing.map{|s| s.to_s }.sort.join(", ")}" if (! missing.empty?)
143
+ end
144
+
145
+ end
146
+
147
+ Param.class_eval do
148
+ include Configliere::Define
149
+ end
150
+ end
151
+
@@ -0,0 +1,78 @@
1
+ Configliere.use :param_store, :define, :crypter
2
+
3
+ module Configliere
4
+ module EncryptedParam
5
+ # The password used in encrypting params during serialization
6
+ attr_accessor :encrypt_pass
7
+
8
+ protected
9
+
10
+ # @example
11
+ # Settings.defaults :username=>"mysql_username", :password=>"mysql_password"
12
+ # Settings.define :password, :encrypted => true
13
+ # Settings.exportable
14
+ # #=> {:username => 'mysql_username', :password=>"\345?r`\222\021"\210\312\331\256\356\351\037\367\326" }
15
+ def export
16
+ hsh = super()
17
+ encrypted_params.each do |param|
18
+ val = hsh.deep_delete(*dotted_to_deep_keys(param)) or next
19
+ hsh.deep_set( *(dotted_to_encrypted_keys(param) | [encrypted(val)]) )
20
+ end
21
+ hsh
22
+ end
23
+
24
+ # decrypts any encrypted params
25
+ # then calls the next step in the resolve! chain.
26
+ def resolve!
27
+ resolve_encrypted!
28
+ begin ; super() ; rescue NoMethodError ; nil ; end
29
+ self
30
+ end
31
+
32
+ # import values, decrypting all params marked as encrypted
33
+ def resolve_encrypted!
34
+ remove_and_adopt_encrypt_pass_param_if_any!
35
+ encrypted_params.each do |param|
36
+ encrypted_val = deep_delete(*dotted_to_encrypted_keys(param)) or next
37
+ self[param] = self.decrypted(encrypted_val)
38
+ end
39
+ end
40
+
41
+ # if :encrypted_pass was set as a param, remove it from the hash and set it as an attribute
42
+ def remove_and_adopt_encrypt_pass_param_if_any!
43
+ @encrypt_pass = self.delete(:encrypt_pass) if self[:encrypt_pass]
44
+ end
45
+
46
+ # the chain of symbol keys for a dotted path key,
47
+ # prefixing the last one with "encrypted_"
48
+ #
49
+ # @example
50
+ # dotted_to_encrypted_keys('amazon.api.key')
51
+ # #=> [:amazon, :api, :encrypted_key]
52
+ def dotted_to_encrypted_keys param
53
+ encrypted_path = dotted_to_deep_keys(param).dup
54
+ encrypted_path[-1] = "encrypted_#{encrypted_path.last}".to_sym
55
+ encrypted_path
56
+ end
57
+
58
+ # list of all params to encrypt on serialization
59
+ def encrypted_params
60
+ params_with(:encrypted)
61
+ end
62
+
63
+ def decrypted val
64
+ return val if val.to_s == ''
65
+ Configliere::Crypter.decrypt(val, encrypt_pass)
66
+ end
67
+
68
+ def encrypted(val)
69
+ return if ( !val )
70
+ Configliere::Crypter.encrypt(val, encrypt_pass)
71
+ end
72
+ end
73
+
74
+ class Param
75
+ include EncryptedParam
76
+ end
77
+ end
78
+
@@ -0,0 +1,38 @@
1
+ require 'yaml'
2
+ Configliere.use :define
3
+ module Configliere
4
+ #
5
+ # Environment -- load configuration from environment variables
6
+ #
7
+ module Environment
8
+ def environment_variables *envs
9
+ envs.each do |env|
10
+ case env
11
+ when Hash
12
+ env.each do |env, param|
13
+ adopt_environment_variable! env, param
14
+ end
15
+ else
16
+ param = env.to_s.downcase.to_sym
17
+ adopt_environment_variable! env, param
18
+ end
19
+ end
20
+ end
21
+
22
+ def adopt_environment_variable! env, param
23
+ define param, :environment => env
24
+ val = ENV[env]
25
+ self[param] = val if val
26
+ end
27
+
28
+ def params_from_environment
29
+ definitions_for(:environment)
30
+ end
31
+ end
32
+
33
+ Param.class_eval do
34
+ # include read / save operations
35
+ include Environment
36
+ end
37
+ end
38
+
@@ -0,0 +1,97 @@
1
+ module Configliere
2
+ #
3
+ # Hash of fields to store.
4
+ #
5
+ # Any field name beginning with 'decrypted_' automatically creates a
6
+ # counterpart 'encrypted_' field using the encrypt_pass.
7
+ #
8
+ class Param < ::Hash
9
+
10
+ # Initialize with the encrypt_pass and the initial contents of the hash.
11
+ #
12
+ # @example
13
+ # # Create a param for a hypothetical database with encrypt_pass "your_mom"
14
+ # Configliere::Param.new 'your_mom',
15
+ # :username=>"mysql_username", :decrypted_password=>"mysql_password"
16
+ #
17
+ def initialize hsh={}
18
+ super()
19
+ merge! hsh
20
+ end
21
+
22
+ #
23
+ # Incorporates the given settings.
24
+ # alias for deep_merge!
25
+ # Existing values not given in the hash
26
+ #
27
+ # @param hsh the defaults to set.
28
+ #
29
+ # @example
30
+ # Settings.defaults :hat => :cat, :basket => :lotion, :moon => { :man => :smiling }
31
+ # Settings.defaults :basket => :tasket, :moon => { :cow => :smiling }
32
+ # Config #=> { :hat => :cat, :basket => :tasket, :moon => { :man => :smiling, :cow => :jumping } }
33
+ #
34
+ def defaults hsh
35
+ deep_merge! hsh
36
+ end
37
+
38
+ # Finalize and validate params
39
+ def resolve!
40
+ begin ; super() ; rescue NoMethodError ; nil ; end
41
+ validate!
42
+ end
43
+ # Check that all defined params are valid
44
+ def validate!
45
+ begin ; super() ; rescue NoMethodError ; nil ; end
46
+ end
47
+
48
+ def []= param, val
49
+ if param =~ /\./
50
+ return deep_set( *( dotted_to_deep_keys(param) | [val] ))
51
+ else
52
+ super param.to_sym, val
53
+ end
54
+ end
55
+
56
+ def [] param
57
+ if param =~ /\./
58
+ return deep_get( *dotted_to_deep_keys(param) )
59
+ else
60
+ super param.to_sym
61
+ end
62
+ end
63
+
64
+ def delete param
65
+ if param =~ /\./
66
+ return deep_delete( *dotted_to_deep_keys(param) )
67
+ else
68
+ super param.to_sym
69
+ end
70
+ end
71
+
72
+ # returns an actual Hash, not a Param < Hash
73
+ def to_hash
74
+ {}.merge! self
75
+ end
76
+
77
+ def use *args
78
+ Configliere.use *args
79
+ end
80
+
81
+ protected
82
+ # turns a dotted param ('moon.cheese.type') into
83
+ # an array of sequential keys for deep_set and deep_get
84
+ def dotted_to_deep_keys dotted
85
+ dotted.to_s.split(".").map{|key| key.to_sym}
86
+ end
87
+
88
+ # simple (no-arg) method_missing callse
89
+ def method_missing meth, *args
90
+ if args.empty? && meth.to_s =~ /^\w+$/
91
+ self[meth]
92
+ elsif args.size == 1 && meth.to_s =~ /^(\w+)=$/
93
+ self[$1] = args.first
94
+ else super(meth, *args) end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,70 @@
1
+ require 'yaml'
2
+ module Configliere
3
+ #
4
+ # ParamStore -- load configuration from a simple YAML file
5
+ #
6
+ module ParamStore
7
+ # Load params from disk.
8
+ # * file is in YAML format, as a hash of handle => param_hash pairs
9
+ # * filename defaults to ParamStore::DEFAULT_CONFIG_FILE (~/.configliere, probably)
10
+ def read handle
11
+ filename = filename_for_handle(handle)
12
+ begin
13
+ params = YAML.load(File.open(filename)) || {}
14
+ rescue Errno::ENOENT => e
15
+ warn "Loading empty configliere settings file #{filename}"
16
+ params = {}
17
+ end
18
+ params = params[handle] if handle.is_a?(Symbol)
19
+ deep_merge! params
20
+ end
21
+
22
+ # save to disk.
23
+ # * file is in YAML format, as a hash of handle => param_hash pairs
24
+ # * filename defaults to ParamStore::DEFAULT_CONFIG_FILE (~/.configliere, probably)
25
+ def save! handle
26
+ filename = filename_for_handle(handle)
27
+ if handle.is_a?(Symbol)
28
+ ParamStore.merge_into_yaml_file filename, handle, self.export
29
+ else
30
+ ParamStore.write_yaml_file filename, self.export
31
+ end
32
+ end
33
+
34
+ protected
35
+
36
+ # form suitable for serialization to disk
37
+ # (e.g. the encryption done in configliere/encrypted)
38
+ def export
39
+ to_hash
40
+ end
41
+
42
+ def self.write_yaml_file filename, hsh
43
+ File.open(filename, 'w'){|f| f << YAML.dump(hsh) }
44
+ end
45
+
46
+ def self.merge_into_yaml_file filename, handle, params
47
+ begin
48
+ all_settings = YAML.load(File.open(filename)) || {}
49
+ rescue Errno::ENOENT => e;
50
+ all_settings = {}
51
+ end
52
+ all_settings[handle] = params
53
+ write_yaml_file filename, all_settings
54
+ end
55
+
56
+ def filename_for_handle handle
57
+ case
58
+ when handle.is_a?(Symbol) then Configliere::DEFAULT_CONFIG_FILE
59
+ when handle.include?('/') then handle
60
+ else File.join(Configliere::DEFAULT_CONFIG_DIR, handle)
61
+ end
62
+ end
63
+
64
+ end
65
+
66
+ Param.class_eval do
67
+ # include read / save operations
68
+ include ParamStore
69
+ end
70
+ end